Python爬虫理论 | (8) Scrapy框架

目录

 

1. Scrapy框架

2. Scrapy入门实例

3. Scrapy详解

4. Scrapy对接Selenium

5. 实战


1. Scrapy框架

在python爬虫中:requests  +  selenium  可以解决目前90%的爬虫需求。

scrapy框架是为了让爬虫更强大、更高效。它是一个为了爬取网站数据,提取结构性数据而编写的应用框架,只需要实现少量代码,就能够快速的抓取到数据。

  • 简介

Scrapy 是一个基于Twisted (用Python实现的基于事件驱动的网络引擎框架)的异步处理框架,是纯Python 实现的爬虫框架,其架构清晰,模块之间的耦合程度低,可扩展性极强,可以灵活完成各种需求,只需要定制开发几个模块就可以轻松实现一个爬虫。

  • 组成

 

Scrapy Engine 引擎(框架核心)

处理整个系统的数据流, 触发事务。

Spiders 蜘蛛

定义爬取逻辑和网页的解析规则, 主要负责解析响应并生成提取结果和新的请求。

Scheduler 调度器

接受引擎发过来的请求并将其加入队列中, 在引擎再次请求的时候将请求提供给引擎。

Downloader 下载器

下载网页内容, 并将网页内容返回给蜘蛛。

Item Pipeline 项目管道

负责处理由蜘蛛从网页中抽取的项目,主要任务是清洗、验证和存储数据。

Item 项目

定义爬取结果的数据结构,爬取的数据会被赋值成该Item 对象。

  • 中间件

Downloader Middlewares 下载器中间件

位于引擎和下载器之间的框架,主要处理引擎与下载器之间的请求及响应。

Spider Middlewares 蜘蛛中间件

介于引擎和蜘蛛之间的框架,主要处理输入蜘蛛的响应和输出的结果及新的请求。

Scheduler Middewares 调度中间件

介于引擎和调度之间的中间件,从引擎发送到调度的请求和响应。

 

  • Spider Middlewares 蜘蛛中间件

当Downloader 生成Response 之后, Response 会被发送给Spider ,在发送给Spider 之前, Response会首先经过Spider Middleware 处理,当Spider 处理生成Item 和 Request 之后, Item 和 Request 还会经过Spider Middleware 的处理。

作用:

1)在Downloader 生成的Response 发送给Spider 之前,也就是在Response 发送给Spider之前对Response 进行处理。

2)在Spider 生成的Request 发送给Scheduler 之前,也就是在Request 发送给Scheduler之前对Request 进行处理。

3)在Spider 生成的Item 发送给Item Pipeline 之前,也就是在Item 发送给Item Pipeline之前对Item 进行处理。

Scrapy 已经提供了许多Spider Middleware ,它们被SPIDER_MIDDLEWARES_BASE变量所定义。若有自定义的Spider Middleware,首先加入到SPIDER_MIDDLEWARES 设置中, 该设置会和SPIDER_MIDDLEWARES_BASE 定义的Spider Middleware 合并。然后根据键值的数字优先级排序,得到一个有序列表。第一个Middleware 是最靠近引擎的,最后一个Middleware 是最靠近Spider 的。

  • 流程

 

(1) Engine 首先打开一个网站,找到处理该网站的Spider,并向该Spider 请求第一个要爬取的URL 。

(2) Engine 从Spider 中获取到第一个要爬取的URL ,并通过Scheduler 以request 的形式调度。

(3) Engine 向Scheduler 请求下一个要爬取的URL 。

(4) Scheduler 返回下一个要爬取的URL 给Engine , Engine 将URL 通过Downloader Middlewares 转发给Downloader 下载。

(5)一旦页面下载完毕, Downloader 生成该页面的Response ,并将其通过Downloader Middlewares发送给Engine 。

(6) Engine 从下载器中接收到Response ,并将其通过Spider Middlewares 发送给Spider 处理。

(7) Spider 处理Response ,并返回爬取到的Item 及新的Request 给Engine 。

(8) Engine 将Spider 返回的Item 给Item Pipeline ,将新的Request 给Scheduler 。

(9) 重复第(2)步到第(8)步,直到Scheduler 中没有更多的Request , Engine 关闭该网站,爬取结束。

  • 自动化处理

 

2. Scrapy入门实例

  • 安装

1)安装 lxml

2)安装 pyOpenSSL

