Wikipedia 是有史以来规模最大的参考文献,几乎所有内容都以相同的方式组织:标题、引言摘要、一系列章节标题、关键事实信息框,以及若干数据表格。这种规律性使其成为网络上最实用的公开来源之一,可用于研究、内容丰富、知识图谱和训练数据集。本指南将向你展示如何以干净、可重复的方式使用 Python 抓取 Wikipedia

本文所有内容都限定在公开的百科全书内容范围内:文章标题、引言摘要、章节标题、信息框字段,以及任何人无需登录即可阅读的表格。我们将构建一个小型爬虫,通过 Crawling API 获取渲染后的文章页面,用 BeautifulSoup 解析这些字段,并将其导出为 JSON 和 CSV。Wikipedia 文本以 CC-BY-SA 协议自由授权,因此在将此工具用于任何真实数据量之前,请阅读结尾附近的署名和合法性部分,并考虑使用官方 MediaWiki API, 这才是大多数项目的推荐途径。

你将构建什么

一个 Python 脚本,接受 Wikipedia 文章 URL,通过 Crawling API 获取页面,并解析若干结构化字段:

  • 文章标题:来自 firstHeading 元素的页面标题。
  • 摘要:文章开头的引言段落。
  • 章节标题:概述文章结构的 h2h3 标题。
  • 信息框字段:侧边信息框中的标签-值对(出生、国籍、职业等)。
  • 表格:wikitable 数据表格,解析为可写入 CSV 的行数据。

每个字段都映射到稳定的 Wikipedia CSS 类或元素 id,因此该爬虫适用于大多数文章页面,而不仅仅是某一篇。

为什么通过 Crawling API 抓取

Wikipedia 文章页面大多是服务器端渲染的 HTML,因此普通请求通常能返回可用的标记。摩擦出现在规模扩大时。Wikimedia 要求自动化客户端进行自我标识,严格遵守速率限制,并会对从单一地址高频请求其服务器的流量进行限速或封禁。如果你在数据中心 IP 上运行紧密循环,很快就会触及这些限制,而被封禁的 IP 会让整个任务停止。

通过 Crawling API 路由请求解决了运营层面的问题。你发送文章 URL,它通过轮换的受信任 IP 池获取页面,处理重试,并将完整的 HTML 返回供你解析。这样就没有单一地址会触发速率限制,也不需要你自己维护代理池。下面的解析方式与你对任何 HTML 来源使用的方式相同,如果你是第一次接触,我们关于如何在 Python 中使用 BeautifulSoup 的入门文章深入介绍了提取部分。

尽可能使用官方 API

Wikipedia 运行 MediaWiki API 并发布完整的数据库转储。对于大型结构化抓取,这些是首选途径,我们在合法性部分会推荐它们。本教程介绍 HTML 抓取,因为这是读取单个页面上读者所见内容最直接的方式,而且这些技术可以迁移到任何网站。

前置条件

首先需要准备好几件事,都不需要太长时间。

基本 Python 知识。你应该能够运行脚本并使用 pip 安装包。熟悉 HTML 结构会有帮助,因为整个任务就是通过类和 id 定位元素。

Python 3.8 或更高版本。使用 python --version 确认。如果没有,请从 python.org 安装。

Crawlbase 账号和 token。注册后打开控制台,从账号文档页面复制你的 token。Wikipedia 是静态 HTML,因此这里普通请求 token 是正确的选择;你不需要 JavaScript 渲染。请将 token 保存在版本控制之外。

设置项目

创建隔离的虚拟环境,然后安装爬虫所需的三个库。

bash
python --version

python -m venv wikipedia_env
source wikipedia_env/bin/activate

pip install crawlbase beautifulsoup4 pandas

在 Windows 上,请用 wikipedia_env\Scripts\activate 替代 source 那行来激活环境。三个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML,pandas 将信息框和表格转化为可写入 CSV 的 DataFrame。

第一步:获取文章 HTML

首先获取原始页面。导入 CrawlingAPI,用你的 token 初始化它,并请求文章 URL。在解析之前检查状态码,可以让错误清晰可见而不是被静默忽略。我们将以 Ada Lovelace 的 Wikipedia 文章作为运行示例,因为它有丰富的信息框和多个表格。

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def crawl(page_url):
    response = api.get(page_url)
    if response["status_code"] == 200:
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['status_code']}")
    return None

if __name__ == "__main__":
    page_url = "https://en.wikipedia.org/wiki/Ada_Lovelace"
    html = crawl(page_url)
    print(html[:500] if html else "No HTML returned")

运行此代码,你应该能看到文章 HTML 的开头打印到终端。这证明请求正常工作,并为你提供了可解析的完整标记。CrawlingAPI 对象只创建一次并重复使用,这是你开始在循环中获取多个页面时需要的模式。

