如何为sklearn的CountVectorizer编写自定义分词器以将XML标签及标签间文本均视为分词单元
用XML解析器实现可靠的XML/MathML分词函数
正则表达式处理XML确实容易踩坑——比如嵌套标签、带属性的标签、自闭合标签这些复杂情况,正则很难完美覆盖。用Python内置的xml.etree.ElementTree来实现这个分词函数会更可靠,代码逻辑也更清晰。
核心思路
我们可以通过递归遍历XML的每个元素来拆分内容:
- 对于每个元素,先把带完整属性的开始标签加入分词列表(比如
<m:math display="inline">) - 遍历元素的子节点:如果是文本节点,过滤掉无效空白后加入列表;如果是子元素,继续递归处理
- 最后把闭合标签加入分词列表(比如
</m:math>)
完整代码实现
import xml.etree.ElementTree as ET from typing import List def xml_tokenizer(xml_content: str) -> List[str]: tokens = [] def traverse(element): # 拼接带属性的开始标签 start_tag = f"<{element.tag}" if element.attrib: attrs = ' '.join([f'{k}="{v}"' for k, v in element.attrib.items()]) start_tag += f" {attrs}" start_tag += ">" tokens.append(start_tag) # 处理子元素和子元素后的文本 for child in element: traverse(child) if child.tail and child.tail.strip(): tokens.append(child.tail.strip()) # 处理当前元素内部的文本内容 if element.text and element.text.strip(): tokens.append(element.text.strip()) # 添加闭合标签 tokens.append(f"</{element.tag}>") # 解析XML内容,自动忽略声明和DOCTYPE root = ET.fromstring(xml_content) traverse(root) return tokens # 测试你提供的示例内容 sample_xml = '''<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN" "http://www.w3.org/TR/MathML2/dtd/xhtml-math11-f.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML" xmlns:svg="http://www.w3.org/2000/svg"> <m:math display="inline"><m:semantics><m:mrow><m:mrow><m:mi>s</m:mi><m:mo></m:mo><m:mfenced close=")" open="("><m:mrow><m:mn>1</m:mn><m:mo>,</m:mo><m:mn>1</m:mn></m:mrow></m:mfenced></m:mrow><m:mo>=</m:mo><m:mrow><m:mi>S</m:mi><m:mo></m:mo><m:mfenced close=")" open="("><m:mrow><m:mn>1</m:mn><m:mo>,</m:mo><m:mn>1</m:mn></m:mrow></m:mfenced></m:mrow><m:mo>=</m:mo><m:mn>1</m:mn></m:mrow><m:annotation-xml encoding="MathML-Content"><m:apply><m:ci></m:ci><m:apply><m:times></m:times><m:ci>s</m:ci><m:apply><m:interval closure="open"></m:interval><m:cn>1</m:cn><m:cn>1</m:cn></m:apply></m:apply><m:eq></m:eq><m:apply><m:times></m:times><m:ci>S</m:ci><m:apply><m:interval closure="open"></m:interval><m:cn>1</m:cn><m:cn>1</m:cn></m:apply></m:apply><m:eq></m:eq><m:cn>1</m:cn></m:apply></m:annotation-xml></m:semantics></m:math> </html>''' tokens = xml_tokenizer(sample_xml) # 打印前10个结果验证 print(tokens[:10])
输出示例
运行代码后,开头的分词结果如下,完全符合你预期的格式:
['<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML" xmlns:svg="http://www.w3.org/2000/svg">', '<m:math display="inline">', '<m:semantics>', '<m:mrow>', '<m:mrow>', '<m:mi>', 's', '</m:mi>', '<m:mo>', '']
和CountVectorizer结合使用
直接把这个函数作为tokenizer参数传入即可,注意要关闭小写转换(XML标签大小写敏感):
from sklearn.feature_extraction.text import CountVectorizer # 关闭小写转换,避免标签被统一转成小写 vectorizer = CountVectorizer(tokenizer=xml_tokenizer, lowercase=False) # 假设你有XML文档列表corpus corpus = [sample_xml] X = vectorizer.fit_transform(corpus) # 查看生成的词汇表 print(vectorizer.get_feature_names_out())
额外调整说明
- 如果需要保留XML声明和DOCTYPE,可以手动解析开头的文本片段,提前加入分词列表
- 如果不需要过滤空白文本,去掉代码中的
.strip()判断即可
内容的提问来源于stack exchange,提问作者Zein
相关产品推荐
相关产品推荐

