文章目录
前言
HTML表格是网站组织数据最常见的方式之一,包括财务报告、产品列表、体育比分、人口统计等。但这些数据被锁定在网页布局中。要使用它,您需要提取它。本指南将向您展示如何使用Python做到这一点,从简单的静态表格开始,逐步处理复杂的动态表格。

理解HTML表格
在抓取表格之前,您需要理解其结构。HTML使用标签来组织内容。要查看这一点,访问任何带有表格的网页,右键单击它,然后选择"检查"(如果需要帮助,请查看我们关于如何检查元素的指南)。
您只需要知道4个标签:
<table>– 整个表格容器(想象成整个电子表格文件)<tr>– “表格行” – 单行(就像电子表格中的一行)<th>– “表格标题” – 标题单元格(列标题,如"名称"或"价格")<td>– “表格数据” – 数据单元格(单个值,如"$19.99")
表格是嵌套的。<table>包含<tr>(行),后者包含<th>(标题)和<td>(数据)。理解这种结构对于稍后选择正确的CSS或XPath选择器很重要。
前提条件
您需要安装Python以及几个库:
- Requests – 发送HTTP请求以下载网页
- BeautifulSoup4 – 将HTML解析为可搜索对象
- Pandas – 将抓取的数据组织成表格并导出为CSV/Excel
- lxml – pandas.read_html需要的快速解析器
- Selenium – 为JavaScript密集型网站自动化浏览器
要安装所有这些库,打开终端(或命令提示符)并运行以下命令:
pip install requests beautifulsoup4 pandas selenium lxml
我们将使用requests和BeautifulSoup处理静态网站,当您需要抓取动态内容时使用Selenium。
如何抓取静态HTML表格
静态表格在初始HTML中包含所有数据,不需要JavaScript加载。有两种方法可以抓取它们。
方法1. 最简单的方法(pandas.read_html)
对于简单的静态表格,pandas有一个完成繁重工作的函数: read_html()。
此函数扫描HTML,找到所有<table>标签,并将它们转换为pandas DataFrames列表。
import pandas as pd
import requests
from io import StringIO # 需要包装HTML字符串
# Wikipedia页面的URL
url = "https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_population"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
# 获取HTML内容
response = requests.get(url, headers=headers)
html_content = response.text
# 从HTML内容中解析所有表格
tables = pd.read_html(StringIO(html_content))
print(f"找到的表格总数: {len(tables)}")
# 在这种情况下,我们想要的表格是第1个
df = tables[0]
# 显示前5行
print(df.head())
# 将数据保存到CSV文件
df.to_csv("population_data.csv", index=False)
print("数据已保存到population_data.csv")
运行脚本时,控制台输出如下所示:

方法2. 手动方法(BeautifulSoup)
有时pandas.read_html会失败,或者您需要更多控制。这是使用BeautifulSoup的手动方法。
我们将在5个清晰的步骤中抓取相同的Wikipedia页面。
步骤1. 获取网页
使用requests下载页面的HTML。
import requests
from bs4 import BeautifulSoup
import pandas as pd
url = "https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_population"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
print("成功获取页面")
html_content = response.text
else:
print(f"获取页面错误: 状态码 {response.status_code}")
exit()
步骤2. 使用BeautifulSoup解析HTML
将原始HTML传递给BeautifulSoup以创建可搜索对象。
soup = BeautifulSoup(html_content, 'html.parser')
步骤3. 定位并提取表格
在浏览器中右键单击表格并选择"检查"。您会看到它有一个class属性"wikitable"。