Crawlbase Crawling API

Wikipedia 会对从单一地址高频请求其服务器的流量进行限速,而被封禁的 IP 会让整个任务停止。Crawling API 通过轮换的受信任 IP 池获取每篇文章并处理重试,这样任何单一地址都不会触发限制,且无需你自己维护代理池。从免费层开始,针对公开文章测试。

第二步:解析标题和摘要

拿到 HTML 后,将其加载到 BeautifulSoup。Wikipedia 给页面标题赋予了 id firstHeading,引言摘要是主体内容第一个章节标题之前的连续段落。我们从该 id 获取标题,然后将开头的段落收集为摘要。

python
from bs4 import BeautifulSoup

def parse_title_and_summary(html):
    soup = BeautifulSoup(html, "html.parser")

    title = soup.find("h1", id="firstHeading").get_text(strip=True)

    content = soup.find("div", class_="mw-parser-output")
    summary = []
    for p in content.find_all("p", recursive=False):
        text = p.get_text(strip=True)
        if text:
            summary.append(text)
        if len(summary) >= 3:
            break

    return title, " ".join(summary)

find('h1', id='firstHeading') 调用精确定位页面标题,与你在浏览器开发者工具中检查页面时找到的选择器相同。对于摘要,我们遍历 mw-parser-output(文章正文的包装器)的直接子段落,并取前几个非空段落。限制为三段,既能获得引言摘要,又不会把整篇文章拼成一个字符串。

第三步:收集章节标题

Wikipedia 用一致的标题结构为每篇文章提供大纲。在渲染的页面中,章节标题文本位于 h2h3 标签下、类名为 mw-headline 的元素内。收集这些内容即可按文档顺序得到目录。

python
def parse_sections(soup):
    sections = []
    for tag in soup.find_all(["h2", "h3"]):
        headline = tag.find("span", class_="mw-headline")
        if headline:
            sections.append({
                "level": tag.name,
                "heading": headline.get_text(strip=True),
            })
    return sections

我们只记录包含 mw-headline span 的标题,这过滤掉了页面框架中不相关的 h2h3 标签,只保留真正的文章章节。记录层级让你之后可以重建层次结构:h2 对应顶级章节,h3 对应子章节。

第四步:提取信息框字段

信息框是许多文章右侧关键事实的汇总框。Wikipedia 用 infobox 类标记它,每条事实是一个配对标签单元格(infobox-label)和数据单元格(infobox-data)的表格行。遍历这些行即可将信息框转化为干净的字段字典。

python
def parse_infobox(soup):
    infobox = soup.find("table", class_="infobox")
    if not infobox:
        return {}

    fields = {}
    for row in infobox.find_all("tr"):
        label = row.find("th", class_="infobox-label")
        data = row.find("td", class_="infobox-data")
        if label and data:
            key = label.get_text(strip=True)
            value = data.get_text(" ", strip=True)
            fields[key] = value

    image = infobox.select_one(".infobox-image img")
    if image and image.has_attr("src"):
        fields["image"] = "https:" + image["src"]

    return fields

每对 infobox-labelinfobox-data 变成一个键值条目,因此人物信息框会产生"出生"、"逝世"、"安葬地"、"国籍"和"职业"等字段,无需你事先指定字段名。get_text(" ", strip=True) 调用用空格连接多行值,使多个国籍等列表在一行中保持可读。我们还从 .infobox-image img 提取头图,并在前面加上 https:,因为 Wikipedia 使用协议相对 URL 提供图片。

第五步:解析数据表格

文章表格使用 wikitable 类。Pandas 可以直接读取 HTML 表格,因此最简洁的方法是将每个表格的 HTML 传递给 pandas.read_html,让它返回 DataFrame。这样几乎无需手动遍历单元格,就能得到随时可写入 CSV 的行和列。如果你想了解超出 Wikipedia 范围的通用技术,请参见我们关于从网站抓取表格的指南。

python
import pandas as pd
from io import StringIO

def parse_tables(soup):
    tables = []
    for node in soup.find_all("table", class_="wikitable"):
        try:
            df = pd.read_html(StringIO(str(node)))[0]
            tables.append(df)
        except ValueError:
            continue
    return tables

我们遍历每个 wikitable,将其 HTML 传递给 read_html,并为每个表格保留它返回的第一个 DataFrame。用 try 包裹调用,意味着格式错误的表格会被跳过而不是导致运行崩溃。结果是一个 DataFrame 列表,每个表格对应一个,可以分别写入 CSV 文件或根据需要拼接。关于在下游使用前清理这些 DataFrame 的方法,我们关于为 AI 和 ML 整理和清洗网络爬虫数据的指南涵盖了常见步骤。

