我的Hexo博客搭好了12 - 自动提交URL到搜索引擎(IndexNow + Google Search Console)

文章系列导航

  1. 第01篇 - 选择困难症发作
  2. 第02篇 - 搭建过程之:披荆斩棘,晋级啦!
  3. 第03篇 - 搭建过程之:一路踩坑
  4. 第04篇 - 备份Hexo博客的源码目录,上传到Gitee仓库
  5. 第05篇 - 更换Hexo主题
  6. 第06篇 - 怎么给文章分类、打标签、展示目录
  7. 第07篇 - 怎么制作/about/、/categories/、/tags/页面
  8. 第08篇 - 让世界找到我------SEO大冒险(站点地图制作和提交)
  9. 第09篇 - 添加统计字数和阅读时长的插件
  10. 第10篇 - 怎么插入图片和视频
  11. 第11篇 - 怎么创建文章系列导航
  12. 第12篇 - 自动提交URL到搜索引擎(IndexNow + Google Search Console)
  13. 持续更新中...

本文目录

    • [1 为什么需要自动提交](#1 为什么需要自动提交)
    • [2 技术方案对比](#2 技术方案对比)
    • [3 方案一:IndexNow 自动提交](#3 方案一:IndexNow 自动提交)
      • [3.1 获取 API Key](#3.1 获取 API Key)
      • [3.2 创建密钥验证文件](#3.2 创建密钥验证文件)
      • [3.3 配置 Hexo](#3.3 配置 Hexo)
      • [3.4 创建 IndexNow 插件](#3.4 创建 IndexNow 插件)
      • [3.5 更新 .gitignore](#3.5 更新 .gitignore)
      • [3.6 测试 IndexNow](#3.6 测试 IndexNow)
    • [4 方案二:Google Search Console 自动提交](#4 方案二:Google Search Console 自动提交)
      • [4.1 创建 Google Cloud 项目](#4.1 创建 Google Cloud 项目)
      • [4.2 启用 Search Console API](#4.2 启用 Search Console API)
      • [4.3 创建服务账号](#4.3 创建服务账号)
      • [4.4 下载服务账号密钥](#4.4 下载服务账号密钥)
      • [4.5 在 Search Console 中授权服务账号](#4.5 在 Search Console 中授权服务账号)
      • [4.6 配置 Hexo](#4.6 配置 Hexo)
      • [4.7 安装依赖](#4.7 安装依赖)
      • [4.8 创建 Google Search Console 插件](#4.8 创建 Google Search Console 插件)
      • [4.9 测试 Google Search Console](#4.9 测试 Google Search Console)
    • [5 代理配置(可选)](#5 代理配置(可选))
      • [5.1 创建代理管理脚本](#5.1 创建代理管理脚本)
      • [5.2 使用代理](#5.2 使用代理)
      • [5.3 使用代理的第二种方法](#5.3 使用代理的第二种方法)
    • [6 验证效果](#6 验证效果)
      • [6.1 IndexNow 验证](#6.1 IndexNow 验证)
      • [6.2 Google Search Console 验证](#6.2 Google Search Console 验证)
    • [7 常见问题](#7 常见问题)
      • [Q1: IndexNow 提交失败怎么办?](#Q1: IndexNow 提交失败怎么办?)
      • [Q2: Google Search Console 显示"Couldn't fetch"怎么办?](#Q2: Google Search Console 显示"Couldn't fetch"怎么办?)
      • [Q3: 代理设置会影响正常上网吗?](#Q3: 代理设置会影响正常上网吗?)
      • [Q4: 为什么要用缓存机制?](#Q4: 为什么要用缓存机制?)
      • [Q5: 方案一:IndexNow 自动提交,首次部署会提交所有 URL 吗?](#Q5: 方案一:IndexNow 自动提交,首次部署会提交所有 URL 吗?)
    • [8 优势总结](#8 优势总结)
    • [9 参考资料](#9 参考资料)

在第08篇中,我们学习了如何制作和提交站点地图(sitemap)。但是,仅仅提交 sitemap 还不够快,搜索引擎可能需要几天甚至几周才会抓取你的新内容。

今天,我们来实现一个更高效的方案:在每次部署时,自动通知搜索引擎索引新增或变化的 URL

1 为什么需要自动提交

传统方式的问题

  • 被动等待:搜索引擎需要定期抓取 sitemap,可能需要几天到几周
  • 效率低下:即使提交了 sitemap,搜索引擎也不知道哪些内容是新增的
  • 索引慢:新文章可能很久才会出现在搜索结果中

自动提交的优势

  • 主动通知:每次部署后立即通知搜索引擎
  • 快速索引:新内容可能在几分钟到几小时内被索引
  • 智能提交:只提交有变化的 URL,节省资源
  • 覆盖全面:同时支持 Google、Bing、Yandex 等搜索引擎

2 技术方案对比

我们将实现两个自动提交方案:

方案 支持的搜索引擎 提交方式 配置复杂度
IndexNow Bing、Yandex 等 提交 URL 列表 简单 ⭐
Google Search Console 仅 Google 提交 sitemap URL 中等 ⭐⭐⭐

两者配合使用,可以覆盖所有主流搜索引擎。

3 方案一:IndexNow 自动提交

IndexNow 是一个开放协议,可以快速通知 Bing、Yandex 等搜索引擎。

参考:https://www.indexnow.org/

3.1 获取 API Key

  1. 访问 Bing Webmaster Tools
  2. 登录并添加你的网站(如果还没有添加)
  3. 进入网站管理界面:https://www.bing.com/webmasters/submiturl?siteUrl=你的网站地址
    • 例如:https://www.bing.com/webmasters/submiturl?siteUrl=https://wittzh.github.io/
  4. 点击右上角的"设置"菜单(齿轮图标)
  5. 在下拉菜单中找到"API 访问"选项
  6. 点击进入后,可以看到"OAuth 客户端"和"API 密钥",点击"API 密钥",可以查看你的"API 密钥",如果没有,则进行创建。
  7. 复制生成的 API Key(类似:3b259475ff0a4125835bd8b9ac314eee

3.2 创建密钥验证文件

source/ 目录下创建一个文本文件,文件名为你的 API 密钥:

bash 复制代码
# 在 source/ 目录下创建文件
echo "3b259475ff0a4125835bd8b9ac314eee" > source/3b259475ff0a4125835bd8b9ac314eee.txt

这个文件用于验证你拥有该网站的所有权。

3.3 配置 Hexo

_config.yml 文件末尾添加 IndexNow 配置:

yaml 复制代码
# IndexNow配置 - 自动提交URL到搜索引擎
indexnow:
  enable: true  # 是否启用自动提交
  key: "3b259475ff0a4125835bd8b9ac314eee"  # 你的 API Key
  keyLocation: "https://你的域名/3b259475ff0a4125835bd8b9ac314eee.txt"  # 密钥文件URL

3.4 创建 IndexNow 插件

scripts/ 目录下创建 indexnow.js 文件:

javascript 复制代码
'use strict';

/**
 * IndexNow自动提交插件
 * 在hexo deploy后自动向IndexNow API提交变化的URL
 * 帮助搜索引擎快速发现和索引网站内容
 * 
 * 优化:只提交有变化的URL(新增、更新、删除)
 */

const https = require('https');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

/**
 * 读取并验证IndexNow配置
 * @param {Object} hexo - Hexo实例
 * @returns {Object|null} 配置对象或null
 */
function readConfig(hexo) {
  const config = hexo.config.indexnow;
  
  // 检查配置是否存在
  if (!config) {
    hexo.log.info('[IndexNow] 未配置indexnow,跳过提交');
    return null;
  }
  
  // 检查是否启用
  if (!config.enable) {
    hexo.log.info('[IndexNow] indexnow.enable=false,跳过提交');
    return null;
  }
  
  // 验证必需字段
  if (!config.key) {
    hexo.log.warn('[IndexNow] 配置错误: 缺少key字段');
    return null;
  }
  
  if (!config.keyLocation) {
    hexo.log.warn('[IndexNow] 配置错误: 缺少keyLocation字段');
    return null;
  }
  
  // 从站点URL提取host
  const siteUrl = hexo.config.url;
  if (!siteUrl) {
    hexo.log.warn('[IndexNow] 配置错误: 缺少站点URL');
    return null;
  }
  
  const host = new URL(siteUrl).hostname;
  
  return {
    enable: config.enable,
    key: config.key,
    keyLocation: config.keyLocation,
    host: host,
    siteUrl: siteUrl
  };
}

/**
 * 从 sitemap XML 文件中提取 URL
 * @param {String} filePath - sitemap 文件路径
 * @returns {Array<String>} URL数组
 */
function extractUrlsFromSitemap(filePath) {
  const urls = [];
  
  try {
    const content = fs.readFileSync(filePath, 'utf8');
    
    // 使用正则表达式提取所有 <loc> 标签中的 URL
    const locRegex = /<loc>(.*?)<\/loc>/g;
    let match;
    
    while ((match = locRegex.exec(content)) !== null) {
      urls.push(match[1]);
    }
  } catch (error) {
    // 文件不存在或读取失败,返回空数组
  }
  
  return urls;
}

/**
 * 计算 URL 列表的哈希值(用于快速比较)
 * @param {Array<String>} urls - URL数组
 * @returns {String} 哈希值
 */
function calculateUrlsHash(urls) {
  const sortedUrls = [...urls].sort().join('\n');
  return crypto.createHash('md5').update(sortedUrls).digest('hex');
}

/**
 * 加载上次提交的 URL 缓存
 * @param {String} cacheFile - 缓存文件路径
 * @returns {Object} 缓存对象 {urls: [], hash: '', timestamp: ''}
 */
function loadUrlCache(cacheFile) {
  try {
    if (fs.existsSync(cacheFile)) {
      const content = fs.readFileSync(cacheFile, 'utf8');
      return JSON.parse(content);
    }
  } catch (error) {
    // 缓存文件不存在或损坏,返回空对象
  }
  return { urls: [], hash: '', timestamp: '' };
}

/**
 * 保存 URL 缓存
 * @param {String} cacheFile - 缓存文件路径
 * @param {Array<String>} urls - URL数组
 */
function saveUrlCache(cacheFile, urls) {
  try {
    const cache = {
      urls: urls,
      hash: calculateUrlsHash(urls),
      timestamp: new Date().toISOString()
    };
    fs.writeFileSync(cacheFile, JSON.stringify(cache, null, 2), 'utf8');
  } catch (error) {
    // 保存失败不影响主流程
  }
}

/**
 * 检测 URL 变化
 * @param {Array<String>} currentUrls - 当前URL列表
 * @param {Array<String>} cachedUrls - 缓存的URL列表
 * @returns {Object} {changed: [], added: [], removed: []}
 */
function detectUrlChanges(currentUrls, cachedUrls) {
  const currentSet = new Set(currentUrls);
  const cachedSet = new Set(cachedUrls);
  
  // 新增的 URL
  const added = currentUrls.filter(url => !cachedSet.has(url));
  
  // 删除的 URL
  const removed = cachedUrls.filter(url => !currentSet.has(url));
  
  // 所有变化的 URL(新增 + 删除)
  const changed = [...added, ...removed];
  
  return { changed, added, removed };
}

/**
 * 收集所有需要提交的URL(从生成的文件中读取)
 * @param {Object} hexo - Hexo实例
 * @returns {Array<String>} URL数组
 */
function collectUrlsFromGenerated(hexo) {
  const urls = new Set();
  const log = hexo.log;
  const publicDir = hexo.public_dir;
  
  log.info('[IndexNow] 从生成的文件中收集URL...');
  
  // 1. 从 post-sitemap.xml 读取文章URL
  const postSitemapPath = path.join(publicDir, 'post-sitemap.xml');
  const postUrls = extractUrlsFromSitemap(postSitemapPath);
  log.info('[IndexNow] 从 post-sitemap.xml 读取到 %d 个URL', postUrls.length);
  postUrls.forEach(url => urls.add(url));
  
  // 2. 从 page-sitemap.xml 读取页面URL
  const pageSitemapPath = path.join(publicDir, 'page-sitemap.xml');
  const pageUrls = extractUrlsFromSitemap(pageSitemapPath);
  log.info('[IndexNow] 从 page-sitemap.xml 读取到 %d 个URL', pageUrls.length);
  pageUrls.forEach(url => urls.add(url));
  
  // 3. 添加主 sitemap
  urls.add(hexo.config.url + '/sitemap.xml');
  
  return Array.from(urls);
}

/**
 * 提交URL到IndexNow API
 * @param {Object} config - 配置对象
 * @param {Array<String>} urls - URL数组
 * @returns {Promise} Promise对象
 */
function submitToIndexNow(config, urls) {
  return new Promise((resolve, reject) => {
    // 构建请求体
    const postData = JSON.stringify({
      host: config.host,
      key: config.key,
      keyLocation: config.keyLocation,
      urlList: urls
    });
    
    // 配置HTTPS请求
    const options = {
      hostname: 'api.indexnow.org',
      port: 443,
      path: '/IndexNow',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json; charset=utf-8',
        'Content-Length': Buffer.byteLength(postData)
      },
      timeout: 30000 // 30秒超时
    };
    
    // 发送请求
    const req = https.request(options, (res) => {
      let data = '';
      
      res.on('data', chunk => {
        data += chunk;
      });
      
      res.on('end', () => {
        if (res.statusCode === 200) {
          resolve({
            success: true,
            statusCode: res.statusCode,
            message: '提交成功'
          });
        } else {
          reject(new Error(`HTTP ${res.statusCode}: ${data || '未知错误'}`));
        }
      });
    });
    
    // 错误处理
    req.on('error', (error) => {
      reject(new Error(`网络错误: ${error.message}`));
    });
    
    req.on('timeout', () => {
      req.destroy();
      reject(new Error('请求超时(30秒)'));
    });
    
    // 发送数据
    req.write(postData);
    req.end();
  });
}

// 在部署完成后提交URL
hexo.on('deployAfter', async function() {
  const log = hexo.log;
  
  try {
    log.info('[IndexNow] 开始处理URL提交...');
    
    // 读取配置
    const config = readConfig(hexo);
    if (!config) {
      return;
    }
    
    log.info('[IndexNow] 配置读取成功');
    
    // 从生成的文件中收集URL
    const currentUrls = collectUrlsFromGenerated(hexo);
    log.info('[IndexNow] 当前共收集到 %d 个URL', currentUrls.length);
    
    if (currentUrls.length === 0) {
      log.warn('[IndexNow] 没有URL需要提交');
      return;
    }
    
    // 缓存文件路径
    const cacheFile = path.join(hexo.base_dir, '.indexnow-cache.json');
    
    // 加载上次提交的URL缓存
    const cache = loadUrlCache(cacheFile);
    log.info('[IndexNow] 上次提交了 %d 个URL (时间: %s)', 
      cache.urls.length, 
      cache.timestamp || '从未提交'
    );
    
    // 检测URL变化
    const changes = detectUrlChanges(currentUrls, cache.urls);
    
    if (changes.changed.length === 0) {
      log.info('[IndexNow] ✓ 没有URL变化,跳过提交');
      log.info('[IndexNow] 提示: 只有在URL新增、更新或删除时才会提交');
      return;
    }
    
    // 显示变化统计
    log.info('[IndexNow] 检测到URL变化:');
    if (changes.added.length > 0) {
      log.info('[IndexNow]   新增: %d 个URL', changes.added.length);
      changes.added.forEach(url => {
        log.info('[IndexNow]     + %s', url);
      });
    }
    if (changes.removed.length > 0) {
      log.info('[IndexNow]   删除: %d 个URL', changes.removed.length);
      changes.removed.forEach(url => {
        log.info('[IndexNow]     - %s', url);
      });
    }
    log.info('[IndexNow]   总计: %d 个URL需要提交', changes.changed.length);
    
    // 调用API提交变化的URL
    log.info('[IndexNow] 正在提交到 api.indexnow.org...');
    const result = await submitToIndexNow(config, changes.changed);
    
    log.info('[IndexNow] ✓ 提交成功! 状态码: %d', result.statusCode);
    log.info('[IndexNow] 已通知搜索引擎索引 %d 个变化的URL', changes.changed.length);
    
    // 保存当前URL列表到缓存
    saveUrlCache(cacheFile, currentUrls);
    log.info('[IndexNow] URL缓存已更新');
    
  } catch (error) {
    log.warn('[IndexNow] 执行失败:', error.message);
    log.warn('[IndexNow] 提交失败不影响部署流程');
  }
});

3.5 更新 .gitignore

将缓存文件添加到 .gitignore,避免提交到 git:

复制代码
# IndexNow URL 缓存文件
.indexnow-cache.json

3.6 测试 IndexNow

运行部署命令:

bash 复制代码
hexo clean && hexo g && hexo d

你应该看到类似的日志输出:

复制代码
[IndexNow] 开始处理URL提交...
[IndexNow] 配置读取成功
[IndexNow] 从生成的文件中收集URL...
[IndexNow] 从 post-sitemap.xml 读取到 13 个URL
[IndexNow] 从 page-sitemap.xml 读取到 2 个URL
[IndexNow] 当前共收集到 16 个URL
[IndexNow] 上次提交了 0 个URL (时间: 从未提交)
[IndexNow] 检测到URL变化:
[IndexNow]   新增: 16 个URL
[IndexNow]     + https://你的域名/
[IndexNow]     + https://你的域名/2026/01/25/new-article/
[IndexNow]     ...
[IndexNow]   总计: 16 个URL需要提交
[IndexNow] 正在提交到 api.indexnow.org...
[IndexNow] ✓ 提交成功! 状态码: 200
[IndexNow] 已通知搜索引擎索引 16 个变化的URL
[IndexNow] URL缓存已更新

Google 不支持 IndexNow,需要使用 Google Search Console API。

4.1 创建 Google Cloud 项目

  1. 访问 Google Cloud Console
  2. 点击"新建项目"
  3. 输入项目名称(如:hexo-blog-indexing
  4. 点击"创建"
  1. 在 Google Cloud Console 中,选择刚创建的项目
  2. 点击左侧菜单"API 和服务" > "库"
  3. 搜索"Google Search Console API"
  4. 点击进入,然后点击"启用"

4.3 创建服务账号

  1. 点击左侧菜单"API 和服务" > "凭据"
  2. 点击"创建凭据" > "服务账号"
  3. 填写服务账号名称(如:hexo-blog-service-account
  4. 点击"创建并继续"
  5. 跳过权限设置,点击"完成"

4.4 下载服务账号密钥

  1. 在"凭据"页面,找到刚创建的服务账号
  2. 点击服务账号邮箱进入详情页
  3. 切换到"密钥"标签页
  4. 点击"添加密钥" > "创建新密钥"
  5. 选择"JSON"格式
  6. 点击"创建",密钥文件会自动下载
  1. 打开下载的 JSON 密钥文件,找到 client_email 字段
  2. 复制该邮箱地址(类似:hexo-blog-service-account@project-id.iam.gserviceaccount.com
  3. 访问 Google Search Console
  4. 选择你的网站
  5. 点击左侧"设置" > "用户和权限"
  6. 点击"添加用户"
  7. 粘贴服务账号邮箱地址
  8. 权限选择"所有者"(必须是所有者,如果页面语言是英文,则是 Owner)
  9. 点击"添加"

4.6 配置 Hexo

  1. 将下载的 JSON 密钥文件重命名为 google-service-account.json
  2. 放到 Hexo 项目根目录(与 _config.yml 同级)
  3. _config.yml 末尾添加配置:
yaml 复制代码
# Google Search Console 配置
google_search_console:
  enable: true  # 启用后改为 true
  service_account_file: "google-service-account.json"
  1. 更新 .gitignore,添加:

    Google Search Console 服务账号密钥文件

    google-service-account.json

4.7 安装依赖

bash 复制代码
npm install googleapis

scripts/ 目录下创建 google-search-console.js 文件:

javascript 复制代码
'use strict';

/**
 * Google Search Console 自动提交插件
 * 在 hexo deploy 后自动向 Google Search Console 提交 sitemap
 * 帮助 Google 快速发现和索引网站内容
 */

const { google } = require('googleapis');
const fs = require('fs');
const path = require('path');

// 设置代理以访问 Google API
process.env.http_proxy = 'http://127.0.0.1:7897';
process.env.https_proxy = 'http://127.0.0.1:7897';

/**
 * 读取并验证 Google Search Console 配置
 * @param {Object} hexo - Hexo 实例
 * @returns {Object|null} 配置对象或 null
 */
function readConfig(hexo) {
  const config = hexo.config.google_search_console;
  
  // 检查配置是否存在
  if (!config) {
    hexo.log.info('[Google Search Console] 未配置 google_search_console,跳过提交');
    return null;
  }
  
  // 检查是否启用
  if (!config.enable) {
    hexo.log.info('[Google Search Console] google_search_console.enable=false,跳过提交');
    return null;
  }
  
  // 验证必需字段
  if (!config.service_account_file) {
    hexo.log.warn('[Google Search Console] 配置错误: 缺少 service_account_file 字段');
    return null;
  }
  
  // 获取站点 URL
  const siteUrl = hexo.config.url;
  if (!siteUrl) {
    hexo.log.warn('[Google Search Console] 配置错误: 缺少站点 URL');
    return null;
  }
  
  // 确保 siteUrl 以 / 结尾(Google Search Console 要求)
  const normalizedSiteUrl = siteUrl.endsWith('/') ? siteUrl : siteUrl + '/';
  
  // 构建 sitemap URL
  const sitemapUrl = siteUrl + '/sitemap.xml';
  
  return {
    enable: config.enable,
    serviceAccountFile: config.service_account_file,
    siteUrl: normalizedSiteUrl,
    sitemapUrl: sitemapUrl
  };
}

/**
 * 创建 Google OAuth 认证客户端
 * @param {String} serviceAccountFile - 服务账号密钥文件路径
 * @param {Object} log - 日志对象
 * @returns {Promise<GoogleAuth>} 认证客户端
 */
async function createAuthClient(serviceAccountFile, log) {
  // 检查文件是否存在
  if (!fs.existsSync(serviceAccountFile)) {
    throw new Error(`服务账号密钥文件不存在: ${serviceAccountFile}`);
  }
  
  log.info('[Google Search Console] 加载服务账号密钥...');
  
  // 创建认证客户端
  const auth = new google.auth.GoogleAuth({
    keyFile: serviceAccountFile,
    scopes: ['https://www.googleapis.com/auth/webmasters']
  });
  
  log.info('[Google Search Console] 认证客户端创建成功');
  
  return auth;
}

/**
 * 提交 sitemap 到 Google Search Console
 * @param {GoogleAuth} auth - 认证客户端
 * @param {String} siteUrl - 网站 URL
 * @param {String} sitemapUrl - sitemap URL
 * @param {Object} log - 日志对象
 * @returns {Promise<void>}
 */
async function submitSitemap(auth, siteUrl, sitemapUrl, log) {
  log.info('[Google Search Console] 正在提交 sitemap...');
  log.info('[Google Search Console] 站点: %s', siteUrl);
  log.info('[Google Search Console] Sitemap: %s', sitemapUrl);
  
  // 创建 webmasters API 客户端
  const webmasters = google.webmasters({ version: 'v3', auth });
  
  try {
    // 调用 sitemap.submit API
    await webmasters.sitemaps.submit({
      siteUrl: siteUrl,
      feedpath: sitemapUrl
    });
    
    log.info('[Google Search Console] ✓ Sitemap 提交成功!');
  } catch (error) {
    // 解析 API 错误
    if (error.response) {
      const status = error.response.status;
      const message = error.response.data?.error?.message || error.message;
      throw new Error(`API 调用失败 (${status}): ${message}`);
    } else {
      throw error;
    }
  }
}

// 在部署完成后提交 sitemap
hexo.on('deployAfter', async function() {
  const log = hexo.log;
  
  try {
    log.info('[Google Search Console] 开始处理 sitemap 提交...');
    
    // 读取配置
    const config = readConfig(hexo);
    if (!config) {
      return;
    }
    
    log.info('[Google Search Console] 配置读取成功');
    
    // 创建认证客户端
    const auth = await createAuthClient(config.serviceAccountFile, log);
    
    // 提交 sitemap
    await submitSitemap(auth, config.siteUrl, config.sitemapUrl, log);
    
    log.info('[Google Search Console] 已通知 Google 索引网站内容');
  } catch (error) {
    log.error('[Google Search Console] 执行失败:', error.message);
    log.warn('[Google Search Console] 提交失败不影响部署流程');
    
    // 提供常见错误的解决建议
    if (error.message.includes('文件不存在')) {
      log.warn('[Google Search Console] 提示: 请确保服务账号密钥文件已正确放置');
    } else if (error.message.includes('403')) {
      log.warn('[Google Search Console] 提示: 请确保服务账号已在 Google Search Console 中授予所有者权限');
    } else if (error.message.includes('404')) {
      log.warn('[Google Search Console] 提示: 请确保站点已在 Google Search Console 中验证');
    }
  }
});

运行部署命令:

bash 复制代码
hexo clean && hexo g && hexo d

你应该看到类似的日志输出:

复制代码
[Google Search Console] 开始处理 sitemap 提交...
[Google Search Console] 配置读取成功
[Google Search Console] 加载服务账号密钥...
[Google Search Console] 认证客户端创建成功
[Google Search Console] 正在提交 sitemap...
[Google Search Console] 站点: https://你的域名/
[Google Search Console] Sitemap: https://你的域名/sitemap.xml
[Google Search Console] ✓ Sitemap 提交成功!
[Google Search Console] 已通知 Google 索引网站内容

5 代理配置(可选)

如果你在国内访问 Google API 有困难,需要配置代理。

5.1 创建代理管理脚本

创建 proxy-functions.sh 文件:

bash 复制代码
#!/bin/bash

# 开启代理
proxy_on() {
    export http_proxy=http://127.0.0.1:7897
    export https_proxy=http://127.0.0.1:7897
    export HTTP_PROXY=http://127.0.0.1:7897
    export HTTPS_PROXY=http://127.0.0.1:7897
    export no_proxy=localhost,127.0.0.1
    echo "✅ 代理已开启: http://127.0.0.1:7897"
}

# 关闭代理
proxy_off() {
    unset http_proxy
    unset https_proxy
    unset HTTP_PROXY
    unset HTTPS_PROXY
    unset no_proxy
    echo "❌ 代理已关闭"
}

# 查看代理状态
proxy_status() {
    if [ -n "$http_proxy" ]; then
        echo "✅ 代理状态: 已开启"
        echo "   HTTP:  $http_proxy"
        echo "   HTTPS: $https_proxy"
    else
        echo "❌ 代理状态: 已关闭"
    fi
}

echo "代理管理函数已加载!"
echo "使用方法:"
echo "  proxy_on      - 开启代理"
echo "  proxy_off     - 关闭代理"
echo "  proxy_status  - 查看代理状态"

5.2 使用代理

bash 复制代码
# 加载代理函数
source proxy-functions.sh

# 开启代理
proxy_on

# 部署(会使用代理访问 Google API)
hexo deploy

# 关闭代理
proxy_off

5.3 使用代理的第二种方法

google-search-console.jsindexnow.js 的开头,添加以下代码,作用是,在运行js脚本期间,能访问外网:

js 复制代码
process.env.http_proxy = 'http://127.0.0.1:7897';
process.env.https_proxy = 'http://127.0.0.1:7897';

注意 :将 127.0.0.1:7897 替换为你的实际代理地址和端口。

6 验证效果

6.1 IndexNow 验证

  1. 查看部署日志,确认提交成功
  2. 等待几分钟到几小时
  3. 在 Bing 中搜索你的文章标题,看是否已被索引
  1. 登录 Google Search Console
  2. 选择你的网站
  3. 点击左侧"索引" > "站点地图"
  4. 查看 sitemap.xml 的状态
  5. 应该显示"成功",并且"上次读取时间"是最近的部署时间

7 常见问题

Q1: IndexNow 提交失败怎么办?

检查清单

  • 确认 API Key 正确
  • 确认密钥验证文件已部署到网站
  • 检查网络连接
  • 查看详细错误日志

Google Search Console管理界面中,Sitemaps菜单下,如果提交的/sitemap.xml,在很长时间内,状态一直是"Couldn't fetch",那么可以参考以下的解决方案。

解决方案

  1. 创建 source/robots.txt 文件:

    User-agent: *
    Allow: /

    Sitemap: https://你的域名/sitemap.xml

  2. 重新部署并等待几天

  3. 在 Search Console 中手动请求重新抓取

Q3: 代理设置会影响正常上网吗?

答案:如果代理关闭了,终端会无法访问网络。

建议 :使用 proxy_onproxy_off 函数灵活控制,不要永久设置代理。

Q4: 为什么要用缓存机制?

原因

  • 避免重复提交相同的 URL
  • 节省 API 调用次数
  • 符合 IndexNow 最佳实践(只提交变化的 URL)

Q5: 方案一:IndexNow 自动提交,首次部署会提交所有 URL 吗?

答案:是的。首次部署时缓存为空,会提交所有 URL。之后只提交有变化的 URL。

8 优势总结

使用自动提交方案后:

  • 快速索引:新文章可能在几分钟到几小时内被索引
  • 智能提交:只提交有变化的 URL,节省资源
  • 覆盖全面:同时支持 Google、Bing、Yandex 等搜索引擎
  • 完全自动:每次部署自动执行,无需手动操作
  • 详细日志:清晰显示提交的 URL 和结果

再也不用担心新文章迟迟不被搜索引擎收录了!

9 参考资料

相关推荐
mediocrep5 天前
我的Hexo博客搭好了04 - 备份Hexo博客的源码目录,上传到Gitee仓库
hexo·博客搭建·github pages·个人技术博客
mediocrep5 天前
我的Hexo博客搭好了10 - 怎么插入图片和视频
hexo·博客搭建·github pages·个人技术博客
竹之却5 天前
【Hexo】Hexo搭建教程
github·hexo·blog
AwakeFantasy2 个月前
关于fluid打字机问题的解决记录
javascript·博客·hexo·fluid
大饼酥2 个月前
Hexo + Obsidian + Git 搭建个人博客及编辑方案
hexo·个人博客·obsidian·fluid
徐sir(徐慧阳)3 个月前
搭建属于自己的网站HEXO静态页(二)发布网站到gihub
服务器·node.js·github·hexo
苏琢玉3 个月前
从 Hexo 到 Astro:重构我的个人博客
前端·hexo
wdfk_prog4 个月前
构建基于Hexo、Butterfly、GitHub与Cloudflare的高性能个人博客
笔记·学习·github·hexo·blog
LunarCod4 个月前
Hexo搭建/部署个人博客教程
后端·hexo·个人博客·vercel