前面我们说过了通过 requests 库获取数据
,这里我们要说如何处理数据
处理数据我们需要用到一个强大的第三方库——BeautifulSoup
!
“美味的汤,绿色的浓汤,在热气腾腾的盖碗里装!谁不愿意尝一尝,这样的好汤?晚餐用的汤,美味的汤!”
BeautifulSoup 库的名字取自刘易斯·卡罗尔在《爱丽丝梦游仙境》里的同名诗歌。就像它在仙境中的说法一样,BeautifulSoup 尝试化平淡为神奇。它通过定位 HTML 标签来格式化和组织复杂的网页源代码,用简单易用的 Python 对象为我们展现出 HTML 结构信息。
处理数据分为两步:
解析数据
:将网页源代码解析成 Python 能“读懂”的格式提取数据
:将网页源代码中无关数据过滤掉,只提取出我们所需要的数据我们以豆瓣读书 Top250 为例,它的网址是:https://book.douban.com/top250。
我们来看看如何将其网页源代码解析成 BeautifulSoup 对象:
import requests
from bs4 import BeautifulSoup
headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
}
res = requests.get('https://book.douban.com/top250', headers=headers)
soup = BeautifulSoup(res.text, 'html.parser')
Tips:相比之前我们添加 headers 参数,这是为了应对豆瓣的反爬虫机制。
我们通过 from bs4 import BeautifulSoup
语句导入 BeautifulSoup,然后使用 BeautifulSoup(res.text, 'html.parser')
语句将网页源代码的字符串形式解析成了 BeautifulSoup 对象。
创建 BeautifulSoup 对象时需要传入两个参数,第一个参数是要解析的 HTML 文本,即网站源代码的字符串形式(res.text)。第二个参数是 解析HTML 的解析器,html.parser 是 Python 中内置的解析器,较为简单方便.
我们将网页源代码解析成了 BeautifulSoup 对象,如果我们将他打印出来会发现竟然和原来的网页源代码(也就是 res.text)一模一样!既然都一样,我们何苦费这么大力将网页源代码解析成 BeautifulSoup 对象 呢?
相比字符串,BeautifulSoup 对象 里有很多强大的方法和属性。通过这些方法和属性,我们就能方便快捷地提取出我们所需要的数据。
BeautifulSoup 对象 里的方法和属性有很多,我们这里只提及其中最常用的一些,这些足以应付大多数场景。
BeautifulSoup 对象 里的 find() 和 find_all() 是我们提取数据最常用的两个方法。借助它们,我们可以过滤掉 HTML 页面里的无用数据,轻松地找到我们需要的数据。
我们来看一下 find() 和 find_all() 的作用和区别:
我们可以通过例子来更好地理解他们:
假设我们获取到的网页源代码如下:
<div class="content">
<a href="https://douban.com">登录/注册a>
<h1>豆瓣读书 Top 250h1>
<div class="artile">
<a href="https://movie.douban.com">豆瓣电影a>
<div class="item">
<a href="https://book.douban.com/subject/1770782/">追风筝的人a>
div>
<div class="item">
<a href="https://book.douban.com/subject/25862578/">解忧杂货店a>
div>
<div class="item">
<a href="https://book.douban.com/subject/1084336/">小王子a>
div>
div>
div>
soup = BeautifulSoup(res.text, 'html.parser')
print(soup.find('a'))
# 输出:登录/注册
print(soup.find_all('a'))
# 输出:[
# 登录/注册,
# 豆瓣电影,
# 追风筝的人,
# 解忧杂货店,
# 小王子
# ]
它俩的用法基本一样,都是传入 HTML 标签名称,返回符合该 HTML 标签的数据。区别是 find() 方法只返回第一个符合条件的标签,而 find_all() 方法返回所有符合条件的标签列表。他们的返回值分别是 BeautifulSoup 中的 Tag 对象 和由 Tag 对象组成的列表。(后面会提到)
除了传入 HTML 标签名称 外,这两个方法还支持传入 HTML 属性 进行筛选,返回符合条件的数据。举个例子:
# 查找 id='doubanapp-tip' 的 div 标签
soup.find('div', id='doubanapp-tip')
# 查找所有 class='rating_nums' 的 span 标签
soup.find_all('span', class_='rating_nums')
class 和 id 这两个 HTML 属性 具有很强的标识性,因此是数据筛选中最常用的两个属性,我们要重点关注。
Tips:因为 class 是 Python 中定义类的关键字,因此用 class_ 表示 HTML 中的 class。
通过 id、class 等 HTML 属性的筛选,我们就可以快速准确的找到我们需要的数据。当一个条件无法精确定位到我们想要的数据时,我们还可以传入多个 HTML 属性进行筛选,返回同时符合这些条件的数据。
我们再来看个例子:
# 查找 id='doubanapp-tip' 且 class='rating_nums' 的 div 标签
soup.find('div', id='doubanapp-tip', class_='rating_nums')
BeautifulSoup 将 HTML 中的元素封装成了 Tag 对象。和 BeautifulSoup 对象 一样,Tag 对象 里也有 find() 和 find_all() 方法。因此,我们可以不断地调用这两个方法,一层一层地找到我们需要的数据。我们还是以前面的 HTML 代码为例提取其中的书名:
<div class="content">
<a href="https://douban.com">登录/注册</a>
<h1>豆瓣读书 Top 250</h1>
<div class="books">
<a href="https://movie.douban.com">豆瓣电影</a>
<div class="item">
<a href="https://book.douban.com/subject/1770782/">追风筝的人</a>
</div>
<div class="item">
<a href="https://book.douban.com/subject/25862578/">解忧杂货店</a>
</div>
<div class="item">
<a href="https://book.douban.com/subject/1084336/">小王子</a>
</div>
</div>
</div>
我们可以看到,书名在 a 标签 中。但如果直接使用 soup.find_all(‘a’) 的话,第二行的“登录/注册”和第五行的“豆瓣电影”也会被获取到,因此我们需要将这些无效数据过滤掉。
我们分析一下不难发现,书名在 class=“item” 的 div 标签 里的 a 标签 内。我们只要先找到所有 class=“item” 的 div 标签,然后再找到其中的 a 标签 即可,因此我们可以像下面这样来获取书名的数据:
# 找到所有 class_='item' 的 div 标签
items = soup.find_all('div', class_='item')
for i in items:
# 找到 class_='item' 的 div 标签中的 a 标签
print(i.find('a'))
# 输出:
# 追风筝的人
# 解忧杂货店
# 小王子
这样,我们就找到了所有书名的数据。此时返回的还是 Tag 对象。如果我们只想要书名和对应的链接呢?这就用到了 Tag 对象 的 text 属性和 HTML 属性名取值。
items = soup.find_all('div', class_='item')
for i in items:
tag = i.find('a')
# 获取 text 属性
name = tag.text
# 获取 href 属性值
link = tag['href']
print(name, link)
# 输出:
# 追风筝的人 https://book.douban.com/subject/1770782/
# 解忧杂货店 https://book.douban.com/subject/25862578/
# 小王子 https://book.douban.com/subject/1084336/
我们通过 Tag 对象 的 text 属性拿到了 a 标签里的文字内容,即 追风筝的人 等。然后我们通过和字典取值一样的方式,将 HTML 属性名 作为键,得到了对应属性的值。这里是以 href 属性为例,其他的 HTML 属性也同样可以。
有没有什么方法可以直接就找到我们需要的数据,而不用多次查找吗?
答案是肯定的,需要用到 CSS 选择器。
在 CSS 选择器中,# 代表 id,. 代表 class。比如:#login 表示 id=‘login’ 的所有元素,.item 表示 class=‘item’ 的所有元素。
我们也可以直接通过标签名选择对应的元素,比如:a 表示所有的 a 元素,p 表示所有的 p 元素。
它们也可以组合在一起,选择同时符合条件的元素,比如:a#login 表示所有 id=‘login’ 的 a 元素,p.item 表示所有 class=‘item’ 的 p 元素,#login.item 表示所有 id=‘login’ 且 class=‘item’ 的元素,.item.book 表示所有 class 同时为 item 和 book 的元素。
需要注意的是,选择同时符合条件的元素,选择器之间不能有空格,如果写成 .item .book 就是另一个意思了。这是新的知识点——子元素选择。
当两个选择器之间加了空格,表示子元素选择。还是以 .item .book 为例,它表示选择所有 class=‘item’ 的元素里面 class=‘book’ 的元素,即嵌套在 class=‘item’ 的元素里面 class=‘book’ 的元素。
这个嵌套可以是任意层级的,只要在里面就行,不要求直接嵌套在第一层。如果只需要直接嵌套在第一层符合条件的元素,可以用 > 分隔。比如:.item > .book。
来看个例子感受一下它们的区别:
from bs4 import BeautifulSoup
html = '''
小王子
追风筝的人
'''
soup = BeautifulSoup(html, 'html.parser')
print(soup.select('.item.book'))
# 输出:[]
print(soup.select('.item .book'))
# 输出:[小王子
, 追风筝的人
]
print(soup.select('.item > .book'))
# 输出:[小王子
]
了解了 CSS 选择器的基本语法后,我们来看看如何在 BeautifulSoup 中使用。
BeautifulSoup 对象 有一个 select() 方法,我们将 CSS 选择器 传进去即可直接找到我们需要的元素。上面查找在 class=“item” 的 div 标签 里的 a 标签 的代码就可以这样写:
items = soup.select('div.item a')
for i in items:
name = i.text
link = i['href']
print(name, link)
# 输出:
# 追风筝的人 https://book.douban.com/subject/1770782/
# 解忧杂货店 https://book.douban.com/subject/25862578/
# 小王子 https://book.douban.com/subject/1084336/
对于静态网页爬虫的过程,可以总结成下图:
我们现在对豆瓣top250中的前25个电影的名字以及名字进行爬取:
import requests
from bs4 import BeautifulSoup
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36 Edg/101.0.1210.32'
}
#首先对网页发出请求并获得响应
req = requests.get('https://movie.douban.com/top250',headers = headers)
#将网页的源代码形式解析
soup = BeautifulSoup(req.text,'html.parser')
#进行元素的第一次提取
result1 = soup.select('.item .pic')
num = 0
for i in result1:
num += 1
name = i.select('a img')[0]['alt']
link = i.select('a')[0]['href']
print(num,' ',name,link)