如何正确解析嵌套结构不规范的 HTML 中的文本节点

3次阅读

如何正确解析嵌套结构不规范的 HTML 中的文本节点

当使用 beautifulsoup 的 find_all() 查找多个标签时,若 html 结构存在嵌套错误(如 意外闭合了 ),不同解析器会生成截然不同的 dom 树——lxml 严格纠错导致文本节点被剥离,而 html.parser 更宽容,可保留原始语义结构。

当使用 beautifulsoup 的 `find_all()` 查找多个标签时,若 html 结构存在嵌套错误(如 `

    ` 意外闭合了 `

    `),不同解析器会生成截然不同的 dom 树——`lxml` 严格纠错导致文本节点被剥离,而 `html.parser` 更宽容,可保留原始语义结构。

    在实际网页抓取中,我们常遇到非标准 HTML:例如

    before

    • 222

    after 这类违反规范的写法(HTML 标准中

      不允许作为

      的子元素)。此时解析器的行为差异尤为关键:

      • lxml 解析器:遵循 XML/HTML 严格规范,自动修复 DOM 结构——它会提前关闭

        标签,使 after 成为

        的直接子文本节点,脱离

        上下文。因此 find_all([“p”, “li”]) 仅返回

        111

        before

        立即学习前端免费学习笔记(深入)”;

      • 222
      • ,而 “after” 作为孤立文本节点(NavigableString)不会被匹配到。

      • html.parser 解析器:Python 内置、容错性强,更贴近浏览器的“尽力而为”解析逻辑。它将整个

        before

        • 222

        after 视为一个完整

        元素,其中 “after” 保留在

        的 .contents 末尾,因而 find_all([“p”, “li”]) 能正确返回包含 “after” 的完整

        标签。

      ✅ 正确做法:显式指定容错解析器

      from bs4 import BeautifulSoup  html = "<html><body><p>111</p><p>before<ul><li>222</li></ul>after</p></body></html>" soup = BeautifulSoup(html, "html.parser")  # ✅ 推荐:使用 html.parser  elements = soup.find_all(["p", "li"]) print([str(e) for e in elements]) # 输出: # ['<p>111</p>', #  '<p>before<ul><li>222</li></ul>after</p>', #  '<li>222</li>']

      ? 验证 “after” 是否被保留:

      p2 = elements[1]  # 第二个 <p> 元素 print(repr(p2.get_text()))      # 'before222after' print([type(c).__name__ for c in p2.contents]) # ['NavigableString', 'Tag', 'NavigableString'] → 最后一个即 "after"

      ⚠️ 注意事项:

      • 不要依赖 prettify() 判断结构——它输出的是解析后的树形视图,而非原始源码;prettify() 中 “after” 出现在
          外,正说明 lxml 已重排 DOM,而 html.parser 的 prettify() 会显示 “after” 仍在

          内。

      • 若需提取 “after” 作为独立文本,可在获取

        后遍历其 .contents,筛选 NavigableString 并排除空白:

        from bs4 import NavigableString p = soup.find("p", string=lambda x: "after" in str(x)) or soup.find_all("p")[1] tail_text = [s.strip() for s in p.stripped_strings if "after" in s] print(tail_text)  # ['after']

      • 极端场景下可结合 soup.find_all(text=True) 获取所有文本节点,再用 .parent 关联上下文,但通常优先通过选择合适解析器解决根本问题。

      总结:面对现实世界中大量不规范 HTML,html.parser 应作为默认解析器;仅在需要高性能或 XML 级别校验时选用 lxml,并主动处理其自动修复带来的结构偏移。

text=ZqhQzanResources