根据 Sitemap 自动推送新网页到必应 IndexNow

English

最近通过nextjs制作了一个静态工具网站 PriceCalc,依然是通过Cloudflare Pages进行部署,并且开启了Crawler Hints,也就是必应平台的IndexNow功能。大概作用就是站点内容改变后,自动推送到必应,可以更快地进行抓取和索引。

但这个功能好像对于我的网站来说是无效的,因为我打开必应后台后,发现IndexNow一直在推送一些无效的页面,比如_next/static下面的文件。这反而起了反效果,甚至这些页面有些还被收录到索引了。

error url

然后我真正需要推送到新页面,反而没有被正确推送,必应经常提示我 最近发布重要的页面未通过 IndexNow 提交。说实话真有点红温了,猪队友了说是。我小手一点就给这个功能关闭了,决定编写脚本通过API进行推送。

error msg

关闭Crawler Hints

造成这个现象的原因我问了AI,大概说是静态网站不是hash模式的问题,总之我们先登录Cloudflare控制台,进入域名-缓存-配置,关闭Crawler Hints按钮。当然如果你的网站类型不是静态的,也许只需要开启这个功能就可以了。

close Crawler Hints

通过api推送

好在IndexNow提供了API可以手动推送,教程地址如下 Bing IndexNow Started 方式非常简单,只需要通过POST请求将URL批量发送给必应即可。

post indexnow

配置api key

打开网页后,会随机生成一个密钥,绑定密钥到域名的方式很简单,点击下载这个密钥文件,放在网站的根目录下,最终实现可以通过域名+密钥访问这个文件。比如:https://example.com/6cb1b9bebef041ae9ab7809a54b93128.txt

这里最好直接下载文件,不要手动输入,可能会造成格式问题。直接下载后放到public或者static等静态目录下,然后上传到线上,确保可以通过链接访问到这个文件,并且文件内容就是这个密钥。

编写js脚本

我这里的思路是对比线上已有的sitemap.xml文件,然后对比新构建后的sitemap.xml文件,找出新增的页面,通过api推送给必应。

新建一个scripts/pushIndexNow.js,注意必须要填写站点域名和密钥。

#!/usr/bin/env node
/**
 * pushIndexNow.js - Auto push new pages to Bing IndexNow
 * Usage:
 *   Local dry-run (check new URLs):
 *     node pushIndexNow.js --check
 *   Actual push online:
 *     node pushIndexNow.js --push
 *
 * Note: This script is intended to run in Node.js (server-side), not in the browser.
 */

import fs from 'fs';
import path from 'path';
import process from 'process';

// ====== Configuration ======
const SITE_URL = 'https://pricecalc.net';         // Your website URL
const INDEXNOW_KEY = '87be74cabfba46a4a30454dc488dd285';           // Your IndexNow Key
const NEW_SITEMAP_FILE = path.join(process.cwd(), 'out', 'sitemap.xml');  // Local sitemap
const OLD_SITEMAP_URL = `${SITE_URL}/sitemap.xml`;  // Online sitemap URL
const CHUNK_SIZE = 1000;                             // Max URLs per push

// ====== Command-line args ======
const args = process.argv.slice(2);
const PUSH_MODE = args.includes('--push');
const CHECK_MODE = args.includes('--check') || !PUSH_MODE;

// ====== Helper functions ======
function chunkArray(arr, size) {
  const out = [];
  for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
  return out;
}

// Parse sitemap.xml using regex to extract <loc> URLs
function parseSitemap(xmlString) {
  const regex = /<loc>(.*?)<\/loc>/g;
  const urls = [];
  let match;
  while ((match = regex.exec(xmlString)) !== null) {
    urls.push(match[1]);
  }
  return urls;
}

