在数据处理和文档迁移过程中,我们经常遇到这样一种需求:手头有一批XML文件,但真正有用的只是其中的文字信息,那些层层嵌套的标签、属性和命名空间反而成了干扰。要把XML里的纯文本抽出来存成TXT,最稳妥的办法不是用字符串替换去砍标签,而是用XML解析器把文档读成树结构,再沿着树把人类可读的文字收集起来。

为什么不能直接用正则删标签
很多临时方案会写一个简单的正则,比如把<[^>]+>全部替换成空字符串,看起来立刻得到了文本。但这种方式有几个隐蔽的坑。首先,XML里经常有CDATA区块,里面的内容可能本身包含类似标签的文字,正则容易误伤。其次,元素尾部文本(tail)往往被忽略,导致句子断裂。最后,注释和预处理指令也会被错误地处理,造成内容缺失或多余符号。
使用正规的XML解析库,能够区分标签、文本、注释和CDATA,按DOM或事件模型把信息呈现出来。这样我们就能精确控制只取元素包含的文字部分,而不是冒险做文本层面的猜测。对于后续要交付给非技术人员阅读的TXT,准确性比省几行代码更重要。
基于Python的递归提取脚本
Python内置的xml.etree.ElementTree模块足以应付大多数XML。核心思路是:解析文件得到根节点,然后递归访问每个元素,把它的text和tail中非空的内容收集到列表里,最后拼成字符串写入TXT。下面的示例脚本支持指定输入XML路径和输出TXT路径,并会自动忽略纯空白文本。
import xml.etree.ElementTree as ET
def extract_text(element, lines):
# 收集当前元素的直接文本
if element.text:
text = element.text.strip()
if text:
lines.append(text)
# 递归处理子元素
for child in element:
extract_text(child, lines)
# 收集子元素尾部的文本
if child.tail:
tail = child.tail.strip()
if tail:
lines.append(tail)
def xml_to_txt(xml_path, txt_path):
tree = ET.parse(xml_path)
root = tree.getroot()
lines = []
extract_text(root, lines)
with open(txt_path, 'w', encoding='utf-8') as f:
f.write('n'.join(lines))
if __name__ == '__main__':
xml_to_txt('sample.xml', 'output.txt')
在上面代码中,extract_text函数承担了核心工作。它先处理当前节点自身夹在标签里的文字,再遍历子节点。特别注意child.tail的收集,因为像<p>你好</p>世界这样的结构里,世界就是p元素的tail,如果不取就会漏掉。写入文件时用了utf-8编码,避免中文乱码。
这个脚本的优点是逻辑直观、零外部依赖,任何装了Python的环境都能跑。缺点是对超大XML文件(几百MB以上)会占用较多内存,因为ET是把整棵树读进来的。如果面临这种场景,可以改用iterparse做流式处理,但普通文档和配置类XML完全不需要。
处理命名空间与特殊结构
现实中的XML常带命名空间,例如<root xmlns:ns="http://ipipp.com/ns">。ElementTree会把标签名变成带花括号的形式,但这不影响我们取text和tail,因为那两个属性与标签名无关。如果某些节点你明确不想提取,比如<script>里的代码,可以在递归时加一个标签名判断跳过。
下面展示一个带跳过逻辑的改良片段,假设我们要忽略名为style的元素:
def extract_text_filtered(element, lines, skip_tags):
tag = element.tag
if isinstance(tag, str) and tag.split('}')[-1] in skip_tags:
return
if element.text:
text = element.text.strip()
if text:
lines.append(text)
for child in element:
extract_text_filtered(child, lines, skip_tags)
if child.tail:
tail = child.tail.strip()
if tail:
lines.append(tail)
# 使用方式
# extract_text_filtered(root, lines, {'style', 'script'})
通过split('}')[-1]我们取到了不带命名空间前缀的本地名,这样无论XML怎么定义ns,都能稳定匹配。把不需要的标签加入skip_tags集合,提取结果就会更干净。这种写法在转换网页类XML或Office文档卸载出的XML时特别实用。
批量转换与命令行化
当有一整个文件夹的XML要处理,可以再包一层遍历。利用os模块列出目录下所有.xml结尾的文件,逐个调用前面的函数,输出到同名txt。为了使用方便,也可以接收命令行参数,让运维或编辑人员直接敲一条命令完成工作。
import os, sys
def batch_convert(folder):
for name in os.listdir(folder):
if name.endswith('.xml'):
xml_path = os.path.join(folder, name)
txt_path = os.path.join(folder, name[:-4] + '.txt')
xml_to_txt(xml_path, txt_path)
print('已转换:', name)
if __name__ == '__main__':
if len(sys.argv) > 1:
batch_convert(sys.argv[1])
else:
print('请传入文件夹路径')
这段批处理代码没有引入复杂框架,就是最朴素的文件操作。实际部署时,如果文件很多,可以加上异常捕获,某一文件解析失败不影响其他文件。同时建议在写入TXT前,对行列表做一次去重或合并空行,让最终文本更紧凑。
总体来看,用Python标准库做XML到TXT的纯文本提取,在可读性、维护成本和运行环境要求之间取得了很好的平衡。你只需要理解元素树里text和tail的分布规律,就能应付绝大多数日常抽取任务,而不必陷入正则的边界问题。