豆瓣电影反爬虫机制升级了,网上的Python爬虫教程基本上都不能用了。以前直接requests.get()就能获取的页面现在是<Response [418]>错误。2020年正确的豆瓣电影爬虫姿势是怎样的?
Request请求要带Headers
Headers是什么?就是你向豆瓣服务器索要网页时候附带递过去的名片,这里记录了你的全部个人信息。
怎样搞到headers
浏览器每次向豆瓣服务器发出请求的时候都会带名片一起送过去,所以我们只要找到浏览器送的这张名片,用Python发送请求的时候也打包送过去,那么就也能获得数据。
使用谷歌浏览器Chrome,以我们要抓取的TOP250电影数据为例,https://movie.douban.com/top250
,打开这个地址,页面空白处【右键/检查】打开开发工具面板,如下图所示。
在开发工具面板点【Network】【top250(网页地址最后一段)】,右侧【Headers】下面就会有一组【Request Headers】内容,然后用鼠标划选全部,右键复制。
转化Headers格式
然后使用下面的代码把这段Headers字符变为可以使用的字典对象格式,{'a':'xxx','b':'yyy'...}
。
header_str='''
Accept: text/html,application/xhtml+xml,application/...
这一段应该是粘贴过来的Headers
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac...
'''
#字符串转dict
def str2dict(s,s1=';',s2='='):
li=s.split(s1)
res={}
for kv in li:
li2=kv.split(s2)
if len(li2)>1:
li2[0]=li2[0].replace(':','')
res[li2[0]]=li2[1]
return res
headers=str2dict(header_str,'\n',': ')
if 'Content-Length' in headers:del headers['Content-Length']
headers
得到输出类似:
{'Accept': 'text/html...,
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac ...}
发送请求获取数据
使用下面的代码获取页面数据。
import requests as req
r = req.get('https://movie.douban.com/top250', headers=headers)
r.text
这一次终于获取成功得到数据,但都是乱码,类似下面这种。
注意!这个乱码不是编码问题,而是网页数据压缩问题!
乱码的原因
注意到我们上面的Headers的输出包含了` 'Accept-Encoding': 'gzip, deflate, br'这么一段。
再注意Response Headers这个信息,它是豆瓣递回来的名片,上面有那么一行Content-Encoding: br
。
这一来一去的两个名片的意思是:
- “给我那个网页,gzip、deflate、br三种压缩方式哪个都能接受。”
- “好的,给你br压缩的。”
- “为什么不用另外两种压缩?”
- “那两种是老格式,Python爬虫都认识,br是新的,爬虫们看起来都是乱码。你不是爬虫吧?”
- “额...”
解决乱码问题
要想破解这个恶劣逻辑,最简单的办法是开头就说,““给我那个网页,别压缩”。代码改一下就是:
import requests as req
headers['accept-encoding'] = 'gzip'
r = req.get('https://movie.douban.com/top250', headers=headers)
pagetxt = r.text
pagetxt
得到正确的文字。
其实我们也可以接受gzip格式压缩,Python默认就支持。如果我们说可以接受br格式的,那么就要想办法对br压缩的乱码重新解码,就需要使用conda install brotli
或者命令行工具中pip install brotli
。然后可以参照下面的代码进行转换。
import brotli
headers['accept-encoding'] = 'br'
r = req.get('https://movie.douban.com/top250', headers=headers)
key = 'Content-Encoding'
pagetxt = res.text
if (key in r.headers and r.headers['Content-Encoding'] == 'br'):
dt = brotli.decompress(r.content)
txt = dt.decode('utf-8')
pagetxt = txt
pagetxt
获取当前页面电影数据
怎样才能获取每个电影的信息?我们先只看一个电影的情况,在电影标题上右击,检查。
在Elements面板下,鼠标上下划过每一行代码,网页上就会高亮显示对应的元素。如下图所示,划到<div class="item">...
这一行的时候整个电影的栏目都被完整高亮了,这表示每个电影信息都对应一个<div class="item">...</div>
。
我们可以使用BeautifulSoup工具的soup.find_all('div', class_='item')
把它们都选择出来,然后添加到allitems
列表里面。如下图所示代码:
from bs4 import BeautifulSoup
allitems = []
soup = BeautifulSoup(pagetxt)
allitems += soup.find_all('div', class_='item')
len(allitems)
分析每页地址变化
点击豆瓣电影页面底部的分页按钮,对照观察浏览器地址变化,可以看到每增加一页,地址栏中的start就增加25。
第一页应该就是https://movie.douban.com/top250?start=0&filter=
,所以后面就是[0,25,50,75,100,125,150,175,200,225]
,这个数列也可以写成range(0,250,25)
。
整理成通用函数
我们需要把上面处理1个页面的办法做成一个命令包,这样就可以用它来提取每一页的电影数据。
import requests as req
from bs4 import BeautifulSoup
def readOnePage(n):
headers['accept-encoding'] = 'gzip'
r = req.get('https://movie.douban.com/top250?start='+str(n*25) ,headers=headers)
soup = BeautifulSoup(r.text)
items = soup.find_all('div', class_='item')
return items
测试一下readOnePage(1)
可以检查读取第2页的结果是否正确。
循环获取全部10页
直接使用下面代码进行获取。
allitems=[]
for i in range(10):
allitems+=readOnePage(i)
allitems
可以用len(allitems)
查看总数是250部电影。
解析单部电影数据
我们可以使用allitems[2]
查看第三部电影的信息,如下所示。
<div class="item">
<div class="pic">
<em class="">3</em>
<a href="https://movie.douban.com/subject/1292720/">
<img width="100" alt="阿甘正传"
src="https://img9.doubanio.com/view/photo/s_ratio_poster/public/p1484728154.webp" class="">
</a>
</div>
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292720/" class="">
<span class="title">阿甘正传</span>
<span class="title"> / Forrest Gump</span>
<span class="other"> / 福雷斯特·冈普</span>
</a>
<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p class="">
导演: 罗伯特·泽米吉斯 Robert Zemeckis 主演: 汤姆·汉克斯 Tom Hanks / ...<br>
1994 / 美国 / 剧情 爱情
</p>
<div class="star">
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.5</span>
<span property="v:best" content="10.0"></span>
<span>1530176人评价</span>
</div>
<p class="quote">
<span class="inq">一部美国近现代史。</span>
</p>
</div>
</div>
</div>
怎样获取标题文字?尝试下面的代码:
import unicodedata
title=allitems[2].find('div',class_='hd').find('a').text
title=title.replace('\n','')
unicodedata.normalize("NFKD", title)
它输出'阿甘正传 / Forrest Gump / 福雷斯特·冈普'
。
我们使用下面的代码可以获得更多信息。
import re
def getinfo(item):
txt = item.find('div', class_='bd').find('p').text
info={
'title':item.find('div',class_='hd').find('a').text,
'daoyan' : re.compile("导演:\\s(.*_?)[\xa0\.\.\.]").findall(txt)[0].replace('导演:',''),
'nianfen' : txt.split('\n')[2].split('/')[0],
'guojia':txt.split('\n')[2].split('/')[1],
'leixing':txt.split('\n')[2].split('/')[2]
}
for k in info:
info[k]=unicodedata.normalize("NFKD", info[k]).strip().replace('\n','')
return info
在上面这里使用了正则表达式提取导演信息,由于有些电影没有显示主演信息,所以这里就不提取了。
re.compile("导演:\\s(.*_?)[\xa0\.\.\.]").findall(txt)
这句话是正则表达式,其中(.*?)
表示要被提取的内容。在这段代码中的txt是类似\n 导演: 罗伯特·泽米吉斯 Robert Zemeckis\xa0\xa0\xa0主演: 汤姆·汉克斯 Tom Hanks / ...\n 1994\xa0/\xa0美国\xa0/\xa0剧情 爱情\n
这样的字符串。
提取多部电影信息
使用下面的代码进行批量解析并存储为excel文件。
import pandas as pd
df=pd.DataFrame.from_dict(movies)
df.to_excel('Movies250.xlsx')
df
输出如下:
欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】
每个人的智能新时代
如果您发现文章错误,请不吝留言指正;
如果您觉得有用,请点喜欢;
如果您觉得很有用,欢迎转载~
END
网友评论