公開するたびに、完成した公開ページ一式からサイトマップを再生成し、その両方をまとめてアップロードします。このMacのワークフローでは、小さなPythonスクリプトを使って追加されたページを含め、削除されたページを自動的に除外し、公開後の結果も確認します。

ここでは、Macからエクスポートした小規模な静的ウェブサイトのサイトマップを維持する方法を説明します。例では、色についての架空のページを使います。既存の公開手順でエクスポートフォルダーをアップロードし、公開サイトから不要になったファイルを削除できることを前提としています。

サイトマップの再生成をどこに組み込むか決める

まず、公開システムがすでにサイトマップを生成しているか確認します。Googleによると、多くのコンテンツ管理システムには自動生成機能があります。利用中のシステムにその機能がある場合は、別の生成ツールを導入する前に、テストページを追加・削除した後のサイトマップを確認してください。サイトマップについて。

静的サイトをエクスポートする場合は、ページのエクスポートを完了し、そのエクスポートからサイトマップを生成した後、できあがったフォルダーを公開します。これは実装方法の一例です。これにより、サイトマップが公開システムに渡すページ一式と同じ内容を記述できます。

公開の一環として生成すれば、追加と削除に一貫したルールを適用できます。完成したエクスポートにあるページは含まれ、ないページは除外されます。別の一覧を忘れずに更新する必要はありません。

既存の設定に合う連携方法を選んでください。

既存の公開設定 再生成を組み込む場所
CMSがすでにサイトマップを管理している その生成機能を使い続け、ページ変更時の動作をテストする
静的エクスポーターがエクスポート後のコマンド実行に対応している アップロード前にそこで生成スクリプトを実行する
既存のコマンドが完成済みフォルダーを公開する 生成スクリプトの任意の公開用引数を使って実行する
デスクトップのインターフェースからアップロードする 先に生成し、フォルダー全体をアップロードする。公開更新を無人で行うには、対応する公開連携が必要

ローカルのスクリプトだけでは、リモートのウェブサイトを変更できません。以下のワークフローはサイトマップの生成を自動化し、既存の公開コマンドを実行できます。実際のアップロードは、現在利用している公開システムが担当します。

サイトマップに含めるページを明確にする

通常のページURLの場合、Googleは1行に完全なURLを1つ記載するテキスト形式のサイトマップを受け付けています。この作業ではXMLは必須ではありません。同じコンテンツに複数のURLがある場合は、各ページの優先する公開URLだけを含めてください。サイトマップの作成と送信。

テキスト形式ではUTF-8エンコーディングが必要で、見出しやコメントを含めず、URLだけを記載します。1つのファイルには最大50,000件のURLを記載でき、サイズは52,428,800バイトまでです。この例では、エクスポートしたページをその場所以下すべて網羅できるよう、サイトのルートにサイトマップを置きます。サイトマッププロトコル:テキストファイルと設置場所。

この生成スクリプトでは、意図的に単純なエクスポート規則を使います。

エクスポートされたファイル 公開URLの例
public/index.html https://example.com/
public/colors/blue.html https://example.com/colors/blue.html
public/tiles/index.html https://example.com/tiles/

この規則は、ホストがこれらのURLをそのまま配信する場合にのみ使ってください。公開システムが blue.html を /colors/blue/ に変換する場合や、/collection/ のようなパスの下でサイトを配信する場合は、公開システムのルート一覧を使うか、公開前にマッピングを調整してください。ファイル名のスキャンだけでは、独自のルーティングを判定できません。

エクスポートフォルダーには、含める予定のページだけを残してください。この例では、ドットで始まるパスと、404.html という名前のファイルを除外します。ページのメタデータ、canonical宣言、リダイレクト規則は調べません。こうした条件もあるサイトでは、公開システムが持つ対象ページ一覧から生成してください。

通常のナビゲーションリンクも引き続き管理してください。WHATWGは、a 要素の href 属性で作成されるハイパーリンクを定義しており、Googleは適切にリンクされたページは通常、ナビゲーションを通じて発見できると説明しています。サイトマップの更新にあわせて、追加・削除したページを指すリンクも更新してください。WHATWG HTML標準:リンク、サイトマップについて。

Macで生成スクリプトを作成する

Python 3.8以降と、プレーンテキストエディターが必要です。Terminalで python3 --version を実行し、利用できるインタープリターを確認してください。Pythonが必要な場合は、Python Software FoundationのmacOS向けインストール手順に従ってください。同団体のドキュメントには、Terminalから python3 を使ってスクリプトを実行する方法も記載されています。macOSでPythonを使う。

デスクトップに sitemap-demo というフォルダーを作成します。以下の完全なプログラムをその中に regenerate_sitemap.py という名前で保存してください。このプログラムはPython標準ライブラリを使うため、追加パッケージは不要です。

