
使用 lxml 的 XMLParser(target=...) 进行事件驱动解析时,含 Unicode(如 Łódź)的文本可能被拆分为多个 data() 调用,导致本应为单个字符串的内容被错误分割;需通过 start() 和 end() 协同累积文本,而非在 data() 中直接追加。
使用 lxml 的 `xmlparser(target=...)` 进行事件驱动解析时,含 unicode(如 `Łódź`)的文本可能被拆分为多个 `data()` 调用,导致本应为单个字符串的内容被错误分割;需通过 `start()` 和 `end()` 协同累积文本,而非在 `data()` 中直接追加。
在 lxml 的基于 SAX 的事件驱动解析(target 接口)中,data() 回调不保证一次性传递元素内全部文本——尤其当文本包含混合编码边界(如 ASCII 与 UTF-8 多字节字符交界处)时,底层 libxml2 可能将一个 <entry>data Łódź</entry> 拆分为两次 data() 调用:"data " 和 "Łódź"。这是符合 XML 规范的合法行为,但若直接在 data() 中 append(),就会得到错误的四元素列表 ['data', 'Łódź', 'data ', 'Łódź']。
正确做法是:将文本累积逻辑移至元素生命周期内,即利用 start() 初始化缓冲区、data() 追加原始片段、end() 合并并提交结果。关键点如下:
- ✅
start():为每个新元素重置self.current_text = [],确保隔离不同<entry></entry>的内容; - ✅
data():无条件追加原始data(保留空格与换行),避免过早strip()或append到最终列表; - ✅
end():仅当self.current_text非空时,用"".join()合并所有片段,并清空缓冲区; - ⚠️ 不依赖
encoding='UTF-8'参数修复此问题(如原代码注释所示,它对此类拆分无效)。
以下是可直接运行的修正版示例:
from lxml import etree
class ParseTarget:
def __init__(self):
self.entries = []
self.current_text = []
def start(self, tag, attrib):
# 新标签开始时清空当前文本缓冲区
self.current_text = []
def end(self, tag):
# 标签结束时合并所有已接收的文本片段
if self.current_text:
full_text = "".join(self.current_text)
self.entries.append(full_text)
self.current_text = [] # 重置,为下一个元素准备
def data(self, data):
# 原样追加(包括空格、换行、零宽字符),不 strip、不过滤
if data: # 可选:跳过完全空字符串(但保留 '\n ' 等有意义空白)
self.current_text.append(data)
def close(self):
entries = self.entries
self.entries = []
return entries
# 解析 XML 文件(确保文件实际存在)
target = etree.XMLParser(target=ParseTarget())
tree = etree.parse("./test.xml", target)
print(tree) # 输出: ['data', 'Łódź', 'data Łódź']注意事项:
- 若需去除首尾空白(如
<entry> data Łódź </entry>→'data Łódź'),应在end()中对full_text.strip(),而非在data()中提前处理; -
data()可能被多次调用,也可能一次不调用(如空标签<entry></entry>),因此end()必须做空检查; - 此模式天然支持嵌套文本节点(如含 CDATA 或实体引用),因其始终按元素粒度聚合;
- 在 Python 3 中,
lxml默认使用 UTF-8,无需显式指定encoding参数(除非源文件声明了其他编码且未正确 BOM 标记)。
通过这种“缓冲+合并”模式,即可稳健地获取每个元素的完整文本内容,彻底规避非 ASCII 字符引发的意外拆分问题。


















