要点(2025-11-05 JST)
- 現在のタブのサイトから /robots.txt と /llms.txt を取得し、中身を日本語で要約表示する Manifest V3 のChrome拡張サンプルを用意しました(そのまま動きます)。
- robots.txt は正式仕様(RFC 9309)で、クローラーへのアクセス方針(Allow/Disallow等)を伝えるもの。セキュリティ対策の代替にはなりません。(RFCエディタ)
- llms.txt は Jeremy Howard らの提案で、サイトをLLMが参照・要約しやすいように構造化リンクや説明を載せる“慣行”です(まだ標準ではありません)。Mintlify等が自動生成をサポート。(llms-txt)
- OpenAIの GPTBot など主要AIクローラーは robots.txt の指示を読む想定です(例:
User-agent: GPTBot)。(OpenAI Platform)
- ブラウザ: Google Chrome(拡張読み込み可)
- 権限: ローカルで拡張を「デベロッパーモード」で読み込む
- ネットワーク: 通常のインターネット接続(CORS回避のため manifest の
host_permissionsを使用)
manifest.json
{
"manifest_version": 3,
"name": "Robots & LLMs.txt Explainer",
"version": "0.1.0",
"description": "現在のサイトの robots.txt と llms.txt を読み取り、要点を日本語で説明します。",
"action": {
"default_title": "Explain robots.txt / llms.txt",
"default_popup": "popup.html"
},
"permissions": ["activeTab", "storage"],
"host_permissions": ["<all_urls>"]
}popup.html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Robots & LLMs.txt Explainer</title>
<style>
body { font: 13px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 12px; width: 360px; }
h1 { font-size: 16px; margin: 0 0 8px; }
.host { color: #555; margin-bottom: 8px; }
.card { border: 1px solid #ddd; border-radius: 8px; padding: 10px; margin: 10px 0; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; white-space: pre-wrap; }
details { margin-top: 6px; }
.ok { color: #0a6; }
.warn { color: #b50; }
.err { color: #c00; }
button { padding: 6px 10px; border-radius: 6px; border: 1px solid #ccc; background: #fafafa; cursor: pointer; }
</style>
</head>
<body>
<h1>Robots & LLMs.txt Explainer</h1>
<div class="host" id="host"></div>
<button id="reload">再読み込み</button>
<div class="card">
<strong>robots.txt の要点</strong>
<div id="robots-summary">読み込み中…</div>
<details><summary>生データを見る</summary><pre class="mono" id="robots-raw"></pre></details>
</div>
<div class="card">
<strong>llms.txt の要点</strong>
<div id="llms-summary">読み込み中…</div>
<details><summary>生データを見る</summary><pre class="mono" id="llms-raw"></pre></details>
</div>
<div style="margin-top:8px;color:#666;font-size:12px">
※ robots.txt は正式仕様(RFC 9309)ですが、llms.txt は提案段階の慣行です。
</div>
<script src="popup.js"></script>
</body>
</html>popup.js
// 使い方: 拡張のポップアップを開くと、現在タブのオリジンから
// /robots.txt と /llms.txt を取得し、要点を要約表示します。
const notableAgents = [
"GPTBot", // OpenAI
"Googlebot", // Google
"bingbot", // Microsoft
"CCBot", // Common Crawl
"Anthropic", // Claude系(表記ゆれ対策で部分一致)
"PerplexityBot" // Perplexity
];
document.getElementById('reload').addEventListener('click', () => run());
async function run() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const url = new URL(tab.url);
const origin = url.origin;
document.getElementById('host').textContent = `対象サイト: ${origin}`;
const robotsUrl = origin + "/robots.txt";
const llmsUrl = origin + "/llms.txt";
const robots = await fetchText(robotsUrl);
const llms = await fetchText(llmsUrl);
// robots.txt
const robotsRawEl = document.getElementById('robots-raw');
const robotsSumEl = document.getElementById('robots-summary');
if (robots.ok && robots.text.trim()) {
robotsRawEl.textContent = robots.text;
const parsed = parseRobots(robots.text);
robotsSumEl.innerHTML = renderRobotsSummary(parsed);
} else if (robots.status === 404) {
robotsRawEl.textContent = "";
robotsSumEl.innerHTML = `<span class="warn">robots.txt は見つかりませんでした(404)。</span>`;
} else if (!robots.ok) {
robotsRawEl.textContent = "";
robotsSumEl.innerHTML = `<span class="err">robots.txt の取得に失敗: HTTP ${robots.status}</span>`;
} else {
robotsRawEl.textContent = "";
robotsSumEl.innerHTML = `<span class="warn">robots.txt は空でした。</span>`;
}
// llms.txt
const llmsRawEl = document.getElementById('llms-raw');
const llmsSumEl = document.getElementById('llms-summary');
if (llms.ok && llms.text.trim()) {
llmsRawEl.textContent = llms.text;
const summary = summarizeLlms(llms.text);
llmsSumEl.innerHTML = summary;
} else if (llms.status === 404) {
llmsRawEl.textContent = "";
llmsSumEl.innerHTML = `<span class="warn">llms.txt は見つかりませんでした(404)。</span>`;
} else if (!llms.ok) {
llmsRawEl.textContent = "";
llmsSumEl.innerHTML = `<span class="err">llms.txt の取得に失敗: HTTP ${llms.status}</span>`;
} else {
llmsRawEl.textContent = "";
llmsSumEl.innerHTML = `<span class="warn">llms.txt は空でした。</span>`;
}
}
async function fetchText(u) {
try {
const res = await fetch(u, { method: "GET" });
const text = await res.text().catch(() => "");
return { ok: res.ok, status: res.status, text };
} catch (e) {
return { ok: false, status: 0, text: "" };
}
}
// --- robots.txt 簡易パーサ(仕様の主要部にフォーカス) ---
function parseRobots(content) {
const lines = content.split(/\r?\n/).map(l => l.replace(/\s*#.*$/, "").trim()).filter(l => l.length);
const groups = []; // { agents: [..], rules: [{type, value}], sitemaps: [] }
let curAgents = [];
let curRules = [];
let sitemaps = [];
const flush = () => {
if (curAgents.length || curRules.length) {
groups.push({ agents: curAgents.slice(), rules: curRules.slice() });
}
curAgents = [];
curRules = [];
};
for (const line of lines) {
const m = line.match(/^([A-Za-z\-]+)\s*:\s*(.+)$/);
if (!m) continue;
const key = m[1].toLowerCase();
const val = m[2].trim();
if (key === "user-agent") {
// グループ境界は「User-agentが来て、すでに何らかの指示があればflush」
if (curRules.length && curAgents.length) flush();
curAgents.push(val);
} else if (key === "allow" || key === "disallow" || key === "crawl-delay") {
curRules.push({ type: key, value: val });
} else if (key === "sitemap") {
sitemaps.push(val);
} else {
// その他は保持しない(拡張ディレクティブは多数あるが要約UIの簡潔さを優先)
}
}
flush();
// エージェント別にまとめ直し
const byAgent = {};
for (const g of groups) {
for (const a of g.agents) {
if (!byAgent[a]) byAgent[a] = { allow: [], disallow: [], crawlDelay: [] };
for (const r of g.rules) {
if (r.type === "allow") byAgent[a].allow.push(r.value);
if (r.type === "disallow") byAgent[a].disallow.push(r.value);
if (r.type === "crawl-delay") byAgent[a].crawlDelay.push(r.value);
}
}
}
return { byAgent, sitemaps };
}
function renderRobotsSummary(parsed) {
const agents = Object.keys(parsed.byAgent);
const star = parsed.byAgent["*"];
const hasStar = !!star;
const sitemapCount = parsed.sitemaps.length;
// 気になりがちなエージェントを抽出
const picked = [];
for (const name of notableAgents) {
const key = agents.find(a => a.toLowerCase().includes(name.toLowerCase()));
if (key) picked.push([name, key, parsed.byAgent[key]]);
}
// 表示
let html = "";
html += `<div>定義済み User-agent 数: <strong>${agents.length}</strong>、Sitemap: <strong>${sitemapCount}</strong> 件</div>`;
if (sitemapCount) {
html += `<div class="mono">${parsed.sitemaps.map(s => `Sitemap: ${s}`).join("\n")}</div>`;
}
if (hasStar) {
html += `<div style="margin-top:6px"><strong>共通ルール(User-agent: *)</strong><div class="mono">Allow: ${star.allow.slice(0,5).join(", ") || "(なし)"}\nDisallow: ${star.disallow.slice(0,5).join(", ") || "(なし)"}${star.disallow.length>5?" …":""}</div></div>`;
}
if (picked.length) {
html += `<div style="margin-top:6px"><strong>主なクローラー</strong>`;
for (const [label, actual, r] of picked) {
html += `<div style="margin-top:4px"><em>${label}</em>(記載: <code>${actual}</code>)<div class="mono">Allow: ${r.allow.slice(0,5).join(", ")||"(なし)"}\nDisallow: ${r.disallow.slice(0,5).join(", ")||"(なし)"}${r.disallow.length>5?" …":""}${r.crawlDelay.length?`\nCrawl-delay: ${r.crawlDelay.join(", ")}`:""}</div></div>`;
}
html += `</div>`;
}
if (!hasStar && picked.length===0) {
html += `<div class="warn" style="margin-top:6px">共通ルールや主要クローラーの個別指定は見当たりません。</div>`;
}
html += `<div style="margin-top:6px;color:#666;font-size:12px">※ robots.txt は“遵守が期待される”プロトコルであり、認可やアクセス制御の代替ではありません。</div>`;
return html;
}
// --- llms.txt の簡易要約(提案段階の慣行を想定) ---
function summarizeLlms(text) {
// 代表的な書式(見出し/リンク箇条書き)をざっくり抽出
const title = (text.match(/^#\s+(.+)$/m) || [,""])[1].trim();
const sections = [...text.matchAll(/^##\s+(.+)$/gm)].map(m => m[1].trim());
const links = [...text.matchAll(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g)].map(m => ({ label: m[1], url: m[2] }));
const bullets = text.split(/\r?\n/).filter(l => /^\s*[-*+]\s+/.test(l));
let html = "";
if (title) html += `<div>タイトル: <strong>${escapeHtml(title)}</strong></div>`;
html += `<div>セクション数: <strong>${sections.length}</strong>、掲載リンク数: <strong>${links.length}</strong>、箇条書き数: <strong>${bullets.length}</strong></div>`;
if (sections.length) {
html += `<div style="margin-top:6px"><strong>主なセクション</strong><div class="mono">${sections.slice(0,6).join("\n")}${sections.length>6?"\n…":""}</div></div>`;
}
if (links.length) {
html += `<div style="margin-top:6px"><strong>代表的なリンク</strong><div class="mono">${links.slice(0,5).map(l => `${l.label}: ${l.url}`).join("\n")}${links.length>5?"\n…":""}</div></div>`;
}
html += `<div style="margin-top:6px;color:#666;font-size:12px">※ llms.txt は標準化途上の“提案”。対応はサイト/ツールごとに異なります。</div>`;
return html;
}
function escapeHtml(s){ return s.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
run();chrome://extensionsを開く → 右上 デベロッパーモード ON- パッケージ化されていない拡張機能を読み込む → 上記フォルダを選択
- 任意のページで拡張アイコンを開く → robots.txt / llms.txt の要点が表示されます
https://example.com/robots.txtなどで表示確認User-agent: GPTBotの有無やSitemap:の列挙が要約に反映されます(GPTBot は OpenAI 公開情報に基づくエージェント例)。(OpenAI Platform)
- robots.txt は正式な Robots Exclusion Protocol(RFC 9309) に基づき、クローラーがどのパスへアクセスできるかの方針を示します。ただしアクセス制御ではなく、機密URLを隠す用途には不適(列挙されるためむしろ露出します)。(RFCエディタ)
- Google公式ドキュメントも robots.txt の解釈と基本例を公開しています。(Google for Developers)
- llms.txt は 2024年公開の /llms.txt 提案(Jeremy Howard)に基づく“慣行”で、LLMが推論時に参照しやすいようサイトの構造・要点・リンクを示します。Mintlifyなどドキュメントホスティングが自動生成をサポート。標準化は未了で、実装/対応はサービス次第です。(llms-txt)
- OpenAIの GPTBot など主要AIクローラーは robots.txt を読む前提のため、現実装ではAI向けの可否指定は robots.txt で行うのが確実です(例:
User-agent: GPTBot/Disallow: /など)。(OpenAI Platform)
- ブックマークレット(同一オリジンから取得) (ページで押すと簡易要約のダイアログを出す・超簡易版)
javascript:(async()=>{const o=location.origin;const f=async(p)=>{try{const r=await fetch(o+p);return [r.status,await r.text()]}catch(e){return[0,""]}};const [sr,rt]=await f("/robots.txt");const [sl,lt]=await f("/llms.txt");alert([
`robots.txt: ${sr}`, rt?rt.slice(0,400):(sr===404?"(なし)":"(取得失敗)"),
`\n\nllms.txt: ${sl}`, lt?lt.slice(0,400):(sl===404?"(なし)":"(取得失敗)")
].join("\n"));})();- CLIワンライナー(bash + curl) (GPTBotブロックの有無をざっくり確認)
# 前提: bash, curl
u="https://example.com"; curl -fsS "$u/robots.txt" | awk 'BEGIN{IGNORECASE=1}/^User-agent:/ {ua=$0 ~ /GPTBot/} ua && /^Disallow:/ {print "GPTBot", $0}'- Node/denoのスクリプトで柔軟に解析
Nodeの
node-fetch等で取得 → 上のロジックを拡張(User-agentごとの集計、レポート出力) ※将来 llms.txt 向けに Markdown をパースして、セクション/リンクの表形式エクスポートも容易。
- robots.txtは拘束力のあるアクセス制御ではありません。非公開にしたいパスは認証や適切なACLで保護してください。(RFCエディタ)
- llms.txtは“提案”段階です。対応ツールが増えつつある一方、現時点では各社の解釈・実装が揃っていません(Mintlifyが自動生成を公開)。(llms-txt)
- AIクローラーの銘柄は増減します。最新のユーザーエージェント一覧を参照して随時拡張してください(例:コミュニティのリスト)。(GitHub)
必要なら、この拡張にオプションページ(チェックボックスで「注目するクローラー」を編集)や、日本語の自然文要約をもっと賢くするロジック(例:ルールの衝突検知、サイトマップの数やパターン分析)も足します。