有没有更好的方法来使用 BeautifulSoup 解析以下 xml,该 xml 返回有组织的 panda 表中的所有内容?

Is there a better way to parse the following xml using BeautifulSoup that returns everything in an organized panda table?

提问人:pyj 提问时间:7/14/2023 最后编辑:Parfaitpyj 更新时间:7/15/2023 访问量:45

问:

我正在使用 BeautifulSoup 来解析和 xml 文件。我已经能够成功解析文本列的所有级别。我的代码只返回 xml 中每个段落的最后一项,而不是返回其各自行中的所有项。

以下是我遇到问题的代码块:

        # Rest of text
        article_text = ""
        alinea = article.find_all("ALINEA")
        
        ****** I believe the problem starts here ******
        
        for index, p in enumerate(alinea, 1):
            txt = p.find_all("TXT")
            if p.find_previous_sibling():
                # Captures Alinea text of parent
                if p.find_previous_sibling().name == "NO.PARAG":
                    # Used for main text of paragraph
                    if p.contents[0].name == "P":
                        article_text = p.contents[0].text
                        c8 = c7 + '.' + p.find_previous_sibling().text.replace('.', '')
                        parent_citation = c8
                # used for definitions -- do not have a "NO.PARAG" before ALINEA
                article_text = p.text
        txt = p.find_all("TXT")
        for index, t in enumerate(txt, 1):
            if p.find_previous_sibling():
                if t.find_previous_sibling().name == 'NO.P':
                    citation_level_8 = parent_citation + p.find_previous_sibling().text
                    article_text = t.text

        lst.append([t2, t3, t4, t5, t6, t7,
                    c2, c3, c4, c5, c6, c7, c8, 
                    article_text])

以下是我目前的结果:

T2型 T3型 T4型 T5型 T6型 T7型 C2型 C3型 C4型 C5型 C6型 C7型 C8型 T
第一章 标题 第1条 标题 第一章 第1条 第1条:第3款正文
第一章 标题 第2条 标题 第一章 第1条 第2条:第4款正文
第一章 标题 定义 第一章 第3条 (3) 文本

以下是我想要的结果:

T2型 T3型 T4型 T5型 T6型 T7型 C2型 C3型 C4型 C5型 C6型 C7型 C8型 发短信
第一章 标题 第1条 标题 第一章 第1条 第1.1条 第1条:第1款正文
第一章 标题 第1条 标题 第一章 第1条 第1.1(a)条 (a) 案文
第一章 标题 第1条 标题 第一章 第1条 第1.1(i)条 (a) 案文(换行符) (i) 案文
第一章 标题 第1条 标题 第一章 第1条 第1.1(ii)条 (a) 案文(换行符) (二) 案文
第一章 标题 第1条 标题 第一章 第1条 第1.1(b)条 (b) 案文
第一章 标题 第1条 标题 第一章 第1条 第1.2条 第1条:第2款正文
第一章 标题 第1条 标题 第一章 第2条 第2.1条 第2条:第1款正文
第一章 标题 第2条 标题 第一章 第2条 第2.1(a)条 (a) 案文
第一章 标题 第2条 标题 第一章 第2条 第2.1(b)条 (b) 案文
第一章 标题 第1条 标题 第一章 第2条 第2.2条 第2条:第2款正文
第一章 标题 第1条 标题 第一章 第2条 第2.3条 第2条:第3款正文
第一章 标题 第1条 标题 第一章 第2条 第2.3(a)条 (a) 案文
第一章 标题 定义 第一章 第3条 第3条:定义正文
第一章 标题 定义 第一章 第3条 第3条第(1)款 第3条:定义 正文(换行符)(1)正文
第一章 标题 定义 第一章 第3条 第3条第(2)款 第3条:定义 正文(换行符)(2)正文

