应使用 lxml.html.fromstring 而非 etree.fromstring 解析 html,因其调用 libxml2 的 html 解析器,自动补全标签、修复未闭合结构、容忍语法错误;etree.fromstring 严格按 xml 规则解析,遇不规范标签即报错。

lxml 不是纯 Python 实现的解析器,它的核心能力来自 libxml2 的 C 层引擎 —— 这直接决定了它快、容错强、但对 malformed HTML 的“修复行为”不是凭空猜测,而是有明确规则。
etree.HTML() 为什么能修歪 HTML?
当你调用 etree.HTML(html_string),lxml 实际把字符串交给 libxml2 的 htmlParseDoc 函数处理。这个函数内置了一套 HTML 恢复算法(HTML recovery algorithm),会自动:
- 补全缺失的
、等外围标签 - 闭合未闭合的标签(比如
<p>hello</p> <div>world → 自动插入 <code>) - 将自闭合标签(如
<br>)标准化为<br>形式(仅在 XML 模式下严格;HTML 模式下按语义处理) - 忽略非法嵌套(如
<p></p>内直接放<div>)并尝试重排树结构 <p>这不是“智能猜测”,而是遵循 WHATWG HTML 规范中定义的 <em>parse tree construction</em> 步骤。所以结果可预期、可复现,但和浏览器 DOM 树仍可能有细微差异(比如某些注释位置、空白文本节点处理)。</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill5493" title="html-to-pptx"><img src="https://img.php.cn/upload/skill/000/000/081/179051045119472.jpg" alt="html-to-pptx" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill5493" title="html-to-pptx" class="overflowclass">html-to-pptx</a> <p class="overflowclass">将多页 HTML 演示文稿转换为美化的 PPTX 文件,便于分享和分发。</p> </div> <a rel="nofollow" href="/xiazai/skill5493" title="html-to-pptx" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div> <h3>etree.fromstring() 和 etree.HTML() 的关键区别</h3> <p>二者底层调用不同解析器,行为差异直接影响结果:</p> <ul> <li> <code>etree.fromstring()走的是 XML 解析路径(libxml2 的xmlParseDoc),遇到任何语法错误(如未闭合标签、属性无引号)直接抛XMLSyntaxError -
etree.HTML()走的是 HTML 解析路径(htmlParseDoc),默认宽容模式,几乎不报错 —— 即使传入纯文本或乱码,也会返回一个根为的 Element 对象(内容可能为空或退化) - 若你明确知道输入是格式良好的 XML,必须用
fromstring();若处理真实网页源码(含各种手写错误),必须用HTML()
混淆两者最常见后果:用 fromstring() 解析网页 → 程序崩溃;用 HTML() 解析严格 XML(如 RSS、SOAP 响应)→ 标签被重写、命名空间丢失、@xml:lang 等属性失效。
XPath 查询在 lxml 中如何真正执行
lxml 的 XPath 并非 Python 层遍历模拟,而是把表达式编译成 libxml2 内部的 xmlXPathCompExpr 结构,再由 C 引擎原生执行。这意味着:
-
//div[@class="item"]不是“先找所有 div,再过滤 class”——而是 libxml2 在构建文档树时就建立索引式匹配路径 - 使用
text()或@href等轴(axis)操作时,结果是list,但每个元素是 libxml2 的xmlNodePtr封装,不是 Python 字符串,直到你取值才触发转换 - 重复使用同一 XPath 表达式时,建议先用
etree.XPath()编译一次:find_items = etree.XPath('//div[@class="item"]'),避免每次解析开销
注意:xpath() 返回空列表不等于没匹配到,可能是匹配到但内容为空(如 <div class="item"></div>);需额外检查 .text 或 .getchildren()。
内存与编码问题最容易被忽略的点
lxml 默认以 UTF-8 解析输入,但真实网页常带 <meta charset="gb2312">










