BeautifulSoup默认无法解析非标准XML,因其xml解析器严格遵循规范;需先用lxml.etree.XMLParser(recover=True)容错解析,再转字符串交由BeautifulSoup处理。

为什么 BeautifulSoup 默认无法正确解析非标准 XML
BeautifulSoup 的 xml 解析器(即 lxml-xml 或 xml)严格遵循 XML 规范,遇到常见非标准情况会直接报错或丢弃内容。比如:自闭合标签未写斜杠( 而非 )、属性值无引号()、缺失根节点、含非法字符实体(&foo;)、或混用 HTML 实体( )——这些在真实爬虫/遗留系统中极常见,但 lxml.etree.XMLParser(recover=True) 也不买账。
改用 lxml 的 recover parser + BeautifulSoup 的组合方案
核心思路是:不把原始文本直接喂给 BeautifulSoup(..., 'xml'),而是先用 lxml.etree.fromstring() 带容错解析,再把修复后的树转成字符串交给 BeautifulSoup 处理。这样既能保留 lxml 的恢复能力,又不放弃 BeautifulSoup 熟悉的 API。
-
lxml.etree.XMLParser(recover=True)可容忍多数语法错误,如缺失引号、未闭合标签、乱码实体 - 必须显式传入
parser参数,否则fromstring()仍走严格模式 - 解析后调用
etree.tostring(tree, encoding='unicode', method='xml')转回字符串,避免字节/编码问题 - 再用
BeautifulSoup(..., 'xml')加载该字符串,后续操作完全不变
from lxml import etree from bs4 import BeautifulSoupbroken_xml = '
' - text
关键:启用 recover 模式
parser = etree.XMLParser(recover=True) tree = etree.fromstring(broken_xml.encode('utf-8'), parser) fixed_xml = etree.tostring(tree, encoding='unicode', method='xml')
soup = BeautifulSoup(fixed_xml, 'xml') print(soup.find('item')['id']) # 输出:123
遇到命名空间或 CDATA 时的特殊处理
非标准 XML 常混用命名空间前缀(如 )却不声明 xmlns:ns,或把 JSON/JS 代码塞进 却漏写右括号。BeautifulSoup 对 CDATA 默认当普通文本,而 lxml 的 recover parser 会把未闭合的 CDATA 当注释吞掉。
立即学习“Python免费学习笔记(深入)”;
- 若需保留 CDATA 内容原样,解析前先用正则临时替换:
re.sub(r',解析完再还原 - 命名空间报错(如
Namespace prefix ns not declared)可提前移除所有xmlns:属性和带冒号的标签名,用re.sub(r'xmlns:[^=]+="[^"]*"', '', xml_str) - lxml 的
recover=True对开头但无结尾的情况会截断后续内容,务必检查输出字符串是否完整
比对 lxml.etree 和 BeautifulSoup 的实际行为差异
直接用 lxml.etree 能更细粒度控制容错,但丢失了 soup.select()、.find_next_siblings() 这类便利方法。而 BeautifulSoup 的 'xml' 解析器底层若用 lxml,本质仍是调用 etree.fromstring() —— 区别只在是否暴露 parser 控制权。
- 不要用
BeautifulSoup(xml_str, 'lxml-xml'):它不接受自定义 parser,且内部仍走严格模式 - 不要依赖
features='xml':该参数已被弃用,且无 recover 能力 - 性能上,先
etree.fromstring()再转字符串会有一次序列化开销,但对 MB 级以下数据影响可忽略 - 若原始数据含大量非法 UTF-8 字节(如 GBK 混入),需先用
chardet检测编码并 decode,再喂给etree.fromstring()
真正麻烦的从来不是语法错误本身,而是错误位置不可预测:可能在第 1 行,也可能藏在嵌套 7 层深的某个属性值里。recover parser 能救活结构,但不会告诉你哪里被悄悄修正了——建议解析后用 soup.prettify() 快速扫一眼关键路径是否符合预期。