以下是 xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<DIVISION>
   <TITLE>
      <TI>
         <P>
            <HT TYPE="ITALIC">CHAPTER I</HT>
         </P>
      </TI>
      <STI>
         <P>
            <HT TYPE="BOLD">
               <HT TYPE="ITALIC">Chapther I Title</HT>
            </HT>
         </P>
      </STI>
   </TITLE>
   <ARTICLE IDENTIFIER="001">
      <TI.ART>Article 1</TI.ART>
      <STI.ART>Article 1 Title</STI.ART>
      <PARAG IDENTIFIER="001.001">
         <NO.PARAG>1.</NO.PARAG>
         <ALINEA>
            <P>Article 1 : Paragraph 1 Main Textt</P>
            <LIST TYPE="alpha">
               <ITEM>
                  <NP>
                     <NO.P>(a)</NO.P>
                     <TXT>(a) Text</TXT>
                     <P>
                        <LIST TYPE="roman">
                           <ITEM>
                              <NP>
                                 <NO.P>(i)</NO.P>
                                 <TXT>(i) text</TXT>
                              </NP>
                           </ITEM>
                           <ITEM>
                              <NP>
                                 <NO.P>(ii)</NO.P>
                                 <TXT>(ii) text</TXT>
                              </NP>
                           </ITEM>
                        </LIST>
                     </P>
                  </NP>
               </ITEM>
               <ITEM>
                  <NP>
                     <NO.P>(b)</NO.P>
                     <TXT>(b) text</TXT>
                  </NP>
               </ITEM>
            </LIST>
         </ALINEA>
      </PARAG>
      <PARAG IDENTIFIER="001.002">
         <NO.PARAG>2.</NO.PARAG>
         <ALINEA>Article 1 : Paragraph 2 Main Text</ALINEA>
      </PARAG>
   </ARTICLE>
   <ARTICLE IDENTIFIER="002">
      <TI.ART>Article 2</TI.ART>
      <STI.ART>Article 2 Title</STI.ART>
      <PARAG IDENTIFIER="002.001">
         <NO.PARAG>1.</NO.PARAG>
         <ALINEA>
            <P>Article 2 : Paragraph 1 Main Text</P>
            <LIST TYPE="alpha">
               <ITEM>
                  <NP>
                     <NO.P>(a)</NO.P>
                     <TXT>(a) text;</TXT>
                  </NP>
               </ITEM>
               <ITEM>
                  <NP>
                     <NO.P>(b)</NO.P>
                     <TXT>(b) text</TXT>
                  </NP>
               </ITEM>
            </LIST>
         </ALINEA>
      </PARAG>
      <PARAG IDENTIFIER="002.002">
         <NO.PARAG>2.</NO.PARAG>
         <ALINEA>Article 2 : Paragraph 2 Main Text.</ALINEA>
      </PARAG>
      <PARAG IDENTIFIER="002.003">
         <NO.PARAG>3.</NO.PARAG>
         <ALINEA>
            <P>Article 2 : Paragraph 3 Main Text</P>
            <LIST TYPE="alpha">
               <ITEM>
                  <NP>
                     <NO.P>(a)</NO.P>
                     <TXT>(a) text</TXT>
                  </NP>
               </ITEM>
            </LIST>
         </ALINEA>
      </PARAG>
      <PARAG IDENTIFIER="002.004">
         <NO.PARAG>4.</NO.PARAG>
         <ALINEA>Article 2 : Paragraph 4 Main Text</ALINEA>
      </PARAG>
   </ARTICLE>
   <ARTICLE IDENTIFIER="003">
      <TI.ART>Article 3</TI.ART>
      <STI.ART>Definitions</STI.ART>
      <ALINEA>
         <P>Article 3: Definitions Main Text</P>
         <LIST
            TYPE="ARAB">
            <ITEM>
               <NP>
                  <NO.P>(1)</NO.P>
                  <TXT>(1) text</TXT>
               </NP>
            </ITEM>
            <ITEM>
               <NP>
                  <NO.P>(2)</NO.P>
                  <TXT>(2) text</TXT>
               </NP>
            </ITEM>
         </LIST>
      </ALINEA>
   </ARTICLE>
