ぽっぺんしゃんにょろりんこ

匿名・非追跡型アダルト動画検索エンジンの設計ノート

動的ウェブサイトで、動画タイトルの文字列を使って動的サイトマップを作る②

こんにちは、にょろりんこの備忘録的技術ブログです。

前回の記事では、動画サイト運営において

  • 無機質なURL(例: /video.php?id=1234)ではSEO評価が上がらない
  • 日本語タイトルをURLスラッグ化して、よりSEOフレンドリーにする

という課題感と、その解決方法として静的ページ用のサイトマップ生成までの実装を解説しました。

www.n-rinko.com

今回は、その続きを書いていきます。

テーマは、「動画DBからタイトルを取得し、動的ページ用のサイトマップを生成する」です。

これができると、

  • 動画ページがGoogleにインデックスされやすくなる
  • タイトル入りURLでCTRも改善する
  • SNSシェアでも「何の動画か」がパッと分かる

といった、運営者として絶対に捨てがたい効果が得られます。

実際に、私が運営している「あなたを追跡しないアダルト動画検索エンジン SaePorns」でもこの方法を導入したところ、インデックスされるまでのスピードが向上した(ように思えます)。

では、コードを見ながら解説していきましょう。

DB接続処理

まずは、動画データを格納している MySQLデータベースに接続していきましょう。
いたって普通のDB接続モジュールですが、順番的に非常に重要な箇所なので、念のためフルコードで記入します。

try {
    $pdo = new PDO(
        "mysql:host=" . DB_SERVERNAME . ";dbname=" . DB_NAME . ";charset=utf8mb4",
        DB_USERNAME,
        DB_PASSWORD,
        [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]
    );
} catch (PDOException $e) {
    fwrite(STDERR, "DB 接続エラー: " . $e->getMessage() . "\n");
    exit(1);
}

これで、動画テーブルからデータを取得する準備が整いました。

次に、実際に動画データを取得していく処理を見ていきましょう。

動画レコードの取得とサイトマップ分割数の計算

サイトマップ生成には「動画データ」と「分割数」 の両方が必要なので、それを準備します。

// videos テーブルから全件取得
$stmt   = $pdo->query("SELECT id, url, title, thumbnail_url, embed_url, duration, updated_at FROM videos");
$videos = $stmt->fetchAll();

$totalUrls     = count($videos);
$totalSitemaps = (int)ceil($totalUrls / $maxUrlsPerSitemap);
echo "総動画 URL: {$totalUrls}, 分割サイトマップ数: {$totalSitemaps}\n";

ここでは、$pdo->query() を使って videos テーブルから必要なカラムを全件取得し、fetchAll() で結果を配列として $videos に格納しています。

そして、count($videos) で取得した動画レコード数を $totalUrls に代入。

さらに、sitemap.xml は 1ファイルにつき最大50,000件 というGoogleの仕様があるため、ceil() を使って切り上げ、必要な分割数を $totalSitemaps に計算しています。

最後に、総動画数と分割サイトマップ数を echo で出力。

この処理で、サイトマップ生成に必要な 「動画データ」と「分割数」 の両方が準備できました。

次は、分割サイトマップを生成する処理に進んでいきます。

サイトマップインデックスXMLの準備

次に、複数のサイトマップファイルをまとめる インデックスXML の準備をしていきます。

前述のとおり、サイトマップにはグーグルの仕様だと上限5万件(5万URL)という制約があります。

ですので、それを束ねる元になるインデックスXMLが必要になるわけですね。

これで理論上25億件(5万件×5万件)までのサイトマップ作成が可能になります。

25億件を超えたらどうなるんだ?とお考えの方もいらっしゃるかと思いますが、これがいったんGoogleの仕様上の上限(らしい)です。

とはいえ、このあたりは個人開発の規模をはるかに超えているので、もしそんな状況になったら、その時に考えればいいかなと思います。

XMLヘッダの定義

$xmlIndex = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";

これはXMLファイルの最初に必須となる宣言で、バージョン1.0、文字エンコーディングUTF-8を指定しています。

