使用Pandas读取含合并单元格的表格时如何保持列对齐
解决Pandas读取含合并单元格的HTML表格(伪装成.xls)时的列偏移问题
我之前处理过不少这种“披着xls外衣的HTML表格”,合并单元格(尤其是rowspan)确实会让pandas.read_html()犯糊涂,直接读很容易出现列错位的情况。下面是两种亲测有效的解决思路,你可以根据需求选:
方法一:预处理HTML,展开合并单元格(最通用可靠)
核心思路是用BeautifulSoup先把HTML里的rowspan合并单元格展开,让每一行的单元格数量保持一致,这样Pandas读取时就不会乱偏移了。
具体代码:
from bs4 import BeautifulSoup import pandas as pd # 读取伪装成xls的HTML文件内容 with open("your_target_file.xls", "r", encoding="utf-8") as file: html_raw = file.read() # 用BeautifulSoup解析HTML soup = BeautifulSoup(html_raw, "html.parser") # 遍历所有表格,处理rowspan合并单元格 for table in soup.find_all("table"): rows = table.find_all("tr") # 记录需要向下填充的单元格信息:(列索引, 单元格值, 剩余填充行数) pending_fill = [] for row in rows: cells = row.find_all(["td", "th"]) # 先把上一行需要延续的单元格插入到当前行对应位置 for col_idx, cell_val in pending_fill: if col_idx < len(cells): cells.insert(col_idx, soup.new_tag("td")) cells[col_idx].string = cell_val else: # 如果当前行单元格数量不够,直接新增单元格 new_td = soup.new_tag("td") new_td.string = cell_val row.append(new_td) # 更新pending_fill:减少剩余填充行数,移除已完成的条目 updated_pending = [] for col_idx, cell_val, remain_count in pending_fill: remain_count -= 1 if remain_count > 0: updated_pending.append((col_idx, cell_val, remain_count)) pending_fill = updated_pending # 检查当前行的单元格是否有rowspan属性,记录需要填充的内容 for col_idx, cell in enumerate(cells): if cell.has_attr("rowspan"): span_count = int(cell["rowspan"]) if span_count > 1: # 把当前单元格的值和剩余需要填充的行数记录下来 pending_fill.append((col_idx, cell.string, span_count - 1)) # 移除rowspan属性,避免重复处理 del cell["rowspan"] # 现在处理后的HTML已经展开了所有合并单元格,用Pandas读取 dfs = pd.read_html(str(soup), header=0) target_df = dfs[0] # 假设只有一个表格,按需调整索引
为什么这个方法有效?
HTML里的rowspan合并单元格只会在第一行显示值,后面的行对应位置是缺失<td>标签的,Pandas读取时会自动把后面列的内容往前补,导致错位。我们通过BeautifulSoup手动给缺失的位置补上对应的单元格和值,让每一行的单元格数量完全匹配,Pandas就能正确识别列了。
方法二:读取后手动修正(适合数据规律明确的场景)
如果不想引入BeautifulSoup依赖,且你清楚第一列合并单元格的规律(比如第一列的有效值有固定格式,比如都是分类名称),可以先读取数据,再手动调整列和填充空值。
举个例子,假设读取后的数据是这样的(第一列混入了原本第二列的内容):
| 分类/项目 | 数值 |
|---|---|
| 电子产品 | 1000 |
| 手机 | 500 |
| 电脑 | 500 |
| 日用品 | 800 |
| 纸巾 | 300 |
我们可以这样修正:
import pandas as pd # 读取文件 dfs = pd.read_html("your_target_file.xls", header=0) df = dfs[0] # 步骤1:新增一列存放原本的数值 df["真实数值"] = df["数值"] # 步骤2:把第一列里的项目内容移到第二列 df["项目"] = df["分类/项目"].where(~df["分类/项目"].str.contains("电子产品|日用品"), df["项目"]) # 步骤3:把第一列的空值(非分类的行)用前一个分类值填充 df["分类"] = df["分类/项目"].where(df["分类/项目"].str.contains("电子产品|日用品"), None) df["分类"] = df["分类"].ffill() # 步骤4:整理列顺序,删除多余列 df = df[["分类", "项目", "真实数值"]]
这个方法的缺点是需要你对数据特征有明确认知,通用性不如第一种,但胜在不用额外安装库。
内容的提问来源于stack exchange,提问作者adarsha joisa
相关产品推荐
相关产品推荐