3)安装 Twisted

4)安装 pyWin32

5)安装 scrapy

pip install scrapy

  • 入门实例

(1)创建scrapy项目(命令行方式)

首先在命令行进入PyCharm的项目目录,然后执行 scrapy startproject 项目名(如ScrapyExample),生产爬虫项目。会自动生成项目结构和一些文件:

系统自动生成的文件说明如下:

scrapy.cfg : Scrapy 项目的配置文件,其内定义了项目的配置文件路径、部署相关信息等内容。

Items.py : 定义Item 数据结构。

pipelines.py : 定义Item Pipeline 的实现。

settings.py : 定义项目的全局配置。

middlewares.py : 定义Spider Middlewares 和Downloader Middlewares 的实现。

spiders : 包含Spider 的实现,每个Spider 都有一个文件。

(2) 创建Spider(命令行方式)

Spider 是一个自定义的类, Scrapy 用它来从网页里抓取内容,并解析抓取的结果。这个类必须继承Spider 类(scrapy.Spider) ,需定义Spider 的名称和起始请求,以及解析爬取结果的方法。

命令:scrapy  genspider  Spider名称  网站域名

例:scrapy genspider quotes quotes.toscrape.com

进入之前生成的spiders目录,执行上述命令:

此时会在spiders目录下生成一个以爬虫名字命名的.py文件:

打开quotes.py看一下:

# -*- coding: utf-8 -*-
import scrapy


class QuotesSpider(scrapy.Spider):    #自定义爬虫类 继承scrapy.Spider
    name = 'quotes'                   #爬虫名字
    allowed_domains = ['quotes.toscrape.com']  #待爬取网站域名
    start_urls = ['http://quotes.toscrape.com/']  #待爬取网站的起始网址

    def parse(self, response):        #解析/提取规则 需要我们自己写
        pass

(3)创建 Item

Item 是保存爬取数据的容器。创建Item 需要继承scrapy.Item 类,并且定义类型为scrapy.Field 的字段。

首先我们来看一下,我们之前要爬取的那个网站是什么,打开http://quotes.toscrape.com/:

网站上主要是一些名人名言,每一条包含三个部分:名言、作者、标签。

接下来我们要自定义items.py(原本是空的,只有主要结构),定义我们想要的字段:

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class QuoteItem(scrapy.Item): #类名默认是项目名+Item,可以修改.如QuoteItem
    # define the fields for your item here like:
    # name = scrapy.Field()
    text = scrapy.Field()  #名言
    author = scrapy.Field() #作者
    tags = scrapy.Field() #标签
    pass

(4) 编辑spider中的parse方法(用于解析response)

对response 变量包含的内容进行解析,可以使用CSS选择器或Xpath选择器,解析结果赋值给Item中的字段。

# -*- coding: utf-8 -*-
import scrapy
from ScrapyExample.items import QuoteItem #把QuoteItem类导入 二者建立关联

class QuotesSpider(scrapy.Spider):    #自定义爬虫类 继承scrapy.Spider
    name = 'quotes'                   #爬虫名字
    allowed_domains = ['quotes.toscrape.com']  #待爬取网站域名
    start_urls = ['http://quotes.toscrape.com/']  #待爬取网站的起始网址

    def parse(self, response):        #解析/提取规则

        quotes = response.css('.quote')
        for quote in quotes:
            item = QuoteItem()
            #.text css选择器 ::text获取节点的文本内容,结果是列表,用extract_first()获取第一个元素
            item['text'] = quote.css('.text::text').extract_first()
            item['author'] = quote.css('.author::text').extract_first()
            item['tags'] = quote.css('.tags .tag::text').extract() #extract()获取整个列表
            yield item

        next = response.css('.pager .next a::attr(href)').extract_first() #下一个要爬取的页面url
        url = response.urljoin(next)
        yield scrapy.Request(url=url, callback=self.parse)  #当请求完成后,引擎将响应作为参数传递给回调函数



(5)运行(命令行执行)

在spiders目录下执行下面的命令。

scrapy crawl Spider名称

运行并显示结果,例:scrapy crawl quotes

scrapy crawl Spider名称 –o 文件名

运行并将结果保存到文件(json、csv、xml等),例:scrapy crawl quotes –o output.json

  • 入门实例 --- 进阶

使用 Item Pipeline

