2016-12-29 3 views
5

Есть ли способ превратить find_all в более эффективный генератор памяти? Например:Генератор BeautifulSoup `find_all`

Дано:

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

Я хотел бы использовать вместо:

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

(предположим, надлежащее вручение окончательного StopIteration исключения)

Есть некоторые генераторы, построенные в , но не дать следующий результат в поиске. find возвращает только первый элемент. С тысячами пунктов, find_all засасывает лот памяти. Для 5792 пунктов, я вижу всплеск чуть более 1 ГБ ОЗУ.

Мне хорошо известно, что есть более эффективные парсеры, такие как lxml, которые могут это сделать. Предположим, что существуют другие ограничения для бизнеса, которые мешают мне использовать что-либо еще.

Как я могу превратить find_all в генератор для итерации в более памяти с эффективным способом.

ответ

6

Там нет «найти» генератор в BeautifulSoup, от того, что я знаю, но мы можем сочетать использование SoupStrainer и .children generator.

Давайте представим, что мы имеем этот пример HTML:

<div> 
    <item>Item 1</item> 
    <item>Item 2</item> 
    <item>Item 3</item> 
    <item>Item 4</item> 
    <item>Item 5</item> 
</div> 

, из которого мы должны получить текст всех item узлов.

Мы можем использовать SoupStrainer разобрать только item теги, а затем перебрать .children генератора и получить тексты:

from bs4 import BeautifulSoup, SoupStrainer 

data = """ 
<div> 
    <item>Item 1</item> 
    <item>Item 2</item> 
    <item>Item 3</item> 
    <item>Item 4</item> 
    <item>Item 5</item> 
</div>""" 

parse_only = SoupStrainer('item') 
soup = BeautifulSoup(data, "html.parser", parse_only=parse_only) 
for item in soup.children: 
    print(item.get_text()) 

Печатает:

Item 1 
Item 2 
Item 3 
Item 4 
Item 5 

Другими словами, идея отрезать дерево до желаемых тегов и использовать one of the available generators, например .children. Вы также можете использовать один из этих генераторов напрямую и вручную фильтровать тег по имени или другим критериям внутри корпуса генератора, например. что-то вроде:

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

The .descendants генерирует дочерние элементы рекурсивно, а .children рассматривать лишь прямые ребенок узла.

+0

Красивые. Отличный способ взглянуть на проблему. –

+0

Очень приятное решение :) – Dekel

4

Самый простой способ заключается в использовании 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) 
+0

'find_next()' интересная идея! – alecxe

+0

@alecxe. Еще одна приятная вещь в том, что она позволяет начинать с любой точки документа. – ekhumoro

+0

Ницца, выглядит как замена для генератора «найти» для меня. Благодарю. – alecxe

0

Document:

Я дал генераторы PEP 8-совместимые имена, и превратили их в свойства:

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

Есть chapte r в Документе с именем Generators, вы можете прочитать его.

SoupStrainer только разобрать часть HTML, он может сохранить память, но только исключить ненужный тег, если HTML имеет thounds тега вы хотите, это приведет к тому же проблеме памяти.