美文网首页Python软件技巧
Python爬虫-豆瓣电影2020最新版

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

作者: zhyuzh3d | 来源:发表于2020-05-22 00:05 被阅读0次

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

    相关文章

      网友评论

        本文标题:Python爬虫-豆瓣电影2020最新版

        本文链接:https://www.haomeiwen.com/subject/ejcxahtx.html