IndexNow 收录加速实战:新站文章发布后主动推送必应,附百度 API 与 sitemap lastmod 正确写法

新站最大的痛点不是写不出内容,而是没人来抓

个人站长做 SEO 会遇到一个很郁闷的阶段:文章写了几十篇,每篇都是认真手写的两千字原创,站内结构也做了,可搜素引擎的蜘蛛就是不来。去站长后台一看,"已收录"是 0,"已发现未编入索引"倒是一堆。这时候大部分人会把精力花在"怎么提高排名"上,其实是搞错了顺序——你连收录都没有,谈排名是没有意义的。

收录慢的根源在于:搜索引擎发现新内容的传统方式是"顺着外链爬"。一个没有外链的新站,蜘蛛没有理由、也没有入口来访问你的新文章。它只能靠 sitemap 定期回访,而这个周期可能是几天到几周。你昨天写的那篇关于 Nginx 调优的文章,可能一个月后才被第一次抓取。

解决这个问题的标准答案叫 IndexNow:主动告诉搜索引擎"我这个 URL 变了,现在来抓"。这篇文章讲清楚它的原理、和传统 sitemap/API 推送的关系、以及怎么用几行脚本和 Nginx 自动完成推送。

IndexNow 是什么,谁在支持

IndexNow 是一个开放的即时收录协议,最早由 Bing 在 2021 年发起,后来 Yandex、Seznam、Naver 等陆续加入,而且这些引擎共享同一个提交池——也就是说你用 Bing 的接口推一个 URL,Yandex 那边也能收到通知。协议本身极其简单,核心只有一个 HTTP 请求:

GET https://api.indexnow.org/indexnow?url={URL}&key={KEY}&keyLocation={KEY_URL}

需要注意的几个现实问题:

  • 百度不支持 IndexNow。百度有自己的一套入口:普通收录 API(需要 token)+ 快速收录(需要移动端适配资质)。所以中文站做收录加速,要"两条腿走路":IndexNow 对面是 Bing/必应系流量,百度用它的站长平台 API。
  • Google 不支持 IndexNow。Google 的做法是通过 sitemap 里的 <lastmod> 时间戳 + Search Console 的 URL 检查工具来"请求编入索引"。sitemap 的 lastmod 必须真实,用当前时间糊弄会让 Google 降低对你整个 sitemap 的信任度。
  • IndexNow 是通知不是保证。你推了,引擎会更快地来抓,但抓了之后收不收、排不排名,还是它自己的判断。

所以合理的策略是三通道并行:IndexNow 负责必应系,百度站长 API 负责百度,真实 lastmod 的 sitemap 负责 Google。

第一步:生成并放置 key 文件

IndexNow 需要一个"证明这站是你的"的 key 文件。key 必须满足两个条件:长度 8–128 位,只包含 a-z 0-9 -。

# 生成一个 32 位 key
KEY=$(openssl rand -hex 16)
echo "你的 key: $KEY"

# 写到网站根目录(注意:必须是能被公开访问的静态文件)
echo "$KEY" > /www/wwwroot/site/${KEY}.txt

# 验证可访问(这一步很重要,keyLocation 不可访问会导致 403)
curl -s "https://www.yoursite.com/${KEY}.txt"

两个坑:

  1. 文件内容就是 key 本身,不要加换行之外的任何东西,不要放 JSON。echo 会带一个换行符,这是允许的。
  2. Nginx 不能拦截 .txt 文件。有些站长在 Nginx 里写了规则禁止访问某些后缀(含 txt)用于防采集,结果 key 文件返回 403,IndexNow 接口就报 403 Forbidden。遇到这个报错先 curl 一下 key 的 URL 看是不是 200。

另外,key 可以放在子目录,但要通过 keyLocation 参数显式告诉接口。最省事的做法是放根目录,然后省略 keyLocation(接口会默认去根目录找)。

第二步:最简的推送脚本

先用 curl 手推一个 URL 验证整个链路:

KEY="你生成的32位key"
URL="https://www.yoursite.com/1214.html"

curl -s -o /dev/null -w "HTTP %{http_code}\n" \
  "https://api.indexnow.org/indexnow?url=${URL}&key=${KEY}"

返回码的含义:

状态码含义处理
200URL 已成功提交完成
202已接受,但 key 校验还在异步进行中正常,稍后回查
400URL 或 key 格式不对检查 URL 是否 http/https 完整、key 长度
403key 校验失败key 文件不可访问或内容不匹配
422URL 不属于 key 对应的域名URL 域名和 key 不匹配
429推送过于频繁降低频率,批量推送要限速

验证成功后,把推送做成一个可复用的脚本。支持单个 URL 和批量文件两种模式:

