返回介绍

13.5 项目实战:爬取豆瓣电影信息

发布于 2024-02-05 21:13:20 字数 10438 浏览 0 评论 0 收藏 0

最后,我们来完成一个使用代理爬取的实战项目。豆瓣网的电影专栏是国内权威电影评分网站,其中包括海量影片信息,在浏览器中访问https://movie.douban.com,并选择一个分类(如“豆瓣高分”),可看到如图13-4所示的影片列表页面。

图13-4

单击其中一部影片,进入其页面(简称影片页面),如图13-5所示。

在影片页面中可以看到一部影片的基本信息,如导演、编剧、主演、类型等,我们可以编写爬虫在豆瓣电影中爬取大量影片信息。

图13-5

13.5.1 项目需求

爬取豆瓣电影中“豆瓣高分”分类下的所有影片信息,需要爬取一部影片的信息字段如下:

导演

编剧

主演

类型

制片国家/地区

语言

上映日期

片长

又名

由于豆瓣网对爬取速度做了限制,高速爬取可能会被封禁IP,因此使用代理进行爬取。

13.5.2 页面分析

首先分析影片列表页面,页面中的每一部电影都是通过JavaScript脚本加载的。单击页面最下方的“加载更多”,可以在Chrome开发者工具中捕获到jQuery发送的HTTP请求(加载更多影片),该请求返回了一个json串,如图13-6所示。

图13-6

复制图中请求的url,使用scrapy shell进行访问,查看其中json串的内容:

  $ scrapy shell
'https://movie.douban.com/j/search_subjects?type=movie&tag=%E8%B1%86%E7%93%A3%E9%AB%98
%E5%88%86&sort=recommend&page_limit=20&page_start=20'
  ...
>>> import json
>>> res = json.loads(response.body.decode('utf8'))
>>> res
{'subjects': [
 {'cover': 'https://img1.doubanio.com/view/movie_poster_cover/lpst/public/p511146957.jpg',
  'cover_x': 1538,
  'cover_y': 2159,
  'id': '1292001',
  'is_beetle_subject': False,
  'is_new': False,
  'playable': False,
  'rate': '9.2',
  'title': '海上钢琴师',
  'url': 'https://movie.douban.com/subject/1292001/'},
  {'cover': 'https://img1.doubanio.com/view/movie_poster_cover/lpst/public/p2360940399.jpg',
  'cover_x': 1500,
  'cover_y': 2145,
  'id': '25986180',
  'is_beetle_subject': False,
  'is_new': False,
  'playable': False,
  'rate': '8.2',
  'title': '釜山行',
  'url': 'https://movie.douban.com/subject/25986180/'},
  {'cover': 'https://img1.doubanio.com/view/movie_poster_cover/lpst/public/p2404978988.jpg',
  'cover_x': 703,
  'cover_y': 1000,
  'id': '26580232',
  'is_beetle_subject': False,
  'is_new': False,
  'playable': False,
  'rate': '8.7',
  'title': '看不见的客人',
  'url': 'https://movie.douban.com/subject/26580232/'},

...省略中间部分...

  {'cover': 'https://img3.doubanio.com/view/movie_poster_cover/lpst/public/p1454261925.jpg',
  'cover_x': 2181,
  'cover_y': 3120,
  'id': '6786002',
  'is_beetle_subject': False,
  'is_new': False,
  'playable': False,
  'rate': '9.1',
  'title': '触不可及',
  'url': 'https://movie.douban.com/subject/6786002/'},
  {'cover': 'https://img3.doubanio.com/view/movie_poster_cover/lpst/public/p2411622136.jpg',
  'cover_x': 1000,
  'cover_y': 1500,
  'id': '26354572',
  'is_beetle_subject': False,
  'is_new': False,
  'playable': True,
  'rate': '8.2',
  'title': '欢乐好声音',
  'url': 'https://movie.douban.com/subject/26354572/'},
  {'cover': 'https://img3.doubanio.com/view/movie_poster_cover/lpst/public/p1280323646.jpg',
  'cover_x': 1005,
  'cover_y': 1437,
  'id': '1299398',
  'is_beetle_subject': False,
  'is_new': False,
  'playable': False,
  'rate': '8.9',
  'title': '大话西游之月光宝盒',
  'url': 'https://movie.douban.com/subject/1299398/'}
]}

