Что такое BS4 «элемент», как подсчитываются элементы, какой парсер принимает решение? Очевидно запутался - PullRequest
1 голос
/ 07 мая 2019

Я теперь смущен чем-то, что я думал, что понял, но оказывается, что я воспринимал как должное

Часто встречается такой тип цикла for:

from bs4 import BeautifulSoup as bs
mystring = 'some string'
soup = bs(mystring,'html.parser')
for elem in soup.find_all():
    [do something with elem]

На что я не обращал особого внимания, так это на самом деле elem, пока я не наткнулся на версию этой упрощенной строки:

mystring = 'opening text<p>text one<BR> text two.<br></p>\
<p align="right">text three<br/> text four.</p><p class="myclass">text five. </p>\
<p>text six <span style="some style">text seven</span></p>\
<p>text 8. <span style="some other style">text nine</span></p>closing text'

Я больше не уверен, какой результат я ожидал, но когда я запустил этот код:

counter = 1 #using 'normal' counting for simplification
for elem in soup.find_all():
    print('elem ',counter,elem)
    counter +=1

Вывод был:

elem  1 <p>text one<br/> text two.<br/></p>
elem  2 <br/>
elem  3 <br/>
elem  4 <p align="right">text three<br> text four.</br></p>
elem  5 <br> text four.</br>
elem  6 <p class="myclass">text five. </p>
elem  7 <p>text six <span style="some style">text seven</span></p>
elem  8 <span style="some style">text seven</span>
elem  9 <p>text 8. <span style="some other style">text nine</span></p>
elem  10 <span style="some other style">text nine</span>

Итак, bs4 + html.parser нашел 10 элементов в строке. Их выбор и презентация показались мне не интуитивно понятными (например, пропуск opening text и closing text). Мало того, выход print(len(soup)) оказался 7!

Так что просто чтобы убедиться, я поменял html.parser на lxml и html5lib. В обоих случаях print(len(soup)) было не только 1, но число elem с подскочило до 13! И, естественно, дополнительные элементы были разные. С 4-го elem до конца обе библиотеки были идентичны html.parser. Для первых трех, однако ...

С html5lib вы получите:

elem  1 <html><head></head><body>opening text<p>text one<br/> text two.<br/></p><p align="right">text three<br/> text four.</p><p class="myclass">text five. </p><p>text six <span style="some style">text seven</span></p><p>text 8. <span style="some other style">text nine</span></p>closing text</body></html>
elem  2 <head></head>
elem  3 <body>opening text<p>text one<br/> text two.<br/></p><p align="right">text three<br/> text four.</p><p class="myclass">text five. </p><p>text six <span style="some style">text seven</span></p><p>text 8. <span style="some other style">text nine</span></p>closing text</body>

С lxml, с другой стороны, вы получаете:

elem  1 <html><body><p>opening text</p><p>text one<br/> text two.<br/></p><p align="right">text three<br/> text four.</p><p class="myclass">text five. </p><p>text six <span style="some style">text seven</span></p><p>text 8. <span style="some other style">text nine</span></p>closing text</body></html>
elem  2 <body><p>opening text</p><p>text one<br/> text two.<br/></p><p align="right">text three<br/> text four.</p><p class="myclass">text five. </p><p>text six <span style="some style">text seven</span></p><p>text 8. <span style="some other style">text nine</span></p>closing text</body>
elem  3 <p>opening text</p>

Так в чем же заключается философия? Чья это вина? Есть ли «правильный» или «неправильный» ответ? И, собственно говоря, я должен просто неукоснительно следовать одному парсеру или есть время и место для каждого?

Извиняюсь за длину вопроса.

1 Ответ

2 голосов
/ 07 мая 2019

Во-первых, корневой объект, в вашем случае переменная soup, является объектом BeautifulSoup.Вы можете думать об этом как о document объекте в браузере.В BeautifulSoup объект BeautifulSoup является производным от объекта Element, но на самом деле он не является «элементом», он больше похож на документ.

Когда вы вызываете len дляэлемент (или объект BeautifulSoup), вы получите количество узлов в contents член объекта.Это может содержать комментарии, операторы обработки документов, текстовые узлы, узлы элементов и т. Д.

Хорошо сформированный документ должен иметь один корневой элемент, но комментарии и операторы обработки документов также подходят для корневого уровня.В вашем случае, без комментариев и операторов обработки, я бы обычно ожидал, что длина 1.

lxml и html5lib попытается убедиться, что у вас есть правильно сформированный документ, если он видит, что у вас естьнесколько корневых элементов, они обернут его в теги html и body и дадут вам один корневой элемент.Хотя, как упоминалось ранее, вы можете иметь длину> 1, если ваш документ уже имеет правильный корневой элемент html, а также имеет комментарии или операторы обработки на корневом уровне.В зависимости от синтаксического анализатора они могут манипулировать другим контентом, чтобы придерживаться любых правил, которые они также применяют, когда снабжены странным искаженным HTML.

С другой стороны.html.parser очень снисходительно.Он не пытается исправить то, что вы делаете, а просто анализирует вещи такими, какие они есть.В вашем случае он возвращает странный документ с несколькими текстовыми узлами на корневом уровне, а также с несколькими элементами <p> на корневом уровне.Поэтому, когда вы вызываете length для soup, вы получаете значение, намного превышающее 1.

В общем.Начальным элементом, возвращаемым BeautifulSoup, является объект BeautifulSoup.Он может содержать Element узлов или NaviagableString узлов (текст), которые могут быть различных подтипов, если это зависит от того, являются ли они комментарием, удалением документа, CDATA или другим оператором обработки.NaviagableStrings (и связанные подтипы) не являются Element узлами, но обычно содержатся в содержимом объекта Element или BeautifulSoup.

В зависимости от того, предпочитаете ли вы снисходительность, скорость, HTML5правильность, поддержка XML и т. д., это может повлиять на то, какой парсер вы хотите использовать.Кроме того, иногда вы можете использовать другие парсеры для очень специфических случаев.

...