您的位置:首页 > 理论基础 > 计算机网络

BeautifulSoup 4.4.0官方文档学习小结

2017-12-12 15:33 288 查看

0 引言

最近学习Python网络爬虫,用到一款实战利器:BeautifulSoup,学习了BeautifulSoup的官方文档,本文以BeautifulSoup 4.4.0官方文档为基础,参考了一些其他博客内容,叙述了BeautifulSoup在网络爬虫方面的应用方法,结合自己的体会对其进行了一点总结。

1 What:BeautifulSoup是什么

BeautifulSoup是用python写的一个HTML/XML的解析器,它能够通过你喜欢的转换器实现管用的文档导航、查找、修改文档的方式。

简单点来说,BeautifulSoup就是在进行爬虫项目时识别HTML代码段并处理的一种容器。

2 How:如何使用BeautifulSoup

2.1 对象的种类

BeautifulSoup将HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,这些对象可以归纳为4种:Tag(标签)、NavigableString(可遍历字符串)、BeautifulSoup(整个文档)、Comment(注释及特殊字符串)

2.1.1 Tag——标签对象

BeautifulSoup中的Tag和XML/HTML中的Tag用法相同,其方法和属性很多,待后续介绍,本节主要介绍两个最重要的属性:name和attributes。

2.1.1.1 name属性

name属性及Tag的标签名,如HTML中的b标签、div标签等。

获取标签名的方法:
tag.name


修改标签名的方法(直接赋值):
tag.name = 'new_name'


2.1.1.2 attributes属性

Tag的attributes属性即Tag中包含的各种属性,例如:

tag = BeautifulSoup('<b class = "boldest">attributes</b>','html.parser').b


其中class就是Tag的一个attributes属性。对于属性其操作方法与Python字典相同:

>>> tag['class']
['boldest']`


(注意:这里返回值为一个只有一个元素的列表,这是由于class属性为多值属性,也就是说一个Tag的class属性可以有多个不同的值。多值属性后文会介绍到。如果这里是id等单值属性,则返回结果为一个字符串。)

也可以通过
tag.attrs
来获取所有属性-值对,其返回值为

>>> tag.attrs
{'class': ['boldest']}


既然Tag的属性和字典操作方法相同,自然字典一样增删修改属性内容:

>>> tag['class'] = ['verybold','bolder']
>>> tag
<b class="verybold bolder"> attributes </b>


>>> tag['id'] = '1'
>>> tag
<b class="verybold bolder" id="1"> attributes </b>


>>> del tag['class']
>>> tag
<b id="1"> attributes </b>


2.1.2 NavigableString——可遍历字符串对象

BeautifulSoup中NavigableString字符串即被包含在一个标签对中的字符串内容,可用
tag.string
来获取其内容。

通常处理NavigableString时药先转换为Unicode,可以通过
string = unicode(tag.string)
来实现。

NavigableString不可编辑,但可以被替换,可以通过tag.string.replace_with(”)实现,更多具体操作方法待后文叙述。

2.1.3 BeautifulSoup——整个文档对象

BeautifulSoup大部分时间可以看作Tag对象,但是没有Tag对象的attributes属性。此外,对于BeautifulSoup对象使用
soup.name
属性时会返回[document]值。除以上两条外,几乎和Tag使用方式相同。

2.1.4 Comment——注释及特殊字符串对象

Comment对象是特殊的NavigableString对象,例如:

>>> markup = "<b><!--Hey, buddy, want to buy a used Parser:--></b>"
>>> soup = BeautifulSoup(markup,'html.parser')
>>> comment = soup.b.string
>>> type(comment)
<class 'bs4.element.Comment'>


>>> print(comment)
Hey, buddy, want to buy a used Parser:


>>> print(soup.b)
<b><!--Hey, buddy, want to buy a used Parser:--></b>


这里的内容仔细体会一下可以发现其中的细枝末节。

2.2 遍历文档树

2.2.1 子节点

每个Tag可能包含多个其他的Tag或者字符串,这些称谓Tag的子节点,下面介绍BeautifulSoup中操作和遍历子节点的属性,应该注意的是BeautifulSoup中的字符串节点没有子节点,因此下面一些属性是不支持的。

2.2.1.1 直接使用Tag的名字获取子节点

这里以一个例子进行介绍:

>>> text = "<head><title>The Dormouse's story</title><head>"
>>> soup = BeautifulSoup(text,'html.parser')
>>> soup.head
<head><title>The Dormouse's story</title><head></head></head>
>>> soup.title
<title>The Dormouse's story</title>
>>> soup.head.title
<title>The Dormouse's story</title>


使用
soup.head
获取head子节点,用
soup.title
soup.head.title
获取title子节点。

小结:直接用Tag的名字可获取所有子孙节点中的第一个,并返回字符串,其作用结果与
soup.find('tag_name')
相同。如果需要获取所有特定的子孙节点,则需要用
soup.find_all('tag_name')
,其返回值为一个列表:

>>> soup.find("title")
<title>The Dormouse's story</title>
>>> soup.find_all('title')
[<title>The Dormouse's story</title>]


2.2.1.2 使用.contents和.children获取直接子节

tag.contents
可以将Tag的所有子节点以列表形式输出,可以使用列表的操作方式操作
tag.contents
的内容,例如:

>>> tlist = soup.title.contents
>>> tlist
["The Dormouse's story"]
>>> tlist[0]
"The Dormouse's story"


字符串没有子节点,所以没有.contents属性。

2.2.1.3 使用.descendants获取子孙节点

tag.descendants
可以对所有tag的子孙节点进行递归循环,是一个生成器,可以使用生成器的方法进行操作:

>>> for tag in soup.descendants:
print(tag)

<head><title>The Dormouse's story</title></head>
<title>The Dormouse's story</title>
The Dormouse's story


2.2.1.4 使用.string、.strings和.stripped_strings获取字符串子节点

若一个tag仅有一个字符串子节点,使用
tag.string
可获取字符串内容,若一个tag含有多个字符串子节点,使用
tag.string
则会返回None,此时需要使用
tag.strings
生成器来进行遍历。而
tag.stripped_strings
则可以除去多余的空白行。

2.2.2 父节点

2.2.2.1 使用.parent属性获取父节点

tag.parent
可以获取tag的父节点标签,NavigableString也有parent节点,而BeautifulSoup由于表示整个文档,因此没有父节点,返回值为None。

2.2.2.2 使用.parents属性获取所有父辈节点

tag.parents
是一个生成器(到这里可以大致发现表示复数的大部分是生成器类型),可以对tag的所有父辈节点进行遍历,直至BeautifulSoup的父节点None停止。

2.2.3 兄弟节点

2.2.3.1 使用.next_sibling和.previous_sibling获取相邻兄弟节点

tag.next_sibling
可获取同一父节点下tag的下一个兄弟节点,最后一个tag返回None;

tag.previous_sibling
可获取同一父节点下tag的上一个兄弟节点,第一个tag返回None。

注意:兄弟节点一定要是同一个父节点下的亲兄弟,表兄弟、堂兄弟啥的都不能用兄弟节点搜出来。

2.2.3.2 使用.next_siblings和.previous_siblings获取所有兄弟节点

tag.next_siblings
生成器可遍历同一父节点下tag之后的所有兄弟节点,最后一个tag返回None;

tag.previous_siblings
生成器可遍历同一父节点下tag之前的所有兄弟节点,第一个tag返回None;

2.2.4 回退和前进

以一下一段HTML代码为例,介绍HTML解析器解析过程:

<html>
<head>
<title>
title_name
</title>
</head>
<p class = 'title'>
<b>
paragraph_name
</b>
</p>
</html>


其解析过程为:找到html标签—找到head标签—找到title标签—写入title_name字符串—找到p标签—写入paragraph_name字符串。这里的每一个步骤可以看做一个element解析对象,从这里也可以看出
tag.children
等生成器遍历的顺序,BeautifulSoup中所有的有关输出或者遍历顺序都可以从这里看出端倪。

需要注意的是BeautifulSoup对象的previous_element和next_element均为None。

2.2.4.1 使用.next_element和.previous_element获取相邻解析对象

tag.next_element
可获取tag的下一个解析对象;

tag.previous_element
可获取tag的上一个解析对象。

2.2.4.2 使用.next_elements和.previous_elements获取相邻解析对象

tag.next_elements
生成器可用于获取tag之后的所有解析对象;

tag.previous_elements
生成器可用于获取tag之前的所有解析对象。

2.3 搜索文档树

2.3.1 过滤器

2.3.1.1 字符串

字符串是最简单的过滤器,传入一个字符串参数即匹配与字符串完全相同的内容,如
soup.find_all('b')
则会输出soup中所有的b标签列表,至于这里为什么输出的是标签列表,待后文详述。

2.3.1.2 正则表达式

传入正则表达式作为参数,BeautifulSoup会通过正则匹配找出满足正则的内容,如
soup.find_all(re.compile('^b'))
会输出soup中所有的以b开头的标签列表,关于正则表达式,这里不作详细介绍。

2.3.1.3 列表

传入列表参数,BeautifulSoup会将与列表中任一元素匹配的内容返回,如
soup.find_all(['a','b'])
会输出soup中所有的a标签和b标签。

2.3.1.4 True

True可以匹配任何值,如
soup.find_all(True)
会输出所有的标签节点。

2.3.1.5 方法

在没有合适的过滤器的情况下可以定义一个方法,方法只接受一个元素参数,若方法返回True,则当前元素匹配被找到,否则返回False。例如:

>>> text = "<html><head><title class = 'title'>title_name</title></head><p class = 'title' id = '1'><b>paragraph_name</b></p></html>"
>>> soup = BeautifulSoup(text,'html.parser')
>>> def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
>>> soup.find_all(has_class_but_no_id)
[<title class="title">title_name</title>]


上述过程定义了一个搜寻含有class属性但不含有id属性的方法,其返回结果是一个列表。

方法可以简单看做一个接收BeautifulSoup对象,返回布尔值的函数,根据返回值判断是否搜索到满足方法的结果。

2.3.2 find_all()

find_all(name,attrs,recursive,string,**kwargs)

find_all()的返回值为一个列表。本节分别讨论find_all()的所有参数。

2.3.2.1 name参数

name参数用于查找所有名字为name的tag。

搜索name参数的值可以用任一种过滤器。

2.3.2.2 attrs参数

可以通过指定tag的属性名即属性值来查找tag,如:
soup.find_all(id='1')


对于class属性,由于class是python内置参数,故CSS属性搜索时需用class_替代,但是需要注意,查找所有包含id等其他属性的tag时,可以用
sou.find_all(id)
soup.find_all(id)= True
两种方式(注意两者输出结果不同),但是对于class属性,只能用
soup.find_all(class = True)
。如:

>>> soup.find_all(id)
[<html><head><title class="title">title_name</title></head><p class="title" id="1"><b>paragraph_name</b></p></html>, <head><title class="title">title_name</title></head>, <title class="title">title_name</title>, <p class="title" id="1"><b>paragraph_name</b></p>, <b>paragraph_name</b>]
>>> soup.find_all(class)
SyntaxError: invalid syntax
>>> soup.find_all(class_)
Traceback (most recent call last):
File "<pyshell#74>", line 1, in <module>
soup.find_all(class_)
NameError: name 'class_' is not defined
>>> soup.find_all(class = True)
SyntaxError: invalid syntax
>>> soup.find_all(class_ = True)
[<title class="title">title_name</title>, <p class="title" id="1"><b>paragraph_name</b></p>]
>>> soup.find_all(id = True)
[<p class="title" id="1"><b>paragraph_name</b></p>]


无论是用class还是用class_都会发生错误。我的理解是:由于class是python内置参数,无法识别为CSS属性;而只输入class_则因为没有上下文,python无法分辨出这是一个CSS属性,因此必须要给class_一个值。

当需要查找含有多种属性名—值对的tag时,可以用字典的方式(其实我们之前在2.1.1.2节中已经说过,tag的attributes属性是可以和python字典一样操作的)来搜搜索,如:

>>> soup.find_all('p',{'class':'title','id':'1'})
[<p class="title" id="1"><b>paragraph_name</b></p>]


对于class等多值属性可以分别搜索每个class名,如:

>>> css_soup = BeautifulSoup('<p class = "body aaa bbb"></p>','html.parser')
>>> css_soup.find_all(class_ = 'body')
[<p class="body aaa bbb"></p>]
>>> css_soup.find_all(class_ = 'aaa')
[<p class="body aaa bbb"></p>]
>>> css_soup.find_all(class_ = 'bbb')
[<p class="body aaa bbb"></p>]
>>> css_soup.find_all(class_ = 'body aaa')
[]
>>> css_soup.find_all(class_ = 'body aaa bbb')
[<p class="body aaa bbb"></p>]
>>> css_soup.find_all(class_ = 'body bbb aaa')
[]


总结以上运行结果可以发现:对于含有多个值的属性,可以通过其中某一个属性值或者所有顺序相同的属性值来搜索到该标签,其他搜索方式均不可行。

2.3.2.3 String参数

通过String参数可以搜索文档中字符串内容,可以使用任意过滤器进行匹配。

2.3.2.4 limit参数

find_all()可以返回全部搜索结果,使用limit可限制返回结果数量,如:
soup.find_all('a',limit = 2)
可以输出soup中的前2个a标签,至于前2个是哪2个,可以参照2.2.4节中介绍的html解析过程。

2.3.2.5 recursive参数

recursive默认值为True,表示搜索当前tag下所有子节点,若recursive = False则只搜索tag的直接子节点。

2.3.2.6 简写

soup.find_all()可以简写为find(),其作用效果是相同的。

2.3.3 find()

与find_all()用法相同,其返回值是find_all(limit = 1)情况下列表中的值。

2.3.4 find_parents()和find_parent()

find_parents()和find_all()用法相同,用于查找当前tag所有父辈节点,返回一个列表,可以结合.parents生成器和find_all()进行理解;

find_parent()和find()用法相同,用于查找当前tag所有父辈节点的第一个值,返回一个字符串。

2.3.5 find_next_siblings()和find_next_sibling()

find_next_siblings()和find_all()用法相同,用于查找当前tag后所有的兄弟节点,返回一个列表,可以结合.next_siblings生成器和find_all()进行理解;

find_next_sibling()和find()用法相同,用于查找当前tag所有后所有的兄弟节点的第一个值,返回一个字符串。

2.3.6 find_previous_siblings()和find_previous_sibling()

与2.3.5节同理。

2.3.7 find_all_next()和find_next()

通过.next_element属性对当前tag之后的tag和字符串进行迭代,find_all_next()返回所有符合条件值得列表;

find_next()返回符合条件的列表的第一个值,是一个字符串。

2.3.8 find_all_previous()和find_previous()

与2.3.7节同理。

2.3.9 CSS选择器

BeautifulSoup支持大部分CSS选择器,可以通过.select()方法使用CSS选择器语法,这里不详述。

2.4 修改文档树

2.4.1 修改tag的名称和属性

这部分内容在2.1.1.1和2.1.1.2节中已经介绍过。

2.4.2 修改.string

除了2.1.2节中提到的replace_with()方法外还可以通过tag.string = ‘new_string’直接赋值的方法来修改。

2.4.3 append()

>>> soup = BeautifulSoup('<a>Foo</a>','html.parser')
>>> soup.a.append('bar')
>>> soup.a
<a>Foobar</a>
>>> soup.a.contents
['Foo', 'bar']


从上面的例子可以看出,可以像python列表一样操作字符串内容。

2.4.4 .insert()

tag.insert()方法与tag.append()类似,区别是insert()方法是将元素插入到指定位置。

2.4.5 insert_before()和insert_after()

insert_before()是在当前tag或文本节点前插入内容;

insert_after()是在当前tag或文本节点后插入内容。

2.4.6 clear()

tag.clear()方法用于移除当前tag内的内容,即删除所有子节点,如:

>>> text = "<html><head><title>title_name</title></head><p class = 'title'><b>paragraph_name</b></p></html>"
>>> soup = BeautifulSoup(text,'html.parser')
>>> soup.html.clear()
>>> soup
<html></html>


2.4.7 extract()

tag.extract()方法将当前tag移除文档树,并作为方法结果返回,例如:

>>> text = "<html><head><title>title_name</title></head><p class = 'title'><b>paragraph_name</b></p></html>"
>>> soup = BeautifulSoup(text,'html.parser')
>>> soup.title.extract()
<title>title_name</title>
>>> soup
<html><head></head><p class="title"><b>paragraph_name</b></p></html>


也就是说,extract()方法将一个文档树拆分成了两个文档树。

2.4.8 decompose()

tag.decompose()方法将当前节点移除并完全销毁,与extract()方法类似,但不生成被移除的文档树,如:

>>> text = "<html><head><title>title_name</title></head><p class = 'title'><b>paragraph_name</b></p></html>"
>>> soup = BeautifulSoup(text,'html.parser')
>>> soup.title.decompose()
>>> soup
<html><head></head><p class="title"><b>paragraph_name</b></p></html>


注意:clear()方法是移除tag标签内的所有内容,但是还是保留当前标签的;extract()和decompose()方法是移除包括当前标签的当前标签内的所有内容,extract()方法将移除内容作为返回值返回,而decompose()方法则是完全销毁,没有返回值。

2.4.9 wrap()

tag.wrap()可将指定的tag元素进行包装,并返回包装后的结果,如:

>>> soup = BeautifulSoup('<p>I love you</p>','html.parser')
>>> soup.p.string.wrap(soup.new_tag('love'))
<love>I love you</love>
>>> soup.p.wrap(soup.new_tag('hhh'))
<hhh><p><love>I love you</love></p></hhh>
>>> soup
<hhh><p><love>I love you</love></p></hhh>


tag.wrap(new_tag)方法就是将当前节点封装到新标签中并返回结果。

2.4.10 unwrap()

tag.unwrap()方法与tag.wrap()方法作用效果相反,移除tag内的所有tag标签,常用于标记的解包,如:

>>> text = "<html><head><title>title_name<p class = 'title'>paragraph_name</p></title></head></html>"
>>> soup = BeautifulSoup(text,'html.parser')
>>> soup.title.p.unwrap()
<p class="title"></p>
>>> soup
<html><head><title>title_nameparagraph_name</title></head></html>


2.5 输出

2.5.1 格式化输出

prettify()方法可以是文档树以Unicode编码输出,并且采用缩进方式,每个标签独占一行,如:

>>> soup = BeautifulSoup(text,'html.parser')
>>> soup.prettify()
'<html>\n <head>\n  <title>\n   title_name\n  </title>\n </head>\n <p class="title">\n  <b>\n   paragraph_name\n  </b>\n </p>\n</html>'
>>> print(soup.prettify())
<html>
<head>
<title>
title_name
</title>
</head>
<p class="title">
<b>
paragraph_name
</b>
</p>
</html>


2.5.2 压缩输出

如果只想得到结果字符串,不重视格式,可以使用str()或者unicode()方法。

2.5.3 get_text()

get_text()方法可以获取子孙标签中所有的NavigableString内容,并将结果以unicode字符串返回,如:

>>>markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
>>>soup = BeautifulSoup(markup,'html.parser')
>>>soup.get_text()
u'\nI linked to example.com\n'
>>>soup.i.get_text()
u'example.com'


可以通过参数指定tag的文本内容的分隔符:

>>>soup.get_text("|")
u'\nI linked to |example.com|\n'


还可以去除获得文本内容的前后空白:

>>>soup.get_text("|", strip=True)
u'I linked to|example.com'


或者使用 .stripped_strings 生成器,获得文本列表后手动处理列表:

>>>[text for text in soup.stripped_strings]
[u'I linked to', u'example.com']


参考资料

[ 1 ] http://beautifulsoup.readthedocs.io/zh_CN/latest/#id17

[ 2 ] https://www.w3.org/TR/CSS2/selector.html%20CSS%E9%80%89%E6%8B%A9%E5%99%A8
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python 网络爬虫