如上所示,返回结果(json)中的'subjects'字段是一个影片信息列表,一共有20项,每一项都是一部影片的信息,其中可以找到影片片名(title)、评分(rate)以及影片页面url等信息。

连续单击加载按钮,捕获更多jQuery发送的HTTP请求,可以总结出其url的规律:

type参数:类型,movie代表电影。

tag参数:分类标签,当前为“豆瓣高分”。

page_start:从第几部影片开始加载,即结果列表中第一部影片在服务器端的序号。

page_limit:期望获取的影片信息的数量,当前为20。

我们可以通过分析出的API,每次获取固定数量的影片信息,从中提取每一个影片页面的url,例如:

先获取20部影片信息:

[BASE_URL]?type=movie&tag=豆瓣高分&sort=recommend&page_limit=20&page_start=0

再获取20部影片信息:

[BASE_URL]?type=movie&tag=豆瓣高分&sort=recommend&page_limit=20&page_start=20

再获取20部影片信息:

[BASE_URL]?type=movie&tag=豆瓣高分&sort=recommend&page_limit=20&page_start=40

直到返回结果中的影片信息列表为空,说明没有影片了。

接下来分析影片页面。在scrapy shell中下载任意一个影片页面,并调用view函数在浏览器中查看页面,如图13-7所示。

图13-7

从图13-7中可以看出,影片的信息在<div id="info">中,其中每一个信息字段名("导演","编剧"等)都位于一个<span class="pl">中,比较容易提取,但字段的值很难找到统一的规律,我们可以使用XPath的string函数将<div id="info">中的所有文本提取到一个字符串,然后用提取到的字段名分割该字符串,得到其中值的部分。

首先提取包含所有信息的字符串:

  >>> info = response.css('div#info').xpath('string(.)').extract_first()
  >>> print(info)
  导演:涅提·蒂瓦里
  编剧:比于什·古普塔 / 施热亚·简
  主演:阿米尔·汗 / 法缇玛·萨那·纱卡 / 桑亚·玛荷塔 / 阿帕尔夏克提·库拉那 / 沙克希·坦
沃
  / 泽伊拉·沃西姆 / 苏哈妮·巴特纳格尔 / 里特维克·萨霍里 / 吉里什·库卡尼
  类型:剧情 / 传记 / 运动
  制片国家/地区:印度
 语言:印地语
  上映日期:2017-05-05(中国大陆) / 2016-12-23(印度)
  片长:161分钟(印度) / 140 分钟(中国大陆)
  又名:我和我的冠军女儿(台) / 打死不离3 父女(港) / 摔跤吧!老爸 / 摔跤家族 / ???? /
  Wrestling Competition
  IMDb链接: tt5074352

再提取所有字段名到一个列表:

>>> fields = [s.strip().replace(':', '') for s in \
...     response.css('div#info span.pl::text').extract()]
>>> fields
['导演','编剧','主演','类型','制片国家/地区','语言','上映日期','片长','又名','IMDb 链接']

使用字段名对info进行分割,得到所有值的列表:

>>> import re
>>> values = [re.sub('\s+', ' ', s.strip()) for s in \
...     re.split('\s*(?:%s):\s*' % '|'.join(fields), info)][1:]
>>> values
['涅提·蒂瓦里',
'比于什·古普塔 / 施热亚·简',
'阿米尔·汗 / 法缇玛·萨那·纱卡 / 桑亚·玛荷塔 / 阿帕尔夏克提·库拉那 / 沙克希·坦沃 /
泽伊拉·沃西姆 / 苏哈妮·巴特纳格尔 / 里特维克·萨霍里 / 吉里什·库卡尼',
 '剧情 / 传记 / 运动',
 '印度',
 '印地语',
 '2017-05-05(中国大陆) / 2016-12-23(印度)',
 '161 分钟(印度) / 140 分钟(中国大陆)',
 '我和我的冠军女儿(台) / 打死不离3 父女(港) / 摔跤吧!老爸 / 摔跤家族 / ???? /
Wrestling Competition',
 'tt5074352']

