数据获取:​网页解析之BeautifulSoup

Python
234
0
0
2024-01-05
标签   Python库

在上一节中,认识了Python中的lxml库,可以通过XPath来寻找页面中的位置,这也是仅仅对于结构完整的页面,但是对于有针对性的获取内容的时候并不很友好,比如说链接中以XXX开头或者结尾,而且中间符合某些特定规则,所以这时候需要认识一个新朋友,那就是另外一个很强大的解析库——Beautiful Soup。

与 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,通过解析文档为用户提供需要抓取的数据的功能。

安装BeautifulSoup

Beautiful Soup也有很多版本,不过Beautiful Soup3已经停止更新了,目前最新的都是Beautiful Soup4,而且也已经移植到bs4库中,我们安装bs4库后就可以直接使用。安装库使用pip安装,安装命令:

pip install beautifulsoup4

安装解析器

Beautiful Soup中支持的解析器有很多种,不仅仅支持Python标准库中的HTML解析器,还可以使用一些第三方的解析器,比如说lxml等,如表所示,是几种常见的解析器的优缺点。

解析器

使用方式

优点

缺点

Python标准库

BeautifulSoup(html, "html.parser")

Python的内置标准库、文档容错性较强

执行速度适中

lxml解析器

BeautifulSoup(html, "lxml")

速度快、文档容错性较强

依赖C语言库

html5lib

BeautifulSoup(html, "html5lib")

以浏览器的方式解析文档、容错性最好

执行速度慢

一般情况下可以使用Python标准库或者lxml作为常用的解析器,对于爬虫来说,比起速度来说,准确性的要求并不是很高。如果在解析文档上花费的时间太多,必然会导致爬虫的效率低。

Python标准库解析器并不需要安装,因为本身自带的,lxml解析器在上一节使用它作为解析器时候已经安装过了,也不需要额外安装,直接使用即可。html5lib的安装跟BeautifulSoup一样,使用pip安装:

pip install html5lib

生成解析对象

from bs4 import BeautifulSoup
from lxml import etree
text = '''
<html>
<head>
<title>实例HTML</title>
</head>
<body>
<div>
<h1>这是标题</h1>
</div>
<div>
<ul>
<li class="c1"><a href="link1.html" title="链接1">第一个链接</a></li>
<li class="c2"><a href="link2.html" title="链接2">第二个链接</a></li>
<li class="c3"><a href="link3.html" title="链接3">第三个链接</a></li>
</ul>
</div>
</body>
</html>
'''
# 生成一个BeautifulSoup对象
soup = BeautifulSoup(text, 'html.parser')
# 对象类型
print(type(soup))
#代码结果:
<class 'bs4.BeautifulSoup'>

现在就获得了一个BeautifulSoup的对象,Beautiful Soup其实是将HTML文档转换成一个复杂的树形结构,每个节点都是Python中的对象,所有对象可以归纳为 4 种:Tag、NavigableString、BeautifulSoup、Comment,后两种根本上讲也是前面两种的特殊情况。下面我们简要讲解这几个对象。

Tag

Tag是最容易理解的,跟字面意思一样,就是HTML中的标签。比如:一个a标签就是一个对象:

<a href="link1.html" title="链接1">第一个链接</a>

在tag对象中比较重要的两个属性name和attrs。通过这两个属性可以获取到标签中的信息:

print(soup.a.name)
print(soup.a.attrs)
#代码结果:
a
{'href': 'link1.html', 'title': '链接1'}

name其实就是获取标签的名称,这个是使用的不多,毕竟在日常使用的时候都会知道需要找哪些标签中的内容。attrs获取是标签中的属性,结果是一个字典类型的集合。

NavigableString

在上面两个属性中,并没法获取标签中的内容,那么NavigableString就是用来获取标签中文本内容的,用法也比较简单,直接使用string即可。

print(soup.a.string)
print(type(soup.a.string))
#代码结果:
第一个链接
<class 'bs4.element.NavigableString'>

BeautifulSoup

这个对象在前面提到过,表示一个页面(文档)的内容,可以作为一个特殊的Tag。

print(type(soup))
#代码结果:
<class 'bs4.BeautifulSoup'>

Comment

Comment对象也是一个特殊的NavigableString,读取的内容是注释里面的内容。把上面示例中的第一个a标签的内容更改成如下:

<a href="link1.html" title="链接1"><!--Hello--></a>
print(soup.a.string)
print(type(soup.a.string))
#代码结果:
Hello
<class 'bs4.element.Comment'>

注意:如果在标签内的文本既有正常文字也有注释,这时候string属性就无法获取到内容:

<a href="link1.html" title="链接1">第一个链接<!--Hello--></a>
print(soup.a.string)
#代码结果:
None

获取文本内容可以使用text方法,虽然text和string结果都是字符串,但是两个对象其实并不相同。

<a href="link1.html" title="链接1">第一个链接<!--Hello--></a>
print(soup.a.text)
print(type(soup.a.text))
#代码结果:
第一个链接
<class 'str'>

搜索文档树