如果想进行更复杂的操作,如将结果保存到MongoDB 数据库,或者筛选某些有用的Item ,则可以定义Item Pileline 来实现。当Item 生成后,它会自动被送到Item Pipeline 进行处理,常用ItemPipeline 来做如下操作:

 

1)清理HTML 数据

2)验证爬取数据,检查爬取字段

3)查重并丢弃重复内容

4)将爬取结果保存到数据库

实现 Item Pipeline(修改pipelines.py)

定义一个类并实现process_item(),必须返回包含数据的字典或Item 对象,或者抛出Dropltem 异常。process_item()方法主要用到了两个参数:一个参数是item ,每次Spider 生成的Item 都会作为参数传递过来;一个参数是spider ,就是Spider 的实例。启用Item Pipeline后, Item Pipeline 会自动调用process_item()方法。

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import pymongo
from scrapy.exceptions import DropItem


class ScrapyexamplePipeline(object):
    def process_item(self, item, spider):
        return item

#定义Item处理的类 筛掉text长度大于50的Item
class TextPipeline(object):

    def __init__(self):
        self.limit = 50

    #该方法必须定义,而且必须要有item和spider两个参数 其他方法可以随便写
    def process_item(self,item,spider):
        if item['text']:
            if len(item['text']) > self.limit:
                item['text'] = item['text'][0:self.limit].rstrip() + '...'
            return item
        else:
            return DropItem('Missing Text')

#定义数据库存储类 将数据存储到mongodb数据库
class MongoPipeline(object):

    def __init__(self,mongo_uri,mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    # 从配置文件setting.py中获取mongo_uri,mongo_db 需要自己在setting.py中定义
    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DB')
        )

    #连接并打开数据库
    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    # 该方法必须定义,而且必须要有item和spider两个参数 其他方法可以随便写
    def process_item(self, item, spider):
        name = item.__class__.__name__
        self.db[name].insert(dict(item)) #将数据插入数据库 要转换为字典形式 键值对
        return item

    #关闭连接
    def close_spider(self, spider):
        self.client.close()

注意要把pipeline在setting.py里面进行注册,告诉scrapy增加了pipeline(把下面的代码加到setting.py中):

ITEM_PIPELINES = {
    'ScrapyExample.pipelines.TextPipeline': 300,
    'ScrapyExample.pipelines.MongoPipeline': 400,
}

再运行scrapy crawl quotes.

入门实例完整项目

3. Scrapy详解

在实现Scrapy 爬虫项目时,最核心的类就是Spider 类,它定义了爬取某个网站的流程和解析方式。简单来讲, Spider 要做好二件事:

 

1)定义爬取网站的动作

2)分析爬取下来的网页

  • 爬取循环过程

对于Spider 类来说,整个爬取循环过程如下:

1)以初始的URL 初始化Request ,并设置回调函数。当该Request 成功请求并返回时, Response生成井作为参数传给该回调函数。

2)在回调函数内分析返回的网页内容。返回结果有两种形式。一种是解析到的有效结果返回字典或Item 对象,它们可以经过处理后(或直接)保存。另一种是解析得到下一个(如下一页)链接,可以利用此链接构造Request 并设置新的回调函数,返回Request 等待后续调度。

3)如果返回的是字典或Item 对象,可将结果存入到文件。如果设置了Pipeline 的话,可以使用Pipeline 处理(如过滤、修正等)并保存(数据库)。

4)如果返回的是Reqeust ,那么Request 执行成功得到Response 之后, Response 会被传递给Request 中定义的回调函数,在回调函数中我们可以再次使用选择器来分析新得到的网页内容,并根据分析的数据生成Item 。

通过以上几步循环往复进行,完成站点的爬取。

  • Spider类

Spider 类: scrapy.Spider类中提供了start_requests()方法的默认实现,读取并请求start_urls属性,并根据返回的结果调用parse ()方法解析结果。它还有如下一些基础属性:

1)name :爬虫名称,是定义Spider 名字的字符串。Spider 的名字定义了Scrapy 如何定位并初始化Spider ,它必须是唯一的。

2)allowed_domains :允许爬取的域名,是可选配置,不在此范围的链接不会被跟进爬取。

3)start_urls :起始URL 列表,当没有实现start_requests()方法时,默认会从这个列表开始抓取。

4)custom_ settings :它是一个字典,是专属于本Spider 的配置,此设置会覆盖项目全局的设置。此设置必须在初始化前被更新,必须定义成类变量。

