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,依此类推)。

html
=IMPORTHTML("https://en.wikipedia.org/wiki/List_of_largest_cities", "table", 1)

按回车后,Sheets 会获取页面、提取第一个表格,并从你输入公式的单元格开始将数据填充到各单元格中。如果第一个表格不是你想要的,可以增加序号直到找到目标表格;页面上往往在主要数据之前有导航或信息框类的表格。数据填入后,你可能需要调整列宽或重新格式化单元格,也可以保留公式,以便在来源变化时自动刷新。

当 IMPORTHTML 返回错误时

IMPORTHTML 获取的是原始 HTML,不会执行 JavaScript,因此只能看到页面源代码中已有的表格。如果返回 #N/A 或空结果,说明该表格可能是在页面加载后由 JavaScript 构建的,或者网站屏蔽了请求。这正是带有 Crawling API 的 Python 方法所处理的情况,因为它可以先渲染页面并从受信任的 IP 获取数据。

Crawlbase Crawling API

那个 IMPORTHTML 公式能正常工作,是因为维基百科将表格作为纯 HTML 提供,且来源开放。一旦表格是用 JavaScript 构建的,或者位于反爬虫保护之后,该公式就会返回 #N/A,普通 Python 请求也会如此。Crawling API 在服务端轮换住宅 IP,根据需要在真实浏览器中渲染页面,并返回完整的 HTML,让你无需自己运行无头浏览器集群和代理池。先在免费版中测试一个公开页面。

方法二:Python 结合 pandas 和 Crawling API

Python 是处理此类任务的主力工具。对于干净的公开表格,pandas.read_html 几乎用一行代码就能完成所有工作:传入 URL 或一段 HTML,它会以数据框列表的形式返回页面上的每张表格。首先安装相关库。

bash
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,然后索引到它返回的列表中。

python
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。唯一的变化是标记的来源。

python
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 选择器指南中的逐字段方法相同,应用于表格单元格。

python
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 控制台中一次性安装该包。

r
install.packages("rvest")

加载 rvest 后,读取页面、提取表格并选择所需的那张。其模式与 Python 完全一致:读取 HTML,获取表格列表,按索引提取。

r
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 后,最大城市表格如下所示。

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 次请求免费,无需信用卡。

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