第六步:整合并导出

现在将各部分连接成一个可运行的脚本。它获取文章,解析每个字段,将结构化数据写入 JSON,并将每个表格写入独立的 CSV 文件。

python
import json
from io import StringIO
import pandas as pd
from bs4 import BeautifulSoup
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def crawl(page_url):
    response = api.get(page_url)
    if response["status_code"] == 200:
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['status_code']}")
    return None

def scrape_article(html):
    soup = BeautifulSoup(html, "html.parser")

    title = soup.find("h1", id="firstHeading").get_text(strip=True)

    content = soup.find("div", class_="mw-parser-output")
    summary = []
    for p in content.find_all("p", recursive=False):
        text = p.get_text(strip=True)
        if text:
            summary.append(text)
        if len(summary) >= 3:
            break

    sections = []
    for tag in soup.find_all(["h2", "h3"]):
        headline = tag.find("span", class_="mw-headline")
        if headline:
            sections.append(headline.get_text(strip=True))

    infobox = {}
    box = soup.find("table", class_="infobox")
    if box:
        for row in box.find_all("tr"):
            label = row.find("th", class_="infobox-label")
            data = row.find("td", class_="infobox-data")
            if label and data:
                infobox[label.get_text(strip=True)] = data.get_text(" ", strip=True)

    tables = []
    for node in soup.find_all("table", class_="wikitable"):
        try:
            tables.append(pd.read_html(StringIO(str(node)))[0])
        except ValueError:
            continue

    return {
        "title": title,
        "summary": " ".join(summary),
        "sections": sections,
        "infobox": infobox,
    }, tables

def main():
    page_url = "https://en.wikipedia.org/wiki/Ada_Lovelace"
    html = crawl(page_url)
    if not html:
        return

    article, tables = scrape_article(html)
    article["source_url"] = page_url
    article["license"] = "CC BY-SA 4.0"

    with open("wikipedia.json", "w", encoding="utf-8") as f:
        json.dump(article, f, indent=2, ensure_ascii=False)

    for i, df in enumerate(tables):
        df.to_csv(f"wikipedia_table_{i}.csv", index=False)

    print(json.dumps(article, indent=2, ensure_ascii=False))
    print(f"Saved {len(tables)} table(s) to CSV")

if __name__ == "__main__":
    main()

脚本将标题、摘要、章节和信息框写入 wikipedia.json,并将每个 wikitable 写入各自编号的 CSV 文件。注意我们在保存前附加的两个额外字段:source_urllicense。记录数据来源及其 CC-BY-SA 授权不是可选的日常工作,而是满足署名要求的方式, 下面的合法性部分会具体介绍。

输出结果示例

运行完整脚本,JSON 文件将包含文章的干净结构化记录。

json
{
  "title": "Ada Lovelace",
  "summary": "Augusta Ada King, Countess of Lovelace, was an English mathematician and writer...",
  "sections": [
    "Biography",
    "Work",
    "Commemoration",
    "In popular culture"
  ],
  "infobox": {
    "Born": "Augusta Ada Byron 10 December 1815 London, England",
    "Died": "27 November 1852 (aged 36) Marylebone, London, England",
    "Occupation": "Mathematician"
  },
  "source_url": "https://en.wikipedia.org/wiki/Ada_Lovelace",
  "license": "CC BY-SA 4.0"
}

与此同时,你还会得到每个表格对应的 CSV 文件,命名为 wikipedia_table_0.csvwikipedia_table_1.csv 等,每个文件都有 pandas 解析出的表头行和数据行。从这里,JSON 可以输入知识库或内容管道,CSV 可以直接放入电子表格或数据库。

扩展到多篇文章

要构建数据集,将获取和解析循环扩展到文章 URL 列表,并对请求进行节流。Wikipedia 对突发流量很敏感,因此在页面之间添加小延迟能让你成为一个礼貌的客户端。

python
import time

urls = [
    "https://en.wikipedia.org/wiki/Ada_Lovelace",
    "https://en.wikipedia.org/wiki/Alan_Turing",
    "https://en.wikipedia.org/wiki/Grace_Hopper",
]

dataset = []
for url in urls:
    html = crawl(url)
    if html:
        article, _ = scrape_article(html)
        article["source_url"] = url
        dataset.append(article)
    time.sleep(2)

print(f"Collected {len(dataset)} articles")

请求之间的 time.sleep(2) 是在任何数据量下从 Wikipedia 收集时最重要的单一习惯。控制运行节奏,将批次保持在合理范围内,一旦获得所需的样本就停下来。不过,对于大规模结构化抓取,MediaWiki API 和数据库转储实际上更快、对 Wikimedia 服务器的压力也更小,我们接下来推荐它们。

抓取 Wikipedia 是否合法?