把HTML内容解析成为一个BeautifulSoup对象后,对这个对象的操作才是BeautifulSoup这个模块所能体验的强大之处。本身BeautifulSoup本身有着丰富的节点遍历功能,包括父节点、子节点、子孙节点的获取和逐个元素的遍历。

不过在实际应用上,我们使用遍历的还是少数,使用搜索的还是多数,现在很多网页中的元素很丰富,我们很少会把一个页面中的所有内容都获取下来,基本是需要的重点内容,这对于遍历来说,搜索更加显得便捷实用。

find_all()

说到搜索,最常使用的肯定是BeautifulSoup的find_all()方法,它会搜索当前 tag 的所有 tag 子孙节点,并判断每个节点是否符合过滤器的条件。

find_all()方法的完整参数为find_all(name, attrs, recursive, text,limit, **kwargs):

name:标签名称的过滤,支持正则

attrs:标签的属性条件的过滤,支持正则;

recursive:bool选项,如果为True,find_all()将遍历所有节点,否则只有子节点,默认为True;

text:标签中的文本过滤,;

limit:搜索限制过滤,如果不为空,表示找到指定数量的元素后将停止搜索,默认为空,将搜索全部;

kwargs:表示可以添加多个属性值参数过滤。

1.name参数

搜索所有a标签

links = soup.find_all('a')
print(links)

代码结果:

[<a href="link1.html" title="链接1">第一个链接</a>, <a href="link2.html" title="链接2">第二个链接</a>, <a href="link3.html" title="链接3">第三个链接</a>]

搜索所有名字带“a”标签

links = soup.find_all(re.compile(".*a.*"))
print(links)

代码结果(head和a标签都符合)

[<head>
<title>实例HTML</title>
</head>, <a href="link1.html" title="链接1">第一个链接</a>, <a href="link2.html" title="链接2">第二个链接</a>, <a href="link3.html" title="链接3">第三个链接</a>]

2. arrts参数

搜索所有a标签中title值为“链接1”

links = soup.find_all('a', attrs={"title": "链接1"})
print(links)

代码结果:

[<a href="link1.html" title="链接1">第一个链接</a>]

3. kwargs参数:

搜索所有a标签中herf中带“1”的标签

links = soup.find_all('a', href=re.compile(".*1.*"))
print(links)

代码结果:

[<a href="link1.html" title="链接1">第一个链接</a>]

4. text参数:

#搜索所有a标签中,文本带“二”的标签

links = soup.find_all('a', text=re.compile(".*二.*"))
print(links)

代码结果:

[<a href="link2.html" title="链接2">第二个链接</a>]

如果不加a标签,搜索的内容则仅仅是文本。

#搜索所有a标签中,文本带“二”的标签

links = soup.find_all('text=re.compile(".*二.*"))
print(links)

代码结果:

['第二个链接']

5. limit参数

#搜索所有a标签中,超链接以link开头,最多2个

links = soup.find_all('a', href=re.compile("link.*"), limit=2)
print(links)

代码结果:

[<a href="link1.html" title="链接1">第一个链接</a>, <a href="link2.html" title="链接2">第二个链接</a>]

find()

find()方法相当于给find_all()方法默认添加limit=1,仅仅发挥符合条件的第一个Tag。方便有时候我们仅仅需要一个值的时候,直接可以调用。参数跟find_all()一样,用法也是相同。

CSS选择器

Beautiful Soup中用select()方法来CSS样式的进行筛选,当然也可以筛选标签。在标签的属性中,class的属性就是当前标签的CSS样式,返回的结果同样也是list。

1.通过标签名查找

查找所有a标签

links = soup.select('a')
print(links)

代码结果:

[<a href="link1.html" title="链接1">第一个链接</a>, <a href="link2.html" title="链接2">第二个链接</a>]

2.通过CSS样式类名查找

查找样式类名为c1的标签

links = soup.select('.c1')
print(links)

代码结果:

[<li class="c1"><a href="link1.html" title="链接1">第一个链接</a></li>]

3.通过标签属性查找

查找属性中href="link1.html"的a标签

links = soup.select('a[href="link1.html"]')
print(links)

代码结果:

[<a href="link1.html" title="链接1">第一个链接</a>]

在标签+属性组合中,属性不支持正则表达式。

4.获取查找到的内容

除了以上集中还可以使用标签的id等元素来进行查找,但是不管使用哪种方式,最终的是回去标签的内容或者属性中的值,那么找到相应的标签后,怎么取值呢?如果是去标签属性值,跟使用字典取值方式一样。如果是获取标签的文本,直接使用get_text()方法,可以获取到标签的文本内容。

查找属性中href="link1.html"的a标签

links = soup.select('a[href="link1.html"]')
#打印标签中的超链接值
print(links[0][‘href])
#打印标签文本内容
print(links[0].get_text())

代码结果:

第一个链接
link1.html

不管是使用lxml还是Beautiful Soup,多数结果都是获取文本内容或者是标签的属性值。文本内容多数是需要获取的内容,整理下来放到list中,最后可能保存本地文件或者数据库,而标签的中属性值多数可以找到子链接(详情链接),知道了怎么定位和获取页面的元素,下面我们就可以动手爬取页面的内容了。