◉ANTIGRAVITY LABEN
記事一覧/Tips & 活用術
✦ Tips & 活用術/2026-10-17中級

検索の管理画面に「ページにリダイレクトがあります」と出たとき、直すものと放っておくものを分けた基準

Search Console の「ページにリダイレクトがあります」を、自分のサイトで仕分けた記録です。http→https・www・末尾スラッシュ・言語の振り分けは想定内、チェーン・ループ・転送先のエラーや noindex は要対応という線引きと、転送を一つずつ辿って分類する Python スクリプト、素の要求では見えない言語依存の転送を二通りの要求で見つける方法まで書き残します。

Search Consoleリダイレクト301言語判定個人開発109Antigravity387

✦ プレミアム記事

検索の管理画面を開くと、「インデックスに登録されていない理由」の欄に「ページにリダイレクトがあります」という項目が並んでいました。件数のところに数字が付いていると、どうしても直さなければいけないものに見えてしまいます。

私自身、自分の Web サイトでこの通知を見たとき、最初は転送を全部洗い出して消そうと考えました。ところが、一覧の URL を辿ってみると、私が意図して置いた言語判定の転送が含まれており、これは対応不要と判断できました。

ここでは、転送を「対応不要」「束ねる」「直す」に分けた基準と、その仕分けを自動で行ったスクリプトを書き残します。エージェントに調査を頼む場合の指示文も添えました。

先にお伝えしたいのは、通知の意味です

この項目が示しているのは、「その URL は転送元なので登録の対象にならず、転送先のほうが対象になる」という状態です。転送そのものが悪いという意味ではありません。

だからこそ、見るべきは転送元ではなく、転送先と、そこへ着くまでの道のりです。私は次の表で仕分けました。

転送の種類判断理由
http から https への統一対応不要転送元が残るのは意図どおりです
www の有無の統一対応不要どちらか一方に寄せるための転送です
末尾スラッシュの統一対応不要同じ中身への別表記を一本化しています
言語による振り分け対応不要(ただし 302/307 か確認)利用者ごとに行き先が変わるため、一時的な転送のほうが安全です
2 回以上の連続転送1 回に束ねる経由のたびに到達が遅れ、途中の設定変更で壊れやすくなります
ループ直すどこにも着けません
転送先が 404 や 5xx直す転送元の価値を捨てています
転送先が noindex意図を確認わざとなら正しく、うっかりなら登録の機会を逃します

大事なのは、見た目の件数ではなく、この表のどの行に当たるのかを URL ごとに言えることだと考えています。

一覧を一つずつ辿る仕分けスクリプト

管理画面から書き出した URL の一覧を渡すと、転送を自動追従せずに一歩ずつ辿り、上の表の判断を付けて返すスクリプトです。標準ライブラリだけで動きます。

ポイントは二つあります。一つめは、urllib の自動追従を止めて、3xx をそのまま受け取ることです。自動追従に任せると、途中の経由地がすべて見えなくなります。二つめは、同じ URL を素の要求と Accept-Language: en 付きの要求の二通りで辿ることです。理由は、このあと表で説明します。

#!/usr/bin/env python3
"""redirect_triage.py — Search Console の「ページにリダイレクトがあります」を仕分ける。
使い方: python3 redirect_triage.py urls.txt   (1 行 1 URL。管理画面から書き出した一覧)
標準ライブラリのみ。自動追従を止め、1 ホップずつ自分で辿る。"""
import sys, re, urllib.request, urllib.error, urllib.parse
 
class NoFollow(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, *a, **k):  # 追従しない = 3xx をそのまま例外で受ける
        return None
 
OPENER = urllib.request.build_opener(NoFollow)
 
def fetch(url, lang=None):
    """(status, location, headers, body先頭) を返す。ネットワーク失敗は status=0。"""
    req = urllib.request.Request(url, headers={"User-Agent": "redirect-triage/1.0"})
    if lang:
        req.add_header("Accept-Language", lang)
    try:
        with OPENER.open(req, timeout=10) as r:
            return r.status, None, r.headers, r.read(4096).decode("utf-8", "ignore")
    except urllib.error.HTTPError as e:
        return e.code, e.headers.get("Location"), e.headers, ""
    except Exception:
        return 0, None, {}, ""
 
