XML サイトマップバリデーター

プロトコルの全規則を確認。URL 数の制限も登録も不要。

配信元から直接取得します。多くのサーバーはこれをブロックするため、その場合は貼り付けてください。
入力
待機中文書を貼り付けると検査します。入力中にそのまま検証されます。

すべてこのタブ内で実行されます。貼り付けた内容がアップロード・記録・送信されることはありません。 ネットワークパネルを開いて確認する.

上にサイトマップを貼り付けるか、アドレスを入力して「取得」を押すと、sitemaps.org プロトコルの規則をまとめて検査します。URL 数が 50,000 件の上限を超えていないか、非圧縮のサイズが 50 MB の上限を超えていないか、そして loc・lastmod・changefreq・priority それぞれの書式。サイトマップインデックスには専用の規則一式があります。

ここへ来る人の多くは Search Console から「サイトマップを読み込めませんでした」や「サイトマップは読み取れますが、エラーがあります」を手に持って来ます。どちらも行を教えてくれません。ここでは指摘のたびに、問題とした値を引用し、根拠となる規則を示し、代わりに何を書くべきかを伝えます。

この検索の他と違う点が 2 つあります。アップロードが一切ないので、まだ公開していないページを並べたステージング用サイトマップもあなたのマシンに留まります。そして実行できなかった検査は「合格」ではなく「実行しなかった」と報告します。

2 つの厳格な上限

1 つのサイトマップに列挙できる URL は 50,000 件、非圧縮サイズは 50 MB までで、プロトコルはこれを 52,428,800 バイトと明記しています。両方とも検査し、バイトサイズは上限に近いかどうかにかかわらず表示します。ファイルを眺めても判断できない唯一の数字だからです。

gzip で余裕が生まれることはありません。上限は展開後に適用されるので、3 MB の sitemap.xml.gz が 61 MB に展開されるなら上限超過です。サイトマップインデックスにも同じ 2 つの上限が掛かります。どちらも 90 パーセントで警告を出します。増え続けるカタログから生成されるサイトマップは、なんでもない火曜日に線を越えるからです。

  • サイトマップ 1 ファイルにつき <url> 要素 50,000 件。
  • gzip するかどうかに関係なく、非圧縮で 52,428,800 バイト。
  • <loc> の値 1 つにつき 2,048 文字。
  • サイトマップインデックス 1 つにつき <sitemap> 要素 50,000 件。

名前空間は寸分違わず

ルートは <urlset>、インデックスなら <sitemapindex> で、http://www.sitemaps.org/schemas/sitemap/0.9 を宣言しなければなりません。つまずきやすい点が 3 つ。https のサイトであっても https ではなく http であること、末尾にスラッシュを付けないこと、バージョンは 0.9 であり昔からずっとそうであること。

間違えても、大声で文句を言うものはありません。ファイルは依然として整形式で、ブラウザーでも開けます。検索エンジンは単に、間違った名前空間の要素を認識しないだけです。その結果、あなたのサイトマップは「何も入っていない文書」として読まれます。

<url> と <sitemap> の子を混ぜるのはエラーです。ファイルはサイトマップかインデックスのどちらかだからです。宣言する文字符号化は UTF-8 でなければなりません。拡張要素については、プレフィックスが宣言されているかだけを確認します。画像・動画・ニュースの規則そのものはここでは検証しません。

lastmod、changefreq、priority

lastmod は W3C Datetime でなければなりません。YYYY-MM-DD だけでも有効です。完全なタイムスタンプは、2026-03-14T09:30:00+00:00 のようにゾーンが付いていれば有効です。T の代わりの空白、Unix タイムスタンプ、14/03/2026、ゾーンのないタイムスタンプは、いずれもエラーです。

他のどの無料バリデーターもやらない 2 つめの lastmod 検査を行います。5 件以上のエントリーが lastmod を持ち、その値がすべて同一なら警告が出ます。あなたの生成器は、内容が変わった日付ではなくビルド時刻を押しているということです。Google は lastmod を、一貫して検証可能なほど正確なときにだけ使います。そのパターンは、この項目を無視するよう Google に教えているようなものです。

changefreq が受け付けるのは小文字の 7 つの値だけです。always、hourly、daily、weekly、monthly、yearly、never。priority は 0.0 から 1.0 のあいだでなければなりません。どちらも、存在するだけで情報として報告します。Google が自社ドキュメントで、どちらも無視すると述べているからです。

