爬取准备
所需第三方库:requests,re,time,pymongo,random
爬取网站需联网
思路分析
网址: https://movie.douban.com/subject/26266893/reviews?start=0
网址: https://movie.douban.com/subject/26266893/reviews?start=20
由此分析,每页评论有20个,而网址的改变也只是在最后,网址的前半部分https://movie.douban.com/subject/26266893/reviews?start=都一样,而后面变的参数代表了第多少条评论,所以要爬取所以影评的动态网址的格式为:https://movie.douban.com/subject/26266893/reviews?start=加上20的倍数。
#前半部分固定的内容
url = r'https://movie.douban.com/subject/26266893/reviews?start='
page=0#表示评论页数
k = 0#表示评论条数
while page<1073:#检查网页可知流浪地球共有1073页影评
urls = url+str(k)#获得一个完整的网址
page = page+1
k = k+20#每一页有20条评论
动态网页网址已经分析清楚后就可以直接用requests库抓取网页代码
import requests
while page<1073:
urls = url+str(k)#拼接成完整的网址
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0'}#根据HTTP协议,设置请求头
response = requests.get(urls, headers=header)#获取源代码
print(response.text)#打印网页源代码
影评格式:
网页中需要提取的目标字段包括作者,评价星级,发布时间及影评内容,所以我采用的是正则匹配来获取
#将提取目标字段的功能封装成函数
import re
def re_select(string):
# 取出作者字段
pat_author = r'class="name">(.*?)</a>'
re_au = re.compile(pat_author, re.S)
auList = re_au.findall(string)#获取目标文档中的所有符合条件的字段
# 取出评价等级字段
pat_level = r'main-title-rating" title="(.*?)"></span>'
re_level = re.compile(pat_level, re.S)
levelList = re_level.findall(string)
# 取出影评内容
pat_story = r'<div class="short-content">(.*?) '
re_story = re.compile(pat_story, re.S)
storyList = re_story.findall(string)
for i in range(len(storyList)):#将获取的字段进行格式调整
storyList[i] = storyList[i].strip()#去掉字段中的空白字符
storyList[i] = re.sub(r"[a-z\s\"\<\>\/\=\-]", "", storyList[i])#删除多余的字符
# 取出评价时间
pat_time = r'class="main-meta">(.*?)</span>'
re_time = re.compile(pat_time, re.S)
timeList = re_time.findall(string)
#返回所有的目标字段列表
return auList,levelList,storyList,timeList
获取数据后可以选择保存为文件格式,也可以保存到本地数据库中。我选择的是保存到本地mongodb数据库中,也将其封装为函数:
#连接数据库
def linkmongo():
# 连接mongodb数据库
myclient = pymongo.MongoClient("mongodb://localhost:27017")
# 查看数据库名
dblist = myclient.list_database_names()
# 指定mydb数据库
mydb = myclient.mydb
# 指定mydb数据库里豆瓣集合
collection = mydb.douban
# 返回游标
return collection
#调用函数,只调用一次,不要加在后面的循环中
collection = linkmongo()
最后就是把所有功能整合就行了,但是还需要提高代码健壮性,本应该加一个异常处理,但是不想搞了,所有直接将出现异常的数据不要,直接请求下一页,总共几万条数据,少几条也没啥事,就这样吧!!!
总代码:
#author mjz
#data 2019/12/4/16:19
import requests
import re
import time
import pymongo
import random
url = r'https://movie.douban.com/subject/26266893/reviews?start='
#定义一个内核列表
u_a = [ 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
'Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0']
def linkmongo():
# 连接mongodb数据库
myclient = pymongo.MongoClient("mongodb://localhost:27017")
# 查看数据库名
dblist = myclient.list_database_names()
# 指定mydb数据库
mydb = myclient.mydb
# 指定mydb数据库里user集合
collection = mydb.douban
return collection
collection = linkmongo()
def re_select(string):
# 取出作者字段
pat_author = r'class="name">(.*?)</a>'
re_au = re.compile(pat_author, re.S)
auList = re_au.findall(string)
# 取出评价等级字段
pat_level = r'main-title-rating" title="(.*?)"></span>'
re_level = re.compile(pat_level, re.S)
levelList = re_level.findall(string)
# 取出影评内容
pat_story = r'<div class="short-content">(.*?) '
re_story = re.compile(pat_story, re.S)
storyList = re_story.findall(string)
for i in range(len(storyList)):
storyList[i] = storyList[i].strip()
storyList[i] = re.sub(r"[a-z\s\"\<\>\/\=\-]", "", storyList[i])
# 取出评价时间
pat_time = r'class="main-meta">(.*?)</span>'
re_time = re.compile(pat_time, re.S)
timeList = re_time.findall(string)
return auList,levelList,storyList,timeList
page=0
k = 0
while page<1073:
urls = url+str(k)
user = random.randint(0, 2)
header = {'User-Agent': u_a[user]}
response = requests.get(urls, headers=header)
page = page + 1
#防止短时间内请求次数过多被网站封IP,所有爬一个页面后休息5秒,爬100个后休息一分钟
if page % 100 == 0:
time.sleep(60)
else:
time.sleep(5)
k = k + 20
if response.status_code == 200:
print("成功爬取评论第%d页......" % page)
auList, levelList, storyList, timeList = re_select(response.text)
length = len(len(auList))
#如果提取的页面信息有缺失,则不要当前页面的信息,直接跳转到下一页面继续爬取
if(len(levelList)!=length or len(levelList)!=length or len(timeList)!=length):
continue
for i in range(len(auList)):
di = {'作者': auList[i], '评级': levelList[i], '评论': storyList[i], '评论时间': timeList[i]}
collection.insert(di)
else:
print("爬取失败.......")
因为这次用的是单线程,要爬完得很久,没有等到运行结束,但是也运行了两百多个页面,代码还是稳定的,应该没问题的。。。
运行截图:
所有的东西就是这样了,算是敏捷开发吧,只实现了功能,要修改也有许多地方可以修改,就当开源吧,希望有人能帮我完善哈。。。