- date
- entry
- 038
- topic
- infrastructure
- rev
- —
三支 IndexNow 腳本預設送整份 sitemap,被拒絕也回 0
同一套提交邏輯出現在我三支腳本裡:不帶參數就送整份 sitemap,202 印成「submitted」,被拒絕也回 0。對照 IndexNow 的文件和一台假伺服器。
IndexNow 是一個推送協定:網站把新增、修改、刪除的網址 送到一個端點(我的腳本用 JSON 批次 POST),參與的搜尋引擎(Bing、Yandex 等)彼此分享。它不包含 Google,這在〈Search Console 的兩個時間戳〉寫過。
我四個站各有一支提交腳本。這次把四支並排讀,發現有三支沿用同一套提交邏輯:2026 年 7 月 16 日寫第一支(同一天從 curl 改成這個 Python 版本),8 月 22 日、10 月 9 日各沿用一次,細節(sitemap 路徑、輸出的箭頭)略有不同,三支都帶著同樣的三個問題。這篇要談的就是這三個問題:不帶參數時送了什麼、202 被印成什麼、被拒絕時結束碼是多少;最後是第四支的寫法和我會怎麼改。
不帶參數會送什麼
範本的核心是一支內嵌在 shell 裡的 Python(節錄,省略 import 與金鑰宣告):
urls = sys.argv[1:] or re.findall(r'<loc>(.*?)</loc>', open("public/sitemap.xml").read())
payload = {"host": "www.yutuo-tech.com", "key": key,
"keyLocation": f"https://www.yutuo-tech.com/{key}.txt", "urlList": urls}
有給網址就送那幾個,沒給就送 sitemap 裡符合這個 regex 的每一個 <loc>。用法說明的第一行就是不帶參數的寫法,註解寫「提交 sitemap 全部 URL」。2026 年 10 月 13 日三個站 repo 裡已 commit 的 sitemap 大小:
| 站 | 腳本寫成 | 不帶參數時送出 |
|---|---|---|
| yutuo-tech.com | 2026-07-16 | 413 個網址 |
| 一個外銷站 | 2026-08-22 | 1,920 個網址 |
| 一個新站 | 2026-10-09 | 15 個網址 |
IndexNow 的 FAQ 有一題就是「可以用 IndexNow 送全站網址嗎」,回答是:它是用來通知最近新增、修改或刪除的網址,「not designed for submitting every URL on your site at once」;如果整站內容最近都更新過,例如搬家或改版之後,一次送全部可以接受;平常的發現與長期收錄用 XML sitemap。另一題說同一個網址一天不要送很多次,除非內容有實質改變。
所以範本的預設值不篩選變更,容易導向 FAQ 不建議的日常全站重送。假設在 1,920 個網址的那個站,每發一篇文章就跑一次不帶參數的版本,那一次真正更新的可能只有新文章和幾個列表頁,其餘一千九百多個網址也會一起送出(我沒有這三個站實際送了幾次的紀錄,這是照預設值推的)。
202 被印成「submitted」
範本處理成功的那一行:
r = urllib.request.urlopen(req)
print("HTTP", r.status, "→ submitted", len(urls), "URLs")
200 和 202 都會正常拿到回應、走到這裡(HTTP 錯誤會變成 HTTPError,連線失敗則是 URLError 之類的例外),印出來的狀態碼不同,標籤都是「submitted 413 URLs」。IndexNow 的文件把兩者分開:200 是「URL submitted successfully」,202 是「URL received. IndexNow key validation pending」。FAQ 補充說第一次提交可能拿到 202,搜尋引擎會先驗證金鑰再處理內容。
也就是說,看到 202 時,金鑰檔對不對還沒有答案,而共用的「submitted」標籤沒有說出這一點。FAQ 自己的範例程式也把 200、202 一起當成功,印的是「URLs submitted successfully」。另外,200 和 202 都不代表網址已經被抓取或收錄。
被拒絕也回 0
範本把 HTTPError 接住、印出狀態碼和回應,然後讓程式正常結束。我寫了一台十行左右的假伺服器,依參數回指定的狀態碼,把範本的端點換成它(提交與例外處理的邏輯不動,端點、host、金鑰和 sitemap 的讀法換成測試用的),送 yutuo-tech.com 那份 413 個網址的 sitemap(整理後的輸出與結束碼):
HTTP 200 -> submitted 413 URLs exit=0
HTTP 202 -> submitted 413 URLs exit=0
HTTP 403 … exit=0
HTTP 422 … exit=0
HTTP 429 … exit=0
(伺服器沒開) exit=1
文件裡 403 是金鑰無效(找不到金鑰檔,或檔案裡沒有那個金鑰),422 是網址不屬於這個 host 或金鑰格式不對,429 是請求太多、可能被當成垃圾。這三種被拒絕的情況,結束碼都是 0;這組測試裡只有伺服器沒開的那次是 1(sitemap 讀不到之類其他沒被接住的例外,也會是非零)。假伺服器回的 body 是我自己寫的,只能證明腳本的控制流程,不代表 IndexNow 實際回什麼。
目前三個站的部署腳本都沒有呼叫它(我 grep 過),也沒有執行紀錄能說明實際怎麼用;如果是手動跑、看輸出,風險就在「看」:一行 HTTP 403 夾在一長串輸出裡,結束碼又是 0,可能被略過。哪天有人把它接進只檢查結束碼的部署流程,提交失敗就會被判成成功。
當天抓得到四站的金鑰檔
既然 403 可能被略過,我順手確認了金鑰檔。IndexNow 的文件要求金鑰 8 到 128 個字元,只能有大小寫字母、數字和連字號;建議的做法是在網站根目錄放一個 UTF-8 的 {key}.txt,內容就是金鑰本身(也可以用 keyLocation 指到同一個 host 的其他位置,但那樣只能提交該路徑底下的網址)。四個站的金鑰檔,10 月 13 日用 curl 抓:都回 200,內容和檔名一致。這只說明當天抓得到,不代表搜尋引擎已經驗證成功。只有一個細節:yutuo-tech.com 的金鑰檔在 www 底下,apex 網域的同一個路徑回 301;範本的 host 和 keyLocation 都寫 www,對得上。
第四支的寫法
2026 年 9 月 12 日寫的第四支(一個資料庫站,sitemap 索引底下約兩萬個網址,大部分由資料庫產生)沒有沿用範本:
- 預設只印、不送。有網址時預設只列出來,要加
--submit才真的送出,因為通知外部服務是收不回的動作;一個網址都沒有就直接報錯。 - 沒有「送全部」的選項。要送什麼得自己給網址(
--url或--stdin),或用--since <git ref>讓 git 列出從那個 ref 到HEAD新增或修改過的文章(不含刪除、不含未 commit 的改動),再依改動的語言換算成網址;資料庫產生的頁面不走這條路。 - 金鑰從檔案讀。程式去
web/public/找唯一一個金鑰檔(它只認 16 到 64 個小寫十六進位字元的檔名,比協定允許的窄),檢查內容和檔名相同,不在程式裡另寫一份。 - 真的送出時,只有 200、202 回 0。其他狀態碼回 1;分批送時只要一批失敗,整體就回 1。
它的註解裡有一句「送沒有變動的 URL 會被降低信任」。我這次回去讀 IndexNow 的文件與 FAQ,沒有找到這個說法;FAQ 寫的是「不要一天送很多次」「不是設計來一次送全站」。那句話應該改成照文件的寫法,不要比原文強。
範本要改的三件事:不帶參數時不要讀 sitemap,改成報錯並印出用法;202 印成「已收到,金鑰驗證中」而不是「submitted」;200、202 以外的狀態碼都 sys.exit(1)。下面這段取代範本裡從 urls = 到結尾的部分,key、payload、req 的設定照舊放在中間;用法那行的腳本路徑各站不同,照各自的改。
urls = sys.argv[1:] # 不再退回讀 sitemap
if not urls:
sys.exit("用法:bash scripts/indexnow-ping.sh https://… [https://…]\n"
"這支只送明確指定的新增、修改、刪除網址,不讀 sitemap。")
# …(key、payload、req 的設定同原本)
try:
r = urllib.request.urlopen(req, timeout=30)
label = {200: "submitted", 202: "received, key validation pending"}.get(r.status)
print("HTTP", r.status, label or "unexpected", len(urls), "URLs")
sys.exit(0 if label else 1)
except urllib.error.HTTPError as e:
print("HTTP", e.code, e.read().decode(errors="replace")[:300])
sys.exit(1)
如果只記得一件事
抄範本會連它的預設值一起抄。開頭那三個問題,一個是預設就做 FAQ 不建議的日常全站重送,一個是用「送出了」的標籤蓋過「還在驗證」,一個是把拒絕變成結束碼 0;三支腳本各帶了一份,第四支用了不同的預設值、輸出和錯誤處理。三個站的腳本我還沒改,這篇是改之前的對照。
修訂紀錄
- 首次發布