</DIVISION>

以下是我当前的所有代码

import pandas as pd
from bs4 import BeautifulSoup
import unicodedata

from bs4 import BeautifulSoup


def determine_level(bs4_object):
    if "subsection" in bs4_object.find('TI').text.lower():
        c6 = unicodedata.normalize("NFKD", bs4_object.find("TI").text)
        if (bs4_object.find("STI") is not None):
            t6 = unicodedata.normalize("NFKD", bs4_object.find("STI").text)
            return 6, c6, t6
        return 6, c6, ''

    elif "sub-section" in bs4_object.find('TI').text.lower():
        c6 = unicodedata.normalize("NFKD", bs4_object.find("TI").text)
        if (bs4_object.find("STI") is not None):
            t6 = unicodedata.normalize("NFKD", bs4_object.find("STI").text)
            return 6, c6, t6
        return 6, c6, ''

    elif "section" in bs4_object.find('TI').text.lower():
        c5 = unicodedata.normalize("NFKD", bs4_object.find("TI").text)
        if (bs4_object.find("STI") is not None):
            t5 = unicodedata.normalize("NFKD", bs4_object.find("STI").text)
            return 5, c5, t5
        return 5, c5, ''

    elif "chapter" in bs4_object.find('TI').text.lower():
        c4 = unicodedata.normalize("NFKD", bs4_object.find("TI").text)
        if (bs4_object.find("STI") is not None):
            t4 = unicodedata.normalize("NFKD", bs4_object.find("STI").text)
            return 4, c4, t4
        return 4, c4, ''

    elif "title" in bs4_object.find('TI').text.lower():
        c3 = unicodedata.normalize("NFKD", bs4_object.find("TI").text)
        if (bs4_object.find("STI") is not None):
            t3 = unicodedata.normalize("NFKD", bs4_object.find("STI").text)
            return 3, c3, t3
        return 3, c3, ''

    elif "part" in bs4_object.find('TI').text.lower():
        c2 = unicodedata.normalize("NFKD", bs4_object.find("TI").text)
        if (bs4_object.find("STI") is not None):
            t2 = unicodedata.normalize("NFKD", bs4_object.find("STI").text)
            return 2, c2, t2
        return 2, c2, ''
    else:
        "Cannot be determined"


