BeautifulSoup `find_all` generator

¿Hay alguna manera de convertir find_all en un generador más eficiente en memoria? Por ejemplo:

Dado:

 soup = BeautifulSoup(content, "html.parser") return soup.find_all('item') 

Me gustaría utilizar en su lugar:

 soup = BeautifulSoup(content, "html.parser") while True: yield soup.next_item_generator() 

( StopIteration entrega correcta de la excepción final de StopIteration )

Hay algunos generadores incorporados, pero no para obtener el siguiente resultado en un hallazgo. find devuelve sólo el primer artículo. Con miles de elementos, find_all absorbe mucha memoria. Para 5792 artículos, veo un pico de poco más de 1 GB de RAM.

Soy consciente de que hay analizadores más eficientes, como lxml, que pueden lograr esto. Supongamos que existen otras restricciones comerciales que me impiden utilizar cualquier otra cosa.

¿Cómo puedo convertir find_all en un generador para iterar de una manera más eficiente en memoria ?

No hay un generador de “búsqueda” en BeautifulSoup , por lo que sé, pero podemos combinar el uso del generador SoupStrainer y .children .

Imaginemos que tenemos este ejemplo de HTML:

 
Item 1 Item 2 Item 3 Item 4 Item 5

de la cual necesitamos obtener el texto de todos los nodos de item .

Podemos usar el SoupStrainer para analizar solo las tags de los item y luego iterar sobre el generador de .children y obtener los textos:

 from bs4 import BeautifulSoup, SoupStrainer data = """ 
Item 1 Item 2 Item 3 Item 4 Item 5
""" parse_only = SoupStrainer('item') soup = BeautifulSoup(data, "html.parser", parse_only=parse_only) for item in soup.children: print(item.get_text())

Huellas dactilares:

 Item 1 Item 2 Item 3 Item 4 Item 5 

En otras palabras, la idea es cortar el árbol hasta las tags deseadas y usar uno de los generadores disponibles , como .children . También puede usar uno de estos generadores directamente y filtrar manualmente la etiqueta por nombre u otro criterio dentro del cuerpo del generador, por ejemplo, algo como:

 def generate_items(soup): for tag in soup.descendants: if tag.name == "item": yield tag.get_text() 

Los .descendants generan los elementos hijos de forma recursiva, mientras que .children solo consideraría hijos directos de un nodo.

El método más simple es usar find_next :

 soup = BeautifulSoup(content, "html.parser") def find_iter(tagname): tag = soup.find(tagname) while tag is not None: yield tag tag = tag.find_next(tagname) 

Documento :

Le di a los generadores nombres compatibles con PEP 8 y ​​los transformé en propiedades:

 childGenerator() -> children nextGenerator() -> next_elements nextSiblingGenerator() -> next_siblings previousGenerator() -> previous_elements previousSiblingGenerator() -> previous_siblings recursiveChildGenerator() -> descendants parentGenerator() -> parents 

Hay un capítulo en el documento llamado Generadores , puedes leerlo.

SoupStrainer solo analizará la parte de html, puede ahorrar memoria, pero solo excluye la etiqueta irrelevante, si html tiene los sonidos de etiqueta que desea, resultará en el mismo problema de memoria.