最后,使用以上两个列表构造影片信息字典:

  >>> dict(zip(fields, values))
  {'IMDb链接': 'tt5074352',
   '上映日期': '2017-05-05(中国大陆) / 2016-12-23(印度)',
   '主演':'阿米尔·汗 / 法缇玛·萨那·纱卡 / 桑亚·玛荷塔 / 阿帕尔夏克提·库拉那 / 沙克
希·坦沃 /
  泽伊拉·沃西姆 / 苏哈妮·巴特纳格尔 / 里特维克·萨霍里 / 吉里什·库卡尼',
   '制片国家/地区': '印度',
   '又名':'我和我的冠军女儿(台) / 打死不离3 父女(港) / 摔跤吧!老爸 / 摔跤家族 / ???? /
  Wrestling Competition',
   '导演':'涅提·蒂瓦里',
   '片长': '161 分钟(印度) / 140 分钟(中国大陆)',
   '类型':'剧情 / 传记 / 运动',
   '编剧': '比于什·古普塔 / 施热亚·简',
   '语言':'印地语'}

经上述操作,我们得到了除片名和评分之外的所有信息,片名和评分信息可以在json串中获取。

到此,页面分析的工作完成了。

13.5.3 编码实现

创建Scrapy项目,取名为douban_movie:

$ scrapy startproject douban_movie

在页面分析中,我们详细阐述了爬取过程,现在可以轻松实现MoviesSpider了,代码如下:

# -*- coding: utf-8 -*-
import scrapy
  from scrapy import Request
  import json
  import re
  from pprint import pprint

  class MoviesSpider(scrapy.Spider):
    BASE_URL = 'https://movie.douban.com/j/search_subjects?type=movie&tag=%s&sort=
recommend&page_limit=%s&page_start=%s'
    MOVIE_TAG   = '豆瓣高分'
    PAGE_LIMIT = 20
    page_start = 0

    name = "movies"
    start_urls = [BASE_URL % (MOVIE_TAG, PAGE_LIMIT, page_start)]

    def parse(self, response):
    # 使用json 模块解析响应结果
    infos = json.loads(response.body.decode('utf-8'))

    # 迭代影片信息列表
    for movie_info in infos['subjects']:
      movie_item = {}

      # 提取"片名"和"评分", 填入item.
      movie_item['片名'] = movie_info['title']
      movie_item['评分'] = movie_info['rate']

      # 提取影片页面url,构造Request 发送请求,并将item通过meta 参数传递给影片
页面解析函数
      yield Request(movie_info['url'], callback=self.parse_movie,
                meta={'_movie_item': movie_item})

    # 如果json 结果中包含的影片数量小于请求数量,说明没有影片了,否则继续搜索
    if len(infos['subjects']) == self.PAGE_LIMIT:
      self.page_start += self.PAGE_LIMIT
      url = self.BASE_URL % (self.MOVIE_TAG, self.PAGE_LIMIT, self.page_start)
      yield Request(url)
def parse_movie(self, response):
 # 从meta 中提取已包含"片名"和"评分"信息的item
 movie_item = response.meta['_movie_item']

 # 获取整个信息字符串
 info = response.css('div.subject div#info').xpath('string(.)').extract_first()

 # 提取所有字段名
 fields= [s.strip().replace(':', '') for s in \
       response.css('div#info span.pl::text').extract()]

 # 提取所有字段的值
 values = [re.sub('\s+', ' ', s.strip()) for s in \
       re.split('\s*(?:%s):\s*' % '|'.join(fields), info)][1:]

 # 将所有信息填入item
 movie_item.update(dict(zip(fields, values)))

 yield movie_item

