You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用BeautifulSoup处理嵌套错误的破损HTML表格

解决嵌套错误的破损HTML表格解析问题

问题场景

你遇到的是典型的破损HTML标签嵌套错误问题:这段代码里<td>和<tr>的位置完全不符合规范,本该在第6行闭合的</td>被放到了第25行,导致常规解析器会把内层所有<td>的内容都包含到最外层<td>里,最终输出重复内容。

先看一下这段问题代码:

1 <td valign="top"> <!-- closing </td> should be on 6 -->
2 <font face="arial" size="1">
3 <center>
4 06-30-95
5 </center>
6 <tr valign="top">
7 <td>
8 <center>
9 <font ,="" arial,="" face="arial" sans="" serif"" size="1">
10 1382
11 <p>
12 (23)
13 </p>
14 </font>
15 </center>
16 </td>
17 <td>
18 <font ,="" arial,="" face="arial" sans="" serif"" size="1">
19 <center>
20 06-18-14
21 </center>
22 </font>
23 </td>
24 </tr>
25 </td> <!-- this should should be on 6 -->

你的期望是按照自定义规则解析:未闭合的<td>遇到新<td>时自动闭合,最终得到结果:['06-30-95', '1382\n(23)', '06-18-14']。

当前尝试的问题

你用BeautifulSoup的代码得到了重复内容,这是因为默认解析器把最外层<td>的范围扩展到了最后,包含了所有内层<td>的文本:

html = """
<td valign="top"> <font face="arial" size="1"> <center> 06-30-95 </center> <tr valign="top"> <td> <center> <font ,="" arial,="" face="arial" sans="" serif"" size="1"> 1382 <p> (23) </p> </font> </center> </td> <td> <font ,="" arial,="" face="arial" sans="" serif"" size="1"> <center> 06-18-14 </center> </font> </td> </tr> </td>
"""
from bs4 import BeautifulSoup, SoupStrainer
strainer = SoupStrainer('td')
soup = BeautifulSoup(html, 'html.parser', parse_only=strainer)
print([tag.text.replace('\n', '') for tag in soup.find_all('td')])
# 输出: [' 06-30-95 1382 (23) 06-18-14 ', ' 1382 (23) ', ' 06-18-14 ']

可行的解决方案

方案1:预处理HTML,手动修复标签位置

既然你已经知道标签的错误位置,最简单的方法就是先调整HTML结构,把错位的</td>移到正确位置:

# 把末尾的 </tr> </td> 换成 </td> </tr>,修复标签闭合顺序
fixed_html = html.replace('</tr> </td>', '</td> </tr>')

soup = BeautifulSoup(fixed_html, 'html.parser', parse_only=strainer)
# 使用get_text更优雅地处理文本,strip=True去除首尾空格,separator='\n'保留换行
result = [tag.get_text(strip=True, separator='\n') for tag in soup.find_all('td')]
print(result)
# 输出: ['06-30-95', '1382\n(23)', '06-18-14']

这种方法适合错误模式固定的场景,快速有效。

方案2:自定义遍历逻辑,跳过嵌套内容

如果无法提前修改HTML,我们可以针对性地提取内容:最外层<td>的目标文本是它的第一个子节点(<font>包裹的日期),后面的<td>都是独立的,直接提取即可:

soup = BeautifulSoup(html, 'html.parser', parse_only=strainer)
td_list = soup.find_all('td')

# 提取第一个td的核心文本:取它的第一个font标签的文本
first_content = td_list[0].find('font').get_text(strip=True)

# 提取剩下的td的文本,这些没有嵌套的td,直接处理
other_contents = [tag.get_text(strip=True, separator='\n') for tag in td_list[1:]]

final_result = [first_content] + other_contents
print(final_result)
# 输出: ['06-30-95', '1382\n(23)', '06-18-14']

这里利用了标签的层级关系,精准定位需要的内容,避免包含嵌套的子<td>文本。

方案3:用lxml的XPath精准提取

如果破损HTML结构更复杂,BeautifulSoup的遍历不够灵活,可以换成lxml库,用XPath来定位内容:

from lxml import html

tree = html.fromstring(html)

# 提取第一个td下第一个font标签的文本(即日期)
first_text = tree.xpath('string(//td[1]//font[1])').strip()

# 提取剩下的td下的所有文本,合并换行
other_texts = []
for td in tree.xpath('//td[position()>1]'):
    texts = [t.strip() for t in td.xpath('.//text()') if t.strip()]
    other_texts.append('\n'.join(texts))

final_result = [first_text] + other_texts
print(final_result)
# 输出: ['06-30-95', '1382\n(23)', '06-18-14']

XPath可以更灵活地筛选节点,适合处理不规则的HTML结构。

关于BeautifulSoup的handle_endtag参数

你提到的handle_endtag其实是BeautifulSoup底层解析器的内部钩子,并没有对外提供可配置的接口。BeautifulSoup的解析逻辑依赖于它使用的底层解析器(比如Python内置的html.parser、lxml或html5lib),这些解析器的容错规则是固定的,无法直接自定义“遇到新<td>就闭合前一个<td>”的逻辑,所以还是需要通过预处理或自定义提取逻辑来解决。

内容的提问来源于stack exchange,提问作者Brad Solomon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:49:42