先上一波爬取的结果:
数据库中部分截图
引入类库
import requests
from bs4 import BeautifulSoup
from requests.exceptions import RequestException
from urllib.parse import urlencode
import pymongo
import numpy as np
import time
from faker import Faker
分析页面请求
分析目标页面
打开开发者模式,查看链接
点击任意标签,分析页面请求 分别请求不同的标签页面,分析请求链接,可以发现如下规律:
tag_url = 'https://book.douban.com' + 标签页中a标签括起来的内容
由此,我们可以构建以下代码,以获取标签页面所有标签链接:
# 解析总标签页面,并拼接获得所有标签页页面链接
def splice_tags_indexhtml(html):
url = 'https://book.douban.com'
book_tags = []
tags_url = []
soup = BeautifulSoup(html, 'lxml')
tagurl_lists = soup.select('#content > div > div.article > div > div > table > tbody > tr > td > a')
for tag_url in tagurl_lists:
# 获取全部标签的a标签内容,并拼接到一起
book_tags += [tag_url.attrs["href"]]
for book_tag in book_tags:
tags_url.append([url + book_tag])
return tags_url
我们进入单个标签页面,分析图书列表页面,解析我们需要存储的字段 我们通过bs4解析我们需要的字段,如:出版时间,作者/译者,豆瓣评分,售价,评价人数等。
# 解析单个tag页面下单页的信息
def parse_tag_page(html):
try:
soup = BeautifulSoup(html,"lxml")
tag_name = soup.select('title')[0].get_text().strip()
list_soup = soup.find('ul', {'class': 'subject-list'})
if list_soup == None:
print('获取信息列表失败')
else:
for book_info in list_soup.findAll('div', {'class': 'info'}):
# 书名
title = book_info.find('a').get('title').strip()
# 评价人数
people_num = book_info.find('span', {'class': 'pl'}).get_text().strip()
# 出版信息,作者
pub = book_info.find('div', {'class': 'pub'}).get_text().strip()
pub_list = pub.split('/')
try:
author_info = '作者/译者: ' + '/'.join(pub_list[0:-3])
except:
author_info = '作者/译者: 暂无'
try:
pub_info = '出版信息: ' + '/'.join(pub_list[-3:-1])
except:
pub_info = '出版信息: 暂无'
try:
price_info = '价格: ' + '/'.join(pub_list[-1:])
except:
price_info = '价格: 暂无'
try:
rating_num= book_info.find('span', {'class': 'rating_nums'}).get_text().strip()
except:
rating_num = '0.0'
book_data = {
'title': title,
'people_num': people_num,
'author_info': author_info,
'pub_info': pub_info,
'price_info': price_info,
'rating_num': rating_num
}
# return book_data
if book_data:
save_to_mongo(book_data,tag_name)
except:
print('解析错误')
return None
到这里,我们已经可以获取到单个tag下单页的图书信息,这个时候我们只需要加入翻页功能就可以实现单个tag下所有图书的信息爬取。
点击下一页,分析页面请求 可以看到页面多了start和type两个参数,同时start参数是从0开始并以20的偏移量递增的,我们按照这个规律可以构建一个生成器以生成start参数。 从文章的第一张图,可以看出不同的tag页有不同的数量的图书,那页面数量也不尽相同,这时应该如何构建生成器? 这个时候我们发现所有的tag在第50页之后都请求不出信息了,所以我们只需构建前50页的页面链接即可,第51页显示如下:
第51页的显示结果
# 请求tag下的每个页面
def get_tag_page(tag_url,page):
formdata = {
'start': page,
'type': 'T'
}
url = tag_url[0]+'?'+ urlencode(formdata)
try:
reponse = requests.get(url, headers=headers)
if reponse.status_code == 200:
return reponse.text
return None
except RequestException:
print('请求列表页错误')
return None
反反爬 豆瓣的反爬简单粗暴,直接封IP,为了爬虫的健壮,可以使用代理或者随机Header+随机时延的方式,随机时延可以设置为30到40之间,不过这样大大影响了爬取速率,如果需要快速爬取可以采用代理+多线程+随机Header+随机时延这样就能避过反爬又能快速爬取。
#使用Faker库随机生成虚假header
from faker import Faker
fake = Faker()
headers ={'User-Agent':fake.user_agent()}