为了使用代理进行爬取,我们将之前实现的RandomHttpProxyMiddleware复制到该项目中。

在配置文件settings.py中添加如下配置:

# 我们的爬取不符合豆瓣爬取规则,强制爬取
ROBOTSTXT_OBEY = False

# 伪装成常规浏览器
USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) Chrome/42.0.2311.90 Safari/537.36'

# 可选。设置下载延迟,防止代理被封禁IP,依据代理数量而定
DOWNLOAD_DELAY = 0.5

# 启用随机代理中间件
DOWNLOADER_MIDDLEWARES = {
 'douban_movie.middlewares.RandomHttpProxyMiddleware': 745,
}

# 指定所要使用的代理服务器列表文件
HTTPPROXY_PROXY_LIST_FILE = 'proxy_list.json'

为了爬取稳定,使用在云服务器上自行搭建的代理服务器,手工编辑代理服务器列表文件proxy_list.json:

[
{"proxy_scheme": "https", "proxy": "http://116.29.35.201:8118"},
{"proxy_scheme": "https", "proxy": "http://197.10.171.143:8118"},
{"proxy_scheme": "https", "proxy": "http://112.78.43.67:8118"},
{"proxy_scheme": "https", "proxy": "http://124.59.42.145:8118"}
]

最后,运行爬虫,将结果保存到文件moveis.json:

$ scrapy crawl movies -o movies.json

在Python中观察爬取结果,代码如下:

>>> import json
>>> movies = json.load(open('movies.json'))
>>> for movie in movies:
...  print(movie['片名'], movie['评分'], movie['导演'])
...
忠犬八公的故事 9.2 拉斯·霍尔斯道姆
楚门的世界 9.0 彼得·威尔
怦然心动 8.9 罗伯·莱纳
泰坦尼克号 9.2 詹姆斯·卡梅隆
血战钢锯岭 8.7 梅尔·吉布森
驴得水 8.3 周申 / 刘露
星际穿越 9.1 克里斯托弗·诺兰
盗梦空间 9.2 克里斯托弗·诺兰
阿甘正传 9.4 罗伯特·泽米吉斯
千与千寻 9.2 宫崎骏
三傻大闹宝莱坞 9.1 拉吉库马尔·希拉尼
霸王别姬 9.5 陈凯歌
你的名字 8.5 新海诚
金刚狼3:殊死一战 8.3 詹姆斯·曼高德
疯狂动物城 9.2 拜伦·霍华德 / 瑞奇·摩尔 / 杰拉德·布什
爱乐之城 8.3 达米恩·查泽雷
大话西游之月光宝盒 8.9 刘镇伟
触不可及 9.1 奥利维·那卡什 / 艾力克·托兰达
无间道 9.0 刘伟强 / 麦兆辉
让子弹飞 8.7 姜文
...省略中间部分...
哈尔的移动城堡 8.8 宫崎骏
阿凡达 8.6 詹姆斯·卡梅隆
教父 9.2 弗朗西斯·福特·科波拉
罗马假日 8.9 威廉·惠勒
龙猫 9.1 宫崎骏
火星救援 8.4 雷德利·斯科特
超能陆战队 8.6 唐·霍尔 / 克里斯·威廉姆斯
欢乐好声音 8.2 加斯·詹宁斯 / 克里斯托夫·卢尔德莱
少年派的奇幻漂流 9.0 李安
釜山行 8.2 延尚昊
大话西游之大圣娶亲 9.2 刘镇伟
这个杀手不太冷 9.4 吕克·贝松
海上钢琴师 9.2 朱塞佩·托纳多雷
宣告黎明的露之歌 8.1 汤浅政明
肖申克的救赎 9.6 弗兰克·德拉邦特
摔跤吧!爸爸 9.2 涅提·蒂瓦里
>>> len(movies)
500

如上所示,我们成功爬取了“豆瓣高分”分类下的500部影片信息。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文