使用此类查找表格,然后提取所有标题(<th>)和行(<tr>)。
table = soup.find('table', attrs={'class': 'wikitable'})
if table is None:
print("找不到表格。请检查您的选择器。")
exit()
# --- 获取标题 ---
headers_html = table.find_all('th')
headers = [th.text.strip() for th in headers_html]
print(f"找到的标题: {headers}")
# --- 获取所有行 ---
rows_html = table.find('tbody').find_all('tr')
all_rows = []
for row in rows_html:
cells_html = row.find_all('td')
row_data = [cell.text.strip() for cell in cells_html]
if row_data:
all_rows.append(row_data)
步骤4. 将数据转换为pandas DataFrame
将您的列表转换为结构化DataFrame。
pandas DataFrame是一个强大的内存中表格,类似于电子表格。虽然我们的all_rows变量只是一个Python列表的列表,但将其转换为DataFrame允许我们轻松地使用标题标记数据、清理数据、分析数据,最重要的是,使用单个命令将其导出为CSV或Excel等格式。
df = pd.DataFrame(all_rows, columns=headers)
print(df.head())
步骤5. 保存数据
将DataFrame导出为CSV。
df.to_csv('scraped_data_manual.csv', index=False)
最终的CSV文件将如下所示:

此方法使用了Python网页抓取的所有基本原理,并为您提供完全控制。您可以进一步修改代码以仅提取特定行或列,或筛选相关数据。
快速提示 – 始终首先尝试方法1。它快速简单。如果失败或您需要更多控制,请使用方法2。
处理复杂的表格结构(colspan和rowspan)
某些表格使用colspan(跨越多列)或rowspan(跨越多行)合并单元格。
问题 – 简单的循环会将数据放在错误的列中。
修复方法 – 首先尝试pandas.read_html。它通常足够智能,可以自动正确解析带有colspan和rowspan的表格。
让我们以"网页浏览器比较"页面为例。提到"Arora"浏览器的行使用rowspan跨越6行。

"Latest release"标题使用colspan跨越2列(“Version"和"Date”)。

让我们看看pandas是否可以处理它。
import pandas as pd
import requests
from io import StringIO
# 配置
WIKI_URL = 'https://en.wikipedia.org/wiki/Comparison_of_web_browsers'
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36'
OUTPUT_FILE = 'browser_comparison.csv'
# 获取页面
response = requests.get(WIKI_URL, headers={'User-Agent': USER_AGENT})
html_content = response.text
# 提取所有表格
all_tables = pd.read_html(StringIO(html_content))
print(f"找到 {len(all_tables)} 个表格")
# 通过检查其列找到正确的表格
for index, table in enumerate(all_tables):
if 'Browser' in str(table.columns):
print(f"\n在索引 {index} 处找到浏览器表格")
# 从pandas清理多级标题
table.columns = ['_'.join(col).strip() for col in table.columns.values]
print(table.head(10))
table.to_csv(OUTPUT_FILE, index=False)
print(f"\n已保存到 {OUTPUT_FILE}")
break
然后您将获得成功扁平化复杂标题和行的CSV。
当pandas.read_html(方法1)在复杂表格上失败时,您可以使用专门的库,如html-table-extractor。其主要目的是正确解析复杂表格,自动扩展合并的单元格以构建干净的矩形数据网格。
首先,您需要安装它:
pip install html-table-extractor
以下是结合BeautifulSoup查找表格和html-table-extractor解析表格的代码:
import requests
import csv
from bs4 import BeautifulSoup
from html_table_extractor.extractor import Extractor
# 配置
WIKI_URL = "https://en.wikipedia.org/wiki/Comparison_of_web_browsers"
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"
OUTPUT_FILE = "browser_comparison_extractor.csv"
print(f"正在获取页面: {WIKI_URL}")
response = requests.get(WIKI_URL, headers={"User-Agent": USER_AGENT})
html_content = response.text
soup = BeautifulSoup(html_content, "html.parser")
all_tables = soup.find_all("table")
print(f"找到 {len(all_tables)} 个表格。正在搜索正确的表格...")
for index, table in enumerate(all_tables):
try:
# 我们将'table'对象转换回字符串
extractor = Extractor(str(table))
extractor.parse()
table_data = extractor.return_list()
if table_data:
# 清理标题值(例如,"Browser\n"变为"Browser")
headers = [str(header).strip() for header in table_data[0]]
if "Browser" in headers:
print(f"\n在索引 {index} 处找到浏览器表格")
with open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(table_data)
print(f"成功保存到 {OUTPUT_FILE}")
# 找到第一个后中断循环
break
except Exception as e:
print(f"无法解析索引 {index} 处的表格: {e}")
清理和保存数据
原始抓取的数据通常很混乱。例如,在我们在方法2(BeautifulSoup)中创建的DataFrame中,Population列包含字符串,如"1,417,492,000"。因为这是一个字符串而不是数字,所以您无法使用它进行计算。让我们清理它。
您可以将以下行添加到方法2脚本中以清理数据。此代码直接从您之前创建的df变量继续:
# 此代码扩展了方法2(BeautifulSoup)示例
# 它假设'df'是我们已经创建的DataFrame
# 'Population'列是一个字符串: '1,417,492,000'
# 删除逗号
df['Population_Clean'] = df['Population'].str.replace(',', '', regex=False)
# 转换为数字
df['Population_Clean'] = pd.to_numeric(df['Population_Clean'])
# 现在您可以执行计算
print(f"前5名的总人口: {df.head(5)['Population_Clean'].sum()}")
清理后,保存您的数据。有关更多格式(JSON、数据库等),请查看我们关于如何保存抓取数据的指南。
# 保存为CSV
df.to_csv('population_data_clean.csv', index=False)
# 保存为JSON
df.to_json('population_data_clean.json', orient='records')
如何抓取动态(JavaScript)表格
您运行抓取器,但表格数据为空。您检查HTML,看到"Loading…"或空的<div>。发生这种情况是因为网站使用JavaScript动态加载数据。requests.get()仅检索初始HTML,它不执行JavaScript。如果表格数据在页面渲染后加载,您的抓取器将看不到它。
解决方案#1. 查找隐藏的API
许多网站通过后台API请求加载数据。您可以拦截此API并直接请求数据:
- 在浏览器中打开开发者工具(F12)
- 转到Network选项卡 – 按Fetch/XHR筛选
- 重新加载页面
- 查找返回JSON数据的请求(这是您的表格内容)
像Yahoo Finance和Google Finance这样的金融网站通常使用这种模式。在下图中,您可以看到Fetch/XHR选项卡捕获返回干净JSON数据的请求。

