Python自动化开发学习-Scrapy

讲师博客:https://www.cnblogs.com/wupeiqi/p/6229292.html
中文资料(有示例参考):http://www.scrapyd.cn/doc/

站在用户的角度思考问题,与客户深入沟通,找到托克逊网站设计与托克逊网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:做网站、网站建设、企业官网、英文网站、手机端网站、网站推广、域名申请、网络空间、企业邮箱。业务覆盖托克逊地区。

项目准备

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。使用之前有一个类似django的创建项目以及目录结构的过程。

Scrapy 安装

使用pip安装(windows会有问题):

pip3 install scrapy

装不上主要是因为依赖的模块Twisted安装不上,所以得先安装Twisted,并且不能用pip直接下载安装。先去下载Twisted的whl安装文件:https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
然后使用pip本地安装:

pip install E:\Downloads\Twisted-18.9.0-cp36-cp36m-win_amd64.whl
pip install -i https://mirrors.aliyun.com/pypi/simple/ scrapy
pip install -i https://mirrors.aliyun.com/pypi/simple/ pywin32

Scrapy 组件

Scrapy主要包括了以下组件:

  • 引擎(Scrapy):
    用来处理整个系统的数据流处理, 触发事务(框架核心)
  • 调度器(Scheduler):
    用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
  • 下载器(Downloader):
    用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
  • 爬虫(Spiders):
    爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
  • 项目管道(Pipeline):
    负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
  • 下载器中间件(Downloader Middlewares):
    位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
  • 爬虫中间件(Spider Middlewares):
    介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
  • 调度中间件(Scheduler Middewares):
    介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。

Python自动化开发学习-Scrapy

工作流程:
绿线是数据流向,引擎是整个程序的入口。首先从初始 URL 开始(这步大概是引擎把初始URL加到调度器),Scheduler 会将其交给 Downloader 进行下载,下载之后会交给 Spider 进行分析,Spider 分析出来的结果有两种:一种是需要进一步抓取的链接,例如“下一页”的链接,这些东西会被传回 Scheduler ;另一种是需要保存的数据,它们则被送到 Item Pipeline 那里,那是对数据进行后期处理(详细分析、过滤、存储等)的地方。
另外,引擎和其他3个组件直接有通道。在数据流动的通道里还可以安装各种中间件,进行必要的处理。

Scrapy 项目结构

启动项目
打开终端进入想要存储 Scrapy 项目的目录,然后运行 scrapy startproject (project name)。创建一个项目:

> scrapy startproject PeppaScrapy

执行完成后,会生成如下的文件结构:

ProjectName/
├── ProjectName
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       └── __init__.py
└── scrapy.cfg

文件说明

  • scrapy.cfg : 项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
  • items.py : 设置数据存储模板,用于结构化数据,如:Django的Model
  • pipelines : 数据处理行为,如:一般结构化的数据持久化
  • settings.py : 配置文件,如:递归的层数、并发数,延迟下载等
  • spiders : 爬虫目录,如:创建文件,编写爬虫规则

关于配置文件,需要的时候可以先去下面的地址查,版本不是最新的,不过是中文。
https://www.jianshu.com/p/df9c0d1e9087

创建爬虫应用
先切换到项目目录,在执行grnspider命令 scrapy genspider [-t template] (name) (domain) 。比如:

> cd PeppaScrapy
> scrapy genspider spider_lab lab.scrapyd.cn

效果就是在spiders目录下,创建了一个spider_lab.py的文件。这里没有用-t参数指定模板,就是用默认模板创建的。其实不用命令也行了,自己建空文件,然后自己写也是一样的。
可以使用-l参数,查看有哪些模板:

> scrapy genspider -l
Available templates:
  basic
  crawl
  csvfeed
  xmlfeed

然后再用-d参数,加上上面查到的模板名,查看模板的内容:

> scrapy genspider -d basic
# -*- coding: utf-8 -*-
import scrapy

class $classname(scrapy.Spider):
    name = '$name'
    allowed_domains = ['$domain']
    start_urls = ['http://$domain/']

    def parse(self, response):
        pass

爬取页面

把之前的创建的应用的文件修改一下,简单完善一下parse方法:

import scrapy

class SpiderLabSpider(scrapy.Spider):
    name = 'spider_lab'
    allowed_domains = ['lab.scrapyd.cn']
    start_urls = ['http://lab.scrapyd.cn/']

    def parse(self, response):
        print(response.url)
        print(response.body.decode())

查看应用列表:

> scrapy list
spider_lab

运行单独爬虫应用,这里加上了--nolog参数,避免打印日志的干扰:

> scrapy crawl spider_lab --nolog

在python里启动爬虫

每次都去命令行打一遍命令也很麻烦,也是可以直接写python代码,执行python来启动的。把下面的代码加到引用文件的最后:

if __name__ == '__main__':
    from scrapy import cmdline
    log_level = '--nolog'
    name = SpiderLabSpider.name
    cmdline.execute(('scrapy crawl %s %s' % (name, log_level)).split())

其实就是提供了在python里调用命令行执行命令的方法。之后,还可以写一个main.py放到项目根目录下,写上启动整个项目的命令。

Windows 编码问题

有可能会遇到编码问题,不过我的windows没问题,如果遇到了,试一下下面的方法:

import io
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')

踩坑(爬虫Robots协议)

Robots协议就是每个网站对于来到的爬虫所提出的要求。并非强制要求遵守的协议,只是一种建议。
默认scrapy遵守robot协议。我在爬 http://dig.chouti.com/ 的时候遇到了这个问题。把 --nolog 参数去掉,查看错误日志,有如下的信息:

