HTML 表格是网页存储最易分析数据的方式:股票行情、体育积分榜、人口数据、产品对比、历史记录。公开页面上的表格本身已经是行列结构,因此难点往往不在于分析,而在于如何将数据从页面中干净、可重复地提取到你能使用的工具中,而不必手动复制单元格。
本指南介绍三种从网站抓取表格的方法,分别适合不同的技能水平和工作流程:无需编写代码的 Google Sheets 公式、几行 Python 加 pandas,以及使用 rvest 的 R 脚本。示例贯穿始终的是一张公开的维基百科表格,它非常适合入门,因为数据开放且标记结构清晰。本文所有方法均限于公开数据,文末的负责任抓取一节值得在实际应用前仔细阅读。
你将构建的内容
学完本文,你将掌握三种从公开表格中提取数据并转为可用格式的方法。每种方法针对同一类来源,即公开页面上的 HTML <table>,并产生同一类输出,即可排序、过滤或导出的整洁网格。你最终将得到以下内容:
-
一个 Google Sheets 公式,只需一个
IMPORTHTML调用即可导入实时表格,无需编写任何代码。 -
一个 Python 脚本,用一行
pandas.read_html读取表格,并提供针对混乱标记的 BeautifulSoup 备用方案。 -
一个 R 脚本,使用 rvest 的
html_table将表格提取为数据框。 - 来自每种方法的整洁表格输出,可随时导出为 CSV 或用于分析。
为何有些表格需要的不只是普通请求
对于像大多数维基百科页面那样干净的服务端渲染页面,以下三种方法都可以直接使用:表格标记已存在于 HTML 中,因此公式或一行代码就能直接提取。这是最简单、最常见的情况,应从这里开始尝试。
麻烦出现在那些不那么"慷慨"提供表格的来源上。最常见的两个问题是:第一,某些网站在页面加载后通过 JavaScript 构建表格,因此你获取到的原始 HTML 中没有你所需的行;第二,许多网站会监测爬虫流量,在来自数据中心 IP 的请求达到一定数量后进行限速或封锁。一旦遇到这两种情况,IMPORTHTML 会返回错误,而 Python 中的普通 requests.get 则会返回拦截页面或空标记,而非你想要的表格。
两种问题的解决方案相同:通过一个能运行脚本的浏览器,从网站认为是真实访客的 IP 获取页面。你可以自己搭建一个带有轮换 IP 池和无头浏览器的方案,但这几乎是工作量的主体。在 Python 部分,我们针对这类被屏蔽或需 JavaScript 渲染的来源使用 Crawling API,而对于不需要这些功能的页面则保持原有的简单路径。
前提条件
你需要什么取决于选择哪种方法。每种方法的配置都不复杂。
Google Sheets:只需一个 Google 账户和浏览器,无需安装任何东西。
Python:需要 Python 3.8 或更高版本。使用 python --version 确认版本。你应该能够运行脚本并使用 pip 安装包。如果你对 HTML 解析还不熟悉,我们的Python 中使用 BeautifulSoup 指南涵盖了本节所假设的基础知识。
R:需要较新版本的 R(4.0 或更高即可)以及从 CRAN 安装包的能力。
对于被屏蔽或需 JavaScript 渲染的来源:需要 Crawlbase 账户和 token。注册后打开控制台并复制你的普通 token。请像对待密码一样保管该 token,不要放入版本控制。只有当 Python 路径中普通请求失败时才需要此项。
方法一:Google Sheets 与 IMPORTHTML
从公开页面获取表格最快的方式完全不需要编写代码。Google Sheets 内置了 IMPORTHTML 函数,可以获取页面、按索引找到表格或列表,并将其作为实时单元格填入你的表格。它甚至能自动刷新,保持数据最新。
打开一个新工作表,点击空白单元格,然后输入公式。它接受三个参数:页面 URL、你想要的是 "table" 还是 "list",以及要获取的序号(页面上第一个表格为 1,第二个为 2,依此类推)。
=IMPORTHTML("https://en.wikipedia.org/wiki/List_of_largest_cities", "table", 1)
按回车后,Sheets 会获取页面、提取第一个表格,并从你输入公式的单元格开始将数据填充到各单元格中。如果第一个表格不是你想要的,可以增加序号直到找到目标表格;页面上往往在主要数据之前有导航或信息框类的表格。数据填入后,你可能需要调整列宽或重新格式化单元格,也可以保留公式,以便在来源变化时自动刷新。
IMPORTHTML 获取的是原始 HTML,不会执行 JavaScript,因此只能看到页面源代码中已有的表格。如果返回 #N/A 或空结果,说明该表格可能是在页面加载后由 JavaScript 构建的,或者网站屏蔽了请求。这正是带有 Crawling API 的 Python 方法所处理的情况,因为它可以先渲染页面并从受信任的 IP 获取数据。
那个 IMPORTHTML 公式能正常工作,是因为维基百科将表格作为纯 HTML 提供,且来源开放。一旦表格是用 JavaScript 构建的,或者位于反爬虫保护之后,该公式就会返回 #N/A,普通 Python 请求也会如此。Crawling API 在服务端轮换住宅 IP,根据需要在真实浏览器中渲染页面,并返回完整的 HTML,让你无需自己运行无头浏览器集群和代理池。先在免费版中测试一个公开页面。
方法二:Python 结合 pandas 和 Crawling API
Python 是处理此类任务的主力工具。对于干净的公开表格,pandas.read_html 几乎用一行代码就能完成所有工作:传入 URL 或一段 HTML,它会以数据框列表的形式返回页面上的每张表格。首先安装相关库。
python -m venv tables_env source tables_env/bin/activate pip install pandas lxml beautifulsoup4 crawlbase
在 Windows 上,用 tables_env\Scripts\activate 代替 source 那行来激活环境。pandas 负责读取表格,lxml 是其底层解析器,beautifulsoup4 用于处理混乱标记的备用方案,crawlbase 是 Crawling API 的官方客户端。
对于公开的服务端渲染页面,最简单的版本确实只需一次调用。将 URL 传给 read_html,然后索引到它返回的列表中。
import pandas as pd url = "https://en.wikipedia.org/wiki/List_of_largest_cities" # read_html returns a list of every table it finds on the page tables = pd.read_html(url) print(f"Found {len(tables)} tables") # pick the one you want by index, then work with it as a DataFrame df = tables[0] print(df.head()) df.to_csv("cities.csv", index=False)
当页面配合时,这就是全部工作:read_html 扫描 HTML,为每个 <table> 构建一个数据框,你保留需要的那个并写入 CSV。与 Sheets 的序号类似,你可能需要尝试几个索引才能找到主表格,而不是侧边栏或导航块。
当来源屏蔽普通请求或通过 JavaScript 构建表格时,先通过 Crawling API 获取 HTML,然后再将该 HTML 传给 read_html。唯一的变化是标记的来源。
import pandas as pd from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def fetch_html(url, render=False): options = {"country": "US"} if render: # use the JS token for JavaScript-built tables options["ajax_wait"] = "true" response = api.get(url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None url = "https://en.wikipedia.org/wiki/List_of_largest_cities" html = fetch_html(url) if html: tables = pd.read_html(html) df = tables[0] print(df.head()) df.to_csv("cities.csv", index=False)
country 选项将请求固定到美国出口 IP,而传入 render=True 则告知调用使用 JavaScript token(通过 ajax_wait),以便在获取 HTML 前完成页面渲染。拿到标记后,read_html 的行为与之前完全相同。当普通路径返回拦截页面或空表格列表时,就应该使用这个版本。
对混乱表格回退到 BeautifulSoup
有时 read_html 会遇到困难:含合并单元格的表格、没有真实 <table> 标签的表格,或者行分散在嵌套标记中。遇到这种情况,退而使用 BeautifulSoup,通过逐个读取单元格来自行构建行。这与我们CSS 选择器指南中的逐字段方法相同,应用于表格单元格。
import pandas as pd from bs4 import BeautifulSoup def table_to_rows(html, index=0): soup = BeautifulSoup(html, "html.parser") table = soup.find_all("table")[index] rows = [] for tr in table.find_all("tr"): cells = tr.find_all(["th", "td"]) row = [cell.get_text(strip=True) for cell in cells] if row: rows.append(row) return rows rows = table_to_rows(html) df = pd.DataFrame(rows[1:], columns=rows[0]) print(df.head())
该辅助函数遍历每个 <tr>,将每个 <th> 和 <td> 读取为去除空白的文本,并跳过空行。将第一行视为表头,其余行视为数据,即使 read_html 无法自行解析该表格,你也能得到一个干净的数据框。当混乱的表格同时还被屏蔽或需要渲染时,将相同的 Crawling API HTML 传给此辅助函数即可。
方法三:R 与 rvest
如果你的分析已经在 R 中进行,无需离开它。rvest 包能读取 HTML,其 html_table 函数可将页面上的每张表格转换为数据框,与 pandas 的做法非常类似。在 R 控制台中一次性安装该包。
install.packages("rvest")
加载 rvest 后,读取页面、提取表格并选择所需的那张。其模式与 Python 完全一致:读取 HTML,获取表格列表,按索引提取。
library(rvest) url <- "https://en.wikipedia.org/wiki/List_of_largest_cities" # read the page, then pull every table into a list of data frames page <- read_html(url) tables <- page %>% html_elements("table") %>% html_table() # keep the first table and inspect it df <- tables[[1]] head(df) # write it out for analysis elsewhere write.csv(df, "cities.csv", row.names = FALSE)
R 使用双方括号 tables[[1]] 从列表中提取单个元素,而 Python 使用单方括号。除此之外,流程完全相同:读取页面、收集表格、保留所需的那张并导出。从此处开始,该数据框与 R 中的其他数据框完全一样,可随时用于 dplyr、ggplot2 或导出 CSV。如果来源屏蔽了直接读取,则按 Python 部分的方式通过 Crawling API 获取渲染后的 HTML,然后将该字符串传给 read_html 而非 URL。
输出结果是什么样的
无论使用哪种方法,结果形式相同:带表头行、每条记录对应一行的整洁网格。导出为 CSV 后,最大城市表格如下所示。
Rank,City,Country,Population,Year 1,Tokyo,Japan,37468000,2018 2,Delhi,India,28514000,2018 3,Shanghai,China,25582000,2018 4,Sao Paulo,Brazil,21650000,2018 5,Mexico City,Mexico,21581000,2018
具体列名取决于来源表格,但结构是一致的:表头成为列名,每个后续行是一条记录。有了这样的 CSV,你可以直接加载到电子表格、笔记本或数据库中,除偶尔需要类型转换外无需进一步清洗。
负责任地抓取表格
以上方法运行起来很容易,因此在使用前有必要认真考虑指向哪些来源。在大量采集之前,请检查每个来源的服务条款及其 robots.txt,优先选择真正公开的数据,即访客无需登录即可看到的内容,而非账户或付费墙后面的任何内容。合理控制请求速率,不要在紧循环中频繁请求同一服务器;当来源提供官方 API 或公开数据集时,应优先使用,因为这通常是更轻量、更可靠的途径。
如果你转载了抓取的表格,请注明来源并遵守其许可协议。维基百科等开放数据集有明确的再利用条款(该情况下需在知识共享许可下注明来源),而许多其他网站则完全不允许再发布。将公开数据用于自己的分析,远比将他人汇编的数据重新发布为己有轻微得多,因此如有疑问,请链接到来源,而不是整段复制。
核心要点
-
选择适合你工作流程的工具。Google Sheets 的
IMPORTHTML适合无代码场景,pandas 的read_html适合 Python,rvest 的html_table适合 R,每种方式只需一两行即可提取公开表格。 - 按索引从表格列表中选取。三种方法都会返回页面上的所有表格,因此需要尝试几个索引,找到主要数据而非侧边栏或导航块。
-
标记混乱时退而使用 BeautifulSoup。合并单元格或非标准表格导致
read_html失败时,可通过<tr>、<th>和<td>标签逐行重建。 - 对于被屏蔽或 JavaScript 构建的表格,使用 Crawling API。当公式或普通请求返回空结果时,通过受信任 IP 获取渲染后的 HTML,再用相同代码解析。
- 负责任地抓取。遵守每个来源的 ToS 和 robots.txt,优先使用公开数据和官方 API,并对再发布的数据注明来源或遵守许可。
常见问题
从网站抓取表格最简单的方法是什么?
对于公开的服务端渲染页面,Google Sheets 是最简单的方式。在单元格中输入 =IMPORTHTML(url, "table", 1),Sheets 就会获取页面并将第一张表格作为实时单元格填入你的电子表格,无需编写任何代码。它适用于任何在原始 HTML 中已存在表格的页面,并能自动刷新。
为什么 IMPORTHTML 返回 #N/A 或空结果?
IMPORTHTML 读取的是原始 HTML,不执行 JavaScript,因此无法看到页面在加载后通过脚本构建的表格。当网站屏蔽请求时也会失败。在这两种情况下,请切换到 Python 方法并通过 Crawling API 获取页面,它可以在真实浏览器中渲染页面,并从受信任的 IP 发起请求后再解析。
如何用 Python 抓取表格?
最简路径是 pandas.read_html(url),它返回一个数据框列表,每张表格对应一个。按索引提取所需的那个,并用 to_csv 导出。对于屏蔽普通请求或用 JavaScript 渲染表格的来源,先通过 Crawling API 获取 HTML,然后将该标记传给 read_html 而非 URL。
如何在 R 中抓取表格?
安装 rvest,用 read_html 读取页面,然后运行 html_elements("table") %>% html_table() 将表格列表作为数据框获取。用双方括号索引,例如 tables[[1]],并用 write.csv 导出。流程与 pandas 方法几乎完全相同。
如果 pandas read_html 无法解析表格怎么办?
有些表格含合并单元格、缺少 <table> 标签,或行嵌套在不寻常的标记中,导致 read_html 无法处理。此时回退到 BeautifulSoup:遍历每个 <tr>,将每个 <th> 和 <td> 读取为文本,并自行构建行存入数据框。这样你就能完全控制每个单元格的解读方式。
如何抓取屏蔽我的网站上的表格?
对自动流量进行限速或挑战的网站,需要请求看起来像真实访客。通过轮换住宅 IP 路由请求,使任何单一地址都不会触发限制,并在表格是客户端构建时渲染 JavaScript。Crawling API 通过一次调用同时处理这两种情况;一旦获得返回的 HTML,就像对待任何其他页面一样用 pandas、BeautifulSoup 或 rvest 解析即可。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