def scrape_file():

    #   Reading data from the xml file
    with open('./info.xml', 'r') as f:
        data = f.read()

    info_data = BeautifulSoup(data, 'xml')

    articles = info_data.find_all("ARTICLE")
    lst = []

    for index, article in enumerate(articles):

        c2 = None
        t2 = None
        c3 = None
        t3 = None
        c4 = None
        t4 = None
        c5 = None
        t5 = None
        c6 = None
        t6 = None
        t7 = None
        c7 = None
        c8 = None

        # Article level 7
        c7 = unicodedata.normalize("NFKD", article.find("TI.ART").text)
        if article.find("STI.ART"):
            t7 = unicodedata.normalize("NFKD", article.find("STI.ART").text)

        # Determining the number and levels of parents
        article_parents = article.findParents('DIVISION')
        for parent in reversed(article_parents):
            level_determined = determine_level(parent)

            if level_determined is None:
                continue

            elif 2 == level_determined[0]:
                c2 = level_determined[1].title()
                t2 = level_determined[2].capitalize()

            elif 3 == level_determined[0]:
                c3 = level_determined[1].split(" ")[0].capitalize() + ' ' + \
                    level_determined[1].split(" ")[1]
                citations = (c2, c3)
                c3 = ', '.join(str(citation)
                               for citation in citations if citation is not None)
                t3 = level_determined[2].capitalize()

            elif 4 == level_determined[0]:
                c4 = level_determined[1].split(" ")[0].capitalize() + ' ' + \
                    level_determined[1].split(" ")[1]
                citations = (c3, c4)
                c4 = ', '.join(str(citation)
                               for citation in citations if citation is not None)
                t4 = level_determined[2].capitalize()

            elif 5 == level_determined[0]:
                c5 = level_determined[1].split(" ")[0].capitalize() + ' ' + \
                    level_determined[1].split(" ")[1]
                citations = (c4, c5)
                c5 = ', '.join(str(citation)
                               for citation in citations if citation is not None)
                t5 = level_determined[2].capitalize()

            elif 6 == level_determined[0]:
                c6 = level_determined[1].split(" ")[0].capitalize() + ' ' + \
                    level_determined[1].split(" ")[1]
                citations = (c5, c6)
                c6 = ', '.join(str(citation)
                               for citation in citations if citation is not None)
                t6 = level_determined[2].capitalize()

        # Rest of text
        article_text = ""
        alinea = article.find_all("ALINEA")
        
        #I believe the problem starts here 
        
        for index, p in enumerate(alinea, 1):
            txt = p.find_all("TXT")
            if p.find_previous_sibling():
                # Captures Alinea text of parent
                if p.find_previous_sibling().name == "NO.PARAG":
                    # Used for main text of paragraph
                    if p.contents[0].name == "P":
                        article_text = p.contents[0].text
                        c8 = c7 + '.' + p.find_previous_sibling().text.replace('.', '')
                        parent_citation = c8
                # used for definitions -- do not have a "NO.PARAG" before ALINEA
                article_text = p.text
        txt = p.find_all("TXT")
        for index, t in enumerate(txt, 1):
            if p.find_previous_sibling():
                if t.find_previous_sibling().name == 'NO.P':
                    citation_level_8 = parent_citation + p.find_previous_sibling().text
                    article_text = t.text

        lst.append(
            [c2, t2, c3, t3, c4, t4,
             c5, t5, c6, t6, t7,
             c7, c8, article_text])
    df = pd.DataFrame.from_records(
        lst, columns=["T2", "T3", "T4","T5", "T6", "T7", 
                      "C2", "C3", "C4", "C5", "C6", "C7","C8", "Text"]
    )

提前感谢您的任何帮助/指导

python xml 解析 beautifulsoup

评论

0赞 Barry the Platipus 7/14/2023
您要抓取的网址是什么?
0赞 pyj 7/14/2023
我不是从 URL 抓取。我正在从上面提供的 xml 文件中报废它。我正在阅读文件。

答:

0赞 aspen l 7/14/2023 #1

我用这个,pip install lxml Lib:https://lxml.de/xpathxslt.html 我的示例,它解析所有级别,只需过滤掉您不需要的东西,快速示例:https://github.com/spawnmarvel/test/blob/main/xmlwalk.jpg

这里的快速例子是必须改进。

# pip install lxml
from lxml import etree as et

def xml_to_dict(xmlfile):
    msg = {}
    count = 1
    with open(xmlfile) as r:
        xml = r.read()
    # print(type(xml))
    parser = et.XMLParser(recover=True, collect_ids=True)
    root = et.fromstring(bytes(xml, encoding="utf-8"), parser)
    x = root.xpath("//*")# get all
    #x = root.xpath("//*[@id]")# get all
    for y in x:
        key = et.QName(y).localname
        print(key)
        value = y.text
        print(value)
        i = y.attrib.get("id")
        print(i)
        # the rest is for making a dict and using it later if needed
        if key in msg:
            key = key + str(count)
            count += 1
        tmp = {key:value}
        msg.update(tmp)
    for k, v in msg.items():
        print(k, ":", v)

#call
xml_to_dict("test.xml")

Xml 示例文件

{<?xml version="1.0"?>
<company>
    <name>Prod</name>
    <sys id="1001">
        <name>sr1</name>
        <task>dest</task>
    </sys>
    <sys id="1002">
        <name>sr2</name>
        <task>dest</task>
    </sys>
    <sys id="1003">
        <name>sr3</name>
        <task>source</task>
    </sys>
</company>}
0赞 Parfait 7/14/2023 #2