找到正确的请求后,右键单击它并选择Copy as cURL(或记下URL、标头和参数)。然后,您可以在Python中重新创建该请求。

将"Copy as cURL"命令转换为Python的专业提示是使用在线工具,如curl转换器。您可以粘贴整个cURL命令,它将自动使用requests库生成等效代码。

运行此代码将打印干净的JSON数据,看起来像这样:
{
"marketCap": {
"raw": 4.580887901397705E12,
"fmt": "4.581T",
"longFmt": "4,580,887,901,397"
},
"ticker": "NVDA",
"avgDailyVol3m": {
"raw": 1.818218125E8,
"fmt": "181.822M",
"longFmt": "181,821,812"
},
...
}
重要说明: 如果您的请求失败或没有获得数据,网站可能正在阻止您的脚本。要解决此问题,您需要使您的请求看起来更像真实用户。我们在本指南后面的"最佳实践和道德考虑"部分中介绍了这些技术。
解决方案#2. 使用无头浏览器
如果没有API可以定位,您需要自动化真实浏览器来渲染JavaScript。在服务器上运行时,这称为无头浏览器。像Selenium这样的工具将:
- 打开真实浏览器(如Chrome)
- 等待JavaScript执行和表格加载
- 为您提供最终渲染的HTML进行解析
这也是处理动态分页的唯一方法,即当您需要单击触发JavaScript的Next按钮时。我们将在下一节中介绍这一点。
这种方法比直接API请求更慢且资源更密集,因此始终首先检查隐藏的API。
如何抓取分页表格
表格通常分布在多个页面上(“第1页”、"第2页"等)。关键是弄清楚"Next"按钮的工作原理。
情况#1. 静态分页
要查找的内容 – 单击"Next"并检查URL是否更改(例如,更改为/?page=2)。如果是,您正在处理静态网站。
解决方案 – 在循环中使用requests。以CoinMarketCap为例。它使用像/?page=2、/?page=3这样的URL。如果您检查"Next"按钮,您会看到它只是一个简单的链接(<a>标签),具有静态href属性。

