还是之前的字符串作为栗子:
html_doc = """
The Dormouse's story
The Dormouse's story
Once upon a time there were three little sisters; and their names were
Elsie,
Lacie and
Tillie;
and they lived at the bottom of a well.
...
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
通过这段例子来演示怎样从文档的一段内容找到另一段内容
子节点
一个Tag可能包含多个字符串或其他的Tag,这些都是这个Tag的子节点。Beautiful Soup提供了许多操作和遍历子节点的属性.
注意: Beautiful Soup中字符串节点不支持这些属性,因为字符串没有子节点
tag的名字
操作文档树最简单的方法就是告诉它你想获取的tag的name.如果想获取
标签,只要用 soup.head :>>> soup.head
The Dormouse's story
>>> soup.title
The Dormouse's story
这是个获取tag的小窍门,可以在文档树的tag中多次调用这个方法.下面的代码可以获取
标签中的第一个标签:>>> soup.body.b
The Dormouse's story
通过点取属性的方式只能获得当前名字的第一个tag:
>>> soup.a
Elsie
如果想要得到所有的标签,或是通过名字得到比一个tag更多的内容的时候,就需要用到 Searching the tree 中描述的方法,比如: find_all()
>>> soup.find_all('a')
[Elsie, Lacie, Tillie]
.contents 和 .children
tag的.contents属可以将tag的子节点以列表的方式输出:
>>> head_tag = soup.head
>>> head_tag
The Dormouse's story
>>> head_tag.contents
[The Dormouse's story ]
>>> title_tag = head_tag.contents[0]
>>> title_tag
The Dormouse's story
>>> title_tag .contents
["The Dormouse's story"]
BeautifulSoup 对象本身一定会包含子节点,也就是说标签也是 BeautifulSoup 对象的子节点:
len(soup.contents)
# 1
soup.contents[0].name
# u'html'
这里,我以字符串的形式操作,是有空格存在的。
字符串没有 .contents属性,因为字符串没有子节点:
>>> text = title_tag.contents[0]
>>> text.contents
Traceback (most recent call last):
File "", line 1, in
text.contents
File "/usr/local/lib/python3.5/site-packages/bs4/element.py", line 730, in __getattr__
self.__class__.__name__, attr))
AttributeError: 'NavigableString' object has no attribute 'contents'
通过tag的.children生成器,可以对tag的子节点进行循环:
>>> for child in title_tag.children:
... print(child)
...
...
...
The Dormouse's story
.descendants
.contents 和 .children 属性仅包含tag的直接子节点.例如,
标签只有一个直接子节点>>> head_tag.contents
[The Dormouse's story ]
但是
>>> for child in head_tag.descendants:
... print(child)
...
The Dormouse's story
The Dormouse's story
上面的例子中,
标签只有一个子节点,但是有2个子孙节点:节点和的子节点, BeautifulSoup 有一个直接子节点(节点),却有很多子孙节点:len(list(soup.children))
# 1
len(list(soup.descendants))
# 25
这里的还是和用字符串的不一样,还是复制了文档。
.string
如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点:
>>> title_tag.string
"The Dormouse's story"
如果一个tag仅有一个子节点,那么这个tag也可以使用 .string 方法,输出结果与当前唯一子节点的 .string 结果相同:
>>> head_tag.contents
[The Dormouse's story ]
>>> head_tag.string
"The Dormouse's story"
如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None :
>>> print(soup.html.string)
None
.strings 和 stripped_strings
如果tag中包含多个字符串,可以使用 .strings来循环获取:
>>> for string in soup.strings:
... print(repr(string))
...
'\n'
"The Dormouse's story"
'\n'
'\n'
"The Dormouse's story"
'\n'
'Once upon a time there were three little sisters; and their names were\n'
'Elsie'
',\n'
'Lacie'
' and\n'
'Tillie'
';\nand they lived at the bottom of a well.'
'\n'
'...'
'\n'
注意:看到了吧,多了两个换行符和文档相比
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容:
>>> for string in soup.stripped_strings:
... print(repr(string))
...
...
"The Dormouse's story"
"The Dormouse's story"
'Once upon a time there were three little sisters; and their names were'
'Elsie'
','
'Lacie'
'and'
'Tillie'
';\nand they lived at the bottom of a well.'
'...'
全部是空格的行会被忽略掉,段首和段末的空白会被删除
父节点
在文档树中每个tag或字符串都有父节点:即被包含在某个tag中
.parent
通过 .parent 属性来获取某个元素的父节点.在文档中,
标签是>>> title_tag = soup.title
>>> title_tag
The Dormouse's story
>>> title_tag .parent
The Dormouse's story
文档title的字符串也有父节点:
>>> title_tag.string.parent
The Dormouse's story
文档的顶层节点比如的父节点是 BeautifulSoup 对象:
>>> html_tag = soup.html
>>> type(html_tag.parent)
BeautifulSoup 对象的 .parent 是None:
>>> print(soup.parent)
None
.parents
通过元素的 .parents 属性可以递归得到元素的所有父辈节点,下面的例子使用了 .parents 方法遍历了标签到根节点的所有节点.
>>> link = soup.a
>>> link
Elsie
>>> for parent in link.parents:
... if parent is None:
... print(parent)
... else:
... print(parent.name)
...
p
body
html
[document]
兄弟节点
看一段简单的栗子:
>>> sibling_soup = BeautifulSoup("text1text2 ")
>>> print(sibling_soup.prettify())
text1
text2
因为标签和
.next_sibling 和 .previous_sibling
在文档树中,使用 .next_sibling 和 .previous_sibling 属性来查询兄弟节点:
>>> sibling_soup.b.next_sibling
text2
>>> sibling_soup.c.previous_sibling
text1
标签有 .next_sibling 属性,但是没有 .previous_sibling 属性,因为标签在同级节点中是第一个.同理,
>>> print(sibling_soup.b.previous_sibling)
None
>>> print(sibling_soup.c.next_sibling)
None
例子中的字符串“text1”和“text2”不是兄弟节点,因为它们的父节点不同:
>>> sibling_soup.b.string
'text1'
>>> print(sibling_soup.b.string.next_sibling)
None
实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白. 看看看文档:
Elsie
Lacie
Tillie
如果以为第一个标签的 .next_sibling 结果是第二个标签,那就错了,真实结果是第一个标签和第二个标签之间的顿号和换行符:
>>> link = soup.a
>>> link
Elsie
>>> link.next_sibling
',\n'
>>> link.next_sibling.next_sibling
Lacie
.next_siblings 和 .previous_siblings
通过 .next_siblings 和 .previous_siblings 属性可以对当前节点的兄弟节点迭代输出:
>>> for sibling in soup.a.next_siblings:
... print(repr(sibling))
...
...
...
',\n'
Lacie
' and\n'
Tillie
';\nand they lived at the bottom of a well.'
>>> for sibling in soup.find(id="link3").previous_siblings:
... print(repr(sibling))
...
...
' and\n'
Lacie
',\n'
Elsie
'Once upon a time there were three little sisters; and their names were\n'
回退和前进
看一下文档:
The Dormouse's story
The Dormouse's story
HTML解析器把这段字符串转换成一连串的事件: “打开标签”,”打开一个
标签”,”打开一个标签”,等等.Beautiful Soup提供了重现解析器初始化过程的方法.
.next_element 和 .previous_element
.next_element 属性指向解析过程中下一个被解析的对象(字符串或tag),结果可能与 .next_sibling 相同,但通常是不一样的.
这是“爱丽丝”文档中最后一个标签,它的 .next_sibling
结果是一个字符串,当前的解析过程因为遇到了标签而中断了:
>>> last_a_tag = soup.find("a",id="link3")
>>> last_a_tag
Tillie
>>> last_a_tag.next_sibling
';\nand they lived at the bottom of a well.'
但这个标签的 .next_element 属性结果是在标签被解析之后的解析内容,不是标签后的句子部分,应该是字符串”Tillie”:
>>> last_a_tag.next_element
'Tillie'
这是因为在原始文档中,字符串“Tillie” 在分号前出现,解析器先进入标签,然后是字符串“Tillie”,然后关闭标签,然后是分号和剩余部分.分号与标签在同一层级,但是字符串“Tillie”会被先解析.
.previous_element 属性刚好与 .next_element 相反,它指向当前被解析的对象的前一个解析对象:
last_a_tag.previous_element
# u' and\n'
last_a_tag.previous_element.next_element
# Tillie
.next_elements 和 .previous_elements
通过 .next_elements 和 .previous_elements 的迭代器就可以向前或向后访问文档的解析内容,就好像文档正在被解析一样:
>>> for element in last_a_tag.next_elements:
... print(repr(element))
...
...
'Tillie'
';\nand they lived at the bottom of a well.'
'\n'
...
'...'
'\n'