
3.5.2 提取数据
在3.5.1节,我们介绍了Beautiful Soup库的基本使用方法,本节我们将介绍节点选择、关联选择以及方法选择器;注意,这里将HTML代码另存为在同目录下的test.html文件。
1.节点选择
直接调用节点的名称就可以选择节点元素,再调用string属性就可以得到节点内的文本了,这种选择方式速度非常快。如果单个节点结构层次非常清晰,则可以选用这种方式来解析。
(1)获取文本值
通过调用String属性可以得到节点内各标签的文本内容,如以下示例所示。
【例3-40】获取string实例
1 from bs4 import BeautifulSoup 2 soup = BeautifulSoup(open("test.html"),"lxml") 3 print(soup.title) 4 print(type(soup.title)) 5 print(soup.title.string) 6 print(soup.p.string)
首先打印输出title节点的选择结果,输出结果正是title节点加里面的文字内容。接着输出它的类型,是bs4.element.Tag类型,这是Beautiful Soup库中一个重要的数据结构。经过选择器选择后,选择结果都是这种Tag类型。Tag具有一些属性,比如string属性,调用该属性可以得到节点的文本内容,所以接下来的输出结果正是节点的文本内容。
运行结果如下:
<title>The Dormouse's story</title> <class 'bs4.element.Tag'> The Dormouse's story The Dormouse's story
如果想通过string获取多个内容,只需要遍历获取,比如下面的例子:
for string in soup.strings: print(repr(string))
输出结果如下:
"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' ',\n' 'Lacie' ' and\n' 'Tillie' ';\nand they lived at the bottom of a well.' '\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' ',' 'Lacie' 'and' 'Tillie' ';\nand they lived at the bottom of a well.' '...'
(2)获取名称
我们可以利用name属性获取节点的名称。这里还是以上面的文本为例,选取p节点,然后调用name属性就可以得到节点名称:
print(soup.p.name)
运行结果如下:
p
(3)获取属性
HTML中的节点有不同的属性,例如该代码里的p节点具有class和name属性,可以使用attrs获取该节点的属性。
print(soup.p.attrs)
运行结果如下:
{'class': ['title'], 'name': 'dromouse'}
可以看到返回的结果是字典类型,我们只需通过attrs['name']即可获取name的属性值。
2.关联选择
使用Python爬虫库Beautiful Soup遍历文档树并对标签进行操作,Beautiful Soup提供了许多操作和遍历子节点的属性。一个标签可能包含多个字符串或者其他标签,这些都是这个标签的子节点。很多时候我们无法直接定位到某个元素,因此可以先定位它的父元素,通过父元素来找子元素就比较容易,如图3-4所示。
图3-4 父节点与子节点
(1)子节点和子孙节点
Tag的.content属性可以将Tag的子节点以列表的方式输出:
print (soup.head.contents)
运行结果如下:
[<title>The Dormouse's story</title>]
输出方式为列表,我们可以用列表索引来获取它的某一个元素:
print (soup.head.contents[0])
运行结果如下:
<title>The Dormouse's story</title>
同样,我们可以调用.children属性得到相应的结果,先看下面的代码:
print (soup.head.children)
运行结果如下:
<list_iterator object at 0x000001BE3F4CB7F0>
它返回的不是一个list,不过我们可以通过遍历来获取所有子节点。我们打印输出.children,可以发现它是一个list生成器对象,于是我们可以遍历输出里面的内容:
for item in soup.body.children: print (item)
运行结果如下:
<p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p>
(2)所有子节点
.contents和.children属性仅包含Tag的直接子节点,.descendants属性则可以对所有Tag的子孙节点进行递归循环,和.children类似,要获取其中的内容,我们需要对其进行遍历:
for item in soup.descendants: print (item)
查看运行结果,可以发现,所有的节点都被打印出来了:
<html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p></body></html> <head><title>The Dormouse's story</title></head> <title>The Dormouse's story</title> The Dormouse's story <body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p></body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <b>The Dormouse's story</b> The Dormouse's story <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a> Elsie <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> Lacie and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a> Tillie; and they lived at the bottom of a well. <p class="story">...</p> ...
(3)父节点和祖先节点
如果要获取某个节点元素的父节点,可以调用.parent属性,依旧使用上文的HTML代码,具体例子如下:
p = soup.p print (p.parent)
运行结果如下:
<body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p></body>
这里我们选择的是第一个p节点的父节点元素。很明显,它的父节点是body节点,输出结果便是body节点及其内部的内容。
需要注意的是,这里输出的仅是p节点的直接父节点,而没有再向外寻找父节点的祖先节点。如果想获取所有的祖先节点,可以调用.parents属性:
content = soup.p for parent in content.parents: print (parent)
(4)兄弟节点和全部兄弟节点
兄弟节点可以理解为和本节点处在统一级的节点,.next_sibling属性获取了该节点的下一个兄弟节点,.previous_sibling属性获取了该节点的上一个兄弟节点,如果节点不存在,则返回None。
注意,实际文档中的Tag的.next_sibling和.previous_sibling属性通常是字符串或空白,因为空白或者换行也可以被视作一个节点,所以得到的结果可能是空白或者换行。
print (soup.p.next_sibling) print (soup.p.prev_sibling) print (soup.p.next_sibling.next_sibling)
通过.next_siblings和.previous_siblings属性可以对当前节点的兄弟节点迭代输出:
for sibling in soup.a.next_siblings: print(repr(sibling))
输出结果如下:
',\n' <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> ' and\n' <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a> ';\nand they lived at the bottom of a well.'
需要注意的是,在节点选择中,如果返回结果是单个节点,那么可以直接调用string、attrs等属性来获得其文本和属性;而如果返回结果是多个节点的生成器,则可以转为列表后取出某个元素,然后再调用string、attrs等属性来获取其对应节点的文本和属性。
3.方法选择器
上文所介绍的节点选择方法是通过属性进行选择的,Beautiful Soup还为我们提供了搜索文档树的方法,比如find_all()和find()等,调用它们,然后传入相应的参数,就可以灵活搜索了。
(1)find_all(self,name=None,attrs={},recursive=True,text=None,limit=None,**kwargs)
find_all()方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件。
1)按照tag(标签)搜索。
# 直接搜索名为tagname的tag,如find_all('head') find_all(tagname) # 搜索在list中的tag,如find_all(['head', 'body']) find_all(list) # 搜索在dict中的tag,如find_all({'head':True, 'body':True}) find_all(dict) # 搜索符合正则的tag,如find_all(re.compile('^p'))搜索以p开头的tag find_all(re.compile('')) # 搜索函数返回结果为true的tag,如find_all(lambda name: if len(name) == 1)搜 索长度为1的tag find_all(lambda) # 搜索所有tag find_all(True)
2)按照attrs(属性)搜索。
# 寻找id属性为xxx的 find_all('id'='xxx') # 寻找id属性符合正则且algin属性为xxx的 find_all(attrs={'id':re.compile('xxx'), 'algin':'xxx'}) # 寻找有id属性但是没有algin属性的 find_all(attrs={'id':True, 'algin':None})
(2)find(name,attrs,recursive,text,**kwargs)
它与find_all()方法唯一的区别是,find_all()方法的返回结果是值包含一个元素的列表,而find()方法直接返回结果。这些参数与过滤器一样可以进行筛选处理。不同的参数过滤可以应用到以下情况:
1)查找标签,基于name参数;
2)查找文本,基于text参数;
3)基于正则表达式的查找;
4)查找标签的属性,基于attrs参数;
5)基于函数的查找。
(3)find_parents()、find_parent()
find_all()和find()只搜索当前节点的所有子节点,孙子节点find_parents()和find_parent()用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同,搜索文档包含的内容。
(4)find_next_siblings()、find_next_sibling()
这两个方法通过.next_siblings属性来对当前tag的所有后面解析的兄弟tag节点进行迭代,find_next_siblings()方法返回所有符合条件的后面的兄弟节点,而find_next_sibling()只返回符合条件的后面的第一个tag节点。
(5)find_previous_siblings()、find_previous_sibling()
这两个方法通过.previous_siblings属性来对当前tag的前面解析的兄弟tag节点进行迭代,find_previous_siblings()方法返回所有符合条件的前面的兄弟节点,find_previous_sibling()方法返回第一个符合条件的前面的兄弟节点。
(6)find_all_next()、find_next()
这两个方法通过.next_elements属性来对当前tag之后的tag和字符串进行迭代,find_all_next()方法返回所有符合条件的节点,find_next()方法返回第一个符合条件的节点。
(7)find_all_previous()、find_previous()
这两个方法通过.previous_elements属性来对当前tag之前的tag和字符串进行迭代,find_all_previous()方法返回所有符合条件的节点,find_previous()方法返回第一个符合条件的节点。
注意 以上方法参数的用法与find_all()完全相同,原理也均类似。