如何在没有非常慢的for循环的情况下迭代xpath子集?

2024-05-15 16:30:32 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试解析一个本地14MB的html文件

我的文件如下所示(不方便,因为它没有以有用的方式嵌套):

<html >
    <head>Title</head>
    <body>
        <p class="SECMAIN">
            <span class="ePub-B">\xc2\xa7 720 ILCS 5/10-8.1.</span>
        </p>
        <p class="INDENT-1”>(a) text</p>
        <p class="INDENT-1”>(b) text</p>
        <p class="INDENT-2”>(1) text</p>
        <p class="INDENT-2”>(2) text</p>
        <p class="SOURCE">(Source)</p>
        <p class="SECMAIN">
            <span class="ePub-B">\xc2\xa7 720 ILCS 5/10-9</span>
        </p>
        <p class="INDENT-1”>(a) something</p>
        <p class="SOURCE">(Source)</p>
        <p class="SECMAIN">
            <span class="ePub-B">\xc2\xa7 720 ILCS 5/10-10.</span>
       </p>
       <p class="INDENT-1”>(a) more text</p>
       <p class="SOURCE">(Source)</p>
    </body>
</html>

虽然我的代码在html文件的小样本(50KB)上可以按照需要即时工作,但它甚至不会开始整个文件的一个循环。我试过使用mac和windows电脑,它们的内存分别为4和8 Gig

我从阅读其他文章中了解到,涉及较大xml文件的循环非常慢,而且不符合Python,但我正在努力实现类似iterparse或列表理解的东西

我尝试使用基于Populating Python list using data obtained from lxml xpath command的列表理解,我也不确定如何继续这篇有趣的文章:python xml iterating over elements takes a lot of memory

这是我的代码中无法处理完整文件的部分

import lxml.html 
import cssselect 
import pandas as pd 

…

tree = lxml.html.fromstring(raw) 

laws = tree.cssselect('p.SECMAIN span.ePub-B') 

xpath_str = ''' 
    //p[@class="SECMAIN"][{i}]/
        following-sibling::p[contains(@class, "INDENT")]
            [count(.|//p[@class="SOURCE"][{i}]/
                        preceding-sibling::p[contains(@class, "INDENT")])
            = 
            count(//p[@class="SOURCE"][{i}]/
                        preceding-sibling::p[contains(@class, "INDENT")])
            ]
    '''

paragraphs_dict = {} 
paragraphs_dict['text'] = [] 
paragraphs_dict['n'] = [] 

# nested for loop:
for n in range(1, len(laws)+1): 
    law_paragraphs = tree.xpath(xpath_str.format(i = n)) # call xpath string
    for p in law_paragraphs: 
        paragraphs_dict['text'].append(p.text_content()) # store paragraph
        paragraphs_dict['n'].append(n)

输出应该给我一个具有等长数组的字典,这样我就可以知道每个段落('p')对应的是哪个法则('n')。目标是捕获类“INDENT”中位于类“SECMAIN”和“SOURCE”元素之间的所有元素,并记录它们遵循的SECMAIN

谢谢你的支持


Tags: 文件textsourcehtmlepubxpathdictclass
1条回答
网友
1楼 · 发布于 2024-05-15 16:30:32

考虑你的XPath表达式:对于每一个^ {CD1>},你将^ {}迭代到那个数,然后在{{CD3}}s上迭代两次,找到匹配的一个,然后检查前面所有的^ {< CD4>},并取其中的节点。即使有一些优化,有限状态自动机将有很多工作要做!它可能比二次型更糟糕(见注释)

我将对sax解析器使用更直接的方法

import xml.sax
import io

class MyContentHandler(xml.sax.ContentHandler):
    def __init__(self):
        self.n = 0
        self.d = {'text': [], 'n': []}
        self.in_indent = False

    def startElement(self, name, attributes):
        if name == "p" and attributes["class"] == "SECMAIN":
            self.n += 1 # next SECMAIN
        if name == "p" and attributes["class"].startswith("INDENT"):
            self.in_indent = True # mark that we are in an INDENT par
            self.cur = [] # to store chunks of text

    def endElement(self, name):
        if name == "p" and self.in_indent:
            self.in_indent = False # mark that we leave an INDENT par
            self.d['text'].append("".join(self.cur)) # append the INDENT text
            self.d['n'].append(self.n) # and the number

    def characters(self, data):
        # https://docs.python.org/3/library/xml.sax.handler.html#xml.sax.handler.ContentHandler.characters
        # "SAX parsers may return all contiguous character data in a single chunk, or they may split it into several chunks"
        if self.in_indent: # only if an INDENT par:
            self.cur.append(data) # store the chunks

parser = xml.sax.make_parser()
parser.setFeature(xml.sax.handler.feature_namespaces, 0)
handler = MyContentHandler()
parser.setContentHandler(handler)
parser.parse(io.StringIO(raw))

print(handler.d)
# {'text': ['(a) text', '(b) text', '(1) text', '(2) text', '(a) something', '(b) more text'], 'n': [1, 1, 1, 1, 2, 3]}

这应该比XPath版本快很多

相关问题 更多 >