Skip to content

VitePress 博客秒收录:Bing IndexNow + GitHub Actions 自动化实战 ​

VitePress IndexNow 自动化实战

最近在查阅 Bing Webmaster Tools 后台时,系统给我推送了一条重要的优化建议:

"了解 IndexNow 如何通过简单的 5 分钟设置提高网站在搜索引擎中的可见性"

查看后台数据,虽然总曝光有 5.6K,但点击量仅有 202 次,且数据曲线波动极大。这说明网站的新内容并未得到搜索引擎的及时抓取。由于我的博客是基于 VitePress 构建的纯静态站点,没有 WordPress 那样现成的插件生态,于是我决定自己动手,通过 API 推送和 CI 流程来实现自动收录。

今天,我们就来实操一下,如何为你的 VitePress 博客接入 IndexNow,实现“发文即收录”的丝滑体验。

什么是 IndexNow? ​

传统 SEO 流程中,我们发布文章后只能被动等待搜索引擎的“蜘蛛”爬虫定期来抓取(可能耗时几天到几周)。

IndexNow 是由 Microsoft Bing 和 Yandex 联合推出的一项开源协议。它的核心理念是:主动推送。当你的网站内容发生更新时,你通过 API 立即通知搜索引擎,搜索引擎收到通知后会第一时间来处理。

简单来说:把“等快递”变成了“发顺丰特快”。

第一步:生成并部署 API Key(VitePress 专属配置) ​

第一步永远是在 Bing Webmaster Tools 中获取一个专属的 Key,并验证网站所有权。

  1. 登录 Bing Webmaster Tools。
  2. 在左侧菜单找到并点击 IndexNow。
  3. 系统会为你生成一串 API Key(例如:74c02e84c5fe401bb1768e0eae41deb6)。
  4. 核心验证步骤(VitePress 实操):
    • 你的 Key 需要能被公网直接访问到:https://fishfinal.com/74c02e84c5fe401bb1768e0eae41deb6.txt。
    • 由于 VitePress 构建后的产物在 .vitepress/dist,你只需要将 74c02e84c5fe401bb1768e0eae41deb6.txt 文件放入你的 docs/public/ 目录中。
    • 文件内容仅需填写这串 Key 本身。
    • 重新 build 并发布你的 VitePress 站点,在浏览器中确认该链接可访问,即代表验证通过。

第二步:VitePress 开发者方案(GitHub Actions 自动化推送) ​

作为 VitePress 博主,我们追求的是自动化。每次发布新文章,还要手动跑一遍脚本太麻烦了。我们可以利用 GitHub Actions,在每次 push 到 main 分支时,自动提取本次提交中新增或修改的 .md 文件路径,将其转换为 URL 并推送给 IndexNow。

1. 编写推送脚本 ​

在你的项目根目录创建一个脚本文件 scripts/indexnow.mjs(这里使用 Node.js 原生 fetch,无需额外依赖):

scripts/indexnow.mjs
javascript
// ====== 配置区 ======
const HOST = 'fishfinal.com'
const KEY = '74c02e84c5fe401bb1768e0eae41deb6'
const KEY_LOCATION = `https://${HOST}/${KEY}.txt`
const ENDPOINT = 'https://api.indexnow.org/indexnow'
// 严格限制只推送 posts 目录下的文章
const POSTS_PREFIX = 'docs/posts/'
// ====================

import fs from 'node:fs'

function writeOutput(key, value) {
  if (!process.env.GITHUB_OUTPUT) return
  fs.appendFileSync(process.env.GITHUB_OUTPUT, `${key}=${value}\n`)
}

function writeUrlsToOutput(urls) {
  const html = urls.map(u => `<li><a href="${u}">${u}</a></li>`).join('')
  writeOutput('pushed_urls_html', html)
  writeOutput('pushed_count', String(urls.length))
}

function writeErrorToOutput(msg) {
  writeOutput('error_msg', msg.replace(/\n/g, '<br>'))
}

// ====== 1. 解析过滤文件 ======
const changedFiles = process.env.CHANGED_FILES
  ? process.env.CHANGED_FILES.split(' ').filter(Boolean)
  : []

// 只保留 docs/posts/ 下的 .md 文件,过滤掉配置文件、脚本等
const mdFiles = changedFiles.filter(
  file => file.startsWith(POSTS_PREFIX) && file.endsWith('.md')
)

if (mdFiles.length === 0) {
  console.log('ℹ️ docs/posts/ 下没有新增或修改的 Markdown 文章,跳过 IndexNow 推送。')
  writeOutput('pushed_count', '0')
  process.exit(0)
}

// ====== 2. 转换为完整 URL ======
let urlList = mdFiles.map(file => {
  // docs/posts/seo/xxx.md -> /posts/seo/xxx
  let urlPath = file.replace(/^docs/, '').replace(/\.md$/, '.html')
  urlPath = urlPath.replace(/\/index$/, '/')
  if (urlPath === '') urlPath = '/'
  return `https://${HOST}${urlPath}`
})

// 过滤 404 并去重
urlList = [...new Set(urlList.filter(url => !url.includes('/404')))]