import argparse
from pathlib import Path, PurePosixPath
import subprocess
import tempfile
from urllib.parse import quote, urlsplit


def make_sitemap(paths, origin):
    parsed = urlsplit(origin)
    if (parsed.scheme not in ("http", "https") or not parsed.hostname
            or parsed.netloc != parsed.hostname
            or parsed.path not in ("", "/")
            or parsed.query or parsed.fragment):
        raise ValueError("Use a site origin, such as https://example.com")
    base = origin.rstrip("/")
    urls = set()
    for relative in paths:
        path = PurePosixPath(relative)
        if path.name == "index.html":
            parent = path.parent.as_posix()
            route = "" if parent == "." else parent + "/"
        else:
            route = path.as_posix()
        urls.add(base + "/" + quote(route, safe="/"))
    if not urls:
        raise ValueError("No eligible pages; sitemap was not replaced")
    if len(urls) > 50000:
        raise ValueError("Split the sitemap: more than 50,000 URLs")
    data = ("\n".join(sorted(urls)) + "\n").encode("utf-8")
    if len(data) > 50 * 1024 * 1024:
        raise ValueError("Split the sitemap: larger than 50 MB")
    return data


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("folder")
    parser.add_argument("origin")
    parser.add_argument("publish", nargs=argparse.REMAINDER)
    args = parser.parse_args()
    root = Path(args.folder).resolve()
    if not root.is_dir():
        parser.error("The export folder must already exist")
    paths = []
    for file in root.rglob("*.html"):
        relative = file.relative_to(root)
        if (file.is_file() and file.name != "404.html"
                and not any(p.startswith(".") for p in relative.parts)):
            paths.append(relative.as_posix())
    data = make_sitemap(paths, args.origin)
    target = root / "sitemap.txt"
    temporary = None
    try:
        with tempfile.NamedTemporaryFile(dir=root, delete=False) as handle:
            temporary = Path(handle.name)
            handle.write(data)
        temporary.replace(target)
    finally:
        if temporary is not None:
            temporary.unlink(missing_ok=True)
    print(f"Wrote {len(data.splitlines())} URLs to {target}", flush=True)
    if args.publish:
        subprocess.run(args.publish, check=True)


if __name__ == "__main__":
    main()

実行のたびにURL一覧全体を作り直します。結果を並べ替えて重複を取り除き、パス内の空白などの文字をエンコードし、一時ファイルを書き込んでから sitemap.txt を置き換えます。生成でエラーが起きると、任意の公開コマンドを実行する前にプログラムが停止します。

エクスポートが空の場合は意図的にエラーを発生させ、以前のサイトマップをそのまま残します。これにより、誤ったフォルダーや未完成のフォルダーを選んだ場合に気付けます。すべてのページを意図的に削除する場合は、このワークフローに空のエクスポートを渡すのではなく、別途公開方法を決めてください。

公開連携を設定する前に、追加と削除をテストする

Terminalで、デモ用フォルダーに移動します。

cd "$HOME/Desktop/sitemap-demo"

以下の完全なセットアップコマンドで、架空のページを2つ作成します。新しく作ったデモ用フォルダーで実行してください。指定した2つのサンプルファイルが書き込まれます。

python3 - <<'PY'
from pathlib import Path

root = Path("public")
(root / "colors").mkdir(parents=True, exist_ok=True)
pages = {
    "index.html": "<!doctype html><title>Color collection</title><p>Color collection.</p>",
    "colors/blue.html": "<!doctype html><title>Blue tile</title><p>A blue tile.</p>",
}
for name, content in pages.items():
    (root / name).write_text(content, encoding="utf-8")
PY

ローカルのサイトマップを生成して確認します。例に示すオリジンは説明用であり、これらのコマンドがそこに何かを公開することはありません。

python3 regenerate_sitemap.py public https://example.com
cat public/sitemap.txt

ファイルには、次の2行だけが記載されます。

https://example.com/
https://example.com/colors/blue.html

次に、架空の青いページを緑のページに置き換えます。

python3 - <<'PY'
from pathlib import Path

root = Path("public")
(root / "colors/blue.html").unlink()
(root / "colors/green.html").write_text(
    "<!doctype html><title>Green tile</title><p>A green tile.</p>",
    encoding="utf-8",
)
PY
python3 regenerate_sitemap.py public https://example.com
cat public/sitemap.txt

期待される結果は、ホームのURLに続いて https://example.com/colors/green.html が記載されたものです。エクスポートされたファイルがなくなったため、青いページのURLは消えます。検索エンジンが変更に気付くのを待たずに、追加と削除の両方を確認できます。

毎回の公開に再生成を組み込む

