一個是爬過決定不收,一個是根本還沒被看到。搞混會做出完全錯誤的動作。附一支掃全站索引狀態的腳本,以及抽驗會錯位的陷阱。
發布於 2026-09-06・約 6 分鐘
我的網站 sitemap 裡有一百多個網址,Google 自然流量兩個月只有 3 次點擊。
去 Search Console 看,狀態欄有兩種訊息:「已檢索 - 目前尚未建立索引」和「Google 目前無法辨識這個網址」。
我一開始把它們當成同一件事的兩種說法。它們不是,而且該做的事完全相反。
「已檢索 - 目前尚未建立索引」 = Googlebot 來過、讀完了、決定不收。它已經對這一頁下過判決。
要做的事:改內容。 給它一個收錄的理由。重新送出索引請求沒有意義——它已經看過了,再看一次還是同一頁。
「Google 目前無法辨識這個網址」 = 這個網址從來沒被爬過。Google 還不知道它存在,或知道但排不到隊。
要做的事:給它理由過來。 內部連結、sitemap、外部連結、手動送出索引請求。這時改內容沒有用,因為沒有人在讀。
我因為搞混這兩個,浪費了兩週在錯的方向上。
7 月上線,全站是 client-only 的 SPA。101 個網址對不執行 JS 的爬蟲來說 body 完全一樣(md5 相同)。
Googlebot 7/9 到 7/13 爬完一輪,全部判「已檢索 - 尚未建立索引」。7/12 起曝光歸零,連續 45 天。同期 AdSense 以「缺乏價值的內容」退件——兩個系統對同一件事給了同一個判斷,這是很強的訊號。
7/29 修好,每頁注入 3000 字以上的靜態內容給爬蟲。Google 沒有回來。
8/19 網址結構從子網域搬到子目錄。新網址 100/100 是「Google 無法辨識」,從未爬取。
到這裡狀態的性質已經變了:舊網址是「爬了不收」,新網址是「還沒被看到」。 我如果繼續照舊結論去改內容,等於在對一批沒有人在讀的頁面做優化。
一個月後再掃一次:108 個網址 → 1 個已索引 / 39 個已爬未收 / 68 個無法辨識。39 個「已爬未收」是全部的工具主頁,68 個「無法辨識」全是程式化產生的子頁。
主頁已經被爬過並決定不收。處置從「給它理由過來」變回「給它理由收錄」。同一個網站、不同批網址、不同階段,答案一直在變——所以這件事沒辦法查一次就結案。
GSC 的 URL Inspection API 一次只能查一個網址。所以很容易寫成「抽 5 個代表性頁面看看」。
我這樣做過,然後被騙了一次。
抽驗回報 /mbti/ 是「已檢索 - 目前尚未建立索引,最後爬取 07:01:23Z」。但完整掃描的結果顯示 /mbti/ 根本沒被爬過——那個時間戳是 /city/ 的。抽驗把一個網址的結果掛到了另一個網址上。
/mbti/
/city/
我先信了抽驗,把結論寫成「爬了但拒絕收錄」,對照完整資料才發現是相反的。而這兩者的處置完全不同。
判斷索引狀態一律讀全站掃描的結果,抽驗只能當「有沒有變化」的提示。
URL Inspection API 有配額(每天每個資源 2000 次查詢,每分鐘 600 次),掃一百多個網址完全夠用。
from google.oauth2 import service_account from googleapiclient.discovery import build import json, time SITE = "sc-domain:example.com" creds = service_account.Credentials.from_service_account_file( "service-account.json", scopes=["https://www.googleapis.com/auth/webmasters.readonly"]) svc = build("searchconsole", "v1", credentials=creds) def inspect(url): body = {"inspectionUrl": url, "siteUrl": SITE, "languageCode": "zh-TW"} r = svc.urlInspection().index().inspect(body=body).execute() idx = r["inspectionResult"]["indexStatusResult"] return { "url": url, "verdict": idx.get("verdict"), # PASS / NEUTRAL / FAIL "coverageState": idx.get("coverageState"), # 中文那句狀態就是這個 "lastCrawlTime": idx.get("lastCrawlTime"), "robotsTxtState": idx.get("robotsTxtState"), "indexingState": idx.get("indexingState"), } urls = [l.strip() for l in open("urls.txt") if l.strip()] state = [] for u in urls: try: state.append(inspect(u)) except Exception as e: state.append({"url": u, "error": str(e)}) time.sleep(0.5) json.dump(state, open("gsc_index_state.json", "w"), ensure_ascii=False, indent=2)
存檔是重點。下次跑的時候跟上次比對,只看變化。 每天盯 108 個網址的絕對狀態沒有意義,會變的才有資訊。
我把它排成每天早上跑一次,只在狀態有變化時推一則通知。實際上大部分日子什麼都不會發生,那正是它該有的樣子。
跑成子程序而不是直接 import:掃 108 個網址要十分鐘的同步請求,直接跑會卡死非同步的事件迴圈。
「已爬未收」的頁重送沒意義,Google 已經看過了。所以我的重送清單原本只放「未爬取」的頁。
後來加了一個例外:內容真的改過的話,前提就變了,那時候重送有意義。
判斷「改過」不能用 sitemap 的 lastmod——我的 sitemap 產生器給工具頁的 lastmod 是 build 當天,每次部署都會刷新,用它會讓所有頁面每次全部進清單,等於沒有篩選。
lastmod
改成讀線上頁面的 JSON-LD dateModified,比 lastCrawl 新才納入。而 dateModified 只有在我實際查證過、標了驗證日期的頁面上才有,剛好就是「內容真的有更新」的那些。
dateModified
lastCrawl
GSC 的「要求建立索引」按鈕沒有 API。 只能在網頁上手動按,每天大約 10 個。任何宣稱可以自動化這件事的工具都是在做別的事。
Google 的 Indexing API 官方只支援 JobPosting 和 BroadcastEvent 兩種型別。 拿它送一般頁面是違規的,不要走那條路。網路上有很多教學在教這個,它可能短期有效,但那是在賭。
主頁被爬過卻不收錄,我一開始假設是技術問題。實抓確認不是:prerender 正常、title 和 description 都在、內文也在。
真正的問題是體質。我把 33 篇工具文章的中文字數列出來:
2686 2712 2734 2751 2766 ... 2871 2886 (32 篇) 3954 (1 篇例外)
全部落在 2686–2886 這個超窄帶。 標準差這麼小,就是「同一個模具壓出來」的信號。這跟 AdSense 用「缺乏價值的內容」退件是同一個判斷。
所以再寫第 34 篇 2700 字的同質文章不會改善任何事。要的是單頁上真的有別處拿不到的東西——一手的查證、實測的數字、別人沒整理過的對照。
這個發現有點苦,因為它否定的是「多產一點內容」這條最省力的路。但字數分佈這種東西騙不了人,也不需要爭論。
標籤:SEO、Google Search Console、索引、API、Python