Google AdSenseの審査で、このサイトは「有用性の低いコンテンツ」と判定されました。
この記事では、同じ判定を受けた人が最初に確認するとよい点と、Python(Markdown + Jinja2)で作った静的サイトでの実装、公開後の確認コマンド、Claudeなどの生成AIに作業を頼むときのプロンプト例をまとめます。
先に結論を書くと、今回は記事を増やさずに次の3つだけを行いました。
- 中身のないページとカテゴリ一覧を
noindex, followにし、sitemap.xmlから外した - ナビゲーションから記事の少ないページへのリンクを外した
- トップページと記事一覧に、実際に作って試した記事への導線を追加した
再審査の結果はまだ出ていません。 この対応で合格するかは分からないため、結果が出たらこの記事に追記します。
「有用性の低いコンテンツ」で最初に確認すること
判定の理由は具体的には示されません。そこで、審査する人やクローラーから見て「中身が少ないページ」がどこにあるかを、次の順で確認しました。
| 確認すること | このサイトの状態(対応前) |
|---|---|
| 「まだ記事はありません」だけのページがナビやsitemapにないか | 「投資」ページが本文なしでナビとsitemapに入っていた |
| カテゴリ・タグ一覧がsitemapに入っていないか | カテゴリ一覧7ページがすべてsitemapに入っていた |
| ナビが記事の少ないページへ直接リンクしていないか | 「開発」が記事1本のカテゴリへリンクしていた |
| 更新記録だけの短い記事が多くないか | サイト開設のあいさつや、表示変更の記録など3本あった |
| トップページでサイトの内容が伝わるか | 「アプリ開発、技術、投資、日々の記録」と抽象的だった |
| プライバシーポリシー・お問い合わせ・運営者情報があるか | すべてあった |
| ads.txt が置かれているか | /ads.txt が404だった(iOSアプリ用の app-ads.txt のみ) |
ads.txt は今回の作業範囲外として変更していません。AdSense管理画面の案内に従って別途判断します。
逆に、今回は次のことをしないと決めました。
- 生成AIで記事を量産する
- 既存記事を機械的に長くする
- 中身のないFAQやキーワードを足す
- 確認していない検証結果を書く
文字数や記事数を増やしても、空のページや一覧ページが主要なコンテンツとして見えている状態は変わらないためです。
実装方法(Python + Jinja2の静的サイト)
このサイトは、Markdownの記事をPythonのビルドスクリプトでHTMLに変換しています。WordPressなどの場合は、テーマやSEOプラグインのnoindex設定とsitemap設定が同じ役割になります(今回は確認していないので手順は省きます)。
1. robots metaをページ単位で出す
共通テンプレートの <head> に、ページから robots が渡されたときだけmetaを出す1行を追加しました。
{% if robots %}<meta name="robots" content="{{ robots }}">{% endif %}
<link rel="canonical" href="{{ canonical_url }}">
ビルド側では、noindexにしたいページだけに値を渡します。
NOINDEX_FOLLOW = "noindex, follow"
for category in categories:
write_page(
"blog.html",
OUTPUT / "blog/category" / category / "index.html",
posts=category_posts,
active_category=category,
robots=NOINDEX_FOLLOW,
url=f"/blog/category/{category}/",
)
noindex だけでなく follow を付けているのは、ページ自体は検索結果に出さなくても、そこから記事へのリンクはたどってもらうためです。カテゴリページは削除せず、記事からのリンクも残しています。読者は今までどおり一覧で記事を探せます。
2. 空のページは記事数で自動的に切り替える
「投資」ページは、将来記事が増えたら検索対象に戻したいページです。そこで、記事数がしきい値に届くまでは nav非表示・noindex・sitemap除外になるようにしました。
INVESTMENT_INDEX_MIN_POSTS = 3
investment_posts = [post for post in posts if post["category"] == "投資"]
investment_indexable = len(investment_posts) >= INVESTMENT_INDEX_MIN_POSTS
write_page(
"investment.html",
OUTPUT / "investment/index.html",
posts=investment_posts,
robots=None if investment_indexable else NOINDEX_FOLLOW,
url="/investment/",
)
ナビゲーションのテンプレートでも同じフラグを使います。
{% set investment_link = [("/investment/", "投資")] if show_investment_nav else [] %}
{% for href, label in [("/", "ホーム"), ("/blog/", "記事"), ("/apps/", "アプリ")] + investment_link + [("/about/", "運営者情報"), ("/contact/", "お問い合わせ")] %}
<a href="{{ href }}">{{ label }}</a>
{% endfor %}
URLは残しているので、外部からリンクされていても404にはなりません。
3. sitemap.xmlはindex対象のページだけにする
noindexにしたページをsitemapに入れたままにすると、「検索結果に出さないで」と「このページを見て」を同時に伝えることになります。noindexの集合を先に作り、sitemapから外しました。
noindex_urls = {f"/blog/category/{category}/" for category in categories}
if not investment_indexable:
noindex_urls.add("/investment/")
static_urls = ["/", "/apps/", "/investment/", "/blog/", "/about/", "/privacy/", "/contact/"]
sitemap_entries = (
[(url, None) for url in static_urls if url not in noindex_urls]
+ [(post["url"], post.get("updated") or post["date"]) for post in posts]
)
<lastmod> は、記事のfrontmatterにある updated(なければ公開日の date)を使い、記事だけに付けています。固定ページは正確な更新日を持っていないため付けていません。ビルドした日付を全ページに入れると、実際には変わっていないページまで更新されたように見えてしまうからです。
def render_sitemap(entries):
lines = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
]
for url, lastmod in entries:
lastmod_xml = f"<lastmod>{lastmod}</lastmod>" if lastmod else ""
lines.append(f" <url><loc>{html.escape(SITE_URL + url)}</loc>{lastmod_xml}</url>")
return "\n".join(lines + ["</urlset>"]) + "\n"
robots.txt は変更していません。robots.txtで Disallow にするとクローラーがページを読めなくなり、noindexのmetaも見てもらえないためです。
4. ビルド時にnoindexとsitemapの食い違いを検出する
手作業でnoindexを足していくと、いつか記事にnoindexが付いたり、sitemapと食い違ったりします。そこでビルドの最後に、生成したHTMLとsitemapを読み直して次の条件を検証しています。
- noindexのページが、想定した集合(カテゴリ一覧と投資ページ)と完全に一致する
- noindexのページがsitemapに入っていない
- すべての記事がindex可能で、sitemapに入っている
- sitemap.xmlがXMLとしてパースできる
ROBOTS_META_PATTERN = re.compile(r'<meta\s+name="robots"\s+content="([^"]*)"\s*/?>', re.I)
found_noindex = set()
for page in OUTPUT.rglob("*.html"):
if ROBOTS_META_PATTERN.findall(page.read_text(encoding="utf-8")):
found_noindex.add(page_url_for_output(page))
if found_noindex != noindex_urls:
raise ValueError(f"noindex pages do not match: {sorted(found_noindex ^ noindex_urls)}")
sitemap_root = ElementTree.parse(OUTPUT / "sitemap.xml").getroot()
listed = [e.text.removeprefix(SITE_URL)
for e in sitemap_root.iter("{http://www.sitemaps.org/schemas/sitemap/0.9}loc")]
if set(listed) & noindex_urls:
raise ValueError("noindex page listed in sitemap")
5. 強い記事への導線をslugのリストで管理する
トップページと記事一覧の上部に「実際に作って試した記事」を6本表示しました。選んだのは、エラーメッセージから原因と修正まで書いた記事や、実機の配線・コードがある記事です。
表示する記事はslugのリストで管理し、存在しないslugを書いたらビルドを失敗させます。記事のslugを変えたときに、おすすめ欄だけ壊れたまま公開されるのを防ぐためです。
FEATURED_POST_SLUGS = (
"boatrace-raspberry-pi-systemd-venv",
"esp32-arduino-sensors-oled",
# ...
)
def featured_posts(posts):
by_slug = {post["slug"]: post for post in posts}
unknown = [slug for slug in FEATURED_POST_SLUGS if slug not in by_slug]
if unknown:
raise ValueError(f"unknown featured post: {', '.join(unknown)}")
return [by_slug[slug] for slug in FEATURED_POST_SLUGS]
実際、最初はファイル名をslugと勘違いして unknown featured post: keiba-ai-02-data-leakage でビルドが止まりました。frontmatterの slug は keiba-ai-data-leakage だったので、そちらに直しています。
あわせて、トップページの説明文を「iOSアプリ、AI、Python、Raspberry Pi、ESP32などの個人開発で、実際に作って試したことや、失敗から分かったことを記録しています。」に変えました。
公開後に確認したコマンド
ビルド時の検証とは別に、公開したサーバーから返ってくる内容も確認しました。
# robots metaを確認(記事はなし、カテゴリ・空ページは noindex, follow)
curl -s https://example.com/blog/category/sample/ | grep -o '<meta name="robots"[^>]*>'
curl -s https://example.com/blog/sample-post/ | grep -o '<meta name="robots"[^>]*>' || echo "robots metaなし"
# HTTPヘッダー側で X-Robots-Tag が付いていないか
curl -sI https://example.com/blog/ | grep -i x-robots-tag || echo "X-Robots-Tagなし"
# sitemapがXMLとして読めるか、除外したURLが残っていないか
curl -s https://example.com/sitemap.xml | python3 -c '
import sys, xml.etree.ElementTree as ET
root = ET.fromstring(sys.stdin.read())
locs = [e.text for e in root.iter("{http://www.sitemaps.org/schemas/sitemap/0.9}loc")]
print("URL数", len(locs))
print("category残り", [u for u in locs if "/category/" in u])
'
このサイトでの結果は次のとおりです。
- トップ・記事一覧・記事・アプリ・プライバシー・お問い合わせはHTTP 200で、robots metaなし
- カテゴリ一覧7ページと投資ページはHTTP 200で
noindex, follow - X-Robots-Tagヘッダーはなし
- sitemap.xmlは51件から43件になり、カテゴリ一覧と投資ページが外れた
- AdSenseのサイト共通タグは全ページの
<head>に1つだけで、変更していない
このあと、Search Consoleでsitemap.xmlを再送信し、URL検査でnoindexページが除外されることを確認する予定です。
Claudeなど生成AIに依頼するときのプロンプト例
今回の作業は、Claude Codeに依頼して進めました。うまくいったのは、「何を変えるか」と同じくらい「何を壊してはいけないか」「自動でやってはいけないこと」を先に書いたことです。実際に使った依頼文を、他のサイトでも使える形にしたものを載せます。
例1: 変更前に調査だけ頼む
いきなり修正させると、既存の仕組みと違う方法でsitemapやmetaを足されることがあります。最初は調査だけを頼みます。
Google AdSenseの審査で「有用性の低いコンテンツ」と判定されました。
修正の前に、このサイトのコードを調査して、次の項目を報告してください。
まだファイルは変更しないでください。
- 使っているフレームワーク / 静的サイト生成の方法
- グローバルナビゲーションの定義場所
- sitemap.xml と robots.txt の生成方法
- 各ページの title / description / robots meta の管理方法
- 記事一覧・カテゴリページ・記事データの生成方法
- AdSenseコード、CMP、ads.txt に関係するコードの場所
例2: サイト構造を修正してもらう
調査結果をもとに、既存の構成に合わせて最小限の変更で次を行ってください。
1. 記事がない「/investment/」ページ
- グローバルナビから外す
- <meta name="robots" content="noindex, follow"> を付ける
- sitemap.xml から外す
- ページは削除せず、404にしない
- 記事が増えたら index 可能に戻せる構成にする
2. カテゴリ一覧ページ
- noindex, follow を付け、sitemap.xml から外す
- ページと、記事からカテゴリへのリンクは残す
3. 個別記事は index 可能なまま sitemap に含める
4. lastmod は記事の正確な更新日が取れる場合だけ付ける。
取れない場合は付けない
5. トップページか記事一覧に、実装・検証が具体的な既存記事を
4〜6本「おすすめ記事」として表示する
次のものは変更しないでください。
- AdSenseのサイト確認コード・広告コード、Google CMP、ads.txt
- プライバシーポリシー、お問い合わせ機能
- Search Console、Google Analyticsの設定
- robots.txt(AdSenseクローラーやGooglebotを新しくブロックしない)
例3: 記事の整理は監査表だけにする
AIに「低品質な記事を整理して」と頼むと、削除や書き換えまで進むことがあります。判断は人間がするので、表を作るところで止めてもらいます。
既存記事をすべて読み、次のような記事を抽出してください。
- サイト更新の記録だけの記事
- 数百文字程度しかない記事
- 同じプロジェクトを細かく分けすぎている記事
- 他の記事と内容がほぼ重複する記事
結果は LOW_VALUE_CONTENT_AUDIT.md に
| URL | 記事タイトル | 問題点 | 推奨対応 |
の表で書いてください。推奨対応は「維持 / 加筆候補 / noindex候補 / 他記事との統合候補」のどれかにしてください。
記事の削除・統合・本文の書き換えは行わないでください。
例4: 確認項目を先に渡す
「確認してください」だけだと、ビルドが通ったことしか報告されない場合があります。確認してほしい項目を具体的に並べます。
変更後、次を確認して結果を報告してください。
確認できなかった項目は、確認できなかったと書いてください。
1. /investment/ とカテゴリページが noindex, follow になっている
2. それらが sitemap.xml に含まれていない
3. 個別記事に noindex が付いていない
4. sitemap.xml がXMLとしてパースできる
5. robots.txt が変更前と同じ
6. 内部リンク切れがない
7. AdSenseのタグが全ページの <head> に1つだけある
8. スマホ幅とPC幅で表示が崩れていない
禁止事項もはっきり書く
AdSense対策を頼むと、文章を足して文字数を増やす方向に進みやすいので、次のように書いておきました。
次のことはしないでください。
- AIで記事を大量に生成する
- 全記事を機械的に長くする
- キーワードを不自然に詰め込む
- 内容のないFAQを追加する
- 実際に確認していない数値や検証結果を書く
目的は記事数や文字数を増やすことではなく、既存の良い記事を
読者と検索エンジンが見つけやすい構造にすることです。
プロンプトを書くときのコツ
- 最初は「調査だけ・変更しない」と頼み、報告を読んでから修正を頼む
- 壊してはいけないもの(広告タグ、CMP、問い合わせ機能など)を名前で列挙する
- 削除・統合のように戻しにくい作業は「表にするだけ」で止める
- 「最小限の変更」「既存の構成に合わせて」と書き、独自の仕組みを増やさせない
- 確認項目を番号付きで渡し、確認できなかったものはそう報告してもらう
AIが書いたコードでも、公開後の確認は自分でcurlやSearch Consoleで行うほうが安心です。今回も、AIの報告にあったsitemapの件数(52件)は、確認し直すと51件でした。
まとめ
「有用性の低いコンテンツ」と判定されたとき、記事を増やす前に、空のページや一覧ページを主要なコンテンツとして見せていないかを確認しました。noindex, followとsitemapの整理はビルドスクリプトとテンプレートの小さな変更で済み、ビルド時の検証を入れておけば、あとで記事を追加したときの設定漏れも防げます。
再審査の結果が出たら、この記事に追記します。