def chain(url, lang=None, limit=6):
    hops, seen = [], set()
    while len(hops) < limit:
        if url in seen:
            return hops, "loop"
        seen.add(url)
        st, loc, hd, body = fetch(url, lang)
        hops.append((url, st))
        if st in (301, 302, 303, 307, 308) and loc:
            url = urllib.parse.urljoin(url, loc)
            continue
        noindex = "noindex" in (hd.get("X-Robots-Tag", "") if hd else "").lower() \
            or re.search(r'<meta[^>]+name=["\']robots["\'][^>]+noindex', body, re.I)
        return hops, ("noindex" if noindex and st == 200 else st)
    return hops, "too-long"
 
def classify(src, hops, end):
    if end == "loop":      return "NG", "ループしています"
    if end == "too-long":  return "NG", "転送が長すぎます"
    if isinstance(end, int) and (end >= 400 or end == 0):
        return "NG", f"転送先が {end} を返しています"
    if end == "noindex":   return "CHECK", "転送先が noindex です(意図したものか確認)"
    if len(hops) >= 3:     return "FIX", f"{len(hops)-1} 回転送されます(1 回に束ねる)"
    if len(hops) == 1:     return "OK", "転送なし"
    a, b = urllib.parse.urlsplit(src), urllib.parse.urlsplit(hops[-1][0])
    if a.scheme != b.scheme and (a.netloc, a.path) == (b.netloc, b.path):
        return "OK", "http→https の統一"
    if a.netloc.replace("www.", "") == b.netloc.replace("www.", "") and a.netloc != b.netloc:
        return "OK", "www の有無の統一"
    if a.path.rstrip("/") == b.path.rstrip("/") and a.path != b.path:
        return "OK", "末尾スラッシュの統一"
    return "CHECK", f"別の URL へ転送: {b.path}"
 
def main(path):
    for src in [l.strip() for l in open(path) if l.strip()]:
        hops, end = chain(src)                    # 検索ロボットに近い素の要求
        hops_en, end_en = chain(src, lang="en")   # 英語ブラウザ相当
        level, note = classify(src, hops, end)
        if level == "OK" and (hops[-1][0] != hops_en[-1][0]):
            first = hops_en[0][1]
            level = "OK" if first in (302, 303, 307) else "FIX"
            note = "言語による振り分け" + ("(一時転送・想定内)" if level == "OK"
                   else f"({first} は端末に覚えられるので 302/307 へ)")
        elif len(hops) == 1 and len(hops_en) > 1:
            first = hops_en[0][1]
            level = "OK" if first in (302, 303, 307) else "FIX"
            note = "言語による振り分け" + ("(素の要求は転送なし・想定内)" if level == "OK"
                   else f"({first} は端末に覚えられるので 302/307 へ)")
        print(f"{level:5} {src}\n      -> {note}")
 
if __name__ == "__main__":
    main(sys.argv[1])

出力の先頭に付く「OK」「CHECK」「FIX」「NG」は、それぞれ対応不要、意図を確認、束ねる、直す、に対応しています。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦通知の一覧を前に、どの転送は対応不要で、どの転送だけ直すのかを、転送元と転送先の差から判断できるようになります
✦言語判定の転送を 301 で返していないかを、素の要求と Accept-Language 付きの要求の二通りで確かめられるようになり、利用者の端末に古い転送が居座る事態を先に防げるようになります
✦エージェントに「リダイレクトを直して」と頼む前に、触ってよい転送と触ってはいけない転送を指示文に書き込めるようになり、想定内の転送まで消されて手戻りする時間を省けるようになります
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

✦ Tips & 活用術2026-10-15
画像が壊れて見えたのは、確かめ方のせいでした。iframe 検証を疑う順番と、実ページの currentSrc
別タブの iframe に実ページを差し込んで画像を点検し、「読み込まれていない」と誤診した失敗の記録です。非表示の枠では描画幅 0 のまま naturalWidth が 0 になり、遅延読み込みは未開始のまま残ります。手元で再現した数値と、実ページで currentSrc と描画幅を見る検査スクリプト、置換を正規表現で機械的に行ったときの落とし穴まで書き残します。
✦ Tips & 活用術2026-04-27
Antigravity で CHANGELOG.md を git ログから半自動で書く — 個人開発でも意思決定の履歴を残す
半年前の自分の判断が思い出せない — 個人開発でよくある問題です。Antigravity に git log を読ませて CHANGELOG.md のドラフトを作る半自動ワークフローを、実際のスクリプト付きで紹介します。
✦ Tips & 活用術2026-10-09
長いメモを貼る前に、頼みごとを5行へ畳む — 手戻りが減った依頼文の整え方
打ち合わせメモをそのままエージェントに貼ると、返ってくる結果がぼんやりします。目的・材料・範囲・完成形・確認の5行に畳む手順と、5行そろっているかを確かめる小さなスクリプトをお伝えします。