Python软件技巧

Python爬虫-豆瓣电影2020最新版

2020-05-22  本文已影响0人  zhyuzh3d

豆瓣电影反爬虫机制升级了,网上的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

这一来一去的两个名片的意思是:

解决乱码问题

要想破解这个恶劣逻辑,最简单的办法是开头就说,““给我那个网页,别压缩”。代码改一下就是:

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">&nbsp;/&nbsp;Forrest Gump</span>
                <span class="other">&nbsp;/&nbsp;福雷斯特·冈普</span>
            </a>
            <span class="playable">[可播放]</span>
        </div>
        <div class="bd">
            <p class="">
                导演: 罗伯特·泽米吉斯 Robert Zemeckis&nbsp;&nbsp;&nbsp;主演: 汤姆·汉克斯 Tom Hanks / ...<br>
                1994&nbsp;/&nbsp;美国&nbsp;/&nbsp;剧情 爱情
            </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

上一篇 下一篇

猜你喜欢

热点阅读