Python爬虫 爬取豆瓣电影TOP250
最近在b站上学习了一下python的爬虫,实践爬取豆瓣的电影top250,现在对这两天的学习进行一下总结
主要分为三步:
- 爬取豆瓣top250的网页,并通过正则表达式将需要的信息摘取下来
- 将这些信息存入excel表格中
- 将这些信息存入MySQL数据库中(复习一下python对数据库的操作)
对豆瓣电影TOP250网页的爬取
进入豆瓣top250
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN2XjlGcjAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL0cGVPFTUE9keRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zROBlLwIDO3EDO1QTM4ITMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
看它的URL
可以发现start=0时是第一页,点击第二页的时候是start=25,根据这个规律就可以将全部10页的网页都爬取下来。
首先要导入几个python库,分别是urllib,BeautifulSoup,re,可以解决网络请求,网页解析和正则表达式提取所需内容的问题。
代码
from bs4 import BeautifulSoup
import re
import urllib.request,urllib.error
def main():
print("开始爬取数据")
baseUrl = "https://movie.douban.com/top250?start="
dataList = getData(baseUrl) #爬取数据
print("爬取数据完成")
findLink = re.compile(r'<a href="(.*?)" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >') # 提取影片链接的正则表达式
findImgSrc = re.compile(r'<img.*src="(.*?)"',re.S) # 提取图片链接,re.S让换行符包含在字符中
findTitle = re.compile(r'<span class="title">(.*)</span>') # 提取影片名称
findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>') # 提取评分
findJudgeNum = re.compile(r'<span>(\d*)人评价</span>') # 评分人数
findInq = re.compile(r'<span class="inq">(.*)</span>') # 影片介绍
findBd = re.compile(r'<p class="">(.*?)</p>',re.S) # 影片背景
def getData(url):
dataList = []
for i in range(0,10):
tmp_url = url + str(i*25)
html = askURL(tmp_url)
# 逐一解析网页
soup = BeautifulSoup(html,"html.parser")
for item in soup.find_all("div",class_="item"):
item = str(item)
data = []
link = re.findall(findLink,item)[0] # 获取影片链接
data.append(link)
imgSrc = re.findall(findImgSrc,item)[0]
data.append(imgSrc)
title = re.findall(findTitle,item)
if len(title) == 2:
ctitle = title[0]
data.append(ctitle)
foreign_title = title[1].replace("/","")
data.append(foreign_title)
else:
ctitle = title[0]
foreign_title = " " # 没有外文名时留空
data.append(ctitle)
data.append(foreign_title)
rating = re.findall(findRating,item)[0]
data.append(rating)
judgeNum = re.findall(findJudgeNum,item)[0]
data.append(judgeNum)
inq = re.findall(findInq,item)
if len(inq) != 0:
inq = inq[0].replace("。","")
data.append(inq)
else:
inq = " "
data.append(inq)
bd = re.findall(findBd,item)[0]
bd = re.sub(r'<br(\s+)?/>(\s+)?'," ",bd) # 去掉bd中的<br/>
data.append(bd.strip()) # 去掉前后的空格
dataList.append(data)
return dataList
def askURL(url):
head = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"
}# 伪装成浏览器
req = urllib.request.Request(url,headers=head)
html = ""
try:
response = urllib.request.urlopen(req)
html = response.read().decode("utf-8")# 得到网页
except urllib.error.HTTPError as e:
if hasattr(e,"code"):
print(e.code)
if hasattr(e,"reason"):
print(e.reason)
return html
if __name__ == "__main__":
main()
分析
在main函数里创建一个getData函数来获取爬取到的网页数据,在getData函数里使用askURL函数得到每一个网页,在askURL里尤其要注意头部的伪装,将python爬虫程序伪装成浏览器访问样式。
当然豆瓣也使用了对同一ip访问过于频繁的过滤,所以如果访问的太过频繁被403了,可以考虑增加使用代理ip来伪装程序,我就在调试的过程中被ban了,不过由于不想买代理ip,并且网上找到的免费的代理ip全都没用,我就直接梯子开全局模式将https请求的ip改到别的地区了。
通过一次askURL可以得到一页25部电影的网页代码,然后先对豆瓣的这个网页代码分析一下,看看一会要提取的数据在哪些标签中,打开网页源码,找到包含电影信息的标签
<li>
<div class="item">
<div class="pic">
<em class="">1</em>
<a href="https://movie.douban.com/subject/1292052/">
<img width="100" alt="肖申克的救赎" src="https://img2.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp" class="">
</a>
</div>
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292052/" class="">
<span class="title">肖申克的救赎</span>
<span class="title"> / The Shawshank Redemption</span>
<span class="other"> / 月黑高飞(港) / 刺激1995(台)</span>
</a>
<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p class="">
导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...<br>
1994 / 美国 / 犯罪 剧情
</p>
<div class="star">
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.7</span>
<span property="v:best" content="10.0"></span>
<span>2255328人评价</span>
</div>
<p class="quote">
<span class="inq">希望让人自由。</span>
</p>
<p>
<span class="gact">
<a href="https://movie.douban.com/wish/200078450/update?add=1292052" target="_blank" class="j a_collect_btn" name="sbtn-1292052-wish" rel="nofollow">想看</a>
</span>
<span class="gact">
<a href="https://movie.douban.com/collection/200078450/update?add=1292052" target="_blank" class="j a_collect_btn" name="sbtn-1292052-collection" rel="nofollow">看过</a>
</span>
</p>
</div>
</div>
</div>
</li>
这里只简述一下,源码就不全部粘贴了,可以发现每部电影都在<div class=“item”>中,于是只需要在得到的网页中把类是item的这一块提取出来就行了
soup = BeautifulSoup(html,"html.parser")
for item in soup.find_all("div",class_="item"):
也就是这两句的作用,将得到的网页通过html解析器来解析,然后找到div标签中类是item的所有字符,剩下的就是对这些字符的正则表达式提取了,使用re库即可很容易的办到,网上也有很多关于正则表达式的讲解,比如菜鸟教程,本次用到的基本就是懒惰匹配和贪婪匹配。
将这些信息都保存到二维列表dataList中,这样每一行都保存的就是一部电影的信息,至此,对250部电影信息的提取就结束了。
将电影信息保存到excel表中
就是将列表内容保存到excel中,使用python可以很方便的解决,只需要导入xlwt库就可以了。
代码
def main():
print("开始爬取数据")
baseUrl = "https://movie.douban.com/top250?start="
dataList = getData(baseUrl) #爬取数据
print("爬取数据完成")
savePath = r'spider.xls'
saveData(dataList,savePath) #保存数据到excel中
def saveData(dataList,savePath):
book = xlwt.Workbook(encoding="utf-8")
sheet = book.add_sheet('豆瓣电影top250',cell_overwrite_ok=True)
col = ("影片链接","图片链接","影片中文名称","影片外文名","评分","评论人数","影片介绍","影片背景")
for i in range(len(col)):
sheet.write(0,i,col[i])
for k in range(len(dataList)):
data = dataList[k]
for j in range(len(data)):
sheet.write(k+1,j,data[j])
book.save(savePath)
if __name__ == "__main__":
main()
分析
主要注意的就是编码的问题,防止储存的时候出现乱码,当然把所有创建的文件都改成utf-8就完了。
保存后的结果:
可以看出保存的结果非常成功,我发现这些top250的电影大部分都看过,不过大陆电影里没有路边野餐让我有点可惜,毕赣的这个电影给人的感觉也不输这些top,这里指路一下知乎里对它的介绍吧,《路边野餐》究竟讲了个什么故事?。
将信息保存到MySQL中
温习一下python链接mysql数据库的操作,将这些东西存进数据库里,当然和上面一样引入一个pymysql的库就能大大简化操作了。
代码
def main():
print("开始爬取数据")
baseUrl = "https://movie.douban.com/top250?start="
dataList = getData(baseUrl) #爬取数据
print("爬取数据完成")
print(len(dataList))
savePath = r'spider.xls'
saveData(dataList,savePath) #保存数据到excel中
print("保存数据到excel表中完成")
sm = SaveMySQL("用户名","密码","数据库名",dataList) #保存数据到MySQL中
sm.createTable()
sm.insertData()
print("保存数据到MySQL中完成")
class SaveMySQL:
def __init__(self, user:str, password:str, database:str, dataList:List):
self.user = user
self.password = password
self.database = database
self.dataList = dataList
def createTable(self):
conn = pymysql.connect("localhost",self.user,self.password,self.database)
cur = conn.cursor()
sql1 = "drop table if exists moive_doubanTOP250;"
sql2 = '''
create table moive_doubanTOP250(
id int primary key auto_increment,
link varchar(255),
imgsrc varchar(255),
chinese_name varchar(100),
foreign_name varchar(100),
rating float,
judgeNum int,
Inq varchar(255),
Bd varchar(255)
);
'''
cur.execute(sql1)
cur.execute(sql2)
conn.commit()
conn.close()
def insertData(self):
conn = pymysql.connect("localhost",self.user,self.password,self.database)
cur = conn.cursor()
for data in self.dataList:
for index in range(len(data)):
data[index] = '"'+data[index]+'"'
sql = '''insert into moive_doubanTOP250(link,imgsrc,chinese_name,foreign_name,rating,judgeNum,Inq,Bd)
values(%s)'''%",".join(data)
cur.execute(sql)
conn.commit()
conn.close()
if __name__ == "__main__":
main()
分析
这次采取使用python类的方式来写,也当作一起复习了,主要操作就是连接数据库,然后创建游标,游标执行写好的SQL语句,然后提交,关闭连接。
在SaveMySQL这个类里主要就是2个方法,一个创建表,一个插入数据,都比较简单,需要注意的是在写插入数据的SQL的时候,用join函数把每一个数据用逗号隔开,也就是这种写法
sql = '''insert into moive_doubanTOP250(link,imgsrc,chinese_name,foreign_name,rating,judgeNum,Inq,Bd)
values(%s)'''%",".join(data)
非常方便,不愧是python。
保存结果:
发现movie拼错了。。。有点无语
中间这一些问号是爬取网页提取的时候没有换掉的\x0,没有太大的问题,想换的话直接
或者用re里面的sub都可以解决。
总结
这次学习的爬虫知识比较简单,爬的网页也只是对html进行了一些分析,上手还是很快的,基本上就是对Python的库的调用,只能说python永远滴神,当然对于防止被ban也有一定的了解了,学到了网络中应用层的一些机制,这学期计网就是我永远的痛。。。
找个时间继续深入了解一下这个爬虫吧,希望还能写下一篇爬虫的博客。
对了,附上完整的代码:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'WebSpider Module'
__author__ = 'Handson Huang'
from bs4 import BeautifulSoup
import re # 正则表达式
import urllib.request,urllib.error
import xlwt
import pymysql
from typing import List
def main():
print("开始爬取数据")
baseUrl = "https://movie.douban.com/top250?start="
dataList = getData(baseUrl) # 爬取数据
print("爬取数据完成")
print(len(dataList)) # 判断是否爬取到内容
savePath = r'spider.xls'
saveData(dataList,savePath) #保存数据到excel中
print("保存数据到excel表中完成")
sm = SaveMySQL("用户名","密码","数据库名",dataList) #保存数据到MySQL中
sm.createTable()
sm.insertData()
print("保存数据到MySQL中完成")
findLink = re.compile(r'<a href="(.*?)" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >') # 提取影片链接的正则表达式
findImgSrc = re.compile(r'<img.*src="(.*?)"',re.S) # 提取图片链接,re.S让换行符包含在字符中
findTitle = re.compile(r'<span class="title">(.*)</span>') # 提取影片名称
findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>') # 提取评分
findJudgeNum = re.compile(r'<span>(\d*)人评价</span>') # 评分人数
findInq = re.compile(r'<span class="inq">(.*)</span>') # 影片介绍
findBd = re.compile(r'<p class="">(.*?)</p>',re.S) # 影片背景
# 爬取网页数据
def getData(url):
dataList = []
for i in range(0,10):
tmp_url = url + str(i*25)
html = askURL(tmp_url)
# 逐一解析网页
soup = BeautifulSoup(html,"html.parser")
for item in soup.find_all("div",class_="item"):
item = str(item)
data = []
link = re.findall(findLink,item)[0] # 获取影片链接
data.append(link)
imgSrc = re.findall(findImgSrc,item)[0]
data.append(imgSrc)
title = re.findall(findTitle,item)
if len(title) == 2:
ctitle = title[0]
data.append(ctitle)
foreign_title = title[1].replace("/","")
data.append(foreign_title)
else:
ctitle = title[0]
foreign_title = " " # 没有外文名时留空
data.append(ctitle)
data.append(foreign_title)
rating = re.findall(findRating,item)[0]
data.append(rating)
judgeNum = re.findall(findJudgeNum,item)[0]
data.append(judgeNum)
inq = re.findall(findInq,item)
if len(inq) != 0:
inq = inq[0].replace("。","")
data.append(inq)
else:
inq = " "
data.append(inq)
bd = re.findall(findBd,item)[0]
bd = re.sub(r'<br(\s+)?/>(\s+)?'," ",bd) # 去掉bd中的<br/>
data.append(bd.strip()) # 去掉前后的空格
dataList.append(data)
return dataList
def askURL(url):
head = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"
}
req = urllib.request.Request(url,headers=head)
html = ""
try:
response = urllib.request.urlopen(req)
html = response.read().decode("utf-8")
except urllib.error.HTTPError as e:
if hasattr(e,"code"):
print(e.code)
if hasattr(e,"reason"):
print(e.reason)
return html
#保存数据到excel表里
def saveData(dataList,savePath):
book = xlwt.Workbook(encoding="utf-8")
sheet = book.add_sheet('豆瓣电影top250',cell_overwrite_ok=True)
col = ("影片链接","图片链接","影片中文名称","影片外文名","评分","评论人数","影片介绍","影片背景")
for i in range(len(col)):
sheet.write(0,i,col[i])
for k in range(len(dataList)):
data = dataList[k]
for j in range(len(data)):
sheet.write(k+1,j,data[j])
book.save(savePath)
# 保存数据到mysql里
class SaveMySQL:
def __init__(self, user:str, password:str, database:str, dataList:List):
self.user = user
self.password = password
self.database = database
self.dataList = dataList
def createTable(self):
conn = pymysql.connect("localhost",self.user,self.password,self.database)
cur = conn.cursor()
sql1 = "drop table if exists moive_doubanTOP250;"
sql2 = '''
create table moive_doubanTOP250(
id int primary key auto_increment,
link varchar(255),
imgsrc varchar(255),
chinese_name varchar(100),
foreign_name varchar(100),
rating float,
judgeNum int,
Inq varchar(255),
Bd varchar(255)
);
'''
cur.execute(sql1)
cur.execute(sql2)
conn.commit()
conn.close()
def insertData(self):
conn = pymysql.connect("localhost",self.user,self.password,self.database)
cur = conn.cursor()
for data in self.dataList:
for index in range(len(data)):
data[index] = '"'+data[index]+'"'
sql = '''insert into moive_doubanTOP250(link,imgsrc,chinese_name,foreign_name,rating,judgeNum,Inq,Bd)
values(%s)'''%",".join(data)
cur.execute(sql)
conn.commit()
conn.close()
if __name__ == "__main__":
main()