考虑一下 XSLT,这是一种用于转换 XML 文件的特殊用途语言,可用于为 Pandas 表格结构生成更平坦的版本。使用第三方库(底层解析器),Python 可以运行 XSLT 1.0 脚本。lxml'xml'BeautifulSoup

使用解析器,pandas.read_xml在解析为数据帧之前支持 XSLT 转换,所有这些都适用于行代码!lxml

XSLT(另存为 .xsl 文件,一种特殊的 .xml 文件)

  • 将节点解析到最低级别并向下复制祖先,跨节点(即行)重复值。
  • 重点介绍三个部分:带子节点、不带子节点、所有节点。ALINEAPALINEAPTXT
  • 使用(类似于 Python 方法)来避免重复代码。call-templatedef
  • 使用具有条件逻辑的多个变量来连接值。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template name="get_titles">
      <xsl:param name="curr_node"/>
       <xsl:variable name="sect1">
         <xsl:if test="preceding-sibling::NO.PARAG">
             <xsl:value-of select="concat('.', translate(preceding-sibling::NO.PARAG, '.', ''))"/>
         </xsl:if>
       </xsl:variable>
       
       <xsl:variable name="sect2">
         <xsl:if test="ancestor::PARAG/NO.PARAG and name()='TXT'">
             <xsl:value-of select="concat('.', translate(ancestor::PARAG/NO.PARAG, '.', ''))"/>
         </xsl:if>
       </xsl:variable>
       
       <xsl:variable name="sect3">
         <xsl:if test="name()='TXT'">
           <xsl:value-of select="preceding-sibling::NO.P"/>
         </xsl:if>
       </xsl:variable>
       
      <Chapter_Title><xsl:value-of select="ancestor::DIVISION/TITLE/TI"/></Chapter_Title>
      <Chapter_Subtitle><xsl:value-of select="ancestor::DIVISION/TITLE/STI"/></Chapter_Subtitle>
      <Article_Title><xsl:value-of select="ancestor::ARTICLE/TI.ART"/></Article_Title>
      <Article_Subtitle><xsl:value-of select="ancestor::ARTICLE/STI.ART"/></Article_Subtitle>
      <Aline>
        <xsl:value-of select="concat(ancestor::ARTICLE/TI.ART, $sect1, $sect2,  $sect3)"/>
      </Aline>
    </xsl:template>

    <xsl:template match="/*">
     <xsl:copy>
       <xsl:apply-templates select="descendant::ALINEA[not(P)]|descendant::ALINEA[P]|descendant::TXT"/>
     </xsl:copy>
    </xsl:template>
    
    <xsl:template match="ALINEA[P]">
      <row>
        <xsl:call-template name="get_titles"/>
        <TEXT><xsl:value-of select="P"/></TEXT>
      </row>
    </xsl:template>
    
    <xsl:template match="ALINEA[not(P)]">
      <row>
        <xsl:call-template name="get_titles"/>
        <TEXT><xsl:value-of select="text()"/></TEXT>
      </row>
    </xsl:template>
    
    <xsl:template match="TXT">
     <TEXT>
       <xsl:variable name="list">
         <xsl:if test="ancestor::LIST[name(..) != 'ALINEA']">
           <xsl:value-of select="concat(ancestor::LIST/preceding-sibling::P, ' (line break) ', TXT)"/>
         </xsl:if>
       </xsl:variable>
       
       <xsl:call-template name="get_titles"/>
       <TEXT><xsl:value-of select="concat($list, text())"/></TEXT>
     </TEXT>
    </xsl:template>
    
</xsl:stylesheet>

Online Demo

Python(一行代码!

article_df = pd.read_xml("myInput.xml", stylesheet="myStyle.xsl")