
当使用 BeautifulSoup 的 find_all() 查找多个标签时,若 HTML 结构存在嵌套错误(如 意外闭合了 ),不同解析器会生成截然不同的 DOM 树——lxml 严格纠错导致文本节点被剥离,而 html.parser 更宽容,可保留原始语义结构。
当使用 beautifulsoup 的 `find_all()` 查找多个标签时,若 html 结构存在嵌套错误(如 `
- ` 意外闭合了 `
- 222
-
lxml 解析器:遵循 XML/HTML 严格规范,自动修复 DOM 结构——它会提前关闭
标签,使 after 成为
的直接子文本节点,脱离上下文。因此 find_all(["p", "li"]) 仅返回
111
、before
立即学习“前端免费学习笔记(深入)”;
和 - 222 ,而 "after" 作为孤立文本节点(NavigableString)不会被匹配到。
-
html.parser 解析器:Python 内置、容错性强,更贴近浏览器的“尽力而为”解析逻辑。它将整个
before
- 222
元素,其中 "after" 保留在
的 .contents 末尾,因而 find_all(["p", "li"]) 能正确返回包含 "after" 的完整
标签。
`),不同解析器会生成截然不同的 dom 树——`lxml` 严格纠错导致文本节点被剥离,而 `html.parser` 更宽容,可保留原始语义结构。
在实际网页抓取中,我们常遇到非标准 HTML:例如
before
- 不允许作为
的子元素)。此时解析器的行为差异尤为关键:
✅ 正确做法:显式指定容错解析器
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,并主动处理其自动修复带来的结构偏移。










