Wikipedia 中的大量高价值数据存储在表格中:按面积排列的国家列表、人口数据、体育排名、历史年表以及其他数千个可排序的网格。这些表格数据公开且结构良好,是研究和分析的理想原始素材,但逐格复制到电子表格的方式在超过一两张表格后便难以扩展。
本指南向您展示如何用 Python 以正确的方式抓取 Wikipedia 表格。您将构建一个可运行的小脚本,通过 Crawling API 获取文章、定位所需表格、将其直接解析为 pandas DataFrame、进行清洗并导出为整洁的 CSV 文件。本文严格聚焦于公开表格数据,后文的合法性部分涵盖 Wikipedia 的 CC-BY-SA 许可协议和官方批量获取方式,请在将此方法应用于大量文章之前阅读该部分。本文专门讲解表格;如需了解页面标题、图片和信息框字段,请参阅配套指南如何抓取 Wikipedia。
您将构建什么
一个 Python 脚本,接收一个公开的 Wikipedia 文章 URL,通过 Crawling API 获取渲染后的 HTML,按 CSS 类选择特定表格,将其读入 pandas DataFrame,清洗列数据,并将结果写入 CSV 文件。示例文章为"List of countries and dependencies by area"(按面积排列的国家和地区列表)。每行输出记录包含以下字段:
- Rank:该行在源表格中的位置(如有)。
- Country / dependency:表格单元格中显示的名称。
- Total area:以平方公里和平方英里表示的面积数据。
- Land area:表格单独列出时的陆地面积。
- Water area:水域面积及其占总面积的百分比。
- Notes:表格携带的任何脚注或注释列。
了解 Wikipedia 表格的结构
Wikipedia 表格以 HTML 和 wikitext 混合编写,但当页面到达您的爬虫时,它们已被渲染为普通的 <table>、<tr>、<th> 和 <td> 元素,带有表头行和数据单元格。
对抓取影响最大的细节是 CSS 类。大多数数据表格带有 wikitable 类,这个类会应用您在文章中看到的标准带边框样式。这个共享类是您的锚点:它让您选择数据表格,同时跳过同一页面上的布局或导航表格;可排序表格还会额外添加 sortable 类,进一步暗示数据列整洁。一篇文章通常包含多个表格,因此您通常需要通过类、标题文字或索引来缩小范围,确定您想要的那一个。
为什么单纯的 HTTP 请求可能不够用
对于单篇文章,裸 HTTP 请求会返回可用的 HTML,因为 Wikipedia 在服务器端提供文章内容。问题出现在大量请求时:从单个数据中心 IP 以紧凑的循环方式抓取数十或数百篇文章,您看起来完全不像普通读者,而这正是速率限制和机器人防御所针对的流量模式。您还可能遇到瞬态封锁或不完整响应,悄无声息地中断无人值守的运行。
通过 Crawling API 路由请求可以平滑解决这两个问题。它通过可信 IP 轮换池获取每个页面,使较大规模的爬取请求分散到许多地址,而不是集中在一个地址上,并返回完成的 HTML 供您直接交给解析器。更全面的内容请参阅如何在不被封锁的情况下抓取网站。
前提条件
在编写任何代码之前,您需要准备好以下几样东西,都不需要太长时间。
基本 Python 知识。您应该能够编写和运行 Python 脚本,并使用 pip 安装包。如果您刚接触解析这一方面,BeautifulSoup 指南与本教程很好地配合。
Python 3.8 或更高版本。用 python --version 确认您的版本。如果没有,请从 python.org 或通过 Anaconda 等发行版安装,并确保 Python 在您的 PATH 中。
Crawlbase 账户和令牌。注册后,打开仪表板,从账户文档页面复制您的普通令牌。Crawlbase 提供 1,000 次免费请求作为起点,完全够您完成本指南。请像对待密码一样对待该令牌:它用于验证您的请求,因此不要将其纳入版本控制。
设置项目
创建一个虚拟环境以隔离项目依赖,然后安装爬虫所需的库。
python --version python -m venv wiki_env source wiki_env/bin/activate pip install crawlbase beautifulsoup4 pandas lxml
在 Windows 上,用 wiki_env\Scripts\activate 代替 source 那行来激活环境。四个依赖项各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 用于定位您想要的表格,pandas 将表格读入 DataFrame 并导出 CSV,lxml 是 pandas 在 read_html 中使用的快速 HTML 解析器。csv 模块随标准库一起提供,因此导出步骤无需额外安装。
步骤 1:获取渲染后的 Wikipedia 文章
首先获取文章 HTML。导入 CrawlingAPI 类,用您的令牌初始化,请求文章 URL,并在解析之前检查响应状态。首先检查 Crawlbase status_code 可以让失败显而易见,而不是悄悄发生。
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__": article_url = "https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_area" html = crawl(article_url) print(html[:500] if html else "No HTML returned")
用 python wikipedia_scraper.py 运行脚本,您应该在前 500 个字符中看到真实的文章标记,这确认了在编写选择器之前抓取功能正常。crawl 函数在状态 200 时返回解码后的 HTML,否则返回 None,因此脚本的其余部分可以根据假值返回进行保护,而不会在错误响应时崩溃。
上面的 crawl 辅助函数让您的代码保持简洁:一次调用即可返回任意文章的完整 HTML。在这次调用背后,Crawling API 轮换可信 IP 并为您处理封锁,因此当您从一篇文章扩展到数百个表格时,无需自己搭建和维护代理池。先在免费层级用公开文章测试。
步骤 2:定位您想要的表格
一篇文章通常包含多个表格,因此下一步是选择正确的那一个。将 HTML 加载到 BeautifulSoup 中,并使用 select_one 配合 wikitable 类来获取第一个数据表格。当页面有多个 wikitable 时,切换到 select 并索引您想要的那一个,或通过表格的标题文字进行过滤。
from bs4 import BeautifulSoup def find_table(html, index=0): soup = BeautifulSoup(html, "html.parser") tables = soup.select("table.wikitable") if not tables: print("No wikitable found on this page.") return None print(f"Found {len(tables)} wikitable(s); using index {index}.") return tables[index]
find_table 辅助函数返回一个渲染后的 <table> 元素,如果页面没有 wikitable,则返回 None 并给出清晰的提示信息。index 参数让您在内容更丰富的文章中定位第二或第三个数据表格,而无需重写选择器。打印表格数量是个小技巧,它在第一次运行时就告诉您是否在瞄准预期的表格。
您可以将整个页面传给 pandas.read_html,得到包含所有表格的列表,但这会将数据表格与侧边栏和导航框混在一起,让您猜测索引。先用 BeautifulSoup 选择您想要的那个 wikitable,再只解析该元素,每次都能得到一个可预测的单一 DataFrame。
步骤 3:将表格解析为 DataFrame
拿到表格元素后,pandas 负责繁重的工作。将表格的 HTML 传给 pandas.read_html,它返回一个 DataFrame 列表;由于您隔离了单个表格,取第一个元素即可。这是表格抓取的核心:一个函数将渲染后的 HTML 行和单元格转化为带类型的列式 DataFrame。
import pandas as pd from io import StringIO def table_to_df(table): frames = pd.read_html(StringIO(str(table))) if not frames: return None df = frames[0] print(f"Parsed table with {df.shape[0]} rows and {df.shape[1]} columns.") return df
将 str(table) 包装在 StringIO 中,符合当前 pandas 对传递给 read_html 的 HTML 的期望,同时避免弃用警告。打印出的形状是您的合理性检查:如果行数和列数与文章中看到的相符,说明解析落在了正确的表格上。如果您更愿意自己遍历行,可以用 BeautifulSoup 循环处理 table.select("tr") 并逐个读取 th 和 td 单元格,但 read_html 更快,且自动处理表头检测。
步骤 4:清洗 DataFrame
原始 Wikipedia 表格带有各种问题:多级列表头、单元格中的脚注标记、空白列以及合并单元格产生的残留数据。一个简短的清洗步骤可以将解析后的 DataFrame 转化为在导出之前就已准备好用于分析的数据。
def clean_df(df): # Flatten multi-level headers into single strings. if isinstance(df.columns, pd.MultiIndex): df.columns = [" ".join(str(p) for p in col if "Unnamed" not in str(p)).strip() for col in df.columns] # Strip footnote markers like [1] and [a] from string cells. df = df.replace(r"\[.*?\]", "", regex=True) # Drop fully empty rows and columns. df = df.dropna(axis=0, how="all").dropna(axis=1, how="all") # Trim surrounding whitespace on string cells. for col in df.select_dtypes(include="object").columns: df[col] = df[col].astype(str).str.strip() return df.reset_index(drop=True)
每个步骤针对真实世界的一种混乱。将 MultiIndex 展平,把嵌套表头行变成单个可读列名,并去除 pandas 为空白表头单元格插入的 Unnamed 占位符。正则替换删除 Wikipedia 散布在单元格中的括号脚注标记,例如 [1] 或 [a],否则这些标记会污染数字列。删除全空行和列清除合并单元格残留数据,最后的 strip 修剪多余的空白字符。根据您面对的表格调整步骤;并非每个表格都需要所有步骤。
步骤 5:组装完整脚本
现在将各部分组合成一个可运行的脚本:获取文章、隔离表格、解析、清洗并导出为 CSV。
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 find_table(html, index=0): soup = BeautifulSoup(html, "html.parser") tables = soup.select("table.wikitable") if not tables: return None return tables[index] def table_to_df(table): frames = pd.read_html(StringIO(str(table))) return frames[0] if frames else None def clean_df(df): if isinstance(df.columns, pd.MultiIndex): df.columns = [" ".join(str(p) for p in col if "Unnamed" not in str(p)).strip() for col in df.columns] df = df.replace(r"\[.*?\]", "", regex=True) df = df.dropna(axis=0, how="all").dropna(axis=1, how="all") for col in df.select_dtypes(include="object").columns: df[col] = df[col].astype(str).str.strip() return df.reset_index(drop=True) def main(): article_url = "https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_area" html = crawl(article_url) if not html: return table = find_table(html, index=0) if table is None: print("No table found.") return df = table_to_df(table) if df is None: print("Table could not be parsed.") return df = clean_df(df) df.to_csv("wikipedia_table.csv", index=False) print(f"Saved {df.shape[0]} rows to wikipedia_table.csv") print(df.head()) if __name__ == "__main__": main()
该脚本获取文章、隔离第一个 wikitable、将其解析为 DataFrame、执行清洗步骤,并写入带有表头行且不含索引列的 wikipedia_table.csv。最后打印 df.head() 可在终端中立即预览结果。要针对不同文章或同一页面上的不同表格,只需修改 article_url 和传给 find_table 的 index 参数。
输出是什么样的
用 python wikipedia_scraper.py 运行完整脚本,您将得到一个干净的 CSV 文件,每行对应表格中的一行,可直接用于 pandas、数据库或电子表格。
Rank,Country / dependency,Total area (km2),Total area (mi2),Land,Water,Notes ,World,510072000,196940000,148940000,361132000, 1,Russia,17098246,6601665,16376870,721391, 2,Antarctica,14200000,5500000,14200000,0, 3,Canada,9984670,3855100,9093507,891163, 4,China,9596961,3705407,9326410,270550, 5,United States,9525067,3677649,9147593,377424,
列与源表格对齐,脚注标记消失,数据足够整洁,可直接加载到 pandas 中进行过滤、排序或与其他数据集连接。换入任何带有 wikitable 的文章,同样的五个函数都会产生同样整洁的输出。
扩展到多篇文章
获取单个表格是构建模块。要跨多篇文章采集同一个表格,只需将这五个函数包装在 URL 列表的循环中,加入短暂的休眠来控制速度,并拼接清洗后的 DataFrame。一些好习惯可以保持较长时间运行的健康状态。
- 控制请求速度。在请求之间添加短暂休眠,保持适度的请求量,尤其是针对 Wikipedia 这样由社区运营的资源。
- 利用轮换。通过 Crawling API 路由每次抓取,将请求分散到许多 IP,使较大规模的爬取不会集中在一个地址。
- 优先使用官方批量获取方式。对于大量数据,Wikipedia API 和官方数据库转储是预期的途径,对实时站点的负载远小于抓取,下一节将详细介绍。
对于较大的爬取,异步 Crawler 可以将请求排队并将结果发送到 webhook,适合在不保持连接的情况下运行多篇文章。要将清洗后的表格推送到数据仓库,网络抓取到 SQL 中的方法可以直接应用于您在这里构建的 DataFrame。
抓取 Wikipedia 合法吗?
为研究或分析目的抓取 Wikipedia 公开表格通常是可以接受的,但关键在于许可协议,而不仅仅是访问权限。Wikipedia 的文本内容以知识共享署名-相同方式共享许可协议(CC-BY-SA)发布:您可以自由地重用和改编,前提是注明来源,并以相同许可协议分享任何衍生作品。如果您发布或重新分发从中提取的表格,请注明 Wikipedia 并链接回您所使用的文章,并在此基础上构建的任何内容中牢记相同许可协议条件。抓取行为不能消除这些条款,只是移动了数据。
请遵守一些基本原则。尊重 Wikipedia 的服务条款和 robots.txt,保持较低的请求频率以免给非营利组织的服务器造成压力,并将自己限制在表格和其他公开内容,而非重新发布整篇文章。本指南的范围正是为此而设定在表格数据上:这是最适合重用且最容易清晰注明来源的部分。
当您需要大量数据时,抓取通常不是正确的工具。Wikimedia 基金会提供了专为此目的构建的官方渠道:用于结构化查询的官方 MediaWiki API,以及用于批量下载整个维基的定期数据库转储。这些方式对实时站点的负载更小,提供更干净的数据,是该项目明确邀请用于大量使用的途径。优先使用这些方式,将实时抓取作为一次性或小规模工作(转储会显得过于繁琐)的选择。
核心要点
-
wikitable 类是您的锚点。大多数 Wikipedia 数据表格都带有
wikitableCSS 类,因此您通过类选择正确的网格,同时跳过同一页面上的布局和导航表格。 -
pandas read_html 负责解析。用 BeautifulSoup 隔离一个表格,然后将其 HTML 传给
read_html,通过单次调用获得带类型的 DataFrame,而无需手动遍历行和单元格。 - 导出前先清洗。展平多级表头、去除括号脚注标记、删除空行和空列,使 CSV 文件准备好用于分析。
-
通过 Crawling API 路由获取请求以实现扩展。一次
crawl调用返回完整 HTML 并为您轮换 IP,因此跨多篇文章采集表格不会对单个地址造成压力。 - 注明来源并使用官方渠道。Wikipedia 内容遵循 CC-BY-SA 协议,因此重用时需注明来源,尊重服务条款和 robots.txt,并在大量工作时优先使用 Wikipedia API 和数据库转储。
常见问题
如何从 Wikipedia 页面提取特定表格?
用 BeautifulSoup 通过 CSS 类选择它。大多数数据表格带有 wikitable 类,所以 soup.select("table.wikitable") 返回页面上的每个数据表格;索引您想要的那个,并将其 HTML 传给 pandas.read_html。当多个表格共享该类时,通过索引或表格的标题文字缩小范围,如本指南中的 find_table 辅助函数所示。
应该用 pandas read_html 还是 BeautifulSoup 来解析表格?
两者结合使用。先用 BeautifulSoup 通过类隔离您想要的 <table>,然后将该单个元素传给 pandas.read_html,将其转化为 DataFrame。将整个页面传给 read_html 也可以,但它会返回包含页面上所有表格(包括侧边栏)的列表,让您猜测索引。
为什么要将 Wikipedia 请求路由通过 Crawling API?
对于单篇文章可能不需要,因为 Wikipedia 在服务器端提供内容。价值在于大量请求时:从单个数据中心 IP 获取多篇文章看起来像机器人,容易触发速率限制和瞬态封锁。Crawling API 轮换可信 IP 并返回完整 HTML,使较大规模的爬取分散到多个地址。
抓取 Wikipedia 表格合法吗?
为研究目的抓取公开表格通常没问题,但 Wikipedia 内容遵循 CC-BY-SA 许可协议,因此如果您重新发布表格,必须注明来源,并以相同许可协议分享衍生作品。尊重服务条款和 robots.txt,保持较低的请求频率,并坚持使用表格而非重新分发整篇文章。对于大量需求,Wikipedia API 和数据库转储是官方推荐的途径。
如何将抓取到的表格保存为 CSV?
一旦表格成为 DataFrame,调用 df.to_csv("wikipedia_table.csv", index=False)。index=False 参数阻止 pandas 将其行索引作为额外列写入,因此您得到一个干净的表头行和每个表格行对应一行的结果。之后,CSV 可以直接重新加载到 pandas 或任何电子表格中。
这与通用的 Wikipedia 抓取指南有何不同?
本教程专注于表格:选择 wikitable、用 pandas 解析、清洗列数据和导出 CSV。关于如何抓取 Wikipedia 的配套指南涵盖更广泛的页面内容,例如标题、图片和信息框字段。当您的目标是列式数据时使用本指南,需要文章其他部分时使用那个指南。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。