実際のサイトでは、デモ用フォルダーとオリジンを、完成済みのエクスポートフォルダーと公開オリジンに置き換えてください。エクスポートの完了後にのみ生成スクリプトを実行し、公開が終わるまでフォルダーの内容を変更しないでください。公開は順番に実行します。

プロジェクトフォルダーに deploy-site.sh という名前の動作する公開スクリプトがすでにある場合は、次の連携コマンドでサイトマップを生成してから、そのスクリプトを実行します。この例では既存のスクリプトが必要です。生成スクリプトが公開スクリプトを作成したり、ホスティング設定を用意したりすることはありません。

python3 regenerate_sitemap.py public https://example.com sh ./deploy-site.sh

公開スクリプトは、sitemap.txt を含む同じ public フォルダーをアップロードし、エクスポートからなくなった公開ファイルを削除する必要があります。サイトマップの生成後にページを再構築してはいけません。公開システムがサイトのビルドも行う場合は、ビルドとアップロードの間に生成処理を入れてください。

生成に失敗した場合、公開コマンドは実行されません。公開に失敗した場合は、Pythonがエラーを報告しますが、リモートサイトがすでに一部変更されている可能性があります。再試行する前に公開後の結果を確認してください。ホストがリリース全体の一括公開に対応している場合は、公式の手順に従ってその仕組みを使ってください。

デスクトップのアップローダーを使う場合は、エクスポートフォルダーを選択してアップロードする前にサイトマップを生成してください。アップローダーが既存のサイトマップを置き換え、削除したページも処理することを確認します。アップロード操作は引き続き必要です。ローカルでの自動生成だけでは、無人で公開できません。

公開サイトマップと削除したページを別々に確認する

公開が成功したら、ブラウザーで実際の公開 sitemap.txt URLを開きます。追加したページが表示され、削除したページが表示されないことを確認してください。次に、変更対象の各ページに直接アクセスします。これにより、ローカルのエクスポートが正しいかどうかと、公開リリースが正しいかどうかを区別できます。

サイトマップの項目を削除しても、そのページの古いアドレスがどのHTTPレスポンスを返すかは決まりません。置き換えのない削除済みページについては、ホストが適切な「ページなし」のレスポンスを返すことを確認してください。RFC 9110では、現在の表現が存在しないリソースに対して 404 を定義し、利用できない状態が今後も続く可能性が高いと分かっている場合に 410 を定義しています。RFC 9110:HTTPセマンティクス。

ページが「ページなし」のメッセージを表示していても 200 を返す場合、ソフト 404 として扱われることがあります。またGoogleは、以前インデックス登録されたURLが該当する 4xx レスポンスを返すようになると、時間の経過とともにインデックスから削除されると説明しています。そのため、サイトマップからURLを削除することは、ページを削除する作業の一部にすぎません。HTTPステータスコードがGoogleのクローラーに与える影響。

問題があるように見える場合は、次の順に確認してください。

状況 次に確認すること
ローカルのサイトマップに削除したページがまだ載っている エクスポートに残ったファイルがないか、正しいエクスポートフォルダーを選んだかを確認する
ローカルのサイトマップは正しいが、公開版と異なる アップロードしたフォルダー、アップロード先、キャッシュを確認する
公開サイトマップは正しいが、削除したページがまだ開く 公開処理で古いリモートファイルが削除されたか確認する
生成されたURLを開くと別のページが表示される ホストのルート設定をファイル名の規則と照らし合わせる
新しいURLが公開されているが、検索結果に出ない 検索での発見とインデックス登録を別の処理として扱う

公開サイトマップが更新されていないように見える場合は、キャッシュが原因かもしれません。RFC 9111は、キャッシュされたレスポンスが新鮮な状態を保つ方法と再検証される仕組みを定義しています。ホストでレスポンスヘッダーを設定できる場合、Cache-Control: no-cache は保存済みレスポンスを再利用する前に検証を成功させるよう要求します。ローカルのファイルを変更しただけでは、このヘッダーは設定されません。RFC 9111:HTTPキャッシュ。

サイトマップのURLを固定する

Search Consoleの Sitemaps レポートから送信するか、サイトで既に使っている robots.txt ファイルに記載して、公開サイトマップを見つけられるようにします。Googleはこの両方の方法を説明しており、サイトマップの送信はヒントとして扱われると述べています。サイトマップの作成と送信。

説明用のサイトでは、次の行を記載します。

Sitemap: https://example.com/sitemap.txt

公開時に内容を置き換えても、サイトマップのアドレスは固定してください。実際に完了したか確認するには、公開サイトマップが完成済みエクスポートと一致し、追加したページが記載されたURLで開き、削除したページが意図したレスポンスを返すことを確認します。検索エンジンはそれぞれのスケジュールで変更を処理します。サイトマップに含めても、クロールやインデックス登録が保証されるわけではありません。サイトマップについて。

参考資料