摘要:关于Scrapy 爬虫开发实战与开发技巧,重点关注scrapy shell设置ua头,custom_settings 。

In Action-Scrapy 爬虫开发实战与开发技巧1.png

1 概述

本节我们将在 Conda 环境下使用 Pycharm 编写一个简单的爬虫,我们的目标是 豆瓣电影TOP250。在这篇文章中我将和大家分享几个我在爬虫编写中的思路、流程以及技巧

Pycharm 中开发环境的加载如下图:

In Action-Scrapy 爬虫开发实战与开发技巧2.png

2 明确目标

首先明确我要抓取的目标数据是什么?以及我的目标数据分布在哪?下面图例中圈出了我需要解析的数据:

In Action-Scrapy 爬虫开发实战与开发技巧3.png

解析内容依据网页结构可以分为如下几块:

  • 排序
  • 电影中文名、其他名字
  • 导演、主演、年代、地区、类别标签
  • 评分、参与评分人数
  • 电影描述
    这是我们根据一级页面可以获取的数据,其实在二级页面中我们可以获得更多的电影相关数据

In Action-Scrapy 爬虫开发实战与开发技巧4.png

但是本章将不深入二级页面,因为他的方法在我们下面的内容都会涉及到。

另外e 在目标明确中的另一个问题是所有的数据都在哪?豆瓣电影250作为一个榜单显然数据只有250条每个页面有25条,那它们平均分布在10个页面内,也就是说我的爬虫只要翻完10个页面并正确解析数据就能达成目标。

In Action-Scrapy 爬虫开发实战与开发技巧5.png

3 初探页面访问

本次爬虫类我们将继承使用 CrawlSpider,如果你尚未熟悉它的用法可以参考历史文章:

CrawlSpider - Scrapy爬虫详解

框架的搭建我们第一步是先看看使用 Scrapy 能不能直接获取正确的页面,因为可能目标网站的反爬虫策略使得我们无法获取网页数据,因此我们可以这样先试试:

scrapy shell https://movie.douban.com/top250

In Action-Scrapy 爬虫开发实战与开发技巧6.png

访问结果:

In Action-Scrapy 爬虫开发实战与开发技巧7.png

网页状态码是403表示服务器拒绝访问,也就是我们无法通过不加设置的方法进行获取数据,我们看看放回的网页长什么样:

view(response)

In Action-Scrapy 爬虫开发实战与开发技巧8.png

自动打开网页:

In Action-Scrapy 爬虫开发实战与开发技巧9.png

最简单处理的方法是查看真正的浏览器方法时的行为,我们照样子模仿

打开浏览器在目标页面上按 F12 切换 Tab 到 Network

In Action-Scrapy 爬虫开发实战与开发技巧10.png

按 F5 刷新页面,找到我们的目标页面 top250 点击它,在右边的窗口下拉找到 Request Headers,这个表示我们发起请求是的头部标识。

In Action-Scrapy 爬虫开发实战与开发技巧11.png

我们可以首先尝试添加 Host 和 User-Agent 到我们的爬虫请求中,前者一般用于图片反盗链后者由于表示访问者身份。我们可以这样做:

添加UA:

scrapy shell -s USER_AGENT="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "https://movie.douban.com/top250"

In Action-Scrapy 爬虫开发实战与开发技巧12.png

查看请求状态(200):

In Action-Scrapy 爬虫开发实战与开发技巧13.png

查看请求页面:

In Action-Scrapy 爬虫开发实战与开发技巧14.png

到这里我们已经能够访问页面了。

另外一种可选的方法:

先启动 shell

scrapy shell

再请求数据:

>>> from scrapy import Request
>>> req = Request('https://movie.douban.com/top250', headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.361"})
>>> fetch(req)

In Action-Scrapy 爬虫开发实战与开发技巧15.png

4 爬虫框架搭建

做好上面的准备,我们可以用以前的 CrawlSpider 的代码初步写个框架:

# -*- coding: utf-8 -*-
# filename: douban.py
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor


class MySpider(CrawlSpider):
    name = 'movie_douban'
    allowed_domains = ['douban.com']
    start_urls = ["https://movie.douban.com/top250"]

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
    }
    custom_settings = {
        'DEFAULT_REQUEST_HEADERS': headers,
    }

    rules = (
        Rule(LinkExtractor(allow=()),
             follow=True,
             callback='parse_item',
              ),
    )

    def parse_item(self, response):
        pass

这样我们就有了基本框架,接下来看我们需要做的是填充它:

  • 页面跟随规则
  • 数据解析规则

5 页面跟随规则

在爬虫中 LinkExtractor 的规则编写关系到你能不能爬取完整的数据,这里们有个技巧用去调试链接的跟随规则,添加 process_links 回调函数,部分代码改写如下:

  rules = (
    Rule(LinkExtractor(allow=()),
         follow=True,
         callback='parse_item',
         process_links='process_links',
         ),
)

    @staticmethod
    def process_links(links):
        for link in links:
            yield link

到这里我们可以试着通过 Pycharm 的 Debug 模式运行 下面的 run.py 文件对爬虫进行调试。

# -*- coding: utf-8 -*-
from scrapy import cmdline
cmdline.execute("scrapy runspider douban.py".split())

In Action-Scrapy 爬虫开发实战与开发技巧16.png

在上面由于我们在 LinkExtractor 的 follow 设为空意味着提取当前页面的所有链接进行 follow,但是我们只需要翻页那该怎么做呢?使用正则!

