Как я могу перебрать подмножества xpath без очень медленного для l oop? - PullRequest
1 голос
/ 05 марта 2020

Я пытаюсь проанализировать локальный файл размером 14 МБ 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 файла (50 КБ), он не будет начинаться даже с 1 oop всего файла. Я пытался использовать компьютеры ma c и windows с 4 и 8 гигабайтами оперативной памяти соответственно.

Я почерпнул из прочтения других постов циклы for, в которых файлы largi sh xml очень медленные и не pythoni c, но я изо всех сил пытаюсь реализовать что-то вроде iterparse или понимания списка.

Я пытался использовать понимание списка на основе Заполнение списка Python с использованием данных, полученных из команды l xml xpath , и я также не уверен, что делать с этим интересным сообщением : python xml перебор элементов занимает много памяти

Это часть моего кода, которая не может обработать полный файл.

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)

Вывод должен дать мне словарь с массивами равной длины, чтобы я мог сказать, какому закону ('n') соответствует каждый абзац ('p'). Цель состоит в том, чтобы захватить все элементы класса «INDENT», которые находятся между элементами класса «SECMAIN» и «SOURCE», и записать, за каким SECMAIN они следуют.

Спасибо за вашу поддержку.

1 Ответ

1 голос
/ 07 марта 2020

Рассмотрим выражение XPath: для каждого SECMAIN числа вы повторяете число SECMAIN с этим числом, затем дважды повторяете число SOURCE, чтобы найти соответствующее, а затем проверяете все предыдущие INDENT и возьмите узлы, которые находятся среди тех. Даже если есть некоторая оптимизация, автоматам с конечным состоянием будет много работы! Это может быть хуже, чем квадрати c (см. Комментарии).

Я бы использовал более прямой подход с синтаксическим анализатором саксофона.

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.

...