维基百科中文版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 变更?在评论区聊聊,咱们一起避坑。
企业数字化 ERP 产品动态
相关推荐
C#+Halcon+海康相机软解码二维码完整实践 简介:面向C#开发者与机器视觉工程师,围绕Halcon与海康工业相机的二维码解析,提供了一套可直接参考的完整工程示例,覆盖生产线场景中二维码实时识别与软件解码。压缩包共37个文件,约29.61MB,以C#源代码&… · 2026/9/23 17:57:31
世嘉模拟器下载踩坑实录:手写实现解决报错 世嘉模拟器下载踩坑实录:手写实现解决报错 刚跑起那个世嘉模拟器下载脚本,满屏红色 StackTrace 看得人眼晕? ModuleNotFoundError 、 PermissionError… · 2026/9/23 17:57:25
MCP Server实战:统一Agent工具调用,告别胶水代码 1. Agent就差这一步:工具调用为什么一直靠"手写胶水"1.1 一个再常见不过的卡点做Agent开发这段时间,我几乎每个项目都会经历同一种挫败:模型推理能力明明够用,思考链路也清晰,但一落到"调用外部能力&qu… · 2026/9/23 18:38:04
围成语实战速查手册:告别StackTrace报错 围成语实战速查手册:告别StackTrace报错 刚拿到“围成语”实战项目的代码,是不是直接运行就崩了?满屏红色的 StackTrace 像天书一样滚过去,头都大了。别慌,这正是大多数开发者卡在起步期的原因。今天这份 速查手册… · 2026/9/23 18:37:58
猴哥博客实战:5步图解原理,告别Stack Trace报错 猴哥博客实战:5步图解原理,告别Stack Trace报错 盯着屏幕上滚动的红色 StackTrace ,是不是脑子瞬间一片空白?那行 java.lang.NullPointerException… · 2026/9/23 18:37:58
大模型工程落地的五维决策地图:预训练、微调、量化、剪枝、蒸馏实战指南 1. 这不是“技术名词扫盲”,而是大模型工程落地的决策地图你手头正跑着一个Qwen2.5-7B模型,显存占用32GB,推理延迟800ms,业务方催着上线——这时候翻文档查“什么是量化”“剪枝和蒸馏有啥区别”,已经来不及了。我干这… · 2026/9/23 18:37:58
六丁神火手写实现:3步跑通完整示例,告别文档迷茫 六丁神火手写实现:3步跑通完整示例,告别文档迷茫 打开官方文档看“六丁神火”相关并发模型,是不是感觉像进了迷宫?全是理论图表,找不到一个能直接跑通的 完整示例 。… · 2026/9/23 18:37:51
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29