同一ホスト、同一ディレクトリ、そして URL からの取得

https://example.com/catalog/sitemap.xml にあるサイトマップは、https://example.com/catalog/ 以下の URL を列挙できますが、https://example.com/images/ 以下の URL は列挙できません。同じホスト、同じスキーム、自分のディレクトリかそれ以下。これらの規則には、そのサイトマップが公開されるアドレスが必要ですが、それはファイルの中に書かれていません。

URL の入力欄はそのためにあり、取得せずにアドレスを入力するだけでもかまいません。入力すると 3 つの検査が有効になります。別のホストはエラー、別のスキームはエラー、サイトマップ自身のディレクトリの外にある loc は警告。空のままにすると、3 つとも黙って合格にはならず「実行しなかった」と並びます。

取得は、プロキシを挟まずブラウザーからあなたのサーバーへ直接ファイルを要求します。ですから多くのホスティングでは CORS に阻まれます。阻まれたときは理由と、自分で実行できる curl コマンドを表示します。

コードでサイトマップを検査する

ビルド工程に組み込む価値があります。50,000 URL の上限を超えたサイトマップが、インデックス登録ではなくデプロイを落としてくれるようになります。どのサンプルも URL を数え、非圧縮サイズを測り、lastmod を検査します。

// Node 18 or later.  npm i @xmldom/xmldom
import { DOMParser } from '@xmldom/xmldom';

const SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
const MAX_URLS = 50_000;
const MAX_BYTES = 52_428_800;

const response = await fetch('https://example.com/sitemap.xml');
const xml = await response.text();
const bytes = Buffer.byteLength(xml, 'utf8');

// xmldom does not resolve external entities, so there is no XXE surface here.
const doc = new DOMParser().parseFromString(xml, 'text/xml');
const root = doc.documentElement;

if (root.namespaceURI !== SITEMAP_NS) {
  throw new Error('Namespace is "' + root.namespaceURI + '", expected "' + SITEMAP_NS + '"');
}

const locs = doc.getElementsByTagNameNS(SITEMAP_NS, 'loc');
console.log(locs.length + ' URLs, ' + bytes + ' bytes uncompressed');
if (locs.length > MAX_URLS) throw new Error('Over the 50,000 URL limit');
if (bytes > MAX_BYTES) throw new Error('Over the 50 MB limit');

const lastmods = doc.getElementsByTagNameNS(SITEMAP_NS, 'lastmod');
for (let i = 0; i < lastmods.length; i++) {
  const v = lastmods.item(i).textContent.trim();
  // W3C Datetime: a bare date, or a timestamp that carries a zone.
  const ok = /^\d{4}-\d{2}-\d{2}$/.test(v)
    || (/^\d{4}-\d{2}-\d{2}T/.test(v) && /(Z|[+-]\d{2}:\d{2})$/.test(v));
  if (!ok) console.error('Invalid lastmod: ' + v);
}
# pip install lxml requests
import re
import sys
import requests
from lxml import etree

SITEMAP_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9'
MAX_URLS, MAX_BYTES = 50_000, 52_428_800

raw = requests.get('https://example.com/sitemap.xml', timeout=30).content

# The three flags are the safe form. lxml will otherwise expand entities and
# fetch a DTD the document references.
parser = etree.XMLParser(resolve_entities=False, no_network=True, load_dtd=False)
root = etree.fromstring(raw, parser)

if etree.QName(root).namespace != SITEMAP_NS:
    sys.exit('Wrong namespace: %s' % etree.QName(root).namespace)

locs = root.findall('{%s}url/{%s}loc' % (SITEMAP_NS, SITEMAP_NS))
print('%d URLs, %d bytes uncompressed' % (len(locs), len(raw)))
if len(locs) > MAX_URLS:
    sys.exit('Over the 50,000 URL limit')
if len(raw) > MAX_BYTES:
    sys.exit('Over the 50 MB limit')

W3C = re.compile(r'^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2}))?$')
for lm in root.iter('{%s}lastmod' % SITEMAP_NS):
    if not W3C.match((lm.text or '').strip()):
        print('Invalid lastmod:', lm.text)