#!/bin/bash
# /usr/local/bin/indexnow.sh
# 用法: indexnow.sh         单条推送
#       indexnow.sh -f     批量推送(每行一个URL)
set -uo pipefail

KEY="替换成你的32位key"
HOST="www.yoursite.com"
API="https://api.indexnow.org/indexnow"

push_one() {
    local url="$1"
    local code
    code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \
        "${API}?url=${url}&key=${KEY}&keyLocation=https://${HOST}/${KEY}.txt")
    echo "[$(date '+%F %T')] ${code} ${url}" | tee -a /var/log/indexnow.log
}

if [ "${1:-}" = "-f" ]; then
    while IFS= read -r u; do
        [ -z "$u" ] && continue
        push_one "$u"
        sleep 2      # 必须限速,否则 429
    done < "$2"
else
    push_one "$1"
fi

批量推送有一个更高效的写法:IndexNow 支持一次 POST 最多 10000 个 URL 的 JSON 数组,比循环 GET 快得多:

# 构造 JSON 并用 POST 批量推送
jq -n --arg host "$HOST" --arg key "$KEY" \
   --arg kl "https://${HOST}/${KEY}.txt" \
   --argjson list "$(jq -R -s 'split("\n")|map(select(length>0))' urls.txt)" \
   '{host:$host, key:$key, keyLocation:$kl, urlList:$list}' | \
curl -s -X POST "https://api.indexnow.org/indexnow" \
   -H "Content-Type: application/json; charset=utf-8" \
   -d @- -o /dev/null -w "HTTP %{http_code}\n"

批量模式适合"存量文章一次性推一遍",日常新文用单条 GET 就够了。

第三步:发布文章后自动推送

最理想的形态是"文章一发布,URL 就自动推出去",不需要人工干预。因为我们的发布流程走的是 XML-RPC,可以在发布脚本里加一行:

# 在 metaWeblog.newPost 拿到 postid 之后追加
import subprocess

def notify_indexnow(cid, host, key):
    url = f"https://{host}/{cid}.html"
    api = f"https://api.indexnow.org/indexnow?url={url}&key={key}"
    r = subprocess.run(["curl", "-s", "-o", "/dev/null",
                        "-w", "%{http_code}", "--max-time", "10", api],
                       capture_output=True, text=True)
    print(f"[indexnow] {url} -> {r.stdout}")

如果不想改发布脚本,也可以做一个"轮询式"的守护任务:每 10 分钟查一次数据库里最近 15 分钟内的新文章,把没推过的 URL 推出去。这样即使发布流程改了也不影响推送。核心是用一个状态文件记录已推送的 URL,避免重复推送:

#!/bin/bash
# /usr/local/bin/indexnow_cron.sh —— 每10分钟执行一次
set -uo pipefail
KEY="你的key"; HOST="www.yoursite.com"
STATE=/var/lib/indexnow_pushed.txt
touch "$STATE"

# 取最近 30 分钟新增的文章 cid
NEW=$(mysql -N -uroot -pXXX zz1984 -e \
  "SELECT cid FROM typecho_contents
   WHERE type='post' AND status='publish'
   AND created > UNIX_TIMESTAMP()-1800;")

for cid in $NEW; do
    url="https://${HOST}/${cid}.html"
    grep -qxF "$url" "$STATE" && continue     # 已推过,跳过
    code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \
        "https://api.indexnow.org/indexnow?url=${url}&key=${KEY}")
    if [ "$code" = "200" ] || [ "$code" = "202" ]; then
        echo "$url" >> "$STATE"
        echo "$(date '+%F %T') $code $url" >> /var/log/indexnow.log
    fi
    sleep 2
done

grep -qxF 用固定字符串精确整行匹配,比 grep -q 安全(URL 里的点号不会被当正则)。这个状态文件就是防止重复推送的关键——IndexNow 对同一 URL 反复推送会返回 429,频繁触发可能被短期限流。

第四步:百度这条路怎么走

百度不支持 IndexNow,得用百度搜索资源平台的普通收录 API。流程:登录百度搜索资源平台 → 站点管理 → 资源提交 → 普通收录 → API 提交,拿到 token,然后:

# 单条推送
curl -s -H 'Content-Type:text/plain' \
  --data-binary 'https://www.yoursite.com/1214.html' \
  "http://data.zz.baidu.com/urls?site=https://www.yoursite.com&token=你的TOKEN"

# 返回示例
# {"remain":99,"success":1,"not_same_site":[],"not_valid":[]}

应答字段读法:remain 是当日剩余配额(普通站点通常 10 条/天,认证站点更多),success 是成功条数,not_same_site 是域名不匹配的 URL,not_valid 是格式非法的 URL。如果 success 一直是 0 而 not_valid 有值,检查 URL 是否带了多余的查询参数或 fragment。

