BS4 'element' 究竟是什么,元素是如何计算的,由哪个解析器来决定?明显糊涂
What exactly is a BS4 'element', how are elements counted, which parser gets to decide? Obviously confused
我现在对我自以为理解的东西感到困惑,但事实证明我一直在接受 g运行ted。
经常遇到这种类型的 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>
那么这一切背后的理念是什么? 'fault'是谁的?是否有 'right' 或 'wrong' 答案?而且,实际上,我应该只虔诚地遵循一个解析器,还是每个解析器都有时间和地点?
抱歉问题太长了。
首先,根对象,在您的例子中,soup
变量,是一个 BeautifulSoup
对象。你可以把它想象成浏览器中的 document
对象。在 BeautifulSoup 中,BeautifulSoup
对象派生自 Element
对象,但实际上并不是 "element" 本身,它更像是文档。
当您在元素(或 BeautifulSoup 对象)上调用 len
时,您将获得对象的 contents
成员中的节点数。这可以包含注释、文档处理语句、文本节点、元素节点等
一个格式良好的文档应该有一个根元素,但是评论和文档处理语句在根级别也是可以的。在您的情况下,没有注释也没有处理语句,我通常期望长度为 1.
lxml
和 html5lib
尝试确保你有一个格式正确的文档,如果它发现你有多个根元素,他们会把它包装在 html
和 body
标签并给你一个单一的根元素。但是,如前所述,如果您的文档已经具有适当的根 html
元素并且在根级别也有注释或处理语句,则长度可能 > 1。根据解析器的不同,他们可能会操纵其他内容以遵守他们在提供奇怪的格式错误 HTML.
时也执行的任何规则。
另一方面。 html.parser
很宽容。它不会尝试纠正您正在做的事情,而只是按原样解析事情。在您的例子中,它 returns 是一个奇怪的文档,在根级别有多个文本节点,在根级别有多个 <p>
元素。因此,当您在 soup
上调用 length 时,您会得到一个比 1 大得多的值。
一般。 BeautifulSoup 返回的初始元素是 BeautifulSoup
对象。它可能包含 Element
个节点或 NaviagableString
个节点(文本),它们可以是各种子类型,具体取决于它们是评论、文档 decleration、CDATA 还是其他处理语句。 NaviagableStrings
(和相关子类型)不是 Element
节点,但通常包含在 Element
或 BeautifulSoup
对象的内容中。
取决于您是否喜欢宽大、速度、HTML5 正确性、XML 支持等,它可能会影响您希望使用哪个解析器。此外,您有时可能希望针对非常具体的用例使用其他解析器。
我现在对我自以为理解的东西感到困惑,但事实证明我一直在接受 g运行ted。
经常遇到这种类型的 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>
那么这一切背后的理念是什么? 'fault'是谁的?是否有 'right' 或 'wrong' 答案?而且,实际上,我应该只虔诚地遵循一个解析器,还是每个解析器都有时间和地点?
抱歉问题太长了。
首先,根对象,在您的例子中,soup
变量,是一个 BeautifulSoup
对象。你可以把它想象成浏览器中的 document
对象。在 BeautifulSoup 中,BeautifulSoup
对象派生自 Element
对象,但实际上并不是 "element" 本身,它更像是文档。
当您在元素(或 BeautifulSoup 对象)上调用 len
时,您将获得对象的 contents
成员中的节点数。这可以包含注释、文档处理语句、文本节点、元素节点等
一个格式良好的文档应该有一个根元素,但是评论和文档处理语句在根级别也是可以的。在您的情况下,没有注释也没有处理语句,我通常期望长度为 1.
lxml
和 html5lib
尝试确保你有一个格式正确的文档,如果它发现你有多个根元素,他们会把它包装在 html
和 body
标签并给你一个单一的根元素。但是,如前所述,如果您的文档已经具有适当的根 html
元素并且在根级别也有注释或处理语句,则长度可能 > 1。根据解析器的不同,他们可能会操纵其他内容以遵守他们在提供奇怪的格式错误 HTML.
另一方面。 html.parser
很宽容。它不会尝试纠正您正在做的事情,而只是按原样解析事情。在您的例子中,它 returns 是一个奇怪的文档,在根级别有多个文本节点,在根级别有多个 <p>
元素。因此,当您在 soup
上调用 length 时,您会得到一个比 1 大得多的值。
一般。 BeautifulSoup 返回的初始元素是 BeautifulSoup
对象。它可能包含 Element
个节点或 NaviagableString
个节点(文本),它们可以是各种子类型,具体取决于它们是评论、文档 decleration、CDATA 还是其他处理语句。 NaviagableStrings
(和相关子类型)不是 Element
节点,但通常包含在 Element
或 BeautifulSoup
对象的内容中。
取决于您是否喜欢宽大、速度、HTML5 正确性、XML 支持等,它可能会影响您希望使用哪个解析器。此外,您有时可能希望针对非常具体的用例使用其他解析器。