<sitemapindex> タグの開始

$xmlIndex .= "<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n";

sitemap-index.xml のルート要素となる <sitemapindex> タグを開き、その中で sitemaps.org のスキーマURLを xmlns 属性としてセットしています。

このタグがあることで、Googleなどの検索エンジンが「これは複数サイトマップをまとめたインデックスファイルですよ」と正しく認識してくれるわけですね。

このインデックスXMLには、後ほど

  • 静的ページ用の sitemap-static.xml
  • 動的に分割生成される sitemap1.xml, sitemap2.xml, …

など、全てのサイトマップファイルを登録していきます。

これで、インデックスXMLの準備が整いましたので、次はまず①で作成した静的URLを登録していきます。

静的サイトマップをインデックスXMLに追加

インデックスXMLの準備ができたら、まずは 静的ページ用のサイトマップ(sitemap-static.xml) を追加していきます。

$xmlIndex .= "  <sitemap>\n";
$xmlIndex .= "    <loc>{$baseUrl}/sitemap-static.xml</loc>\n";
$xmlIndex .= "    <lastmod>" . date('Y-m-d') . "</lastmod>\n";
$xmlIndex .= "  </sitemap>\n";

ここでは、まず <sitemap> タグを使って、静的サイトマップをインデックスXMLに登録しています。

<loc> タグには sitemap-static.xml のURLを指定しており、これによってGoogleに「静的ページ用のサイトマップはここにありますよ」と伝えることができます。

また、<lastmod> タグには date('Y-m-d') でスクリプト実行日の年月日を設定し、最終更新日を記録しています。

ここまでで、静的URLをサイトマップに登録する準備が整いました。

次は動的URLの順番なのですが、まずはslugifyという関数を用意してした準備をします。

slugify 関数の定義

動画タイトルをURL用の文字列(スラッグ:slug)に変換するために、ここで専用の関数を定義しておきます。

// slugify 関数(URL 向けテキスト整形)
function slugify(string $text): string {
    $text = mb_strtolower($text, 'UTF-8');
    $text = preg_replace('/[^\p{L}\p{Nd}]+/u', '-', $text);
    $text = preg_replace('/-+/', '-', $text);
    return trim($text, '-');
}

この slugify 関数では、まず mb_strtolower を使って文字列をすべて小文字に変換しています。

これによって、URLに大文字小文字が混在することで起こる表記ゆれを防ぐことができます。

次に、preg_replace('/[^\p{L}\p{Nd}]+/u', '-', $text) で、アルファベット(\p{L})や数字(\p{Nd})以外の文字をハイフンに置換しています。
これにより、日本語や記号が含まれる文字列でも、URLとして安全に使える文字列に変換できます。

そのあと、preg_replace('/-+/', '-', $text) で、連続したハイフンを1つにまとめています。

最後に、trim($text, '-') で文字列の先頭と末尾のハイフンを削除し、不要なハイフンがURLに残らないようにしています。

この slugify 関数を使うことで、動画タイトルをSEOフレンドリーでURLセーフな形に変換することができます。

次は、このslugifyを使って、実際に動画ページURLを生成する処理を見ていきましょう。

動画用サイトマップを分割して生成する準備

ここからは、実際に動画ページ用のサイトマップを生成していく処理に入ります。

まずは、Googleの仕様(1サイトマップあたり最大5万URL)に合わせて、動画データを分割して処理するためのループを作っています。

