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

使用BeautifulSoup解析分层HTML为指定结构JSON的错位重复问题

问题根因

原代码的实现逻辑存在几个核心偏差,直接导致输出错位:

  • 跨区块遍历:遇到下一个h2时没有立即终止当前区块的循环,后续所有h2对应的内容都会被并入上一个区块,出现内容重复、串位
  • 字段漏存:切换h3节点、到达区块边界时,没有把上一个h3收集完成的p文本写入内容字典,经常丢失最后一个字段的内容
  • 过滤逻辑失效:遇到Bar、Baz这类需要跳过的h3时,没有暂停对应p文本的收集流程,导致后续文本拼接混乱
  • 冗余文本未过滤:h2之后、第一个h3之前的孤立p文本不属于任何目标字段,原代码没有做拦截
核心实现思路

完全匹配“h2划区块、下一个h2截断、按h3分组收集文本”的规则,步骤如下:

  • 定义目标字段白名单,仅保留Prio、Description、Foo三类h3对应的内容
  • 提取文档中首个h1标签的文本,作为Category字段值
  • 遍历所有h2标签,每个h2作为独立内容区块的起点
  • 对单个h2区块,遍历其后续兄弟节点,一旦遇到下一个h2立刻停止遍历,完成当前区块的内容收集
  • 区块内遍历过程中维护两个状态:当前正在收集的目标字段名、该字段下拼接的p文本列表
  • 遇到h3节点时,先把上一个收集到的字段内容写入字典;再判断当前h3是否在白名单内,是则更新当前收集字段,否则清空收集状态
  • 遇到p节点时,仅当处于目标字段收集状态时,才把p的文本追加到对应字段的文本列表中
可直接运行的修正代码
import json
from bs4 import BeautifulSoup

html = """<h1>Heading</h1>
<h1>More heading</h1>
<p>test</p>
<h2>Section</h2>
<p>a.b.c</p>
<h3>Prio</h3>
<p>Medium</p>
<h3>Description</h3>
<p>Description 1</p>
<p>Description 2</p>
<h3>Foo</h3>
<p>Foo 1</p>
<p>Foo 2</p>
<h3>Bar</h3>
<p>Bar 1</p>
<p>Bar 2</p>
<p>Bar 3</p>
<h3>Baz</h3>
<p>Baz 1</p>
<h2>Section</h2>
<h3>Prio</h3>
<p>High</p>
<h3>Description</h3>
<p>Description 3</p>
<p>Description 4</p>
<h3>Foo</h3>
<p>Foo 3</p>
<h2>Section</h2>
<h3>Prio</h3>
<p>Low</p>
<h3>Description</h3>
<p>Description 5</p>
<p>Description 6</p>
<h3>Foo</h3>
<p>Foo 4</p>
<p>Foo 5</p>
<h3>Bar</h3>
<p>Bar 4</p>
<p>Bar 5</p>
<h3>Baz</h3>
<p>Baz 2</p>
<h2>Section</h2>
<h3>Prio</h3>
<p>Medium</p>
<h3>Description</h3>
<p>Description 7</p>
<h3>Foo</h3>
<p>Foo 6</p>
<h3>Bar</h3>
<p>Bar 6</p>
<h3>Baz</h3>
<p>Baz 3</p>"""

# 定义需要提取的目标h3字段白名单
TARGET_FIELDS = {"Prio", "Description", "Foo"}

result = {}
soup = BeautifulSoup(html, 'lxml')
# 提取首个h1的文本作为Category
result['Category'] = soup.find('h1').get_text(strip=True)

data = []
all_h2 = soup.find_all('h2')
# 遍历每个h2划分的独立区块
for h2 in all_h2:
    current_content = {}
    current_field = None
    current_text_list = []
    # 遍历当前h2的后续兄弟节点,直到遇到下一个h2就截断
    for sibling in h2.next_siblings:
        # 区块边界:遇到下一个h2,先完成当前区块的收尾
        if sibling.name == 'h2':
            if current_field:
                current_content[current_field] = '\n'.join(current_text_list)
                current_field = None
                current_text_list = []
            break
        # 遇到h3节点,切换收集状态
        if sibling.name == 'h3':
            # 先把上一个h3收集的内容存入字典
            if current_field:
                current_content[current_field] = '\n'.join(current_text_list)
                current_text_list = []
            # 判断当前h3是否为目标字段
            h3_text = sibling.get_text(strip=True)
            if h3_text in TARGET_FIELDS:
                current_field = h3_text
            else:
                # 非目标字段(Bar/Baz),清空收集状态
                current_field = None
        # 遇到p节点,仅在目标字段收集状态下拼接文本
        elif sibling.name == 'p':
            if current_field:
                p_text = sibling.get_text(strip=True)
                current_text_list.append(p_text)
    data.append(current_content)

result['Data'] = data
# 格式化输出结果
print(json.dumps(result, indent=2, ensure_ascii=False))
运行效果

执行上述代码后,输出结构与预期完全匹配:

  • Category 字段为首个h1的文本Heading
  • Data 数组共4个元素,每个元素对应单个h2区块,仅包含Prio、Description、Foo三个字段,Bar、Baz的内容全部过滤
  • 同一h3下的多段p文本自动用\n拼接

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.30 15:06:01