5)crawler :由from_crawler()方法设置,代表的是本Spider 类对应的Crawler 对象。Crawler 对象包含了很多项目组件,利用它可以获取项目的一些配置信息,如获取项目的设置信息Settings 。

6)settings :它是一个Settings 对象,利用它我们可以直接获取项目的全局设置变量。

  • Spider常用方法

1)start_ requests ():此方法用于生成初始请求,它必须返回一个可迭代对象。此方法会默认使用start _ urls 里面的URL 来构造Request ,而且Request 是GET 请求方式。如果想在启动时以POST 方式访问某个站点,可以直接重写这个方法,发送POST 请求时使用FormRequest即可。

2)parse():当Response 没有指定回调函数时,该方法会默认被调用。它负责处理Response , 处理返回结果,并提取想要的数据和下一步的请求,然后返回。该方法需要返回一个包含Request 或ltem 的可迭代对象。

3)closed():当Spider 关闭时,该方法会被调用,在这里一般会定义释放资源的一些操作或其他收尾操作。

  • Downloader Middleware

Downloader Middleware 是处于Scrapy 的Request 和Response 之间的处理模块。

Scheduler 从队列中拿出一个Request 发送给Downloader 执行下载,这个过程会经过Downloader Middleware 的处理。另外, 当Downloader 将Request 下载完成得到Response 返回给Spider 时会再次经过Downloader Middleware 处理。

 作用:

1)在Scheduler 调度出队列的Request 发送给Doanloader 下载之前,也就是可以在Request执行下载之前对其进行修改。

2)在下载后生成的Response 发送给Spider 之前,也就是可以在生成Resposne 被Spider 解析之前对其进行修改。

Downloader Middleware 的功能十分强大,修改User-Agent 、处理重定向、设置IP代理、失败重试、设置Cookies 等功能都需要借助它来实现。

Scrapy 已经提供了许多Downloader Middleware ,它们被SPIDER_MIDDLEWARES_BASE变量所定义。

 

字典的键名是Scrapy 内置的Downloader Middleware 的名称,键值代表了调用的优先级,数字越小代表越靠近Scrapy 引擎,数字越大代表越靠近Downloader  。

每个Downloader Middleware 都可以定义process_request()和process_response()方法来分别处理请求和响应,对于process_request()方法来说,优先级数字越小越先被调用,对于process_response()方法来说,优先级数字越大越先被调用。

如果自定义的Downloader Middleware 要添加到项目里, DOWNLOADER_MIDDLEWARES_BASE 变量不能直接修改。Scrapy 提供了另外一个设置变量DOWNLOADER_MIDDLEWARES ,直接修改这个变量就可以添加向己定义的Downloader Middleware ,以及禁用DOWNLOADER_MIDDLEWARES_BASE 里面定义的Downloader Middleware 。

  • 自定义Downloader Middleware

每个Downloader Middleware 都定义了包含一个或多个方法的类,核心的方法有三个。实现其中任意一个及以上方法,就可以定义Downloader Middleware ,方法如下:

1)process_request(request, spider)

2)process_response(request,response,spider)

3)process_exception(request, exception, spider)

process_request(request, spider)

Request 被Scrapy 引擎调度给Downloader 之前, process_request()方法就会被调用,也就是在Request 从队列里调度出来到Downloader 下载执行之前,都可以用process_request()方法对Request 进行处理。方法的返回值必须为None 、Response 对象、Request 对象之一,或者抛出IgnoreRequest异常。

当返回是None 时, Scrapy 将继续处理该Request ,接着执行其他Downloader Middleware 的process_request()方法,一直到Downloader 把Request 执行后得到Response 才结束。这个过程其实就是修改Request 的过程,不同的Downloader Middleware 按照设置的优先级顺序依次对Request 进行修改,最后送至Downloader 执行。

当返回为Response 对象时,更低优先级的Downloader Middleware 的process_request()和process_exception()方法就不会被继续调用,每个Downloader Middleware的 process_response()方法转而被依次调用。调用完毕之后,直接将Response 对象发送给Spider 来处理。

当返回为Request 对象时,更低优先级的Down loader Middleware 的process_request()方法会停止执行。这个Request 会重新放到调度队列里,其实它就是一个全新的Request , 等待被调度。如果被Scheduler 调度了,那么所有的Downloader Middleware 的process_request()方法会被重新按照顺序执行。

