美文网首页
Python学习笔记---豆瓣电影爬虫记

Python学习笔记---豆瓣电影爬虫记

作者: 小小的大雄 | 来源:发表于2020-03-27 00:20 被阅读0次
    豆瓣是个好网站,能学习各种知识、能社交、能找房子、找驴友等。

    于我而言,最大的快乐在于豆瓣能提供丰富的在线数据,如:书/电影/音乐等,不仅有这些的基础数据,还有各种榜单/评论等,数据结构也相当丰富。在本人最开始写前端的时候,曾在网上寻找各种在线数据来进行页面的渲染,直到后面找到豆瓣的时候发现竟然还有官方的API提供,可畏业界良心。后面学习各种框架都是界面参考豆瓣,数据直接用API获取,方便得不行。后面学习小程序的时候,也想用直接用豆瓣的API,发现小程序直接调用的时候会出现403(好像跟小程序的UA有关系),后面自己本地起了nginx做了代理就ok了。最近想学点新东西,突然想先去看看豆瓣的API文档,结果发现网站打不开了,所有的api都关掉了。那正好,就学Python吧,我记得我第一次听说Python好像爬虫很厉害,那就试试吧。

    之前有看过一些基本的语法,贴上几个网站 官方中文文档runoob.com-超全的学习网站


    Mac和Linux最新版好像都带自带了python,我自己是Mac,所以用试了一下:

    python
    
    默认python版本.png

    可以看出默认的python是2.7.16,目前最新版本是3.x。
    退出,狂按Command + C,发现没反应,再输入exit,结果提示

    >>> exit
    Use exit() or Ctrl-D (i.e. EOF) to exit
    

    原来python命令的退出方式跟其它的脚本不一样呀,Mac上为 command+d 或者 输入exit() 加回车。
    之前看的文档上有提过,可以使用python3来使用最新版本的python,试了一下:


    pyhtone3.png

    Mac也内置了python3.7.3,目前官方最新版本为3.8.2,还是比较新的,可能由于我的Mac OS是最新版的原因吧。
    python跟node/java一样,安装完环境之后可以直接在命令行里面运行一些简单的指令,如下:


    简单python交互.png
    说明:
    print()是打印语句,类似于console()等。
    其中有一句print("2+3=", 2+3), 在print()方法中可以进行简单的运算。
    in = input(); print("输入的是:"+ in);
        in = input(); print("输入的是:"+ in);
     ^
    SyntaxError: invalid syntax // 这里出错,是因为in是一个关键字,不能用来定义变量
    

    python的其它语法就需要在上面我贴的两个网址去学习哈。

    上面演示的是在命令行运行简单代码,Python也支持以文件的方式运行。
    后面我用vs code来进行开发。

    • 爬虫实战
      新建一个test.py文件,里面可以写上
    print('my first py program ')
    

    然后,在vs 的termial里面运行 test.py,记得在运行之后要保存文件哦。

    python3 test.py
    my first py program
    

    ok,这样我们就知道怎么用一个文件去执行我们写的python代码了。

    接下来,我们先分析一下豆瓣电影(top250)爬虫的需求:

    1. 最终的数据结构:json格式,方便传输与转换。
    单一电影的数据结构,最后生成一个list
    {'ranking': '25', 'title': '触不可及', 'orther_title': '/闪亮人生(港)  /  逆转人生(台)', 'director': ' 奥利维·那卡什 Olivier Nakache / 艾力克·托兰达 Eric Toledano', 'year': '2011', 'region': '剧情 喜剧', 'douban_href': 'https://movie.douban.com/subject/6786002/', 'average_rating': '9.2', 'votes': '684449', 'short_quote': '满满温情的高雅喜剧。'}
    
    1. 数据源网址:豆瓣电影 Top 250
      爬虫的大致原理是:用http方法获取到源网址的整体dom结构,然后根据正则表达式来进行匹配,最终过滤出想要的数据。
      所以我们先去源网站观察一下dom结构,

      电影榜单dom对照.png
      我们需要的数据,是包含在一个<ol></ol>标签对里面,而且经过搜索可以发现,整个dom树里面只含有一个<ol></ol>标签对。
      我们知道了dom的结构,就可以将我们所需要的数据一一拆解出来了。
      这样我们的需求就清楚了,接下来就要看怎么具体实现了。
    2. 先让我们的程序可以访问到源网址,https://movie.douban.com/top250

    # test.py
    # print('my first py program')
    # 1、引入需要要的包  
    # 网络请求相关的包
    from urllib.request import urlopen, Request
    import ssl
    # 正则相关的包
    import re
    # 配置https请求
    ssl._create_default_https_context = ssl._create_unverified_context 
    # 设置数据源地址
    source_url = 'https://movie.douban.com/top250'
    # 自定义http request请求头,豆瓣默认只允许部分UA进行访问 这里自定义User-Agent成浏览器的UA
    custom_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'}
    # 初始化Request
    movie_request = Request(source_url, headers = custom_headers)
    # 发出request请求
    movie_response = urlopen(movie_request)
    # 将读取结果进行utf-8解码,避免乱码
    html = movie_response.read().decode('utf-8')
    # 测试打印结果        
    print(html)
    

    我们在termial里面运行代码,可以看到正常的打印结果,如下图:


    获取源网址返回的结果.png

    这是整个页面的dom结果,这个效果和我们在浏览器里面通过右键>显示网页源代码所看到的内容是一致的。

    1. 接下来,我们将所需要的<ol></ol>标签对里面的内容从完整的dom树里面匹配出来,并保存至一个本地文件里面。
      我们先看一下单个电影数据在dom中的结构是什么样的。
    <li>
                <div class="item">
                    <div class="pic">
                        <em class="">8</em>
                        <a href="https://movie.douban.com/subject/1295124/">
                            <img width="100" alt="辛德勒的名单" src="https://img3.doubanio.com/view/photo/s_ratio_poster/public/p492406163.jpg" class="">
                        </a>
                    </div>
                    <div class="info">
                        <div class="hd">
                            <a href="https://movie.douban.com/subject/1295124/" class="">
                                <span class="title">辛德勒的名单</span>
                                        <span class="title">&nbsp;/&nbsp;Schindler&#39;s List</span>
                                    <span class="other">&nbsp;/&nbsp;舒特拉的名单(港)  /  辛德勒名单</span>
                            </a>
    
    
                                <span class="playable">[可播放]</span>
                        </div>
                        <div class="bd">
                            <p class="">
                                导演: 史蒂文·斯皮尔伯格 Steven Spielberg&nbsp;&nbsp;&nbsp;主演: 连姆·尼森 Liam Neeson...<br>
                                1993&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>753795人评价</span>
                            </div>
    
                                <p class="quote">
                                    <span class="inq">拯救一个人,就是拯救整个世界。</span>
                                </p>
                        </div>
                    </div>
                </div>
            </li>
    

    单个电影是包含在<li></li>标签对里面的,而<ol></ol>里面含有多个<li></li>标签对,所以首先要把这些<li></li>标签对一个个的匹配出来,然后写到本地文件中。
    具体代码如下:

    # 定义一个函数,获取所有跟电影相关的<li></li>
    def getMovieItems(content):
    # 匹配正则表达式 [\s\n]*表示,在<li>标签和<div之前可能会有多个空格(\s)或者换行符(回车或\n)  .*表示 除换行符以外的字符可能多次重复 ?表示匹配0个或1个由前面的正则表达式定义的片段
    reg = '<li>[\s\n]*<div.*?</li>'
    # 生成一个正则表达式对象 re.S表示 使 .匹配包括换行在内的所有字符
    item_wrap = re.compile(reg, re.S)
    print(item_wrap)
    # 在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表
    movie_items = re.findall(item_wrap, html)
    return movie_items
    
    # 将从源网址获取到的结果进行匹配
    movies = getMovieItems(html)
    # 打印匹配结果 len()表示数组长度
    print(movies, len(movies))
    

    运行程序, python3 test.py,可以看到下图的结果:

    25条电影数据匹配结果.png

    我们打印了匹配后的数组,以及数组长度25。 由于250条数据量太大,默认一页只显示25条,我们暂且先看这25条的数据,后面可以增加分页参数,获取剩下的数据。

    ok,到这里我们就已经获取到我们所需要的电影数据源,我们要在此基础上进行数据匹配:

    # 定义一个列表,用来存最终数据
    list = []
    # 循环遍历
    for m in movies:
        # 删除掉 &nbsp;   
        m = re.sub('&nbsp;', '', m)
        dic = {}
        # 评分
        reg = '<em class="">(.*)</em>'
        ranking = re.findall(re.compile(reg), m)
        print(ranking)
        dic['ranking'] = ranking[0]
    
        # title
        reg = '<span class="title">(.*)</span>'
        title = re.findall(re.compile(reg), m)
        print(title)
        dic['title'] = title[0]
    
        # orther_title
        reg = '<span class="other">(.*)</span>'
        orther_title = re.findall(re.compile(reg), m)
        print(orther_title)
        dic['orther_title'] = orther_title[0]
    
        # type_people  导演: 弗兰克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·罗宾斯 Tim Robbins /...<br>   1994&nbsp;/&nbsp;美国&nbsp;/&nbsp;犯罪 剧情
        reg = r'<p class="">(.*?)</p>'
        type_people = re.findall(re.compile(reg, re.S), m)[0]
        # 删除掉 &nbsp;   
        type_people = re.sub('&nbsp;', '', type_people)
        # 删除掉 <br>;   
        type_people = re.sub('<br>', '', type_people)
        # 删除掉 回车/换行符;   
        type_people = re.sub('\n', '', type_people)
        # 删除掉 空格;   
        type_people = re.sub('\s', '', type_people)
        print('type_people', type_people)
    
        # director 导演 匹配导演: 与 主演之前的内容 有些可能没有主演的演字...
        reg = r'导演:(.*)主'
        director = re.findall(re.compile(reg, re.S), type_people)
        print(director)
        dic['director'] = director[0]
    
         # year 年份  匹配4位数字
        reg = r'\d+'
        year = re.findall(re.compile(reg), type_people)
        print(year)
        dic['year'] = year[0]
    
        # actors 主演 从主演: 开始 截取至 4位年份止
        reg = r'主演:(.*)\d{4}'
        director = re.findall(re.compile(reg), type_people)
        print(director)
        if (len(director)):
            dic['director'] = director[0]
    
        # region 国家  从4位年份+'/'截取至下一个'/'
        reg = r'\d{4}/(.*)/'
        region = re.findall(re.compile(reg), type_people)
        print(region)
        if (len(region)):
            dic['region'] = region[0]
        
        # type 类型
        reg = r'\d{4}/.*/(.*)'
        region = re.findall(re.compile(reg), type_people)
        print(region)
        if (len(region)):
            dic['region'] = region[0]
    
        # douban_href <a href="https://movie.douban.com/subject/1292052/" class="">
        reg = r'<a href="(.*?)">'
        douban_href = re.findall(re.compile(reg, re.S), m)
        print(douban_href)
        dic['douban_href'] = douban_href[0]
    
        # average_rating
        reg = '<span class="rating_num" .*>(.*?)</span>'
        average_rating = re.findall(re.compile(reg), m)
        print(average_rating)
        dic['average_rating'] = average_rating[0]
    
        # votes <span>1944072人评价</span>
        reg = '<span>(.*)人评价</span>'
        votes = re.findall(re.compile(reg), m)
        print(votes)
        dic['votes'] = votes[0]
    
        # short_quote <span class="inq">
        reg = '<span class="inq">(.*)</span>'
        short_quote = re.findall(re.compile(reg, re.S), m)
        print(short_quote)
        dic['short_quote'] = short_quote[0]
        list.append(dic)
    
    print(list)
    

    我们执行python3 test.py,就可以看到结果:


    最终电影数组.png

    这样,我们就已经获取到了完整的25条电影数据了,接下来,我们可以将这些数据写入到本地文件,可以拿来给别人分享或者在其它程序里面用啦,具体代码如下:

    import json
    
    json_str = json.dumps(list,  indent=4, ensure_ascii=False)
    json_file = open('movie-douban-top250.json', 'w+')
    json_file.writelines(json_str)
    

    可以直接写在代码最后面,运行之后在test.py同级目录会生成一个movie-douban-top250.json的文件,如下图所示:


    最终完整的json文件.png

    好了,到此我们已经完整的实现了之前的需求,获取原网址的电影数据,并生成了一个本地的json文件。

    写代码的过程中查询了很多文章(感谢万能的bing...),花了好长时间才写完,写文章的时候又花了好几个小时,真心不容易。

    最后,贴上完整的代码,大家可以下载试试哦。
    https://github.com/realfly08/learn-python/blob/master/test.py

    觉得可以的话,请点个赞哦!

    相关文章

      网友评论

          本文标题:Python学习笔记---豆瓣电影爬虫记

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