配额只有 10 条/天的时候,一定要把配额花在最重要的页面上:首页、分类页、以及当天写的最核心的那一篇文章。不要拿去推标签页、归档页这种低价值 URL。

sitemap 的 lastmod 才是 Google 的钥匙

Google 这边没有推送接口,它依赖你 sitemap 里的 <lastmod>。这里有个非常常见的错误做法:每次生成 sitemap 时把所有 URL 的 lastmod 都刷成当前时间。Google 的爬虫会对比"上次抓取时的内容"和"你声称的修改时间",如果时间和内容对不上,它就会判断你的 lastmod 不可信,之后整个 sitemap 的修改时间信号都会贬值,收录反而更慢。

正确做法是 lastmod 取数据库里那条记录真实的更新时间:

<?php
// 生成 sitemap 时用真实 modified 时间
$rows = $db->query(
  "SELECT cid, modified FROM typecho_contents
   WHERE type='post' AND status='publish' ORDER BY modified DESC");
foreach ($rows as $r) {
    $loc  = "https://www.yoursite.com/{$r['cid']}.html";
    $date = gmdate('c', $r['modified']);   // ISO8601,需 UTC
    echo "<url><loc>{$loc}</loc>"
       . "<lastmod>{$date}</lastmod>"
       . "<changefreq>weekly</changefreq>"
       . "<priority>0.7</priority></url>\n";
}
?>

注意 gmdate 而不是 date——sitemap 的 lastmod 要求 W3C 格式且应带时区信息,用本地时间会导致时间戳偏移,Google 会判定格式错误。同时 sitemap 必须放在 robots.txt 里声明:

Sitemap: https://www.yoursite.com/sitemap.xml

推送之后怎么验证效果

推送不是发出去就完事了,要验证。三个验证手段:

  1. 看推送日志。/var/log/indexnow.log 里如果 200/202 占比高,说明推送链路正常。
  2. 看服务端访问日志里有没有对应蜘蛛。推送成功后正常几分钟内就能在 access.log 里看到引擎蜘蛛的访问:
# 统计各引擎蜘蛛今天抓了多少次
grep "$(date '+%d/%b/%Y')" /var/log/nginx/access.log | \
  grep -iE 'bingbot|yandex|googlebot|baiduspider' | \
  awk '{print $NF}' | sort | uniq -c | sort -rn

# 看具体某个新 URL 有没有被蜘蛛访问过
grep '1214.html' /var/log/nginx/access.log | grep -iE 'bot|spider'
  1. 在站长平台回查。Bing Webmaster Tools 的"URL 提交"页面能看到最近提交记录和抓取状态;百度资源平台能看到 API 提交的配额消耗和收录量。一般推送后 1–3 天能在平台上看到收录变化。

如果推送返回 200 但几天后日志里一个新蜘蛛都没有,先怀疑两件事:一是服务器把索引蜘蛛也当恶意爬虫拦了(检查限流规则里有没有把 bingbot 一起限死),二是 CDN/WAF 层面拦了。这两个坑都算"自己把自己的收录之路堵死了"。

频率控制:不要好心办坏事

最后一个容易翻车的点:推送频率。

  • 不要在文章还没发布、URL 还是 404 的时候就推。推了 404 会浪费抓取预算,而且引擎会降低对你这个 key 的信任。推送前务必 curl -I 确认返回 200。
  • 不要每次保存草稿都推一次。只有 status = publish 才推。
  • 同一 URL 不要反复推。改了内容可以重推,但要间隔几小时以上,并且依赖状态文件去重。
  • 批量推送存量文章时,一次别超过几百条,分批 + sleep。新站突然推几万个 URL 会让引擎判定为异常。

小结:收录加速的最小可执行方案

把上面所有内容压缩成一个能在半小时内落地的清单:

  1. 生成 32 位 key,写到网站根目录,curl 确认 200。
  2. curl 手推一条 URL,确认返回 200/202。
  3. 把推送脚本落到 /usr/local/bin/indexnow.sh,带状态文件去重和限速。
  4. 加一条 crontab,每 10 分钟扫一次最近 30 分钟的新文章并推送。
  5. 百度那条路:申请 token,把每日 10 条配额留给最重要的页面。
  6. sitemap 的 lastmod 改成数据库真实的 modified 时间,用 UTC 格式,并在 robots.txt 声明。
  7. 每周看一次 access.log 里蜘蛛的抓取量,确认推送真的换来了抓取。

对新站来说,收录速度往往决定了一个内容站能不能"转起来"。手动等蜘蛛、靠外链引蜘蛛的时代已经过去了,主动推送是成本最低、见效最快的一步。它不能替你写出好文章,但它能保证你写出来的好文章,在最短时间内被搜索引擎看到。

Last modification:September 26th, 2026 at 01:24 pm

Leave a Comment