如果IgnoreRequest 异常抛出, 则所有的Downloader Middleware 的process_exception()方法会依次执行。如果没有一个方法处理这个异常,那么Request 的errorback()方法就会回调。如果该异常还没有被处理,那么它便会被忽略。

process_response(request,response,spider)

Downloader 执行Request 下载之后,会得到对应的Response 。Scrapy 引擎便会将Response 发送给Spider 进行解析。在发送之前,我们都可以用process_response()方法来对Response 进行处理。方法的返回值必须为Request 对象、Response 对象之一,或者抛出IgnoreRequest 异常。

当返回为Request 对象时,更低优先级的Downloader Middleware 的process_ response()方法不会继续调用。该Request 对象会重新放到调度队列里等待被调度,它相当于一个全新的Request 。然后,该Request 会被process_request()方法顺次处理。

当返回为Response对象时,更低优先级的Downloader Middleware 的process _ response()方法会继续调用,继续对该Response 对象进行处理。

如果IgnoreRequest 异常抛出,则Request 的errorback()方法会回调。如果该异常还没有被处理,那么它便会被忽略。

process_exception(request, exception, spider)

当Downloader 或process_request()方法抛出异常时,例如抛出IgnoreRequest 异常,

process_exception()方法就会被调用。方法的返回值必须为None 、Response 对象、Request 对象之一。

当返回为None 时,更低优先级的Downloader Middleware 的process_exception()会被继续顺次调用,直到所有的方法都被调度完毕。

当返回为Response 对象时,更低优先级的Downloader Middleware 的process_exception()方法不再被继续调用,每个Downloader Middleware 的process_response()方法转而被依次调用。

当返回为Request 对象时,更低优先级的Downloader Middleware 的process_exception()也不再被继续调用,该Request 对象会重新放到调度队列里面等待被调度,它相当于一个全新的Request 。然后,该Request 又会被process_request()方法顺次处理。

  • 实例:修改User-Agent

Scrapy 发送的Request 使用的User-Agent 是Scrapy/1.6.0(+http: //scrapy.org),

由Scrapy 内置的UserAgentMiddleware 设置, UserAgentMiddleware 的源码如下:

两种方式:

修改settings里面的USER-AGENT变量

通过Downloader Middleware 的process_request()方法修改

在spiders文件夹下,新建一个简单的测试爬虫,httpbin.py:

# -*- coding: utf-8 -*-
import scrapy


class HttpbinSpider(scrapy.Spider):
    name = 'httpbin'
    allowed_domains = ['httpbin.org']
    start_urls = ['http://httpbin.org/get']

    def parse(self, response):
        self.logger.debug(response.text)
        self.logger.debug('Staus Code:' + str(response.status))

在middlewares.py 中添加下面这个类,对Downloader Middleware做修改:

class RandomUserAgentDownloaderMiddleware(object):
    def __init__(self):
        self.user_agents = [
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.5959.400 SLBrowser/10.0.3544.400',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134'
        ]

    def process_request(self, request, spider):
        request.headers['User-Agent'] = random.choice(self.user_agents)

    def process_response(self, request, response, spider):
        response.status = 201
        return response

首先看一下原始运行效果执行 scrapy crawl httpbin:

 第一种修改方式:

在setting.py中添加:

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'

再执行 scrapy crawl httpbin:

第二种修改方式: 

之前在middlewares.py 中添加了RandomUserAgentDownloaderMiddleware类,在settings.py中对他进行注册:

DOWNLOADER_MIDDLEWARES = {
    'scrapytest.middlewares.RandomUserAgentDownloaderMiddleware': 543,
}

 再执行 scrapy crawl httpbin::

4. Scrapy对接Selenium

Scrapy 抓取页面的方式和requests 库类似,都是直接模拟HTTP 请求,而Scrapy 也不能抓取JavaScript 动态谊染的页面。

抓取JavaScript 渲染的页面有两种方式:

1)一种是分析Ajax 请求,找到其对应的接口抓取, Scrapy 同样可以用此种方式抓取。

2)另一种是直接用Selenium 模拟浏览器进行抓取,不需要关心页面后台发生的请求,也不需要分析渲染过程,只需要关心页面最终结果即可,可见即可爬。

5. 实战

Scrapy+Selenium爬取新闻。

完整项目

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值