// Send URLs to Bing IndexNow
async function sendIndexNow(urls) {
  const payload = {
    host: new URL(SITE_URL).hostname,
    key: INDEXNOW_KEY,
    keyLocation: `${SITE_URL}/${INDEXNOW_KEY}.txt`,
    urlList: urls
  };

  console.log('=== IndexNow POST Payload Preview ===');
  console.log(JSON.stringify(payload, null, 2));
  console.log('====================================');

  const res = await fetch('https://api.indexnow.org/indexnow', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json; charset=utf-8' },
    body: JSON.stringify(payload)
  });
  const text = await res.text();
  return { ok: res.ok, status: res.status, text };
}

// ====== Main ======
(async function main() {
  // 1. Read local sitemap.xml
  if (!fs.existsSync(NEW_SITEMAP_FILE)) {
    console.error('Local sitemap.xml not found:', NEW_SITEMAP_FILE);
    process.exit(1);
  }
  const xml = fs.readFileSync(NEW_SITEMAP_FILE, 'utf-8');
  const newUrls = parseSitemap(xml);

  // 2. Fetch online sitemap.xml
  let oldUrls = [];
  try {
    const res = await fetch(OLD_SITEMAP_URL);
    if (res.ok) {
      const oldXml = await res.text();
      oldUrls = parseSitemap(oldXml);
    } else {
      console.warn('Online sitemap.xml not found (HTTP', res.status, '). Assuming all URLs are new.');
    }
  } catch (e) {
    console.warn('Error fetching online sitemap.xml. Assuming all URLs are new.', e.message);
  }

  // 3. Compute difference (new URLs)
  const oldSet = new Set(oldUrls);
  const toPush = newUrls.filter(u => !oldSet.has(u));

  if (toPush.length === 0) {
    console.log('No new pages to push.');
    process.exit(0);
  }

  console.log(`Found ${toPush.length} new pages.`);

  if (CHECK_MODE) {
    console.log('== Dry run mode ==');
    toPush.forEach(u => console.log(u));
    process.exit(0);
  }

  // 4. Push new URLs to Bing IndexNow
  const chunks = chunkArray(toPush, CHUNK_SIZE);
  for (let i = 0; i < chunks.length; i++) {
    const chunk = chunks[i];
    console.log(`Pushing chunk ${i + 1}/${chunks.length} (${chunk.length} URLs)...`);
    try {
      const res = await sendIndexNow(chunk);
      if (res.ok) console.log(`Chunk ${i + 1} succeeded (HTTP ${res.status})`);
      else console.error(`Chunk ${i + 1} failed (HTTP ${res.status})`, res.text);
    } catch (e) {
      console.error(`Chunk ${i + 1} encountered an error:`, e.message || e);
    }
    if (i < chunks.length - 1) await new Promise(r => setTimeout(r, 500));
  }

  console.log('IndexNow push completed.');
})();

修改build脚本

加上最后一步node scripts/pushIndexNow.js,注意这里我添加了npm run build-dev命令,因为本地运行时就会直接触发推送了,我们要实现的效果是在构建时触发,我这里就新加了一个本地运行的命令

# before build command
"build": "next build && next-sitemap",

# after build command
"build": "next build && next-sitemap && node scripts/pushIndexNow.js --push"

"build-dev": "next build && next-sitemap && node scripts/pushIndexNow.js --check"

本地测试

这段代码可以本地测试运行,附带--check参数进行预检测

node scripts/pushIndexNow.js --check

注意最好直接运行npm run build-dev进行测试,因为本质上是读取sitemap.xml文件,如果你修改了代码但是没有构建,sitemap是不会更新的

npm run build-dev

#output:
Found 1 new pages.
== Dry run mode ==
https://example.com/new-page/

检测是否生效

添加新页面后,可以直接手动执行--push,会打印日志,包括post请求具体参数,会返回的响应结果,200就是直接成功,202只能代表命令执行成功了,至于400和403就是参数和权限错误。

然后去Bing IndexNow控制台,检查最近推送的URL,推送成功的话就会显示你推送的链接,和推送方式,API推送会显示self,到此就大功告成了~

result

代码开源

最后我把代码放在了GitHub上,欢迎大家 Star 谢谢~

加载中...
📊 加载中...
感谢Jimmy | 隐私政策 | 赞赏支持
Liu 的 AI 助手