# The protocol publishes an XSD, which lxml can check structure against:
#   schema = etree.XMLSchema(etree.parse('sitemap.xsd'))
#   schema.assertValid(root.getroottree())
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.format.DateTimeParseException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

final String NS = "http://www.sitemaps.org/schemas/sitemap/0.9";

byte[] bytes = HttpClient.newHttpClient()
    .send(HttpRequest.newBuilder(URI.create("https://example.com/sitemap.xml")).build(),
          HttpResponse.BodyHandlers.ofByteArray())
    .body();

DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
// Without setNamespaceAware(true) every namespace lookup below returns nothing.
// This is the single most common reason sitemap parsing code silently finds zero URLs.
f.setNamespaceAware(true);
f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
f.setXIncludeAware(false);

Document doc = f.newDocumentBuilder().parse(new ByteArrayInputStream(bytes));
NodeList locs = doc.getElementsByTagNameNS(NS, "loc");

System.out.printf("%d URLs, %d bytes uncompressed%n", locs.getLength(), bytes.length);
if (locs.getLength() > 50_000) System.err.println("Over the 50,000 URL limit");
if (bytes.length > 52_428_800) System.err.println("Over the 50 MB limit");

NodeList lastmods = doc.getElementsByTagNameNS(NS, "lastmod");
for (int i = 0; i < lastmods.getLength(); i++) {
    String v = lastmods.item(i).getTextContent().trim();
    try {
        if (v.length() == 10) LocalDate.parse(v);
        else OffsetDateTime.parse(v);   // rejects a timestamp with no zone
    } catch (DateTimeParseException e) {
        System.err.println("Invalid lastmod: " + v);
    }
}
using System.Globalization;
using System.Xml;

const string Ns = "http://www.sitemaps.org/schemas/sitemap/0.9";

// XmlReader streams, so a 50 MB sitemap never becomes a 400 MB DOM.
var settings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Prohibit,  // external entities are never fetched
    XmlResolver = null,
    IgnoreWhitespace = true,
};

string[] w3c =
{
    "yyyy-MM-dd",
    "yyyy-MM-ddTHH:mmK",
    "yyyy-MM-ddTHH:mm:ssK",
    "yyyy-MM-ddTHH:mm:ss.FFFFFFFK",
};

byte[] bytes = await new HttpClient().GetByteArrayAsync("https://example.com/sitemap.xml");

int urls = 0;
using var stream = new MemoryStream(bytes);
using var reader = XmlReader.Create(stream, settings);
while (reader.Read())
{
    if (reader.NodeType != XmlNodeType.Element || reader.NamespaceURI != Ns) continue;

    if (reader.LocalName == "loc")
    {
        urls++;
    }
    else if (reader.LocalName == "lastmod")
    {
        var v = reader.ReadElementContentAsString().Trim();
        // TryParse would accept 03/14/2026. TryParseExact against the four
        // permitted layouts is what the protocol actually asks for.
        if (!DateTimeOffset.TryParseExact(v, w3c, CultureInfo.InvariantCulture,
                                          DateTimeStyles.None, out _))
        {
            Console.Error.WriteLine("Invalid lastmod: " + v);
        }
    }
}

Console.WriteLine(urls + " URLs, " + bytes.Length + " bytes uncompressed");
if (urls > 50_000) Console.Error.WriteLine("Over the 50,000 URL limit");
if (bytes.Length > 52_428_800) Console.Error.WriteLine("Over the 50 MB limit");
# The protocol publishes an XSD, so xmllint can check the structure offline.
curl -sL https://example.com/sitemap.xml -o sitemap.xml
curl -sO https://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd

xmllint --noout --nonet --schema sitemap.xsd sitemap.xml

# The two hard limits:
xmllint --nonet --xpath 'count(//*[local-name()="loc"])' sitemap.xml   # cap 50,000
wc -c < sitemap.xml                                                    # cap 52,428,800

# A gzipped sitemap must be decompressed before either check:
curl -sL https://example.com/sitemap.xml.gz | gunzip > sitemap.xml

# Pull every URL out, one per line, for a crawler or a spreadsheet:
xmllint --nonet --xpath '//*[local-name()="loc"]/text()' sitemap.xml | tr -s '\n'

セキュリティのフラグは飾りではありません。Java も Python も C# も、指示しない限り外部実体を解決します。そしてサイトマップはたいてい、自分で書いたのではないファイルであり、プラグインからネットワーク越しに届いたものです。xmllint の --nonet フラグも同じ仕事をします。

