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

2020-03-27  本文已影响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来进行开发。

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

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

上一篇下一篇

猜你喜欢

热点阅读