[scrapy.core.engine] DEBUG: Crawled (200)  (referer: None)
[scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: 

先去下载robots.txt文件,然后根据文件的建议,就禁止继续爬取了。可以直接浏览器输入连接查看文件内容:

User-agent: *
Allow: /link/
Disallow: /?
Disallow: /*?
Disallow: /user
Disallow: /link/*/comments
Disallow: /admin/login

# Sitemap files
Sitemap: https://dig.chouti.com/sitemap.xml

你要守规矩的的话,就只能爬 https://dig.chouti.com/link/xxxxxxxx 这样的url,一个帖子一个帖子爬下来。
如果可以选择不遵守协议,那么就在爬的时候把这个设置设为False。全局的设置在settings.py文件里:

# Obey robots.txt rules
ROBOTSTXT_OBEY = True

也可以只对一个应用修改设置:

import scrapy

class SpiderLabSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['http://dig.chouti.com/']
    custom_settings = {'ROBOTSTXT_OBEY': False}

    def parse(self, response):
        print(response.url)
        print(response.encoding)
        print(response.text)

if __name__ == '__main__':
    from scrapy import cmdline
    log_level = '--nolog'
    name = SpiderLabSpider.name
    cmdline.execute(('scrapy crawl %s %s' % (name, log_level)).split())

自定义请求头

上面踩坑的过程中,一度以为是请求头有问题,已定义请求头的方法也是设置settings.py文件,里面有一个剩下的默认配置:

# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
#   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
#   'Accept-Language': 'en',
#}

默认都注释掉了,你可以在这里为全局加上自定义的请求头,当然也可以只为单独的应用配置:

import scrapy

class SpiderLabSpider(scrapy.Spider):
    name = 'test'
    allowed_domains = ['chouti.cn']
    start_urls = ['http://dig.chouti.com/']
    # 这个网站会屏蔽User-Agent里包含python的请求
    custom_settings = {'ROBOTSTXT_OBEY': False,
                       'DEFAULT_REQUEST_HEADERS': {'User-Agent': 'python'},
                       }

    def parse(self, response):
        print(response.request.headers)  # 这个是请求头
        print(response.headers)  # 这个是响应头

if __name__ == '__main__':
    from scrapy import cmdline
    log_level = ''
    name = SpiderLabSpider.name
    cmdline.execute(('scrapy crawl %s %s' % (name, log_level)).split())

xpath 选择器

使用xpaht选择器可以提取数据,另外还有CSS选择器也可以用。
XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。对 XPath 的理解是很多高级 XML 应用的基础。

scrapy 里的 xpath

解析页面内容会用到Selector这个类,下面只贴出parse回调函数里的代码:

from scrapy.selector import Selector

    def parse(self, response):
        title1 = response.xpath('//title')
        print('title1', title1)
        title2 = Selector(response).xpath('//title')
        print('title2', title2)

上面的两种用法是一样的,通过response对象也可以直接调用xpath方法。这里说明了xpath方法是Selector这个类提供的。另外用方法二还有一个好处,就是因为之后需要调用Selector类里的方法,这样显示的声明Selector类之后,编辑器可以找到类似的方法,给出各种提示。直接用response调用,就没有这种便利了。
另外还有一个XmlXPathSelector类,作用和Selector类差不多,可能是就版本使用的类。

表达式

常用的表达式:

  • node_name : 选取从节点的所有子节点。就是标签名,比如上面的title
  • // : 匹配当前节点下的所有节点,不考虑位置。就是选择下面的子子孙孙
  • / : 匹配当前节点下的子节点,只往下找一层,就是找儿子。类似文件路径
  • . : 选择当前节点。类似文件路径
  • .. : 选择当前节点的父节点。类似文件路径
  • @ : 选取属性

提取属性
提取属性的话,也是先定位到标签的范围,然后最后@属性名称,拿到所有对应的属性。另外@*可以拿到所有的属性。要当某个标签下的属性,就在标签名之后/@就好了:

Selector(response).xpath('//@href')  # 提取所有的href属性
Selector(response).xpath('//ol[@class="page-navigator"]//@href')  # ol.page-navigator下的所有的href属性
Selector(response).xpath('//head/meta/@*').extract()  # head>meta 标签了所有的属性
Selector(response).xpath('//*[@id="body"]/div/@class')  # id为body的标签的下一级标签里的class属性

查找标签,限定属性
使用这样的表达式:标签[@属性名='属性值'] ,另外还能用not(),注意要用小括号把取反的内容包起来:

Selector(response).xpath('//div[@id="body"]//span[@class="text"]')  # 只要 span.text 的span标签
Selector(response).xpath('//div[@id="body"]//span[not(@class="text")]')  # 没有text这个class的span标签
Selector(response).xpath('//meta[@name]')  # 有name属性的meta
Selector(response).xpath('//meta[not(@name)]')  # 没有name属性meta

提取值
xpath方法返回的是个对象,这个对象还可以无限次的再调用xpath方法。拿到最终的对象之后,我们需要获取值,这里有 extract() 和 extract_first() 这两个方法。因为查找的结果可能是多个值,extract方法返回列表,而extract_first方法直接返回值,但是是列表是第一个元素的值。

提取文字
表达式:/text() 可以把文字提取出来:

    def parse(self, response):
        tags = Selector(response).xpath('//ul[@class="tags-list"]//a/text()').extract()
        print(tags)  # 这样打印效果不是很好
        for tag in tags:
            print(tag.strip())

还有个方法,可以提取整段文字拼到一起。表达式:string() :

Selector(response).xpath('string(//ul[@class="tags-list"]//a)').extract()  # 这样没拿全
Selector(response).xpath('string(//ul[@class="tags-list"])').extract()  # 这样才拿全了

上面第一次没拿全,某个a标签下的文字就是一段。string()表达式看来值接收一个值,如果传的是个列表,可能就只操作第一个元素。
在我们商品详情、小说内容的时候可能会比较好用。

匹配class的问题
xpath中没有提供对class的原生查找方法。因为class里是可以包含多个值的。比如下面的这个标签:

Test

下面的表达式是无法匹配到的:

response.xpath('//div[@class="test"]')

要匹配到,你得写死:

response.xpath('//div[@class="test main"]')

但是这样显然是不能接受的,如果还有其他test但是没出main的标签就匹配不上了。
contains 函数 (XPath),检查第一个参数字符串是否包含第二个参数字符串。用这个函数就能做好了

response.xpath('//div[contains(@class, "test")]')

这样又有新问题了,如果有别的class名字比如:test1、mytest,这种也都会被上面的方法匹配上。
concat 函数 (XPath),返回参数的串联。就是字符串拼接,contains的两个参数的两边都加上空格,就能解决上面的问题。之所以要引入concat函数时因为,后面的字符串可以手动在两边加上空格,但是@class是变量,这个也不能用加号,就要用这个函数做拼接:

response.xpath('//div[contains(concat(" ", @class, " "), " test ")]')

normalize-space 函数 (XPath),返回去掉了前导、尾随和重复的空白的参数字符串。上面已经没问题了。不过还不够完美。在拼接@class之前,先把两边可能会出现的其他空白字符给去掉,可能会有某些操作需要改变一下class,但是又不要对这个class有任何实际的影响。总之这个是最终的解决方案:

response.xpath('//div[contains(concat(" ", normalize-space(@class), " "), " test ")]')

这里已经引出了好几个函数了,还有更多别的函数,需要的时候再查吧。

正则匹配
xpath也是可以用正则匹配的,用法很简单 re:test(x, y) 。第一个参数用@属性比较多,否则就是正则匹配标签了,就和纯的正则匹配似乎没什么差别了。

Selector(response=response).xpath('//a[re:test(@id, "i\d+")]')

xpath 与 css定位方式的比较

https://www.cnblogs.com/tina-cherish/p/7127812.html
xpath很强大,但是不支持原生的class,不过上面已经给了比较严谨的解决方案了。
css有部分功能无法实现。比如不能向上找,只能匹配当前层级,要通过判断子元素来确定当前元素是否匹配就不行。这种情况使用xpath的话,中括号里可以在嵌套中括号的。
不过css感觉更直观,也已经没什么学习成本了。

实战

登录抽屉并点赞。边一步一步实现,边补充用到的知识点。

获取首页的内容

import scrapy
from scrapy.selector import Selector

class SpiderLabSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['http://dig.chouti.com/']
    custom_settings = {'ROBOTSTXT_OBEY': False}

    def parse(self, response):
        items = Selector(response).xpath('//*[@id="content-list"]/div[@class="item"]')
        for item in items:
            news = item.xpath(
                './div[@class="news-content"]'
                '//a[contains(concat(" ", normalize-space(@class), " "), " show-content ")]'
                '/text()'
            ).extract()[-1]
            print(news.strip())

if __name__ == '__main__':
    from scrapy import cmdline
    log_level = '--nolog'
    name = SpiderLabSpider.name
    cmdline.execute(('scrapy crawl %s %s' % (name, log_level)).split())

这里爬取的只是首页的内容

爬取所有页的内容

现在要获取所有分页的url,然后继续爬取。下面就是在parse回调函数后面增加了一点代码是做好了。不过现在的代码还不完善,会无休止的爬取下去,先不要运行,之后还要再改:

import urllib.parse

    def parse(self, response):
        items = Selector(response).xpath('//*[@id="content-list"]/div[@class="item"]')
        for item in items:
            news = item.xpath(
                './div[@class="news-content"]'
                '//a[contains(concat(" ", normalize-space(@class), " "), " show-content ")]'
                '/text()'
            ).extract()[-1]
            print(news.strip())
        # 不找下一页,而是找全部的页,这样会有去重的问题,就是要这个效果
        pages = Selector(response).xpath('//div[@id="dig_lcpage"]//a/@href').extract()
        print(pages)
        url_parse = urllib.parse.urlparse(response.url)
        for page in pages:
            url = "%s://%s%s" % (url_parse.scheme, url_parse.hostname, page)
            yield scrapy.Request(url=url)

这里做的事情就是当从前也分析了分页的信息,把分页信息生成新的url,然后再给调度器继续爬取。
这里用的 scrapy.Request() ,实际上是应该要通过 from scrapy.http import Request 导入再用的。不过这里并不需要导入,并且只能能在scrapy下调用。因为在 scrapy/__init__.py 里有导入这个模块了。并且这里已经不是系统第一次调用这个类了,程序启动的时候,其实就是跑了下面的代码把 start_urls 的地址开始爬取网页了:

for url in self.start_urls:
    yield Request(url, dont_filter=True)

这段代码就是在当前类的父类 scrapy.Spider 里的 start_requests 方法里面。

爬取深度

爬取深度,允许抓取任何网站的最大深度。如果为零,则不施加限制。
这个是可以在配置文件里设置的。默认的配置里没有写这条,并且默认值是0,就是爬取深度没有限制。所以就会永不停止的爬取下去。实际上不会无休止,似乎默认就有去重的功能,爬过的页面不会重复爬取。所以不设置爬取深度,就能把所有的页面都爬下来了
这里要讲的是爬取深度的设置,所以和其他设置一样,可以全局的在settings.py里设置。也可以现在类的公用属性 custom_settings 这个字典里:

    custom_settings = {
        'ROBOTSTXT_OBEY': False,
        'DEPTH_LIMIT': 1,
    }

这个深度可以在返回的response参数里找到,在meta这个字典里:response.meta['depth']

去重规则

默认有下面2条配置:

DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'
DUPEFILTER_DEBUG = False

去重的功能默认就是在 'scrapy.dupefilters.RFPDupeFilter' 这个类里做的。这个类有个父类 BaseDupeFilter 帮我们定义好了接口,我们可以写一个自己的类自定义去重规则,继承 BaseDupeFilter 实现里面的方法:

from scrapy.dupefilters import BaseDupeFilter

class MyFilter(BaseDupeFilter):

    def __init__(self):
        # 去重可以用上集合
        # 在request_seen方法里判断这个set,操作这个set
        self.visited_url = set()

    @classmethod
    def from_settings(cls, settings):
        """初始化时调用的方法
        返回一个实例,作用就是可以调用配置的信息生成实例
        实例化时使用:obj = MyFilter.from_settings()
        所以不要这样实例化:obj = MyFilter()
        什么都不写,上面两重方法生成的实例是一样的
        """
        return cls()

    def request_seen(self, request):
        """过滤规则
        检测当前请求是否需要过滤(去重)
        返回True表示需要过滤,返回False表示不用过滤
        """
        return False

    def open(self):  # can return deferred
        """开始爬虫时,调用一次
        比如要记录到文件的,在这里检查和重建记录文件
        """
        pass

    def close(self, reason):  # can return a deferred
        """结束爬虫时,调用一次
        这里可以把之前的记录文件close掉
        """
        pass

    def log(self, request, spider):  # log that a request has been filtered
        """日志消息的记录或打印可以写在这里"""
        pass

现在知道了,默认就是有去重规则的。所以上面爬取所有页面的代码并并不会无休止的执行下去,而是可以把所有页面都爬完的。

启动和回调函数

程序启动后,首先会调用父类 scrapy.Spider 里的 start_requests 方法。我们也可以不设置 start_urls 属性,然后自己重构 start_requests 方法。启动的效果是一样的:

    # start_urls = ['http://lab.scrapyd.cn/']

    def start_requests(self):
        urls = ['http://lab.scrapyd.cn/']
        for url in urls:
            yield scrapy.Request(url=url, dont_filter=True)

另外就是这个 scrapy.Request 类,回调函数 parse 方法最后也是调用这个方法类。这里还有一个重要的参数 callback 。默认不设置时 callback=parse ,所以可以手动设置callback参数,使用别的回调函数。或者准备多个回调函数,每次调度的时候设置不同额callback。比如第一次用默认的,之后在 parse 方法里再调用的时候,设置 callback=func 使用另外的回调函数。

Cookie

默认就是开启Cookie的,所以其实我们并不需要操作什么。
配置的 COOKIES_ENABLED 选项一旦关闭,则不会有Cookie了,别处再怎么设置也没用。
可以用meta参数,为请求单独设置cookie:

yield scrapy.Request(url, self.login, meta={'cookiejar': True})

不过如果要为请求单独设置的话,就得为每个请求都显示的声明。否则不写,就是认为是不要cookie。meta可以有如下设置:

meta={'cookiejar': True}  # 使用Cookie
meta={'cookiejar': False}  # 不使用Cookie,也就写在第一个请求里。之后的请求不设置就是不使用Cookie
meta={'cookiejar': response.meta['cookiejar']}  # 使用上一次的cookie,上一次必须是True或者这个,否则会有问题

手动设置cookie值
Request 实例化的时候有 cookies 参数,直接传字典进去就可以了。

获取cookie的值
并没有cookie这个专门的属性。本质上cookie就是headers里的一个键值对,用下面的方法去headers里获取:

response.request.headers.getlist('Cookie')  # 请求的Cookie
response.headers.getlist('Set-Cookie')  # 响应的Cookie

登录抽屉并点赞

最后就是综合应用了。登录需要Cookies的操作。不过其实什么都不做就可以了,默认方法就能把Cookies操作好。
然后就是从打开页面、完成登录、到最后点赞,需要发多次的请求,然后每次请求返回后所需要做的操作也是不一样的,这里就需要准备多个回调函数,并且再发起请求的时候指定回调函数。代码如下:

import scrapy
from scrapy.selector import Selector
from utils.base64p import b64decode_str  # 自己写的从文件读密码的方法,不是重点

class SpiderLabSpider(scrapy.Spider):
    name = 'chouti_favor'
    custom_settings = {
        'ROBOTSTXT_OBEY': False,
    }

    def start_requests(self):
        url = 'http://dig.chouti.com/'
        yield scrapy.Request(url, self.login)

    def login(self, response):
        # 避免把密码公开出来,去文件里拿,并且做了转码,这不是这里的重点
        with open('../../utils/password') as f:
            auth = f.read()
            auth = auth.split('\n')
        post_dict = {
            'phone': '86%s' % auth[0],  # 从请求正文里发现,会在手机号前加上86
            'password': b64decode_str(auth[1]),  # 直接填明文的用户名和密码也行的
        }
        yield scrapy.FormRequest(
            url='http://dig.chouti.com/login',
            formdata=post_dict,
            callback=self.check_login,
        )

    def check_login(self, response):
        print(response.request.headers.getlist('Cookie'))
        print(response.headers.getlist('Set-Cookie'))
        print(response.text)
        yield scrapy.Request(
            url='http://dig.chouti.com/',
            dont_filter=True,  # 这页之前爬过了,如果不关掉过滤,就不会再爬了
        )

    def parse(self, response):
        items = Selector(response).xpath('//*[@id="content-list"]/div[@class="item"]')
        do_favor = True
        for item in items:
            news = item.xpath(
                './div[@class="news-content"]'
                '//a[contains(concat(" ", normalize-space(@class), " "), " show-content ")]'
                '/text()'
            ).extract()[-1]
            print(news.strip())
            # 点赞,做个判断,只赞第一条
            if do_favor:
                do_favor = False
                linkid = item.xpath('./div[@class="news-content"]/div[@share-linkid]/@share-linkid').extract_first()
                yield scrapy.Request(
                    url='https://dig.chouti.com/link/vote?linksId=%s' % linkid,
                    method='POST',
                    callback=self.favor,
                )

    def favor(self, response):
        print("点赞", response.text)

if __name__ == '__main__':
    from scrapy import cmdline
    log_level = '--nolog'
    name = SpiderLabSpider.name
    cmdline.execute(('scrapy crawl %s %s' % (name, log_level)).split())

注意:首页的地址 http://dig.chouti.com 一共访问了两次。第二次如果不把 dont_filter 设为True,关闭过滤,就不会再去爬了。当然也可以第一次爬完之后,就保存在变量里,等登录后再从这个返回开始之后的处理。
上面的POST请求,用到了 FormRequest 这个类。这个类继承的是 Request 。里面主要就是把字典拼接成请求体,设置一下请求头的 Content-Type ,默认再帮我们把 method 设为 POST 。也是可以继续用 Request 的,就是把上面的3个步骤自己做了。主要是请求体,大概是按下面这样拼接一下传给body参数:

body='phone=86151xxxxxxxx&password=123456&oneMonth=1',

格式化处理

之前只是简单的处理,所以在parse方法中直接处理。对于想要获取更多的数据处理,则可以利用Scrapy的items将数据格式化,然后统一交由pipelines来处理。
回顾一下 Scrapy 组件和工作流程,项目管道(Pipeline) 组件负责这个工作。

items

先要编辑一下 items.py 里的类,默认会帮我们生成一个类,并有简单的注释。必须要处理2个数据 title 和 href ,则改写 items.py 如下:

import scrapy

class PeppascrapyItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    href = scrapy.Field()

然后去修改之前的 parse 方法,导入上面的类,把要处理的数据传递进去生成实例,然后 yield :

from PeppaScrapy.items import PeppascrapyItem

class SpiderLabSpider(scrapy.Spider):
    name = 'spider_lab'
    allowed_domains = ['lab.scrapyd.cn']
    start_urls = ['http://lab.scrapyd.cn/']

    def parse(self, response):
        print(response.url)
        items = Selector(response).xpath(
            '//div[@id="body"]//div[@id="main"]/div[@class="quote post"]')
        for item in items:
            title = item.xpath('./span[@class="text"]/text()').extract_first()
            href = item.xpath('./span/a/@href').extract_first()
            yield PeppascrapyItem(title=title, href=href)

上面这段代码,只需要注意最后3行。把要保存的数据用items.py里的类实例化后,yield返回。
回顾下流程,之前yield返回给 scrapy.Request ,就是把数据返回给调度器继续继续爬取
这里yield返回给 scrapy.Item ,就是 Item Pipeline 里的 Item 进入数据的处理。
在 Item 里只是把数据传递出来,数据的处理则在 Pipeline 里。
如果有多处数据要返回,则可以自定义多个 scrapy.Item 类,来做数据的格式化处理。

Pipline

还有一个 pipelines.py 文件,默认里面只有一个 return ,但是传入2个参数 item 和 spider,先打印看看:

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

只是编写处理方法还不够,这个方法需要注册。在settings.py文件里,默认写好了注册的方法,只需要把注释去掉。ITEM_PIPELINES 的 key 就是要注册的方法,而 value 则是优先级。理论上字典没有顺序,优先级小的方法先执行:

ITEM_PIPELINES = {
   'PeppaScrapy.pipelines.PeppascrapyPipeline': 300,
}

最后返回的item是个字典,我们报错的变量名是key,值就是value。而spider则是这个爬虫 scrapy.Spider 对象。
执行多个操作
这里一个类就是执行一个操作,如果对返回的数据要有多次操作,也可以多定义几个类,然后注册上即可。
每次操作的item,就是上一次操作最后 return item 传递下来的。第一次操作的item则是从 scrapy.Item 传过来的。所以也可以对item进行处理,然后之后的操作就是在上一次操作对item的修改之上进行的。所以也可以想return什么就return什么,就是给下一个操作处理的数据。
绑定特定的爬虫
Pipline并没有和特定的爬虫进行绑定,也就是所有的爬虫都会依次执行所有的Pipline。对于特定爬虫要做得特定的操作,可以在process_item方法里通过参数spider的spider.name进行判断。

DropItem

接着讲上面的执行多个操作。如果在某个地方要终止之后所有的操作,则可以用 DropItem 。用法如下:

from scrapy.exceptions import DropItem

class PeppascrapyPipeline(object):
    def process_item(self, item, spider):
        print(item)
        raise DropItem()

这样对这组数据的操作就终止了。一般应该把这句放在某个条件的分支里。

初始化操作

Pipeline 这个类里,还可以定义更多方法。除了上面的处理方法,还有另外3个方法,其中一个是类方法。所有的方法名都不能修改,具体如下:

class PeppascrapyPipeline(object):

    def __init__(self, value):
        self.value = value

    def process_item(self, item, spider):
        """操作并进行持久化"""
        print(item)
        # 表示将item丢弃,不会被后续pipeline处理
        raise DropItem()
        # print(spider)
        # return item 给后续的pipeline继续处理
        # return item

    @classmethod
    def from_crawler(cls, crawler):
        """初始化时候,用于创建pipeline对象"""
        val = crawler.settings.get('BOT_NAME')
        # getint 方法可以直接获取 int 参数
        # val = crawler.settings.getint('DEPTH_LIMIT')
        return cls(val)

    def open_spider(self,spider):
        """爬虫开始执行时,调用"""
        print('START')

    def close_spider(self,spider):
        """爬虫关闭时,被调用"""
        print('OVER')

类方法 from_crawler 是用于创建pipeline对象的。主要是接收了crawler参数,可以获取到settings里的参数然后传给构造方法。比如这里获取了settings.py里的值传给了对象。
另外2个方法 open_spider 和 close_spider ,是在爬虫开始和关闭时执行的。即使爬虫有多次返回,处理方法要调用多次,但是这2个方法都只会调用一次。这2个方法是在爬虫 scrapy.Spider 开始和关闭的时候各执行一次的。而不是第一次返回数据处理和最后一次数据处理完毕。
打开文件的操作
以写入文件为例,写入一段数据需要3步:打开文件,写入,关闭文件。如果把这3不都写在 process_item 方法里,则会有多次的打开和关闭操作。正确的做法是,打开文件在 open_spider 方法里执行,写入还是在 process_item 方法里每次返回都可以写入,最后在 close_spider 方法里关闭文件。

中间件

默认有一个 middlewares.py 文件,里面默认创建了2个类,分别是爬虫中间件和下载中间件

爬虫中间件

class PeppascrapySpiderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the spider middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_spider_input(self, response, spider):
        """下载完成,执行,然后交给parse处理"""
        # Called for each response that goes through the spider
        # middleware and into the spider.

        # Should return None or raise an exception.
        return None

    def process_spider_output(self, response, result, spider):
        """spider处理完成,返回时调用
        返回Request或者Item(字典也行,Item本身也是个字典)
        Request就是给调度器继续处理
        Item就是给项目管道保存
        """
        # Called with the results returned from the Spider, after
        # it has processed the response.

        # Must return an iterable of Request, dict or Item objects.
        for i in result:
            yield i

    def process_spider_exception(self, response, exception, spider):
        """异常调用"""
        # Called when a spider or process_spider_input() method
        # (from other spider middleware) raises an exception.

        # Should return either None or an iterable of Response, dict
        # or Item objects.
        pass

    def process_start_requests(self, start_requests, spider):
        """爬虫启动时调用"""
        # Called with the start requests of the spider, and works
        # similarly to the process_spider_output() method, except
        # that it doesn’t have a response associated.

        # Must return only requests (not items).
        for r in start_requests:
            yield r

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

爬虫中间件这里要注意下 process_spider_output() 返回的内容之后是要交给调度器继续爬取的,或者是交给项目管道做保存操作。所以返回的可以是 Request 或者是 Item 。

下载器中间件

class PeppascrapyDownloaderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
        """请求需要被下载时,经过所有下载器中间件的process_request调用"""
        # Called for each request that goes through the downloader
        # middleware.

        # Must either:
        # - return None: continue processing this request
        # - or return a Response object
        # - or return a Request object
        # - or raise IgnoreRequest: process_exception() methods of
        #   installed downloader middleware will be called
        return None

    def process_response(self, request, response, spider):
        """spider处理完成,返回时调用"""
        # Called with the response returned from the downloader.

        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        return response

    def process_exception(self, request, exception, spider):
        """异常处理
        当下载处理器(download handler)
        或 process_request() (下载中间件)抛出异常时执行
        """
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

process_request方法
对不同的返回值,回有不同的效果:
一般返回None,继续后面的中间件或者下载。这里可以修改一下请求头信息。比如,在请求头里添加代理的设置,然后再让后续的操作来执行。
返回Response,下载器就是要去下载生成Response。这里直接返回Response就相当于已经下载完成了。所以之后不再是执行下载了,而是返回给中间件里的process_response方法,执行下载完成后的操作。比如,可以不用默认的下载器来下载。到这里自己用Request模块写段代码去下载,然后创建一个scrap.http.Eesponse对象,把内容填进去返回。
返回Request,调度器就是生成一个个的Request,然后调度执行。如果这里返回了Request,就会停止这次的执行,把Request放回调度器,等待下一次被调度执行。在process_response方法里返回Request也是一样的效果,只是这里是在下载前要重新调度,那个是在下载后。

自定义操作

自定制命令

自定制命令
一、在spiders同级创建任意目录,如:commands
二、在目录里创建 crawlall.py 文件,名字任意取,这个文件名将来就是执行这段代码的命令
下面是一个启动spiders里所有爬虫的代码:

from scrapy.commands import ScrapyCommand
from scrapy.utils.project import get_project_settings

class Command(ScrapyCommand):
    requires_project = True

    def syntax(self):
        return '[options]'

    def short_desc(self):
        return 'Runs all of the spiders'

    def run(self, args, opts):
        spider_list = self.crawler_process.spiders.list()
        for name in spider_list:
            self.crawler_process.crawl(name, **opts.__dict__)
        self.crawler_process.start()

三、在 settings.py 中添加配置 COMMANDS_MODULE = '项目名称.目录名称' ,比如:

COMMANDS_MODULE = "PeppaScrapy.commands"

四、执行命令: scrapy crawlall

自定义扩展

利用信号在指定位置注册制定操作。
自定义的型号要写在写一类,然后在settings里注册。默认的配置文件里是有EXTENSIONS的,注释掉了,这里就放开注释然后改一下:

# Enable or disable extensions
# See https://doc.scrapy.org/en/latest/topics/extensions.html
EXTENSIONS = {
   # 'scrapy.extensions.telnet.TelnetConsole': None,
   'PeppaScrapy.extensions.MyExtension': 100
}

根据上面的操作,就是创建 extensions.py 文件,然后写一个 MyExtension 的类:

# PeppaScrapy/extensions.py 文件
from scrapy import signals

class MyExtension(object):
    def __init__(self, value):
        self.value = value

    @classmethod
    def from_crawler(cls, crawler):
        val = crawler.settings.get('BOT_NAME')
        ext = cls(val)
        # 注册你的方法和信息
        crawler.signals.connect(ext.spider_start, signal=signals.spider_opened)
        crawler.signals.connect(ext.spider_stop, signal=signals.spider_closed)
        return ext

    # 写你要执行的方法
    def spider_start(self, spider):
        print('open')

    def spider_stop(self, spider):
        print('close')

所有的信号
上面的例子里用到了 spider_opened 和 spider_closed 这2个信号。
在 scrapy/signals.py 里可以查到所有的信号:

engine_started = object()
engine_stopped = object()
spider_opened = object()
spider_idle = object()
spider_closed = object()
spider_error = object()
request_scheduled = object()
request_dropped = object()
response_received = object()
response_downloaded = object()
item_scraped = object()
item_dropped = object()

配置文件详细

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

# Scrapy settings for step8_king project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     http://doc.scrapy.org/en/latest/topics/settings.html
#     http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
#     http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html

# 1. 爬虫名称
BOT_NAME = 'step8_king'

# 2. 爬虫应用路径
SPIDER_MODULES = ['step8_king.spiders']
NEWSPIDER_MODULE = 'step8_king.spiders'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
# 3. 客户端 user-agent请求头
# USER_AGENT = 'step8_king (+http://www.yourdomain.com)'

# Obey robots.txt rules
# 4. 禁止爬虫配置
# ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
# 5. 并发请求数
# CONCURRENT_REQUESTS = 4

# Configure a delay for requests for the same website (default: 0)
# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
# 6. 延迟下载秒数
# DOWNLOAD_DELAY = 2

# The download delay setting will honor only one of:
# 7. 单域名访问并发数,并且延迟下次秒数也应用在每个域名
# CONCURRENT_REQUESTS_PER_DOMAIN = 2
# 单IP访问并发数,如果有值则忽略:CONCURRENT_REQUESTS_PER_DOMAIN,并且延迟下次秒数也应用在每个IP
# CONCURRENT_REQUESTS_PER_IP = 3

# Disable cookies (enabled by default)
# 8. 是否支持cookie,cookiejar进行操作cookie
# COOKIES_ENABLED = True
# COOKIES_DEBUG = True

# Disable Telnet Console (enabled by default)
# 9. Telnet用于查看当前爬虫的信息,操作爬虫等...
#    使用telnet ip port ,然后通过命令操作
# TELNETCONSOLE_ENABLED = True
# TELNETCONSOLE_HOST = '127.0.0.1'
# TELNETCONSOLE_PORT = [6023,]

# 10. 默认请求头
# Override the default request headers:
# DEFAULT_REQUEST_HEADERS = {
#     'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
#     'Accept-Language': 'en',
# }

# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
# 11. 定义pipeline处理请求
# ITEM_PIPELINES = {
#    'step8_king.pipelines.JsonPipeline': 700,
#    'step8_king.pipelines.FilePipeline': 500,
# }

# 12. 自定义扩展,基于信号进行调用
# Enable or disable extensions
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
# EXTENSIONS = {
#     # 'step8_king.extensions.MyExtension': 500,
# }

# 13. 爬虫允许的最大深度,可以通过meta查看当前深度;0表示无深度
# DEPTH_LIMIT = 3

# 14. 爬取时,0表示深度优先Lifo(默认);1表示广度优先FiFo

# 后进先出,深度优先
# DEPTH_PRIORITY = 0
# SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue'
# SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue'
# 先进先出,广度优先

# DEPTH_PRIORITY = 1
# SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
# SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue'

# 15. 调度器队列
# SCHEDULER = 'scrapy.core.scheduler.Scheduler'
# from scrapy.core.scheduler import Scheduler

# 16. 访问URL去重
# DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl'

# Enable and configure the AutoThrottle extension (disabled by default)
# See http://doc.scrapy.org/en/latest/topics/autothrottle.html

"""
17. 自动限速算法
    from scrapy.contrib.throttle import AutoThrottle
    自动限速设置
    1. 获取最小延迟 DOWNLOAD_DELAY
    2. 获取最大延迟 AUTOTHROTTLE_MAX_DELAY
    3. 设置初始下载延迟 AUTOTHROTTLE_START_DELAY
    4. 当请求下载完成后,获取其"连接"时间 latency,即:请求连接到接受到响应头之间的时间
    5. 用于计算的... AUTOTHROTTLE_TARGET_CONCURRENCY
    target_delay = latency / self.target_concurrency
    new_delay = (slot.delay + target_delay) / 2.0 # 表示上一次的延迟时间
    new_delay = max(target_delay, new_delay)
    new_delay = min(max(self.mindelay, new_delay), self.maxdelay)
    slot.delay = new_delay
"""

# 开始自动限速
# AUTOTHROTTLE_ENABLED = True
# The initial download delay
# 初始下载延迟
# AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
# 最大下载延迟
# AUTOTHROTTLE_MAX_DELAY = 10
# The average number of requests Scrapy should be sending in parallel to each remote server
# 平均每秒并发数
# AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0

# Enable showing throttling stats for every response received:
# 是否显示
# AUTOTHROTTLE_DEBUG = True

# Enable and configure HTTP caching (disabled by default)
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings

"""
18. 启用缓存
    目的用于将已经发送的请求或相应缓存下来,以便以后使用

    from scrapy.downloadermiddlewares.httpcache import HttpCacheMiddleware
    from scrapy.extensions.httpcache import DummyPolicy
    from scrapy.extensions.httpcache import FilesystemCacheStorage
"""
# 是否启用缓存策略
# HTTPCACHE_ENABLED = True

# 缓存策略:所有请求均缓存,下次在请求直接访问原来的缓存即可
# HTTPCACHE_POLICY = "scrapy.extensions.httpcache.DummyPolicy"
# 缓存策略:根据Http响应头:Cache-Control、Last-Modified 等进行缓存的策略
# HTTPCACHE_POLICY = "scrapy.extensions.httpcache.RFC2616Policy"

# 缓存超时时间
# HTTPCACHE_EXPIRATION_SECS = 0

# 缓存保存路径
# HTTPCACHE_DIR = 'httpcache'

# 缓存忽略的Http状态码
# HTTPCACHE_IGNORE_HTTP_CODES = []

# 缓存存储的插件
# HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

"""
19. 代理,需要在环境变量中设置
    from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware

    方式一:使用默认
        os.environ
        {
            http_proxy:http://root:woshiniba@192.168.11.11:9999/
            https_proxy:http://192.168.11.11:9999/
        }
    方式二:使用自定义下载中间件

    def to_bytes(text, encoding=None, errors='strict'):
        if isinstance(text, bytes):
            return text
        if not isinstance(text, six.string_types):
            raise TypeError('to_bytes must receive a unicode, str or bytes '
                            'object, got %s' % type(text).__name__)
        if encoding is None:
            encoding = 'utf-8'
        return text.encode(encoding, errors)

    class ProxyMiddleware(object):
        def process_request(self, request, spider):
            PROXIES = [
                {'ip_port': '111.11.228.75:80', 'user_pass': ''},
                {'ip_port': '120.198.243.22:80', 'user_pass': ''},
                {'ip_port': '111.8.60.9:8123', 'user_pass': ''},
                {'ip_port': '101.71.27.120:80', 'user_pass': ''},
                {'ip_port': '122.96.59.104:80', 'user_pass': ''},
                {'ip_port': '122.224.249.122:8088', 'user_pass': ''},
            ]
            proxy = random.choice(PROXIES)
            if proxy['user_pass'] is not None:
                request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])
                encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass']))
                request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass)
                print "**************ProxyMiddleware have pass************" + proxy['ip_port']
            else:
                print "**************ProxyMiddleware no pass************" + proxy['ip_port']
                request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])

    DOWNLOADER_MIDDLEWARES = {
       'step8_king.middlewares.ProxyMiddleware': 500,
    }

"""

"""
20. Https访问
    Https访问时有两种情况:
    1. 要爬取网站使用的可信任证书(默认支持)
        DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory"
        DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory"

    2. 要爬取网站使用的自定义证书
        DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory"
        DOWNLOADER_CLIENTCONTEXTFACTORY = "step8_king.https.MySSLFactory"

        # https.py
        from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory
        from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate)

        class MySSLFactory(ScrapyClientContextFactory):
            def getCertificateOptions(self):
                from OpenSSL import crypto
                v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.key.unsecure', mode='r').read())
                v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.pem', mode='r').read())
                return CertificateOptions(
                    privateKey=v1,  # pKey对象
                    certificate=v2,  # X509对象
                    verify=False,
                    method=getattr(self, 'method', getattr(self, '_ssl_method', None))
                )
    其他:
        相关类
            scrapy.core.downloader.handlers.http.HttpDownloadHandler
            scrapy.core.downloader.webclient.ScrapyHTTPClientFactory
            scrapy.core.downloader.contextfactory.ScrapyClientContextFactory
        相关配置
            DOWNLOADER_HTTPCLIENTFACTORY
            DOWNLOADER_CLIENTCONTEXTFACTORY

"""

"""
21. 爬虫中间件
    class SpiderMiddleware(object):

        def process_spider_input(self,response, spider):
            '''
            下载完成,执行,然后交给parse处理
            :param response: 
            :param spider: 
            :return: 
            '''
            pass

        def process_spider_output(self,response, result, spider):
            '''
            spider处理完成,返回时调用
            :param response:
            :param result:
            :param spider:
            :return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)
            '''
            return result

        def process_spider_exception(self,response, exception, spider):
            '''
            异常调用
            :param response:
            :param exception:
            :param spider:
            :return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline
            '''
            return None

        def process_start_requests(self,start_requests, spider):
            '''
            爬虫启动时调用
            :param start_requests:
            :param spider:
            :return: 包含 Request 对象的可迭代对象
            '''
            return start_requests

    内置爬虫中间件:
        'scrapy.contrib.spidermiddleware.httperror.HttpErrorMiddleware': 50,
        'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': 500,
        'scrapy.contrib.spidermiddleware.referer.RefererMiddleware': 700,
        'scrapy.contrib.spidermiddleware.urllength.UrlLengthMiddleware': 800,
        'scrapy.contrib.spidermiddleware.depth.DepthMiddleware': 900,

"""
# from scrapy.contrib.spidermiddleware.referer import RefererMiddleware
# Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
SPIDER_MIDDLEWARES = {
    # 'step8_king.middlewares.SpiderMiddleware': 543,
}

"""
22. 下载中间件
    class DownMiddleware1(object):
        def process_request(self, request, spider):
            '''
            请求需要被下载时,经过所有下载器中间件的process_request调用
            :param request:
            :param spider:
            :return:
                None,继续后续中间件去下载;
                Response对象,停止process_request的执行,开始执行process_response
                Request对象,停止中间件的执行,将Request重新调度器
                raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
            '''
            pass

        def process_response(self, request, response, spider):
            '''
            spider处理完成,返回时调用
            :param response:
            :param result:
            :param spider:
            :return:
                Response 对象:转交给其他中间件process_response
                Request 对象:停止中间件,request会被重新调度下载
                raise IgnoreRequest 异常:调用Request.errback
            '''
            print('response1')
            return response

        def process_exception(self, request, exception, spider):
            '''
            当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
            :param response:
            :param exception:
            :param spider:
            :return:
                None:继续交给后续中间件处理异常;
                Response对象:停止后续process_exception方法
                Request对象:停止中间件,request将会被重新调用下载
            '''
            return None

    默认下载中间件
    {
        'scrapy.contrib.downloadermiddleware.robotstxt.RobotsTxtMiddleware': 100,
        'scrapy.contrib.downloadermiddleware.httpauth.HttpAuthMiddleware': 300,
        'scrapy.contrib.downloadermiddleware.downloadtimeout.DownloadTimeoutMiddleware': 350,
        'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': 400,
        'scrapy.contrib.downloadermiddleware.retry.RetryMiddleware': 500,
        'scrapy.contrib.downloadermiddleware.defaultheaders.DefaultHeadersMiddleware': 550,
        'scrapy.contrib.downloadermiddleware.redirect.MetaRefreshMiddleware': 580,
        'scrapy.contrib.downloadermiddleware.httpcompression.HttpCompressionMiddleware': 590,
        'scrapy.contrib.downloadermiddleware.redirect.RedirectMiddleware': 600,
        'scrapy.contrib.downloadermiddleware.cookies.CookiesMiddleware': 700,
        'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 750,
        'scrapy.contrib.downloadermiddleware.chunked.ChunkedTransferMiddleware': 830,
        'scrapy.contrib.downloadermiddleware.stats.DownloaderStats': 850,
        'scrapy.contrib.downloadermiddleware.httpcache.HttpCacheMiddleware': 900,
    }

"""
# from scrapy.contrib.downloadermiddleware.httpauth import HttpAuthMiddleware
# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
# DOWNLOADER_MIDDLEWARES = {
#    'step8_king.middlewares.DownMiddleware1': 100,
#    'step8_king.middlewares.DownMiddleware2': 500,
# }

分享题目:Python自动化开发学习-Scrapy
URL分享:http://azwzsj.com/article/johspj.html