你是否曾经遇到过这样的情况:在浏览网页时发现了一个包含宝贵数据的 HTML 表格,却因为不想手动复制粘贴或编写复杂的网页爬虫代码而感到苦恼?或者,你是一名数据分析师,需要频繁从 Wikipedia 或其他报表网站获取最新的统计数据来进行可视化分析?
在本文中,我们将深入探讨 Pandas 库中一个非常强大但经常被低估的工具——read_html()。我们将讨论如何利用它直接将网页中的 HTML 表格读取到 Pandas DataFrame 中,无需编写繁琐的 BeautifulSoup 或 Selenium 代码。这个工具对于快速合并来自多个网站的表格、进行数据清洗和准备工作非常高效。我们将通过一系列实战示例,从基础语法到处理复杂的 Wikipedia 数据,再到数据清洗与可视化,带你全面掌握这一技能。让我们开始吧!
什么是 pandas.read_html()?
在开始写代码之前,让我们先理解一下这个函数的本质。Pandas 的 INLINECODE5504404b 实际上是一个封装了 INLINECODEa16dd49f 或 INLINECODEaf45d030 等 HTML 解析库的高级工具。它的工作原理是扫描给定的 HTML 内容(无论是 URL、文件还是字符串),寻找 INLINECODEa596a500 标签,并尝试将其转换为结构化的 DataFrame 列表。
为什么说它是“最简单的方法之一”?因为它隐藏了 DOM 解析的复杂性。你不需要知道如何写 XPath 或 CSS 选择器,你只需要告诉 Pandas:“去这个 URL,把所有的表格拿回来”。这对于数据科学家和分析师来说,大大降低了获取 Web 数据的门槛。
主要应用场景:
- 快速数据原型开发: 在决定是否构建复杂的爬虫之前,先快速抓取数据进行分析。
- 定期报表抓取: 从公开的政府网站或金融报表中获取定期更新的表格。
- Wikipedia 数据分析: Wikipedia 是结构化 HTML 表格的金矿,非常适合用此方法抓取。
pandas.read_html() 的基础语法
首先,让我们来看看它的基本语法结构:
# 导入必要的库
import pandas as pd
# 基本语法
# pd.read_html(io, match=‘.+‘, flavor=None, header=None, index_col=None, skiprows=None, ...)
这里,最关键的参数是 io。它非常灵活,可以是:
- URL (字符串):以 INLINECODEc5573655 或 INLINECODEe50bd9c2 开头的网址。
- HTML 文件路径:指向本地
.html文件的路径。 - HTML 字符串:包含 HTML 代码的实际字符串对象。
重要提示: 默认情况下,该函数会返回一个 DataFrame 列表。这是因为一个网页通常包含多个表格(例如导航栏、页脚、广告等也是表格),Pandas 会把它能找到的所有表格都抓取回来,你需要从中挑选出你需要的那一个。
示例 1:从原始 HTML 字符串中读取表格
让我们从一个最简单的例子开始。假设我们有一段 HTML 代码,其中包含了一个简单的联系人列表。我们可以将这段代码存储在一个 Python 字符串变量中,然后传递给 read_html。
在这个例子中,我们使用三引号 INLINECODEd0ae7eca 将一个多行字符串存储在一个名为 INLINECODE7f76582b 的变量中。然后,我们调用 read_html 函数。该函数会提取所有的 HTML 表格并返回一个列表。由于我们的字符串里只有一个表格,所以它将是列表的第一个元素(索引为 0)。
import pandas as pd
# 定义包含表格的 HTML 字符串
html_string = ‘‘‘
Company
Contact
Country
Alfreds Futterkiste
Maria Anders
Germany
Centro comercial Moctezuma
Francisco Chang
Mexico
‘‘‘
# 使用 read_html 读取字符串
# 注意:read_html 返回的是一个列表
parsed_tables = pd.read_html(html_string)
# 获取第一个表格
df_1 = parsed_tables[0]
# 打印 DataFrame
print("抓取到的 DataFrame:")
print(df_1)
# 输出数据类型信息
print("
数据结构信息:")
df_1.info()
输出结果:
抓取到的 DataFrame:
Company Contact Country
0 Alfreds Futterkiste Maria Anders Germany
1 Centro comercial Moctezuma Francisco Chang Mexico
数据结构信息:
RangeIndex: 2 entries, 0 to 1
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Company 2 non-null object
1 Contact 2 non-null object
2 Country 2 non-null object
dtypes: object(3)
实战见解: 你可能注意到了,我们直接使用了 INLINECODE51d30c22。这是因为 INLINECODE17782c48 总是返回一个列表,哪怕只有一个表格。这是一个常见的初学者陷阱,请务必记住要做索引操作。
示例 2:实战应用 – 从 Wikipedia 获取印度人口统计数据
现在让我们把难度升级。在实际工作中,我们通常是从 URL 直接获取数据。让我们尝试从“印度人口统计”的 Wikipedia 页面读取数据。
Wikipedia 是一个绝佳的数据源,因为它结构非常规整。然而,一个典型的 Wikipedia 页面往往包含几十个表格:导航框、目录、参考资料等等。我们需要学会如何在这堆“噪音”中找到我们需要的数据。
首先,让我们不使用任何过滤条件,看看能抓取到多少表格:
import pandas as pd
import numpy as np
# 目标 URL
url = ‘https://en.wikipedia.org/wiki/Demographics_of_India‘
# 直接读取 URL 中的所有表格
# 这一步可能会花费几秒钟,取决于网络速度和页面复杂度
dfs = pd.read_html(url)
# 打印抓取到的表格数量
print(f"该网页上一共包含 {len(dfs)} 个表格。")
输出结果:
该网页上一共包含 37 个表格。
看,仅仅一个页面就有 37 个表格!如果我们盲目地使用 dfs[0],很可能得到的不是我们要的“人口分布”表,而是页面顶部的某个无关摘要表。
示例 3:精准定位 – 使用 Match 参数过滤特定表格
为了避免手动检查所有 37 个表格,INLINECODEd57143d4 提供了一个非常强大的参数:INLINECODE7a6733c0。它接受一个正则表达式或字符串,只返回包含该文本的表头或表格内容的表格。
在这个例子中,我们想要找到标题为“Population distribution by states/union territories (2011)”的表格。我们可以将这个文本传递给 match 参数。
# 使用 match 参数来筛选特定的表格
# Pandas 会在表格的表头(th标签)或内容中查找匹配项
my_table_list = pd.read_html(
url,
match=‘Population distribution by states/union territories‘
)
# match 参数通常会大幅减少返回列表的长度
print(f"筛选后剩余表格数量: {len(my_table_list)}")
# 获取筛选出的第一个(也是唯一的)表格
my_table = my_table_list[0]
# 查看前 5 行数据
print("目标表格的前 5 行数据:")
print(my_table.head())
输出结果:
筛选后剩余表格数量: 1
目标表格的前 5 行数据:
State/UT Population[57] ... Decadal growth (1991-2001) Decadal growth (2001-2011)
0 Uttar Pradesh 199,812,341 ... 25.85% 20.23%
1 Maharashtra 112,374,333 ... 25.68% 15.99%
2 Bihar 104,099,452 ... 28.62% 25.07%
3 West Bengal 91,276,115 ... 20.89% 13.84%
4 Madhya Pradesh 726,26,809 ... 26.36% 20.30%
技术细节: INLINECODE611357b3 使用 INLINECODEec13b20c 或 INLINECODEd3ac41fd 解析器。当遇到非常复杂的表格结构(例如合并单元格 INLINECODE3a3f4619 或 rowspan)时,Pandas 会尝试自动推断结构。虽然它很聪明,但在某些极其杂乱的表格中,可能仍需后续的手动调整。
示例 4:数据提取与基本清洗
现在我们已经有了正确的 DataFrame,但你会发现列名和数据可能还包含一些我们不想要的东西(例如引用标记 [57],或者是多余的空格)。
让我们提取我们需要的数据列,并做基本的清洗:
# 1. 提取 ‘State/UT‘ 列
states = my_table[‘State/UT‘]
# 2. 提取 ‘Population‘ 列
# 注意:原始列名是 ‘Population[57]‘,我们需要先检查一下确切的列名
# 为了演示,我们假设直接通过索引或清洗后的列名访问
population_raw = my_table[‘Population[57]‘]
# 3. 数据清洗:移除数字中的逗号,并将其转换为数值类型
# 比如 "199,812,341" -> 199812341
# 这一步非常重要,否则 Pandas 会把这一列当成对象(字符串)而不是数字
population_cleaned = population_raw.replace({‘,‘: ‘‘}, regex=True).astype(int)
# 验证清洗结果
print(f"清洗后的 Population 列类型: {population_cleaned.dtype}")
print(population_cleaned.head())
示例 5:数据重构与合并
现在让我们将清洗好的数据重新组合成一个干净、整洁的 DataFrame,方便后续使用。
# 创建一个新的 DataFrame,只包含我们关心的数据
df_clean = pd.DataFrame({
‘State‘: states,
‘Population‘: population_cleaned
})
print("清洗并重组后的 DataFrame:")
print(df_clean.head())
示例 6:高级数据清洗 – 处理“总计”行
在抓取统计表格时,表格的最后一行通常是“总计”或“Total”。在绘图或计算平均值时,这一行往往会因为数值过大而干扰结果,导致其他数据被压缩得看不见。
让我们演示如何删除最后一行:
# 查看当前 DataFrame 的最后几行
print("删除前的最后 3 行:")
print(df_clean.tail(3))
# 使用 tail(1) 获取最后一行的索引,然后用 drop() 删除它
df_final = df_clean.drop(df_clean.tail(1).index)
# 验证删除
print("
删除后的最后 3 行:")
print(df_final.tail(3))
实用提示: 你也可以使用布尔索引来删除,例如 INLINECODEe489db0b。但在不知道确切文本内容的情况下,使用 INLINECODE543932b7 是一种通用的处理策略。
示例 7:数据可视化 – 让数据说话
数据清洗完成后,最激动人心的环节就是可视化。让我们使用 Matplotlib 将印度各邦的人口数据绘制成水平条形图。
import matplotlib.pyplot as plt
# 设置绘图风格
plt.style.use(‘ggplot‘)
# 创建图表
# 我们只选取人口最多的前 10 个邦进行展示,避免图表过于拥挤
top_10_states = df_final.sort_values(by=‘Population‘, ascending=True).tail(10)
ax = top_10_states.plot(
x=‘State‘,
y=‘Population‘,
kind="barh",
figsize=(10, 8),
color=‘skyblue‘,
edgecolor=‘black‘
)
# 设置标题和标签
plt.title(‘Top 10 Most Populous States in India (2011 Census)‘, fontsize=15)
plt.xlabel(‘Population‘, fontsize=12)
plt.ylabel(‘State‘, fontsize=12)
# 在条形图上添加数值标签
for i, v in enumerate(top_10_states[‘Population‘]):
ax.text(v + 3e6, i, str(v), va=‘center‘, fontsize=10)
plt.tight_layout()
plt.show()
示例 8:反向操作 – 将 DataFrame 写入 HTML
Pandas 也允许我们将处理好的 DataFrame 转换回 HTML 表格字符串。这在生成自动化报表或 Web 应用展示时非常有用。
# 将我们处理好的 DataFrame 转换为 HTML 表格
html_output = df_final.to_html(index=False, classes=‘my-table‘)
# 打印 HTML 代码的前 500 个字符
print(html_output[:500])
# 你也可以将其保存为文件
with open(‘population_table.html‘, ‘w‘) as f:
f.write("""
India Population Data
.my-table { border-collapse: collapse; width: 50%; }
.my-table td, .my-table th { border: 1px solid #ddd; padding: 8px; }
.my-table tr:nth-child(even){background-color: #f2f2f2;}
Population by State
""")
f.write(html_output)
f.write("""
""")
print("
HTML 文件已生成。")
进阶技巧与最佳实践
在实际使用 read_html() 时,你可能会遇到一些挑战。这里有几个实用的建议:
- 处理缺失的依赖库:
INLINECODE54e8fc16 依赖于 INLINECODE95652c8e、INLINECODE8a40c240 或 INLINECODE852b8a38 库来解析 HTML。如果你运行代码时遇到 INLINECODE513200ed,请确保安装了这些库。通常情况下,安装 INLINECODEd4967689 就能获得最佳性能。
pip install lxml html5lib beautifulsoup4
- 表头识别:
有时网页的表格没有标准的 INLINECODEdd57b818 标签,或者 Pandas 误判了表头行。你可以使用 INLINECODEe8e8ad07 参数(例如 INLINECODE7150c045 表示第一行是表头,INLINECODE74918942 表示没有表头)来手动指定。
- 指定解析器:
如果你的 HTML 代码非常不规范,默认的 INLINECODEda72a9c2 可能会解析失败。你可以尝试切换到 INLINECODEf2192f9f,它对容错性更强(但速度稍慢):
dfs = pd.read_html(url, flavor=‘html5lib‘)
- 处理分页表格:
INLINECODE917e5154 只能抓取单个页面上的内容。如果一个网站的数据表格分成了“第1页、第2页…”,你需要通过抓取网络请求来找到分页的规律,循环抓取所有页面的 URL,然后使用 INLINECODE8c816289 将它们合并。
总结
在这篇文章中,我们探索了如何使用 Pandas 的 read_html() 函数轻松地将网页表格转换为可分析的 DataFrame。我们学习了:
- 从原始 HTML 字符串和在线 URL 读取数据。
- 如何使用
match参数在包含数十个表格的页面中精准定位目标数据。 - 基础的数据清洗流程,包括移除多余字符和类型转换。
- 如何删除干扰分析的“总计”行。
- 最后,我们还学习了如何将数据可视化以及反向生成 HTML 文件。
虽然对于高度动态的网页(如使用 React 或 Vue 渲染的 SPA 应用),你可能需要使用 Selenium 或 Playwright,但对于传统的、静态的包含大量表格的网站,read_html() 依然是手里最锋利的瑞士军刀。希望这能帮助你在数据采集的道路上少走弯路!
下一步,你可以尝试在自己感兴趣的数据集上应用这些技巧,或者探索如何将抓取的数据定期推送到你的数据库中。祝你分析愉快!