我们先复制第2页、第3页的链接看看该怎么写(直接在2上复制链接,不要看网页源码)

In Action-Scrapy 爬虫开发实战与开发技巧17.png

https://movie.douban.com/top250?start=25&filter=
https://movie.douban.com/top250?start=50&filter=

规律很明显用 start=数字 控制当前页出现的第一个电影序号,匹配多个数字在正则中可以写成 \d+,因此规则编写如下:

rules = (
    Rule(LinkExtractor(allow=('/top250\?start=\d+&filter=',)),
         follow=True,
         callback='parse_item',
         process_links='process_links',
         ),
)

由于 问号 在正则中是表示重复前面内容的0次或1次,要匹配问号本身需要转义。

另一种方法:

LinkExtractor 还有两个参数是 restrict_xpaths 、restrict_css 表示在 xpath 、css 匹配区域提取指定链接,因此我们可以先指定链接提取区域为翻页区块,再提取全部链接:

In Action-Scrapy 爬虫开发实战与开发技巧18.png

xpath如下:

//div[@class="paginator"]

具体 Rule:

rules = (
    Rule(LinkExtractor(allow=(),
                       restrict_xpaths=('//div[@class="paginator"]',)),
         follow=True,
         callback='parse_item',
         process_links='process_links',
         ),
)

css 如下:

div.paginator

具体 Rule:

rules = (
    Rule(LinkExtractor(allow=(),
                       restrict_css=('div.paginator',)),
         follow=True,
         callback='parse_item',
         process_links='process_links',
         ),
)

6 数据解析规则

做好链接跟随规则后就可以正式开始我们数据的解析了。

一般我们可以在回调函数中加载 Shell 进行数据解析调试:

def parse_item(self, response):
    from scrapy.shell import inspect_response
    inspect_response(response, self)

在上面中已经提到数据解析分为如下几块:

  • 排序
  • 电影中文名、其他名字
  • 导演、主演、年代、地区、类别标签
  • 评分、参与评分人数
  • 电影Quote
    在第三块中,这些内容其实是混杂在一起的只能通过字符串查找、正则匹配等方法格式化数据,最好的方法是进入该条目的二级页面从二级页面中在2解析响应的数据。下面我通过了使用 ItemLoader 的方法对数据进行解析清洗。
# -*- coding: utf-8 -*-
from scrapy import Item, Field
from scrapy.loader import ItemLoader
from w3lib.html import remove_tags
from scrapy.loader.processors import Join, MapCompose, TakeFirst

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor


class MovieItem(Item):
    title = Field()
    other = Field()

    director = Field() # 未解析
    starring = Field() # 未解析
    year = Field()
    country = Field() # 未解析
    category = Field()

    rating = Field()
    num = Field()
    quote = Field()


class MovieLoader(ItemLoader):
    """
    数据清洗
    """
    default_output_processor = TakeFirst()
    default_input_processor = MapCompose(unicode.strip)

    title_in = MapCompose(lambda x: x.replace(u'\xa0/\xa0', ''))
    title_out = Join(' / ')

    other_in = MapCompose(lambda x: x[3:], lambda x: x.replace(' '*2, ' '))

    num_in = MapCompose(lambda x: x[:-3])


class MySpider(CrawlSpider):
    name = 'movie_douban'
    allowed_domains = ['douban.com']
    start_urls = ["https://movie.douban.com/top250"]

    headers = {
        'Host': 'movie.douban.com',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
    }
    custom_settings = {
        'FEED_EXPORT_ENCODING': 'utf-8',
        'FEED_FORMAT': 'jsonline',
        'FEED_EXPORT_INDENT': 4,
        'DEFAULT_REQUEST_HEADERS': headers,
        'HTTPCACHE_ENABLED': True,
    }

    rules = (

        Rule(LinkExtractor(allow=(),
                           restrict_css=('div.paginator',)),
             follow=True,
             callback='parse_item',
             ),
    )

    def parse_item(self, response):
        movies = response.css('div.item')
        for movie in movies:
            loader = MovieLoader(item=MovieItem(), selector=movie)
            loader.add_css('title', 'span.title::text')
            loader.add_css('other', 'span.other::text')
            loader.add_xpath('year', './/div[@class="bd"]/p[1]/text()', Join(), remove_tags,
                             re='\d{4}')
            loader.add_xpath('category', './/div[@class="bd"]/p[1]/text()', Join(), remove_tags,
                             lambda x: x[x.rfind('/')+1:])
            loader.add_css('rating', 'span.rating_num::text')

            loader.add_xpath('num', './/div[@class="star"]/span[4]/text()')
            loader.add_xpath('quote', './/span[@class="inq"]/text()')

            yield loader.load_item()

更详细的介绍看历史文章:

Items - Scrapy中数据的传递

Item Loaders - 数据传递的另一中方式

Feed exports - 数据导出配置详解

我们的成品数据大概长这样,当然你也可以使用 csv 格式导出。

In Action-Scrapy 爬虫开发实战与开发技巧19.png

这样的数据可以使用 json 库进行读取:

import json

with open('movies.json', 'r') as f:
    data = json.loads(f.read())
print type(data)

In Action-Scrapy 爬虫开发实战与开发技巧20.png

数据解析这一块还是需要多多练习才能如鱼得水,使用 ItemLoader 作为简单的数据清洗工作。在模块化的 Scrapy 项目中使用 Pipeline 做数据的验证、持久化都是可以的。如果我们只是想简单获取数据建议直接使用单文件爬虫,这样更佳方便。

如果你有什么问题欢迎评论交流。

文章目录