一句话总结 :一个 100 行的 Python 脚本,定期从 sitemap.xml 提取 URL、对比已提交记录、分批提交到搜索引擎 API。核心设计是”只提交新的,不浪费每日配额,断点续传”。
目录
为什么需要这个脚本
脚本架构
核心模块实现
完整代码
运行与维护
遇到的问题与解决
扩展思路
为什么需要这个脚本 静态站点(GitHub Pages、Vercel 等)没有后端服务器,无法像动态网站那样在发布文章时自动通知搜索引擎。每次新增文章后,需要手动将新 URL 提交给搜索引擎,操作繁琐且容易遗漏。
手动提交的痛点:
需要打开站长平台 → 手动粘贴 URL → 提交
文章多了记不清哪些提交过
每日配额有限,胡乱提交浪费配额
针对这些问题,目标很清晰:
1 2 3 输入:sitemap.xml(包含站点所有 URL) 处理:自动找出"尚未提交过"的 URL → 分批提交 输出:已提交记录 → 下次运行时跳过
脚本架构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ┌─────────────────┐ │ sitemap.xml │ │ (所有 URL) │ └────────┬────────┘ ▼ ┌──────────────────────┐ │ 对比历史提交记录 │ │ baidu_submitted.json │ └────────┬─────────────┘ ▼ ┌──────────────────────┐ │ 挑出未提交的 URL │ │ → 分批(5条/批) │ └────────┬─────────────┘ ▼ ┌──────────────────────┐ │ POST 到搜索引擎 API │ │ → 解析返回结果 │ └────────┬─────────────┘ ▼ ┌──────────────────────┐ │ 更新提交记录文件 │ │ → 下次跳过已提交的 │ └──────────────────────┘
核心模块实现 模块一:sitemap 解析 Hexo 生成的 sitemap.xml 遵循标准的 sitemap 协议:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import xml.etree.ElementTree as ETSITEMAP_PATH = 'public/sitemap.xml' def parse_sitemap (path ): """从 sitemap.xml 提取所有 URL""" tree = ET.parse(path) root = tree.getroot() ns = {'ns' : 'http://www.sitemaps.org/schemas/sitemap/0.9' } urls = [] for url in root.findall('ns:url' , ns): loc = url.find('ns:loc' , ns) if loc is not None and loc.text: urls.append(loc.text.strip()) return urls
模块二:提交记录管理 使用 JSON 文件持久化记录,set 结构确保唯一性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import jsonRECORD_PATH = 'baidu_submitted.json' def load_submitted (): """读取已提交记录""" if os.path.exists(RECORD_PATH): with open (RECORD_PATH, 'r' , encoding='utf-8' ) as f: return set (json.load(f)) return set () def save_submitted (urls ): """保存已提交记录""" with open (RECORD_PATH, 'w' , encoding='utf-8' ) as f: json.dump(sorted (urls), f, ensure_ascii=False , indent=2 )
模块三:API 提交 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requestsBAIDU_API = ( 'http://data.zz.baidu.com/urls' '?site=https://example.com' '&token=YOUR_TOKEN' ) def submit_batch (urls, api_url ): """提交一批 URL""" body = '\n' .join(urls) headers = {'Content-Type' : 'text/plain' } resp = requests.post(api_url, data=body.encode('utf-8' ), headers=headers) return resp.json()
模块四:主控逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 def main (): submitted = load_submitted() print (f'已提交记录:{len (submitted)} 条' ) all_urls = parse_sitemap(SITEMAP_PATH) print (f'sitemap 总计:{len (all_urls)} 条' ) new_urls = [u for u in all_urls if u not in submitted] print (f'尚未提交:{len (new_urls)} 条' ) if not new_urls: return BATCH_SIZE = 5 total_submitted = 0 idx = 0 while idx < len (new_urls): batch = new_urls[idx:idx + BATCH_SIZE] result = submit_batch(batch, BAIDU_API) if 'error' in result: handle_error(result) break success = result.get('success' , 0 ) remain = result.get('remain' , 0 ) if success > 0 : submitted.update(batch[:success]) total_submitted += success idx += success if remain == 0 : break if total_submitted > 0 : save_submitted(submitted)
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 """ 收录提交脚本 用法:python baidu_submit.py """ import osimport sysimport jsonimport ioimport requestsimport xml.etree.ElementTree as ETSITEMAP_PATH = os.path.join(os.path.dirname(__file__), 'public' , 'sitemap.xml' ) RECORD_PATH = os.path.join(os.path.dirname(__file__), 'baidu_submitted.json' ) BAIDU_API = 'http://data.zz.baidu.com/urls?site=https://example.com&token=YOUR_TOKEN' def load_submitted (): if os.path.exists(RECORD_PATH): with open (RECORD_PATH, 'r' , encoding='utf-8' ) as f: return set (json.load(f)) return set () def save_submitted (urls ): with open (RECORD_PATH, 'w' , encoding='utf-8' ) as f: json.dump(sorted (urls), f, ensure_ascii=False , indent=2 ) def parse_sitemap (path ): tree = ET.parse(path) root = tree.getroot() ns = {'ns' : 'http://www.sitemaps.org/schemas/sitemap/0.9' } urls = [] for url in root.findall('ns:url' , ns): loc = url.find('ns:loc' , ns) if loc is not None and loc.text: urls.append(loc.text.strip()) return urls def submit_batch (urls, api_url ): body = '\n' .join(urls) headers = {'Content-Type' : 'text/plain' } resp = requests.post(api_url, data=body.encode('utf-8' ), headers=headers) return resp.json() def main (): sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8' , errors='replace' ) if not os.path.exists(SITEMAP_PATH): print ('[错误] 找不到 sitemap.xml,先执行 hexo g' ) sys.exit(1 ) submitted = load_submitted() print ('已提交记录:%d 条' % len (submitted)) all_urls = parse_sitemap(SITEMAP_PATH) print ('sitemap 总计:%d 条' % len (all_urls)) new_urls = [u for u in all_urls if u not in submitted] print ('尚未提交:%d 条' % len (new_urls)) if not new_urls: print ('没有新 URL,跳过。' ) return BATCH_SIZE = 5 total_submitted = 0 idx = 0 while idx < len (new_urls): batch = new_urls[idx:idx + BATCH_SIZE] print ('\n正在提交第 %d~%d 条...' % (idx + 1 , min (idx + BATCH_SIZE, len (new_urls)))) result = submit_batch(batch, BAIDU_API) if 'error' in result: msg = result.get('message' , '' ) if 'quota' in msg: print (' [i] 当日配额已用完' ) else : print (' [!] 提交失败:' + msg) break success = result.get('success' , 0 ) remain = result.get('remain' , 0 ) print (' [OK] 成功:%d 条 | 剩余配额:%d 条' % (success, remain)) if success > 0 : submitted.update(batch[:success]) total_submitted += success idx += success if remain == 0 : print (' [i] 配额已用完,停止。' ) break if total_submitted > 0 : save_submitted(submitted) print ('\n本次共提交 %d 条' % total_submitted) else : print ('\n本次未提交成功。' ) if __name__ == '__main__' : main()
运行与维护 1 2 3 hexo g -d python baidu_submit.py
脚本特点:
幂等 :重复运行不会重复提交已提交过的 URL
断点续传 :每日运行,每次只提交新增的
配额感知 :单批次不超过剩余配额,避免整批被拒
自动跳过 :已提交的记录自动跳过
遇到的问题与解决 问题 1:Windows 编码问题 1 UnicodeEncodeError: 'gbk' codec can't encode character
原因 :Windows 控制台默认 GBK 编码,print 包含特殊字符时报错。
解决 :重定向 stdout:
1 sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8' , errors='replace' )
同时,不要在 print 中使用 emoji ——Windows 对 emoji 支持不稳定。
问题 2:单次提交超过剩余配额 API 返回 {"error":400, "message":"over quota"}。
原因 :剩余配额 7 条时,提交了 10 条,整批被拒。
解决 :批次大小设为 5 条,且每次提交后检查剩余配额。
问题 3:配额不重置 GitHub Pages 无备案站点的 API 配额可能是一次性总限额而不是每日重置。
解决 :配额用完后改用手动提交补充。
扩展思路 这个脚本可以扩展到更多场景:
多搜索引擎支持 1 2 3 4 5 6 7 8 9 10 11 ENGINES = { 'baidu' : { 'api' : 'http://data.zz.baidu.com/urls?...' , 'record' : 'baidu_submitted.json' , }, 'bing' : { 'api' : 'https://ssl.bing.com/webmaster/api?...' , 'record' : 'bing_submitted.json' , }, }
CI/CD 集成 在 GitHub Actions 中每次部署后自动运行:
1 2 3 4 5 6 7 8 9 on: push: branches: [main ] jobs: submit: runs-on: ubuntu-latest steps: - run: python baidu_submit.py
增量监控 配合定时任务(cron job)定期检查 sitemap 变化:
1 2 0 3 * * * cd /path/to/site && python baidu_submit.py
总结
设计原则 :幂等(不重复提交)+ 增量(只提交新的)+ 配额感知(不超过限制)
关键技术 :sitemap 解析 + JSON 记录 + 分批 POST + 错误处理
维护成本 :几乎为零——每次部署后跑一次脚本就行,自动跳过已提交的
扩展性好 :可从单一搜索引擎扩展到多个,也可集成到 CI/CD 流程
总结 :这个 100 行脚本的核心价值不在于代码量,而在于设计思想——幂等性、增量处理、配额感知。这些原则同样适用于其他 API 限速场景的任务自动化。