使用BeautifulSoup4解析XML时的命名空间问题

7 投票
2 回答
2627 浏览
提问于 2025-04-18 14:37

在用beautifulsoup4(需要安装lxml)解析.docx文件内容时,我遇到了一个问题。这个xml文件中的一部分:

    ...
    <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
        <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
            <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
    ...

变成了这个:

    ...
    <graphic>
        <graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
            <pic>
    ...

即使我只是解析文件并保存,没有做任何修改。像这样:

    from bs4 import BeautifulSoup
    soup = BeautifulSoup(open(filepath_in), 'xml')
    with open(filepath_out, "w+") as fd:
        fd.write(str(soup))

或者在Python控制台中解析xml。

对我来说,看起来像是命名空间(namespaces),如果在根文档节点中没有声明,就会被解析器忽略掉。

这是个bug,还是功能?有没有办法在用beautifulsoup4解析时保留这些命名空间?还是说我需要换个工具来处理这个问题?

更新 1:如果我用一些正则表达式和文本替换把这些命名空间声明加到根document节点上,那么beautifulsoup就能正常解析了。但我还是想知道,是否可以在解析之前不修改xml就解决这个问题。

更新 2:在玩了一会儿beautifulsoup后,我发现命名空间声明只在第一次出现时被解析。这意味着,如果标签声明了命名空间,那么它的子标签如果有命名空间声明,就不会被解析。下面是一个代码示例和输出,来说明这一点。

from bs4 import BeautifulSoup

xmls = []
xmls.append("""<name1:tag xmlns:name1="namespace1" xmlns:name2="namespace2">
<name2:intag>
text
</name2:intag>
</name1:tag>
""")
xmls.append("""<tag>
<name2:intag xmlns:name2="namespace2">
text
</name2:intag>
</tag>
""")
xmls.append("""<name1:tag xmlns:name1="namespace1">
<name2:intag xmlns:name2="namespace2">
text
</name2:intag>
</name1:tag>
""")
for i, xml in enumerate(xmls):
    print "============== xml {} ==============".format(i)
    soup = BeautifulSoup(xml, "xml")
    print soup

将产生以下输出:

============== xml 0 ==============
<?xml version="1.0" encoding="utf-8"?>
<name1:tag xmlns:name1="namespace1" xmlns:name2="namespace2">
<name2:intag>
text
</name2:intag>
</name1:tag>
============== xml 1 ==============
<?xml version="1.0" encoding="utf-8"?>
<tag>
<name2:intag xmlns:name2="namespace2">
text
</name2:intag>
</tag>
============== xml 2 ==============
<?xml version="1.0" encoding="utf-8"?>
<name1:tag xmlns:name1="namespace1">
<intag>
text
</intag>
</name1:tag>

你看,前两个xml被正确解析,而第三个中的第二个声明却被忽略了。

实际上,这个问题和docx文件已经没有关系了。我的问题变成了:这种行为是beautifulsoup4硬编码的,还是说我可以改变它?

2 个回答

0

把这一行改成:

soup = BeautifulSoup(open(filepath_in), 'xml')

改成:

soup = BeautifulSoup(open(filepath_in), 'lxml')

或者:

soup = BeautifulSoup(open(filepath_in), 'html.parser')
0

根据W3C的推荐:

前缀是合格名称中的命名空间前缀部分,必须与命名空间声明中的命名空间URI引用相关联。

https://www.w3.org/TR/REC-xml-names/#ns-qualnames

所以我认为这是预期的行为:忽略那些没有声明的命名空间,以便在一些不遵循推荐的文档上进行解析。

撰写回答