// 動画用サイトマップを分割して生成
for ($i = 0; $i < $totalSitemaps; $i++) {
    $batch      = array_slice($videos, $i * $maxUrlsPerSitemap, $maxUrlsPerSitemap);
    $filename   = "sitemap" . ($i + 1) . ".xml";
    $filepath   = "{$sitemapDir}/{$filename}";
    $urlOnSite  = "{$baseUrl}/{$filename}";

    $xml  = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
    $xml .= "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\">\n";

この for ループでは、array_slice を使って、動画データを最大5万件ずつのバッチに切り分けています。

そして、サイトマップのファイル名を "sitemap1.xml", "sitemap2.xml" のように連番で決定し、実際に書き込むファイルパスと、公開URL用のパスも変数に格納しています。

最後に、XMLファイルとして必要なヘッダ情報、つまり、<urlset> タグ(video sitemap extensionも含む)をセットして、1つのサイトマップファイルを生成するための準備が整います。

ここまでで、「どのファイルに、どの動画データを入れるか」 の土台が完成しました。

次は、このバッチデータを使って実際に <url> 要素を生成していく処理に進みます。

各動画レコードをURL要素としてサイトマップに追加

ここからは、いよいよ動画ページのURLを実際にサイトマップXMLに書き込んでいく処理です。

foreach ($batch as $vid) {
    $slug       = slugify($vid['title']);
    $loc        = htmlspecialchars("{$baseUrl}/video/{$vid['id']}-{$slug}", ENT_QUOTES, 'UTF-8');
    $lastmod    = date('Y-m-d', strtotime($vid['updated_at']));
    $thumb      = htmlspecialchars($vid['thumbnail_url'], ENT_QUOTES, 'UTF-8');
    $title      = htmlspecialchars($vid['title'], ENT_QUOTES, 'UTF-8');
    $desc       = $title;
    $embed      = htmlspecialchars($vid['embed_url'], ENT_QUOTES, 'UTF-8');
    $duration   = htmlspecialchars($vid['duration'], ENT_QUOTES, 'UTF-8');

    $xml .= "  <url>\n";
    $xml .= "    <loc>{$loc}</loc>\n";
    $xml .= "    <lastmod>{$lastmod}</lastmod>\n";
    $xml .= "    <changefreq>daily</changefreq>\n";
    $xml .= "    <priority>0.8</priority>\n";
    $xml .= "    <video:video>\n";
    $xml .= "      <video:thumbnail_loc>{$thumb}</video:thumbnail_loc>\n";
    $xml .= "      <video:title>{$title}</video:title>\n";
    $xml .= "      <video:description>{$desc}</video:description>\n";
    if (!empty($vid['embed_url'])) {
        $xml .= "      <video:player_loc allow_embed=\"yes\">{$embed}</video:player_loc>\n";
    }
    if (!empty($vid['duration'])) {
        $xml .= "      <video:duration>{$duration}</video:duration>\n";
    }
    $xml .= "    </video:video>\n";
    $xml .= "  </url>\n";
}

この foreach ループでは、分割された動画データのバッチを1件ずつ処理しています。

まず、slugify($vid['title']) を使って、動画タイトルをSEOフレンドリーなスラッグに変換しています。

これにより、URLが

/video/1234-動画タイトルのスラッグ

のようになり、ユーザーにもGoogleにもわかりやすい構造になります。

続いて、

  • <loc> には、生成した動画ページURLをセット
  • <lastmod> には、動画レコードの更新日をY-m-d形式でセット
  • <changefreq> は「daily」(毎日更新の可能性あり)
  • <priority> は「0.8」(やや高めの重要度)

を設定しています。

そして、ここからが video sitemap extension のタグ群です。

  • <video:thumbnail_loc> に動画サムネイルURL
  • <video:title> に動画タイトル
  • <video:description> に動画説明(ここではタイトルと同じ)
  • <video:player_loc> に埋め込みプレイヤーURL(embed_urlが存在する場合のみ)
  • <video:duration> に動画の再生時間(存在する場合のみ)

をそれぞれセットし、最後にタグを閉じています。

このループを通じて、1つの動画ページに必要なサイトマップ情報が全てXMLとして出力されるわけです。

次は、この生成したXMLをファイルとして書き出し、インデックスXMLに登録する処理を見ていきましょう。

サイトマップXMLファイルの書き出しと完了メッセージ

次に、生成したサイトマップXMLを実際のファイルとして書き出す処理を行います。

$xml .= "</urlset>\n";
file_put_contents($filepath, $xml);
echo "作成: {$filename}\n";

ここでは、まず </urlset> タグを追加して、XMLファイルの末尾を正しく閉じています。

そのあと、file_put_contents を使って、生成した $xml の文字列データを先ほど指定した $filepath に保存しています。

これによって、実際に

  • sitemap1.xml
  • sitemap2.xml
  • ...

というファイルがサーバー上に書き出され、Googleなどの検索エンジンからアクセス可能になります。

最後に、echo で

作成: sitemap1.xml

のように、どのファイルを作成したかを出力しています。

このメッセージはcronバッチなどで実行ログを確認するときに便利ですね。

ここまでで、1つのサイトマップファイルを生成して書き出す処理が完了しました。

次は、この生成したファイルをサイトマップインデックスXMLに登録する処理を見ていきましょう。

生成したサイトマップをインデックスXMLに登録する

最後に、今作成したサイトマップファイルを sitemap-index.xml に登録 していきます。

// インデックスにも追加
$xmlIndex .= "  <sitemap>\n";
$xmlIndex .= "    <loc>{$urlOnSite}</loc>\n";
$xmlIndex .= "    <lastmod>" . date('Y-m-d') . "</lastmod>\n";
$xmlIndex .= "  </sitemap>\n";

ここでは、インデックスXML用の変数 $xmlIndex に

  • <sitemap> タグを追加
  • <loc> には、今作成したサイトマップファイルの公開URLをセット
  • <lastmod> には、スクリプト実行日の年月日をセットしています。

この処理によって、

のように、作成した全てのサイトマップファイルが sitemap-index.xml に登録され、Googleなどの検索エンジンに「このサイトには複数のサイトマップファイルがありますよ」と伝えることができます。

ここまでで、分割生成した各サイトマップを インデックスXMLにまとめる処理 が完了しました。

次は、インデックスXMLを最終的にファイルとして書き出す処理を見ていきましょう。

インデックスXMLの書き出しとスクリプト終了

最後に、サイトマップインデックスXMLをファイルとして書き出す処理を行います。

// サイトマップインデックス出力
$xmlIndex .= "</sitemapindex>\n";
file_put_contents("{$sitemapDir}/sitemap-index.xml", $xmlIndex);
echo "作成: sitemap-index.xml\n";

exit(0);

ここでは、まず </sitemapindex> タグを追加して、インデックスXMLの末尾を正しく閉じています。

続いて、file_put_contents を使って、これまで $xmlIndex に作成してきたインデックスXML

sitemap-index.xml

というファイル名で $sitemapDir に保存しています。

最後に、echo で

作成: sitemap-index.xml

というメッセージを出力し、スクリプトが正常に完了したことをログに残しています。

exit(0); でスクリプトを終了し、処理全体が終了です。

これで、動的URLを含むSEOフレンドリーなサイトマップ生成処理の解説は完了です。

もし運用環境に導入する場合は、cronに組み込んで定期更新すると、Googleインデックス速度の改善に繋がるかもしれません。ぜひ試してみてください。

まとめ

ここまで、読んでくださってありがとうございました!

オフィスのティアラ

①・②と「動的ウェブサイトで、動画タイトルの文字列を使って動的サイトマップを作る」をやってきました。

今回紹介したように、

  • 動画タイトルをslug化してURLに含める
  • 動的URLを含むサイトマップを自動生成する

という仕組みを導入することで、SEO評価が上がるだけでなく、SNSシェア時にもURLが直感的に分かりやすくなるというメリットがあります。

正直、最初に実装する時は「めんどくさい…」と思いましたが、一度仕組みを作ってしまえば、あとはcronで定期実行するだけ。

私はこの仕組みを導入してから、Googleインデックスまでの速度が体感的に早くなった気がしています。(もちろんアルゴリズム次第なので保証はできませんが…)

ぜひ、あなたの開発プロジェクトでも試してみてください。

今回の記事が参考になれば嬉しいです。

今回のコードが実際に動いている「追跡されないアダルト動画の検索エンジン」Sae-Pornsはこちら!よかったら見ていってくださると嬉しいです。
※18歳未満の方はご利用いただけません。

sae-porns.org

それでは、皆さま、よい開発ライフを。