Wikipedia 是较为宽容的大型网站之一,因为其文本内容以知识共享署名-相同方式共享(CC-BY-SA)协议自由授权。这意味着你可以重复使用甚至重新分发文章文本,包括在商业项目中,但有两个条件:你需要注明来源(通常通过链接回文章来感谢 Wikipedia 和贡献者),并以相同的协议分享任何衍生作品。某些嵌入的媒体,例如某些图片,携带各自独立的授权协议,因此在重复使用图片之前请查看文件页面。脚本记录的 licensesource_url 字段正是为了让你在下游满足署名要求而存在的。

宽松的授权并不意味着可以无视服务器的压力。Wikimedia 的条款要求自动化客户端尊重速率限制、进行自我标识,并避免降低人类读者的服务质量。在紧密循环中高频请求是不礼貌的行为,很可能导致 IP 被封禁。如上所示控制请求频率,阅读网站的 robots.txt。本教程仅涉及公开的百科全书内容;不涉及与个人相关的用户账号页面、编辑历史,或任何需要身份验证才能访问的内容。

对于超过少量页面的情况,官方途径是正确选择。MediaWiki API 直接返回结构化的文章内容,包括已解析的章节和信息框数据,无需你抓取渲染后的 HTML;Wikimedia 还发布整个百科全书的完整数据库转储用于批量使用。两者都更快、更稳定,对 Wikimedia 基础设施的负担也远小于大规模 HTML 抓取。当你需要读者在单个页面上看到的确切内容时,选择 HTML 抓取;当你需要大量内容时,选择 API 或转储。

回顾

核心要点

  • Wikipedia 结构高度一致。标题(firstHeading)、引言摘要、mw-headline 章节、infoboxwikitable 表格都映射到稳定的选择器,适用于大多数文章。
  • 通过 Crawling API 路由以保持不被封锁。轮换的受信任 IP 和内置重试确保没有单一地址触发 Wikimedia 的速率限制,且无需维护代理池。
  • 让 pandas 处理表格。将每个 wikitable 传递给 pandas.read_html,即可得到直接可导出为 CSV 的 DataFrame。
  • 注明来源。Wikipedia 文本以 CC-BY-SA 协议授权,因此记录来源 URL 和许可证,并在任何重用中标注 Wikipedia;检查媒体文件各自的授权。
  • 大规模情况下优先选择官方途径。MediaWiki API 和数据库转储是批量拉取结构化 Wikipedia 数据的官方、更轻量的方式。

常见问题

抓取 Wikipedia 需要 JavaScript token 吗?

不需要。Wikipedia 文章页面是服务器渲染的静态 HTML,因此普通请求 token 已经足够。JavaScript token 用于在浏览器中构建内容的客户端渲染网站,而 Wikipedia 不是这样的网站。使用普通 token 使每次请求更轻量,也避免了额外的渲染步骤。

如何可靠地提取信息框字段?

找到 table.infobox 元素,然后遍历其行,将每个 th.infobox-label 与其 td.infobox-data 单元格配对。这种标签-值结构在文章之间是一致的,因此同一个循环可以将人物信息框、国家信息框或电影信息框转化为干净的字典,无需事先指定字段名。

我可以将 Wikipedia 表格抓取为 CSV 吗?

可以。选择每个 table.wikitable,将各自的 HTML 传递给 pandas.read_html,即可得到每个表格对应的 DataFrame,然后用 to_csv 导出。用 try 包裹读取调用,意味着格式错误的表格会被跳过而不是中断运行。

抓取的 Wikipedia 内容可以商业使用吗?

Wikipedia 文本以 CC-BY-SA 协议授权,只要你注明 Wikipedia 和贡献者的署名,并以相同协议分享衍生作品,即允许商业重用。嵌入的媒体可能携带各自的授权协议,因此在重用图片之前请查看各文件。随你的数据记录来源 URL 和许可证,以便之后轻松完成署名。

我应该使用 MediaWiki API 而不是直接抓取吗?

对于任何有一定规模的任务,是的。MediaWiki API 直接返回结构化的文章内容,Wikimedia 也发布供批量使用的完整数据库转储。两者都更快,对 Wikipedia 服务器的负担也远小于 HTML 抓取。当你需要特定页面上读者看到的确切内容时,HTML 抓取是正确工具;当你需要构建大型数据集时,API 和转储更合适。

抓取 Wikipedia 时如何避免被封锁?

保持低请求频率,如扩展章节所示在页面之间添加真实延迟,并通过轮换 IP 路由,确保没有单一地址被限速。Crawling API 为你处理轮换和重试。阅读网站的 robots.txt,将批次保持在合理范围内,对于大型任务优先选择官方 API 或转储,这样你才能成为礼貌的客户端。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量