因为URL简单且可预测,您可以使用requests和pandas循环抓取它:
import pandas as pd
import requests
from io import StringIO
from time import sleep
all_data = []
page = 90
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"
}
while True:
print(f"第 {page} 页...", end=" ")
response = requests.get(f"https://coinmarketcap.com/?page={page}", headers=headers)
if response.status_code != 200:
break
try:
all_data.append(pd.read_html(StringIO(response.text))[0])
except:
break
if (
'aria-disabled="true"' in response.text
or "?page=" + str(page + 1) not in response.text
):
print("最后一页!")
break
page += 1
sleep(2)
pd.concat(all_data, ignore_index=True).to_csv("coinmarketcap_data.csv", index=False)
print(f"\n完成!已保存 {len(all_data)} 页")
情况#2. 动态分页
要查找的内容 – 单击"Next"并检查URL是否保持不变,但表格内容发生变化。这意味着网站使用动态分页。
解决方案 – 使用无头浏览器,如Selenium模拟单击"Next"按钮。
让我们以Datatables为例进行抓取。此页面使用JavaScript加载其数据和分页。
import pandas as pd
from io import StringIO
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from time import sleep
url = "https://datatables.net/examples/data_sources/ajax.html"
all_data = []
options = Options()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
driver.get(url)
sleep(2) # 等待页面和初始JavaScript加载
page = 90
while True:
print(f"正在抓取第 {page} 页...", end=" ")
table_html = driver.find_element(By.ID, "example").get_attribute("outerHTML")
if not table_html:
break
df = pd.read_html(StringIO(table_html))[0]
all_data.append(df)
print("完成。")
# 检查"Next"按钮是否被禁用
try:
next_btn = driver.find_element(By.CSS_SELECTOR, "button.dt-paging-button.next")
btn_class = next_btn.get_attribute("class") or ""
if "disabled" in btn_class:
print("已到达最后一页!")
break
# 如果未禁用,单击它并等待
next_btn.click()
sleep(1) # 等待表格重新加载
page += 1
except:
print("找不到'Next'按钮或它坏了。")
break
driver.quit()
final_df = pd.concat(all_data, ignore_index=True)
final_df.to_csv("datatables_data.csv", index=False)
print(f"\n完成!已将 {len(all_data)} 页保存到datatables_data.csv")
这种相同的逻辑可以使用其他现代工具(如Playwright进行网页抓取)应用。
现代方法. AI可以抓取表格吗?
这是当今最常见的问题,简短的答案是"可以,但有注意事项"。
AI作为助手
AI聊天机器人非常适合生成代码。复制表格的HTML,粘贴到ChatGPT或Claude中,并要求它编写BeautifulSoup脚本。您将在几秒钟内获得可用代码,而不是手动编写它。您可以在我们的其他指南中了解有关使用ChatGPT进行网页抓取或Claude进行网页抓取的更多信息。
但聊天机器人有局限性。它们编写代码,但不为您运行代码。它们无法渲染JavaScript、管理代理或处理被阻止。当网站更改其HTML(这种情况经常发生)时,代码就会崩溃。
AI作为抓取器本身
真正的AI抓取解决方案不仅生成代码,它理解页面结构并适应变化。您不必编写脆弱的CSS选择器,如soup.find(class_="wikitable"),而是用简单的英语告诉它您想要什么数据。
这就是Decodo的AI Parser所做的。它是最好的AI数据收集工具之一,因为当网站发生变化时它保持弹性。有关此方法的更多信息,请参阅Decodo如何处理AI数据收集。
最佳实践和道德考虑
抓取很强大,但您需要尊重他人。每秒向服务器发送数百个请求会很快被阻止。以下是如何负责任地抓取(并避免被禁止):
- 首先检查robots.txt. 大多数网站在website.com/robots.txt处都有一个文件,说明机器人的规则。我们有关于如何检查网站是否允许抓取的完整指南
- 尊重服务条款(ToS). ToS是一份法律文件。查看它以确保您没有违反政策,特别是对于商业用途。这有助于回答"网页抓取合法吗?"的问题
- 使用代理网络. 您的家庭IP地址是网站的明显标志。在10或100个请求后,您将被阻止。代理,尤其是来自动态住宅代理网络的代理,通过不同的真实设备路由您的请求,每次都使您看起来像一个普通的独特用户。这是大规模可靠抓取的关键
- 使用User-Agent. 如我们的示例所示,User-Agent字符串使您的请求看起来像来自浏览器,而不是脚本。这绕过了许多基本的反机器人系统
- 在请求之间添加延迟. 在循环内添加time.sleep(2)。这对服务器是礼貌的,使您看起来不像机器人。(您甚至可以构建Python requests retry系统来自动处理此问题。)
常见问题排查
最常见的错误是AttributeError: 'NoneType' object has no attribute 'find'。这可能是最常见的抓取错误。这意味着您试图在None上调用方法,当您的选择器找不到任何内容时会发生这种情况。
# 您的选择器有拼写错误或错误的类名
table = soup.find('table', attrs={'class': 'my-typo-class'}) # 返回None
# 下一行是崩溃的地方
rows = table.find_all('tr') # 无法在None上调用.find_all()!
修复方法 – 检查崩溃前的行。您的soup.find()返回None,因为它没有匹配任何内容。仔细检查您的选择器是否有拼写错误,或检查实际HTML以查看类名真正是什么。
其他常见问题
以下是一些常见的抓取错误和修复方法:
- 404 Not Found. 您的URL错误,或页面不存在。检查拼写错误
- 403 Forbidden. 服务器正在阻止您。添加User-Agent标头以使其看起来像浏览器。如果这不起作用,您需要代理来隐藏您的身份
- 429 Too Many Requests. 您抓取得太快。在请求之间添加time.sleep(2)以减慢速度。如果您仍然被阻止,请使用代理网络来轮换您的IP地址
- 空数据/缺少内容. 数据可能是用JavaScript加载的。使用浏览器的Network选项卡找到隐藏的API端点,或切换到无头浏览器自动化工具以等待页面完全加载
总结
您已经学会了如何在Python中抓取表格,从简单的pandas一行代码到复杂的Selenium设置。这些工具非常适合小型项目和学习。
但抓取器很脆弱,网站经常更改其HTML。扩展到数千页意味着处理代理、无头浏览器和验证码。如果这听起来很繁琐,还有另一种选择。
您可以使用Decodo网页抓取API,而不是自己构建和维护抓取器。发送URL,API处理混乱的事情(代理、JavaScript渲染、反机器人措施),您将得到干净的JSON。
此外,如果您正在构建AI代理或自动化工作流,您可以与LangChain和n8n集成。
常见问题解答
哪些Python库最适合抓取HTML表格?
对于简单的静态表格,pandas.read_html()是最快的选择。要获得更多控制,请使用requests获取页面并使用BeautifulSoup解析它。对于JavaScript密集型网站,请使用Selenium或Playwright。
如何从使用JavaScript加载数据的网站抓取表格?
两种方法:
- 查找隐藏的API – 使用浏览器的Network选项卡(按Fetch/XHR筛选)查找返回JSON数据的API请求。这比浏览器自动化更快、更可靠
- 使用无头浏览器 – 如果没有API,请使用Selenium或Playwright加载页面、执行JavaScript并解析最终HTML
如果表格结构复杂(例如,合并单元格、多级标题),我该怎么办?
首先尝试pandas.read_html(),它在大多数情况下会自动处理colspan和rowspan。如果失败,您需要编写自定义BeautifulSoup解析器来手动处理单元格结构。
如何将抓取的表格数据导出到.csv或Excel文件?
一旦您有了pandas DataFrame(df),这是一行的工作:
- CSV –
df.to_csv('filename.csv', index=False) - Excel –
df.to_excel('filename.xlsx', index=False)

被折叠的 条评论
为什么被折叠?



