F's Blog

博客 收藏夹
Scrapy 爬虫

07 Aug 2017

简介

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。

其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试

Scrapy 使用了 Twisted异步网络库来处理网络通讯。

框架图如下:

框架图

可见 Spider 与 Scheduler 及 Downloader 之间形成里很好的循环。从第一页面,Spider 从 Downloader 得到 response 解析出 下一个 url,交给 Scheduler 处理形成新当 request。而且三者之间的交互都有中间件,可以插入 hook 代码,对过程进行控制和处理。而访问的页面都会经过 Pipeline 中间件,对数据进行持久化等处理。

Scrapy主要包括了以下组件:

-下载器中间件(Downloader Middlewares): 位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。 -爬虫中间件(Spider Middlewares): 介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。 -调度中间件(Scheduler Middewares): 介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。

中间件

只要覆盖已定义的 hook 方法即可。

Downloader Middlewares

下载中间件 hook 在 Scrapy 的 request/response 的处理过程里。

激活一个下载中间件,只需要把它加到设置文件的 DOWNLOADER_MIDDLEWARES 字典里,key 是类,value 是顺序。如下:

DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.CustomDownloaderMiddleware': 543,
}

顺序关系着这个中间件什么时候被执行,处理 request 时是按升序执行,response则是降序。

在设置顺序时,需要参考 DOWNLOADER_MIDDLEWARES_BASE 的,从而可以确定中间件在系统的哪个中间件调用前执行。

DOWNLOADER_MIDDLEWARES_BASE

{
    'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
    'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
    'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
    'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
    'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
    'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
    'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
    'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
    'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
    'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
}

如何想要禁用默认的中间件,可以把它定义为 None,如下:

DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.CustomDownloaderMiddleware': 543,
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
}

定义方法

需要定义以下方法:

process_request(request, spider)

每次请求都会被调用。

返回:

参数:

Spider Middlewares

Scheduler Middlewares

Pipline

当页面被爬虫解析后,将被发送到项目管道。对,每一个被解析方法被调用后都会走 Pipline 的。

持久化

官方有 MongoDB 的例子,个人也觉得用 mongo 很方便,因为是爬取当信息,数据结构可能会改变。

item

一个入门例子

初始化一个项目

scrapy startproject tutorial

在 spiders 目录里写一个爬虫:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('Saved file %s' % filename)

说明:

执行

scrapy crawl quotes

就会按照 parse 里的过程保存两个 html 文件了。

提取

用 shell 来交互提取 data,方便找到想要的内容当提取表达式:

scrapy shell 'http://quotes.toscrape.com/page/1/'

>>> response.css('title')
[<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]

>>> response.css('title::text').extract()
['Quotes to Scrape']
>>> response.css('title::text').extract_first()
'Quotes to Scrape'
>>> response.css('title::text')[0].extract()
'Quotes to Scrape'

>>> response.css('title::text').re(r'Quotes.*')
['Quotes to Scrape']
>>> response.css('title::text').re(r'Q\w+')
['Quotes']
>>> response.css('title::text').re(r'(\w+) to (\w+)')
['Quotes', 'Scrape']

一个爬虫一般都会输出从页面里摘取包含数据当字典,这里我们用 Python 当 yield.

上一改例子:


import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }

        next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page is not None:
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page, callback=self.par
            # 用 follow 的写法,就不用拼出全部 url 了
            # yield response.follow(next_page, callback=self.parse)

存储

# JSON 格式
scrapy crawl quotes -o quotes.json
# JSON Line 格式
scrapy crawl quotes -o quotes.jl

防止被禁

Agent

supervisor

echo_supervisord_conf > ./supervisord.conf

代码研究

Scrapy 是一个值得深入研究的项目,如何代理、如何调度等等。

参考

本文由 付豪 创作,采用署名 4.0 国际(CC BY 4.0)创作共享协议进行许可,详细声明