// ====== 3. 真实可用性校验 (防止给 Bing 推送 404) ======
async function verifyUrls(urls) {
  const valid = []
  console.log('\n🔍 开始校验 URL 是否真正可访问 (HEAD 请求):')
  for (const url of urls) {
    try {
      const res = await fetch(url, { method: 'HEAD' })
      if (res.ok) {
        console.log(`  ✅ [${res.status}] ${url}`)
        valid.push(url)
      } else {
        console.warn(`  ❌ [${res.status}] ${url} (跳过推送)`)
      }
    } catch (e) {
      console.warn(`  ❌ 请求失败 ${url} (跳过推送)`)
    }
  }
  return valid
}

// ====== 4. 发送请求 ======
async function main() {
  const verifiedUrls = await verifyUrls(urlList)

  if (verifiedUrls.length === 0) {
    console.log('\n⚠️ 所有 URL 校验均未通过(可能网站还未部署完成或存在 CDN 缓存),终止推送。')
    writeErrorToOutput('URL 校验失败,无法访问实际页面')
    process.exit(1)
  }

  const payload = {
    host: HOST,
    key: KEY,
    keyLocation: KEY_LOCATION,
    urlList: verifiedUrls,
  }

  // 打印你要求的完整 Request Body
  console.log('\n📤 即将推送到 IndexNow 的完整请求体:')
  console.log('─────────────────────────────────────────')
  console.log(JSON.stringify(payload, null, 2))
  console.log('─────────────────────────────────────────')

  try {
    const response = await fetch(ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json; charset=utf-8' },
      body: JSON.stringify(payload),
    })

    const responseText = await response.text()
    console.log(`\n📨 响应状态码: ${response.status}`)
    if (responseText) console.log(`📨 响应内容: ${responseText}`)

    // 严格按照官方文档:只有 200 是成功
    if (response.status === 200) {
      console.log('\n✅ [200 OK] URL 提交成功!')
      writeUrlsToOutput(verifiedUrls)
      process.exit(0)
    } else if (response.status === 202) {
      // 针对 202 的特殊说明
      console.log('\n⚠️ [202 Accepted] 请求已被接收,正在排队处理(Bing 通常会在稍后异步完成抓取)。')
      writeUrlsToOutput(verifiedUrls)
      process.exit(0) // 依然视为流程成功
    }

    // 失败分支
    let errMsg = ''
    switch (response.status) {
      case 400: errMsg = '[400 Bad Request] 请求格式无效'; break;
      case 403: errMsg = `[403 Forbidden] Key 无效。请检查:\n  1. 文件是否可通过 ${KEY_LOCATION} 访问?\n  2. 文件内容是否与 KEY 完全一致?`; break;
      case 422: errMsg = '[422 Unprocessable Entity] URL 不属于该 host,或 Key 与协议 schema 不匹配'; break;
      case 429: errMsg = '[429 Too Many Requests] 推送过于频繁'; break;
      default: errMsg = `[${response.status}] 未知状态码,响应: ${responseText}`;
    }
    console.error(`\n❌ ${errMsg}`)
    writeErrorToOutput(errMsg)
    process.exit(1)
  } catch (error) {
    const errMsg = `网络请求异常: ${error.message}`
    console.error(`\n❌ ${errMsg}`)
    writeErrorToOutput(errMsg)
    process.exit(1)
  }
}

main()

2. 配置 GitHub Actions ​

在你的项目根目录创建 .github/workflows/indexnow.yml 文件:

.github/workflows/indexnow.yml
yaml
name: Push to Bing IndexNow

on:
  push:
    branches:
      - master # 你的主分支名称,按需修改

jobs:
  indexnow:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 2 # 拉取最近两次提交,用于对比 diff

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Get changed files
        id: changed-files
        run: |
          # 获取本次 push 中新增(A)或修改(M)的文件列表,并输出到环境变量
          FILES=$(git diff --name-only HEAD^ HEAD --diff-filter=AM | tr '\n' ' ')
          echo "CHANGED_FILES=$FILES" >> $GITHUB_ENV

      - name: Run IndexNow Push Script
        run: node scripts/indexnow.mjs
        env:
          CHANGED_FILES: ${{ env.CHANGED_FILES }}

3. 提交并触发 ​

将这两个文件提交到你的 GitHub 仓库,并随便修改一篇你的 Markdown 文章然后 push。去 GitHub Actions 面板查看,你就会看到脚本自动提取了那篇文章的 URL,并成功推送给了 Bing。

为什么 Bing 会疯狂提示我用 IndexNow? ​

回到文章开头的数据:

  • 点击次数:202
  • 总印象数:5.6K

Bing 的逻辑很清晰:我的站内有很多新页面没有被及时索引,无法参与排名竞争。通过上面的 GitHub Actions 自动化,我将彻底解决“收录慢”的问题。

⚠️ 重要提醒: 收录快 ≠ 排名好。IndexNow 只能帮我解决“被看到”的问题(加速收录),而“排在前面”(提升排名)依然需要依靠高质量的内容和优质的外链建设。

最终效果 ​

配置完成后,你可以在 Bing 后台的 IndexNow 菜单中查看推送历史。现在,你的 VitePress 博客每一次发布,都能在几分钟内被 Bing 知晓。赶紧动手试试吧!

最后更新2026/10/29 09:02
如果你觉得这篇文章有帮助,或者想聊聊技术、工作,欢迎通过下面方式联系我:
contact fishfinal