关于爬虫学习的一些小小记录(三)——BeautifulSoup
前面讲了一些访问页面和提取数据的基础方法,学会了挥锤子就该造刀剑了
但 Python 的哲学就是 “不要重复造轮子”,神兵利器都已经被大神们造好了,我们会用就可以了
这次我们就把白板装丢开,谈一个学习爬虫必不可少的工具Beautiful Soup
Beautiful Soup
Beautiful Soup库是 Python 的第三方库,可以解析 html 或 xml 文件,提供了许多可以高效提取数据的方法,让提取数据变得十分简单、灵活。而且 Beautiful Soup 可以自动把 html 和 xml 文件编码格式转化成 Unicode,输出时转化成 utf-8,不需要再手动转码
老规矩,这是一份中文版的 Beautiful Soup 的官方文档
食不惯中文版的童鞋可以品尝英文版
这里列出最常用的,也是我最喜欢的find_all()方法,该方法用于搜索当前 tag 的所有子孙结点,并以列表形式返回匹配到的所有内容
find_all(name, attrs, recursive, text, **kwargs)
name,查找所有名字为 name 的 tag,自动忽略字符串对象。可接受任一数据类型,如字符串,正则表达式,列表,True
attrs,按照 CSS 类名搜索 tag,因为 class 是 Python 的关键字,使用 class_ 代替使用
recursive,当值为 False 时,find_all() 只搜索当前 tag 的直接子结点
text,搜索文档中的字符串内容,同样可以接受任一数据类型
kwargs,可选的,当字典的 key 不是内置的参数名时,作为当前 tag 的属性名进行搜索
另外find()的用法跟find_all()差不多,但返回的内容是匹配到的第一个 tag,并不是只有一个 tag 的列表
使用 Beautiful Soup 修改代码
在上一篇中,我们写了一个从豆瓣爬取 2019 版《倚天屠龙记》演职员数据信息的爬虫程序,这是最终扩展后的代码
# 爬虫--爬取豆瓣 2019版 《倚天屠龙记》 全体演职员
import re
from urllib import request
url = 'https://movie.douban.com/subject/25865815/celebrities'
response = request.urlopen(url) # 访问 url
page = response.read().decode() # 以 utf-8 的格式读取数据
names = re.findall('" class="name">(.*)</a></span>', page) # 正则表达式匹配
roles = re.findall('span class="role" title="(.*)">', page) # 匹配职位
imgs = re.findall('style="background-image: url\((.*)\)">', page) # 匹配图片链接
i = 0
while i < len(names):
role_list = roles[i].split(' ') # 避免因文件名过长无法建立,只好从这里切短
role = role_list[0] + ' (' + role_list[-1] # 切碎了重新拼一下
img_name = r'19版《倚天屠龙记》演职员/' + names[i] + ' ' + role + r'.jpg'
img = request.urlretrieve(imgs[i], img_name)
i += 1
现在,我们使用 Beautiful Soup 来精简一下这个代码
还是事先分析一波
演职员姓名和职位都在span标签中,CSS 类名分别为name和role,而图片链接在div标签中,CSS 类名为avatar,都可以直接使用find_all()方法匹配
使用 Beautiful Soup 精简后的代码如下
# 爬虫--爬取豆瓣 2019版 《倚天屠龙记》 全体演职员——美丽汤修改版
from bs4 import BeautifulSoup
from urllib import request
url = 'https://movie.douban.com/subject/25865815/celebrities'
response = request.urlopen(url) # 访问 url
page = response.read().decode() # 以 utf-8 的格式读取数据
soup = BeautifulSoup(page, 'html.parser') # 使用 Python 内置的 html.parser 解析器
names = soup.find_all('span', 'name') # tag 名和 CSS 类名匹配姓名
roles = soup.find_all('span', 'role') # 匹配职位
imgs = soup.find_all('div', 'avatar') # 匹配图片链接
i = 0
while i < len(names):
role_list = roles[i].string.split(' ') # 避免因文件名过长无法建立,只好从这里切短
role = role_list[0] + ' (' + role_list[-1] # 切碎了重新拼一下
img_name = r'19版《倚天屠龙记》演职员1/' + names[i].string + ' ' + role + r'.jpg'
img_url = imgs[i].get('style').split('(')[1].split(')')[0] # 选取括号中的部分
img = request.urlretrieve(img_url, img_name)
i += 1
运行结果跟修改前的一样,这里就不给出了
黑猫白猫
想来机智的小伙伴们也发现了,Beautiful Soup 在匹配查找 tag 时十分方便,因为匹配方式多样,而且返回的就是 tag 类型数据。但如果匹配的是 tag 的部分字节时,需要通过匹配到的 tag,对属性进行调用来间接获取,不能像正则表达式一般直接返回匹配的内容
比如在上面的程序中,提取图片链接时,imgs 列表中存储的是 div 标签。我们需要使用 imgs[i].get(‘style’) 获取 tag 中的 style 属性值,然而这还不是一个可以访问的 url。我们要使用 split 把这个值进一步分割,取出括号中的 url。这才算成功提取,显然要比正则表达式麻烦许多。
这样看来,这次选择的例子并不合适来凸显美丽汤的高效简便。
当然,这也说明,我们应当实事求是。美丽汤虽有不足,正则表达式在某些情况也可能出现匹配不精准的情况,我们可以结合使用。黑猫白猫都可以捉到老鼠