上一篇我们学习了BeautifulSoup的基本用法,本节我们使用它来爬取豆瓣图书Top250。
一、网页分析
我们爬取的网页的url是https://book.douban.com/top250?icn=index-book250-all。首页如图
与豆瓣电影Top250差不多,将页面拉到最底部,可以看到分页列表
并且每一页的url也是以25递增,所以爬取思路与豆瓣电影Top250一致。
二、爬取目标
我们本篇要爬取的信息包括书名、作者、出版社、价格、评分、推荐语。
三、爬取首页
- 网页获取源代码
import requestsdef get_html(url):headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}html = requests.get(url,headers=headers)return html.textif __name__ == '__main__':url = 'https://book.douban.com/top250?start=0'html = get_html(url)print(html)
输出结果
- 解析提取所需信息
如图,查看源代码我们可以知道页面中书的信息均包含在一个个<table>标签中,所以我们可以先用CSS选择器将一个个<table>节点选出来,然后在使用循环提取每一本书的信息。提取<table>节点的代码如下:
def parse_html(html):soup = BeautifulSoup(html,'lxml')books = soup.select('div.article div.indent table')print(books)
运行结果如下:
可以看到输出为列表,并且第一个元素包含《追风筝的人》的相关信息。这里我们使用BeautifulSoup中的select()加CSS选择器提取<table>节点。传入的CSS选择器为:div.article div.indent table。其中div.article的意思为选择包含属性class="article"的<div>标签,然后跟空格代表嵌套关系,表示接着选择该<div>下的包含class="indent"的<div>标签,再跟空格表示接着嵌套,继续选择第二个<div>标签下的<table>标签。
接下来,对选出来的<table>标签循环,在每一个<table>标签中去提取图书信息。这里我们先提出书名信息,先看一种写法,代码如下:
def parse_html(html):soup = BeautifulSoup(html,'lxml')books = soup.select('div.article div.indent table')for book in books:title = book.div.a.stringprint(title)
输出结果
可以看到确实获取到了书名信息,但是有些书的书名没有得到,返回了None,这就不是很完美了呀。我们先解释写这里获取标题的方法,这里我们使用了节点选择器的嵌套选择:首先选择了<div>标签,然后继续选择其下的<a>标签。为什么不直接选择<a>标签呢,因为包含书名信息的<a>标签是<table>节点下的第二个<a>标签,直接选择<a>只会选择第一个不包含书名信息的那个<a>标签。下面我们来研究下为什么使用string属性不能获取某些书的书名信息,我们先将获取到的<a>标签打印出来。
def parse_html(html):soup = BeautifulSoup(html,'lxml')books = soup.select('div.article div.indent table')for book in books:title = book.div.aprint(title)
并截取返回None的那本书的位置
可以看到《三体》这本书的<a>标签的内部结构不同,所以导致调用string属性返回None,但是我们可以注意到每条<a>标签都包含title属性,我们是否可以通过title属性获取书名?
def parse_html(html):soup = BeautifulSoup(html,'lxml')books = soup.select('div.article div.indent table')for book in books:title = book.div.a['title']print(title)
输出结果如下:
可以看到不仅获取到了想要的信息,而数据更加干净。这里获取title部分说了这么多主要是想告诉大家一个获取相同的信息有很多的方法,当一种获取方式不理想时可以考虑换一种思路。
接下来我们一次性将所有的信息抓取下来。
def parse_html(html):soup = BeautifulSoup(html,'lxml')tables = soup.select('div.article div.indent table')books = []for table in tables:title = table.div.a['title']'''由于information中包含多个信息,某些书与大多数书的信息格式不一致在进行列表索引的时候非常容易引起IndexError异常,为了保证爬虫的健壮性我们对该异常进行处理'''information = table.p.stringinformations = information.split('/')while(len(informations)>4):del informations[1]try:author = informations[0]press = informations[1]date = informations[2]price = informations[3]except IndexError:continue'''像这样子进行数据提取很容易遇到某一个特殊部分的网页结构与大部分的不一样,这会导致首页能抓取到的节点,在该部分会返回None,从而导致调用string属性产生AttributeError异常,我们需要进行异常处理'''try:score = table.find(attrs={'class':'rating_nums'}).stringrecommendation = table.find(attrs={'class':'inq'}).stringexcept AttributeError:continuebook = {'书名':title,'作者':author,'出版社':press,'出版日期':date,'价格':price,'评分':score,'推荐语':recommendation}books.append(book)return books
输出结果:
这里可以看到输出结果虽然为字典,但是不好看。保存为字典格式只是方便存储与后续使用,假如我们要将其打印到屏幕上的话,并不好看,所以我们接着写一个打印函数,专门用于输出:
def print_(books):for book in books:print('*'*50)for key,value in zip(book.keys(),book.values()):print(key+':'+value)print('*'*50)
我们将爬取首页的代码汇总在一起,看看输出效果
import requests
from bs4 import BeautifulSoupdef get_html(url):headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}html = requests.get(url,headers=headers)return html.textdef parse_html(html):soup = BeautifulSoup(html,'lxml')tables = soup.select('div.article div.indent table')books = []for table in tables:title = table.div.a['title']'''由于information中包含多个信息,某些书与大多数书的信息格式不一致在进行列表索引的时候非常容易引起IndexError异常,为了保证爬虫的健壮性我们对该异常进行处理'''information = table.p.stringinformations = information.split('/')while(len(informations)>4):del informations[1]try:author = informations[0]press = informations[1]date = informations[2]price = informations[3]except IndexError:continue'''像这样子进行数据提取很容易遇到某一个特殊部分的网页结构与大部分的不一样,这会导致首页能抓取到的节点,在该部分会返回None,从而导致调用string属性产生AttributeError异常,我们需要进行异常处理'''try:score = table.find(attrs={'class':'rating_nums'}).stringrecommendation = table.find(attrs={'class':'inq'}).stringexcept AttributeError:continuebook = {'书名':title,'作者':author,'出版社':press,'出版日期':date,'价格':price,'评分':score,'推荐语':recommendation}books.append(book)return booksdef print_(books):for book in books:print('*'*50)for key,value in zip(book.keys(),book.values()):print(key+':'+value)print('*'*50)if __name__ == '__main__':url = 'https://book.douban.com/top250?start=0'html = get_html(url)books = parse_html(html)print_(books)
输出结果:
注意,这里我们没有写存储的相关函数,因为这里只为演示BeautifulSoup的用法,假如需要存储数据参考爬虫系列第三篇 使用requests与正则表达式爬取豆瓣电影Top250
四、爬取整个豆瓣图书Top250
与前面个爬虫实例一样,构造url列表,使用循环即可。全部代码如下
import requests
from bs4 import BeautifulSoupdef get_html(url):headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}html = requests.get(url,headers=headers)return html.textdef parse_html(html):soup = BeautifulSoup(html,'lxml')tables = soup.select('div.article div.indent table')books = []for table in tables:title = table.div.a['title']'''由于information中包含多个信息,某些书与大多数书的信息格式不一致在进行列表索引的时候非常容易引起IndexError异常,为了保证爬虫的健壮性我们对该异常进行处理'''information = table.p.stringinformations = information.split('/')while(len(informations)>4):del informations[1]try:author = informations[0]press = informations[1]date = informations[2]price = informations[3]except IndexError:continue'''像这样子进行数据提取很容易遇到某一个特殊部分的网页结构与大部分的不一样,这会导致首页能抓取到的节点,在该部分会返回None,从而导致调用string属性产生AttributeError异常,我们需要进行异常处理'''try:score = table.find(attrs={'class':'rating_nums'}).stringrecommendation = table.find(attrs={'class':'inq'}).stringexcept AttributeError:continuebook = {'书名':title,'作者':author,'出版社':press,'出版日期':date,'价格':price,'评分':score,'推荐语':recommendation}books.append(book)return booksdef print_(books):for book in books:print('*'*50)for key,value in zip(book.keys(),book.values()):print(key+':'+value)print('*'*50)if __name__ == '__main__':urls = [f'https://book.douban.com/top250?start={i*25}' for i in range(0,10)]for url in urls: html = get_html(url)books = parse_html(html)print_(books)
五、总结
通过本篇的学习,读者应该着重掌握:
- BeautifulSoup库三种节点选择方式的灵活运用
- 对可能的异常要进行处理(请求部分的异常一般不用处理)
- 与正则表达式进行优劣比较
- 读者可以自行将正则表达式与BeautifulSoup结合起来灵活使用
这里解释一下为什么请求部分的异常一般不需要处理,因为请求出现异常一般意味着url错误、网络连接有问题等,这些异常都需要我们处理好而不是用try...except语句跳过它,否则爬虫无法继续。