多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

维基百科中文版API踩坑:手写实现稳定抓取方案

维基百科中文版API踩坑:手写实现稳定抓取方案 维基百科中文版API踩坑:手写实现稳定抓取方案 最近升级了内部数据同步服务,刚跑完测试,生产环境直接报了一堆 404 和字段缺失。检查日志发现,维基百科中文版的 MediaWiki API 在 1.40 版本后对部分批量查询接口做了不兼容变更,导致原有代码全崩。 这种“版本升级后 API 全变了”的情况,在对接开放数据源时太常见了。官方文档更新滞后,社区反馈又碎片化,这时候死磕官方 SDK 或第三方库往往解决不了根本问题。我的经验是:抛弃黑盒,手写实现核心请求逻辑。 今天这篇教程,不聊虚的。我们直接面对市政公用工程中常见的“跨部门数据共享”痛点,结合运维开发视角,手把手带你手写实现一个稳定的维基百科中文版数据抓取器。别被“维基百科”这个名字吓到,这其实是一个绝佳的练习对象:它免费、开放、结构复杂,且经常变动,非常适合用来打磨你的 API 交互能力。 概念速懂:为什么选维基百科做练手 很多读者可能会问,写个爬虫有什么难的?为什么非要盯着维基百科中文版? 在实际的市政公用工程信息化项目中,我们经常需要对接政府公示数据、历史档案库或外部知识库。这些系统的特点和维基百科很像:数据量巨大:单次请求无法获取全量,必须分页。 结构动态:字段名可能随版本迭代调整。 限流严格:IP 被封禁是常态,必须做并发控制。维基百科中文版(zh.wikipedia.org)基于 MediaWiki 平台,其 API 遵循 RESTful 风格。对于运维开发来说,理解它的底层逻辑,比死记硬背某个 Python 库的函数更有价值。 这里有一个关键概念:Action API vs REST API。Action API (/w/api.php):老接口,功能全,但返回的是 JSON 包裹的复杂结构,解析麻烦。 REST API (/api/rest_v1/):新接口,更轻量,但覆盖范围有限。本文我们主要使用 Action API,因为它的稳定性在长期项目中经过验证,且支持更复杂的过滤参数。这也是为什么很多老牌系统还在用它的根本原因。 环境准备:最小化依赖 为了体现“手写实现”的价值,我们尽量少用现成的高层封装库。你需要准备:Python 3.8+:推荐版本,语法特性支持更好。 requests 库:唯一的第三方依赖,用于 HTTP 通信。安装命令:pip install requests一个文本编辑器:VS Code 或 PyCharm 均可。重要提示:在开始写代码前,请务必阅读维基百科的开发者文档(MediaWiki API 官方指南)。特别是关于 User-Agent 的请求头要求。维基百科明确要求用户设置合法的 User-Agent,否则会被 403 拒绝。这是很多新手踩坑的第一道门槛。 核心语法:拆解请求与响应 在动手写完整代码前,我们先拆解一次典型的 API 交互。 假设我们要获取“北京市”这个条目的信息。 URL 构造如下: https://zh.wikipedia.org/w/api.php?action=querytitles=北京市format=jsonprop=extracts 参数解析:action=query:指定操作类型为查询。 titles=北京市:查询的目标页面。 format=json:强制返回 JSON 格式,方便解析。 prop=extracts:指定返回页面的纯文本摘要。关键点:维基百科的响应结构是嵌套的。 {batchcomplete: ,query: {normalized: [],pages: {12345: {pageid: 12345,title: 北京市,extract: 北京市,简称“京”,是中华人民共和国的...}}} }注意 pages 是一个字典,Key 是 pageid,而不是固定的索引。很多开发者在这里犯错,试图用 pages[0] 取值,结果直接 KeyError。 完整代码示例:手写稳定抓取器 下面这段代码是我在实际项目中使用的简化版模板。它包含了重试机制、User-Agent 设置和基础的错误处理。 import requests import time import logging# 配置日志,方便运维排查 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__)class WikipediaClient:def __init__(self):# 必须设置 User-Agent,否则会被维基百科屏蔽# 格式参考:https://meta.wikimedia.org/wiki/User-Agent_policyself.base_url = https://zh.wikipedia.org/w/api.phpself.headers = {User-Agent: MyEngineeringBot/1.0 (contact@example.com) Python-requests}self.session = requests.Session()self.session.headers.update(self.headers)def fetch_page_extract(self, title, retries=3):获取指定页面的纯文本摘要params = {action: query,titles: title,format: json,prop: extracts,exintro: 1, # 只取首段,减少数据量redirects: 1 # 自动重定向}for attempt in range(retries):try:response = self.session.get(self.base_url, params=params, timeout=10)response.raise_for_status() # 非200状态码抛异常data = response.json()# 检查是否成功if error in data:logger.error(fAPI Error: {data['error']})return None# 遍历 pages 字典,因为 Key 是动态的pages = data.get(query, {}).get(pages, {})for page_id, page_data in pages.items():# 检查是否是被重定向的页面if page_data.get(missing) == :logger.warning(fPage {title} not found.)return Nonereturn page_data.get(extract)return Noneexcept requests.exceptions.RequestException as e:logger.warning(fRequest failed (Attempt {attempt + 1}): {e})if attempt retries - 1:time.sleep(2 ** attempt) # 指数退避策略continuereturn None# 测试运行 if __name__ == __main__:client = WikipediaClient()result = client.fetch_page_extract(北京市)if result:print(result[:200]) # 打印前200个字符预览else:print(获取失败)代码逐行解读:requests.Session():复用到维基百科的连接,比每次新建 requests.get 性能高,且能自动保持 Cookie(虽然维基百科大多无状态,但这是好习惯)。 raise_for_status():很多教程忽略这一步。如果服务器返回 500,response.json() 会解析失败或返回错误结构。显式抛出异常能让我们更早发现问题。 指数退避(Exponential Backoff):time.sleep(2 ** attempt)。如果第一次失败,等1秒;第二次失败,等2秒;第三次失败,等4秒。这能有效避免在服务器压力大时持续轰炸,也是遵守网络礼仪的表现。 遍历 pages 字典:再次强调,不要假设 pages 的长度或 Key。这是处理 MediaWiki API 的核心技巧。常见报错与避坑指南 在实际生产环境中,你大概率会遇到以下三类问题: 1. HTTP 403 Forbidden 现象:所有请求都被拒绝。 原因:User-Agent 缺失或格式不规范。 解决:严格按照维基百科的 User-Agent 策略修改。必须包含联系方式和软件名称。不要使用默认的 python-requests/x.x.x。 2. KeyError: 'pages' 或 'query' 现象:代码运行到解析 JSON 时报错。 原因:网络抖动导致返回了 HTML 错误页面。 请求参数错误,API 返回了 Error 对象而非 Query 对象。 解决:在访问 data['query'] 之前,务必先检查 data 中是否存在 error 字段。使用 .get() 方法代替 [] 取值更安全。3. 频率限制(429 Too Many Requests) 现象:批量抓取时突然中断。 原因:并发过高或短时间内请求过多。 解决:控制并发数:建议使用 concurrent.futures.ThreadPoolExecutor,将并发线程控制在 5-10 以内。 增加间隔:在循环中加入 time.sleep(0.5)。 注意:维基百科对 IP 的限流非常严格,尤其是数据中心 IP。如果是生产环境,建议轮换 IP 或申请正式的 Bot 权限。小结与互动 通过上述步骤,我们手写实现了一个基于维基百科中文版 API 的数据抓取器。这个过程不仅解决了“版本升级后 API 全变了”带来的脆弱性,更重要的是,让你彻底理解了 HTTP 交互、JSON 解析和错误处理的底层逻辑。 对于市政公用工程从业者而言,这种能力可以迁移到对接住建局数据接口、环保监测数据平台等场景。核心思想不变:理解协议,掌控请求,妥善处理异常。 你公司项目里是怎么处理的?欢迎评论。 比如,你们在对接外部数据源时,是倾向于封装统一的 SDK,还是像这样每次手写?或者有没有遇到过更奇葩的 API 变更?在评论区聊聊,咱们一起避坑。
返回列表