よくある質問

Search Console はサイトマップにエラーがあると言うのに、このツールは問題ないと言います。なぜですか。

見ているものが違うからです。このページが検査するのはファイルです。構造、名前空間、上限、日付、そしてサイトマップのアドレスを与えればホストとディレクトリの規則。Search Console はそれに加えて、クローラーにしか分からないことも確認します。まず、そのファイルに到達できたかどうかです。

「取得できませんでした」はたいてい、404、リダイレクトの連鎖、サイトマップのパスを覆う robots.txt の規則、あるいはステータス 200 で返された HTML のエラーページを意味します。ここで URL を取得すれば本当のステータスコードが分かります。もう 1 つよくある食い違いは、URL は妥当なのにその先のページが noindex か既に存在しない場合で、これはクロールの問題です。

Google は changefreq と priority を使いますか。

使いません。Google はサイトマップのドキュメントで、どちらも無視すると明記しています。このツールはそれらの存在をエラーではなく情報として報告します。合法な要素であり、害もないからです。

実際的に言えば、priority の値をどう並べても、あるページが別のページより先にクロールされることはありませんし、changefreq を hourly にしてもクロール頻度は上がりません。それらを計算するコードを保守しているなら、そのコードは働きに見合っていません。例外は lastmod で、正しく書く価値のある唯一の任意要素です。

lastmod はどの書式でなければなりませんか。

W3C Datetime、ISO 8601 の部分集合です。最も単純な有効形は日付だけの 2026-03-14 です。時刻を含めるならゾーンも含めなければなりません。2026-03-14T09:30:00Z と 2026-03-14T09:30:00+00:00 はどちらも正しい形です。

弾かれる形は、データベースやテンプレートが自然に吐き出すものばかりです。T の代わりの空白は無効、ゾーンのないタイムスタンプも無効、Unix エポックの数値や 14/03/2026 のような日付先行の表記も無効です。悪い日付が自分のコードではなくプラグイン由来なら、エラーに引用された値からどのプラグインかが分かることが多いはずです。

1 つのサイトマップに URL は何件まで入れられますか。

50,000 件です。あわせて非圧縮で 52,428,800 バイト未満にも収める必要があります。先にぶつかったほうが効いてくるので、URL が長いサイトではたいてい件数より先にサイズの上限が来ます。

どちらかを超えたらファイルを分割し、分割したものをサイトマップインデックスに列挙してください。同じ名前空間の小さなファイルで、子は <sitemap> 要素、それぞれに <loc> が 1 つ入ります。インデックスは 50,000 件のサイトマップを列挙でき、同じ 50 MB の制約を受けます。子を 1 つずつではなくインデックスを送信し、robots.txt からも参照してください。

「取得」ボタンが失敗したのはなぜですか。

ほぼ確実に CORS です。相手のドメインが Access-Control-Allow-Origin ヘッダーを送らない限り、ブラウザーはこのページに他ドメインのレスポンスを読ませません。そして sitemap.xml にそのヘッダーを付けているサーバーはほとんどありません。これはあなたのサーバーの方針であって、ファイルの不備ではありません。

プロキシで回避することはしません。プロキシを使えば、あなたの URL とファイルが私たちの運用するサーバーを通ることになるからです。失敗メッセージには、入力したアドレス用の curl コマンドが付きます。gzip されたサイトマップは先に展開が必要です。curl -sL https://example.com/sitemap.xml.gz | gunzip のように。

サイトマップはどこかにアップロードされますか。

いいえ。パーサーもすべての規則も、このタブで動く JavaScript です。何かを送るバックエンドもなければ、エディターにアクセスできる解析ツールもありません。ネットワークパネルを開いてファイルを検証してみてください。ページのアセットが一度読み込まれ、そのあとは何も起きません。

公開済みのサイトマップは公開情報ですが、デバッグ中のファイルはそうでないことがよくあります。ステージングのサイトマップには未公開のページ、社内ツール、秘密保持契約下のクライアントサイトが並びますし、この検索で上位に出るチェッカーのいくつかはサーバー側で動きます。入力内容は再読み込みで失われないようこのブラウザーの localStorage に保存され、クリアで消せます。

関連ツール

関連する解説