使用 BeautifulSoup4 进行 XML 解析,命名空间问题

xml parsing with beautifulsoup4, namespaces issue

提问人:pavel_form 提问时间:7/24/2014 最后编辑:pavel_form 更新时间:9/17/2018 访问量:2579

问:

在使用 beautifulsoup4(根据需要安装 lxml)以 xml(word/document.xml)形式解析.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。

对我来说,它看起来像命名空间,像这样声明,而不是在根文档节点中,被解析器吃掉了。

这是一个错误,还是一个功能?有没有办法在使用 beautifulesoup4 解析时保留这些?还是我需要为此切换到其他东西?

更新 1:如果使用一些正则表达式和文本替换将这些命名空间声明添加到根节点,那么 beautifulsoup 可以很好地解析它。但我仍然有兴趣是否可以在解析之前不修改 xml 的情况下解决这个问题。document

更新 2:在玩了一会儿 beutifulsoup 之后,我发现命名空间声明仅在第一次出现时才被解析。表示如果 tag 声明了命名空间,那么如果它的子项有命名空间声明,则不会解析它们。下面是带有输出的代码示例来说明这一点。

从 bs4 导入 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 是如何正确解析的,而第三个 xml 中的第二个声明是如何被吃掉的。

实际上,这个问题不再涉及docx。我的问题四舍五入到:这种行为是否在 beautifulsoup4 中硬编码,如果不是,那么我该如何更改它?

python xml 解析 beautifulsoup docx

评论

2赞 Suzana 2/23/2016
我成功地将“lxml”传递给 soup 构造函数而不是“xml”。但并非总是如此......
0赞 ewwink 11/18/2018
lxml更新?
0赞 Rithin Chalumuri 11/3/2019
有什么理由不选择使用其他专用的特定于 xml 的解析内置库,如 ElementTree?

答:

0赞 robermorales 11/17/2016 #1

来自 W3C 建议:

Prefix 提供限定名称的命名空间前缀部分,并且必须与命名空间声明中的命名空间 URI 引用相关联。

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

所以我认为这是预期的行为:丢弃未声明的命名空间,以优雅地允许对不遵守建议的文档进行一些解析。

0赞 Sushant Kathuria 9/17/2018 #2

更改此行:

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

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

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