爬了个爬(三)Scrapy框架

参考博客:武Sir  

  Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。

Scrapy 使用了 Twisted异步网络库来处理网络通讯。整体架构大致如下:

Scrapy主要包括了以下组件:

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

  Scrapy运行流程大概如下:

    1. 引擎从调度器中取出一个链接(URL)用于接下来的抓取
    2. 引擎把URL封装成一个请求(Request)传给下载器
    3. 下载器把资源下载下来,并封装成应答包(Response)
    4. 爬虫解析Response
    5. 解析出实体(Item),则交给实体管道进行进一步的处理
    6. 解析出的是链接(URL),则把URL交给调度器等待抓取

一、scrapy 框架安装方法

Linux:
    pip3 install scrapy
window: 
    注:本人Python版本是64位,若是32位,请到官网下载对应版本文件!
    1、pip3 install wheel 有请跳过;
    2、安装关联模块pypiwin32:pip3 install pypiwin32 有请跳过;
    3、在http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 下载 然后pip3 install 文件路径 安装 twisted 
    4、pip3 install scrapy 

    5、python import scrapy 报错:from .. import etree ImportError: DLL load failed: 找不到指定的程序。
	解决办法:由于本地缺少lxml文件或是lxml文件不符
	pip3 uninstall lxml  先卸载已经安装的lxml
	官网下载新的lxml:http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml 下载 然后pip3 install 文件路径 安装 lxml 
	
    若是有其他报错,查看少哪个模块,pip安装即可,例如:缺少cryptography模块;pip3 install cryptography

文件下载,请猛击:  Twisted 64位下载  lxml 64位下载

 二、基本应用

一、基本命令

#在当前目录下操作
#创建项目:
	scrapy startproject 项目名
	例:scrapy startproject sp1
	sp1项目结构介绍
		- sp1
			- spiders  目录
			- middlewares.py  中间件
			- items.py        格式化
			- pipelines.py    持久化
			- settings.cfg 	  配置
		- scrapy.cfg   配置
		
#创建爬虫:
	cd sp1
	scrapy genspider 爬虫名 域名
	例如:scrapy genspider example example.com
		  scrapy genspider baidu baidu.com
		  scrapy genspider xiaohuar xiaohuar.com
		  scrapy genspider jiandan jandan.net/ooxx
	PS:
		  查看所有命令:scrapy gensipider -l
		  查看模板命令:scrapy gensipider -d 模板名称
	#注意:一般创建爬虫文件时,以网站域名命名

#展示爬虫应用列表 
	- scrapy list
		  
#单独执行某爬虫,进入project
	- scrapy crawl 爬虫名
	例如:scrapy crawl baidu   # 有执行日志流程  
	- scrapy crawl baidu --nolog # 无执行日志

	#可能会遇到执行报错 forbidden 的问题,这是因为在爬取数据的时候,需要遵循爬取协议,如果对方服务器不允许爬取那就会返回这个错误。
	#真的需要暴力爬取的话,那就在settings配置文件中,把协议改成False。这样就能获取到数据!

#打印爬取内容:
	在你创建的爬虫项目下,有一个以项目名命名的py文件【我的是baidu.py】,这里边有一个爬虫的类,类下有个parse方法,用于接收返回值response
	在函数下添加如下代码:print(response.text)。代表执行完毕之后打印爬取的内容!
	response.text  # 字符串类型数据
	response.body  # 字节类型数据
文件说明:

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

二、小试牛刀 

  既然我们已经熟悉了基本操作,那我们就来扒拉一下校花网。

创建scrapy项目
	- scrapy startproject sp1
创建爬虫 (一个项目下可以创建多个爬虫)
	- scrapy genspider xiaohuar xiaohuar.com
创建完成之后,可以用pycharm打开创建的项目,欣赏一下自己的杰作!

在 spiders 文件下,会有一个创建的爬虫文件 - xiaohuar.py。文件内部代码如下:

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

class XiaohuarSpider(scrapy.Spider):
    name = 'xiaohuar'  #爬虫名称
    allowed_domains = ['xiaohuar.com'] #允许的域名
    start_urls = ['http://www.xiaohuar.com/hua/'] #起始URL

    def parse(self, response):
        # 访问起始URL并获取结果后的回调函数
        pass
校花网

我们发现,回调函数parse中没有任何代码,我们可以自定义操作,如:我们写个 print(response.text) 意思是打印返回的结果。[自行添加测试哈]

关于编码的问题:

  我们爬取下来的信息,可能因为字符编码的问题,导致打印的结果是乱码,解决方法是把如下代码粘贴到爬虫文件的头部。

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

执行此爬虫文件,则在终端进入项目目录执行如下命令:

scrapy crawl xiaohuar --nolog

三、scrapy框架内部方法介绍

1、选择器

  当爬虫爬取下来所有页面,但是我们仅需要某一段标签,或是超链接地址,再或者是需要某张照片or文本,这怎么办呢?scrapy框架内部为我们封装了Selector方法,方便我们去截取。

引用方法:

from scrapy.selector import Selector

应用:

#处理返回的字符串,转换成标签
	hxs = Selector(response=response)
	# hxs.xpath(条件)  #通过条件过滤查找,类似与CSS中的选择器
	#lists = hxs.xpath(条件).extract()  #获取条件查找到的所有标签,返回结果是一个列表,列表内都是找到的一个个对应的标签
	#lists = hxs.xpath(条件).extract_first()  #获取条件查找到的第一个标签
#关于条件:
    常用操作:
		// 代表子子孙孙中查找
		/  儿子中查找
		   标签名[@属性名="属性值"] 定位直接找到某一类标签
		支持正则匹配查找,语法结构如下:
			标签名[re:test(@属性名,"正则匹配信息语法")]
		/@属性名 获取某个标签内的属性
		/text()  获取标签的文本
		contains 包含
		starts-with 起始
	也支持链式操作,及查找的时候可以找当前标签下的子标签或是子孙标签
		标签名[@属性名="属性值"]/a/@href   子标签a标签的href属性
		标签名[@属性名="属性值"]//a/@href  子孙标签a标签的href属性
	
	特殊的,也支持在当前标签再做一次过滤,语法不变但查找是需要指定当前位置也就是点(.);
		hxs = Selector(response=response)
        user_list = hxs.xpath('//div[@class="item masonry_brick"]')
        for item in user_list:
            price = item.xpath('.//span[@class="price"]/text()').extract_first()
            url = item.xpath('//div[@class="item_t"]/div[@class="img"]//a/@href').extract_first()
            print(price, url)
			
		#item.xpath(.//) 相对于当前 子孙 中找
        #item.xpath(./) 相对于当前 子代 中找
        #item.xpath(*/) 相对于当前 子代 中找
        #item.xpath('a') 相对于当前 找子代的a标签
# hxs = Selector(response=response).xpath('//a')
# print(hxs)
# hxs = Selector(response=response).xpath('//a[2]')
# print(hxs)
# hxs = Selector(response=response).xpath('//a[@id]')
# print(hxs)
# hxs = Selector(response=response).xpath('//a[@id="i1"]')
# print(hxs)
# hxs = Selector(response=response).xpath('//a[@href="link.html"][@id="i1"]')
# print(hxs)
# hxs = Selector(response=response).xpath('//a[contains(@href, "link")]')
# print(hxs)
# hxs = Selector(response=response).xpath('//a[starts-with(@href, "link")]')
# print(hxs)
# hxs = Selector(response=response).xpath('//a[re:test(@id, "id+")]')
# print(hxs)
# hxs = Selector(response=response).xpath('//a[re:test(@id, "id+")]/text()').extract()
# print(hxs)
# hxs = Selector(response=response).xpath('//a[re:test(@id, "id+")]/@href').extract()
# print(hxs)
# hxs = Selector(response=response).xpath('/html/body/ul/li/a/@href').extract()
# print(hxs)
# hxs = Selector(response=response).xpath('//body/ul/li/a/@href').extract_first()
# print(hxs)
 
# ul_list = Selector(response=response).xpath('//body/ul/li')
# for item in ul_list:
#     v = item.xpath('./a/span')
#     # 或
#     # v = item.xpath('a/span')
#     # 或
#     # v = item.xpath('*/a/span')
#     print(v)

2、请求相关

  scrapy框架内部,也封装了Request方法,与Python内置的requests的应用一致。

引入方法:

from scrapy.http import Request
class Request(object_ref):

    def __init__(self, url, callback=None, method='GET', headers=None, body=None,
                 cookies=None, meta=None, encoding='utf-8', priority=0,
                 dont_filter=False, errback=None, flags=None):
    .........

#url = 请求路由地址
#method 请求方法,默认GET
#headers 请求头
#body 传值
#cookies cookies

#dont_filter 是否过滤,默认False url请求过滤
Request类内部部分源码及参数介绍
分析HTTP请求
	- 请求方式
	- URL
	- Http常用请求头
		各个参数对应的不同名称:
		user-agent: 当前用户访问使用的设备信息
		content-type:xxxx 请求体数据类型
		host:请求访问的主地址
		Referer:访问请求来自哪个页面
		cookies:关键信息

	PS:POST请求,不同的请求体数据类型对应着不同的请求头
		数据是:Form Data,请求头类型为:Content-Type:application/x-www-form-urlencoded;
		数据是:request payload 请求头类型为:content-type: application/json
import scrapy
from scrapy.selector import HtmlXPathSelector
from scrapy.http.request import Request
 
 
class DigSpider(scrapy.Spider):
    # 爬虫应用的名称,通过此名称启动爬虫命令
    name = "dig"
 
    # 允许的域名
    allowed_domains = ["chouti.com"]
 
    # 起始URL
    start_urls = [
        'http://dig.chouti.com/',
    ]
 
    has_request_set = {}
 
    def parse(self, response):
        print(response.url)
 
        hxs = HtmlXPathSelector(response)
        page_list = hxs.select('//div[@id="dig_lcpage"]//a[re:test(@href, "/all/hot/recent/d+")]/@href').extract()
        for page in page_list:
            page_url = 'http://dig.chouti.com%s' % page
            key = self.md5(page_url)
            if key in self.has_request_set:
                pass
            else:
                self.has_request_set[key] = page_url
                obj = Request(url=page_url, method='GET', callback=self.parse)
                yield obj
 
    @staticmethod
    def md5(val):
        import hashlib
        ha = hashlib.md5()
        ha.update(bytes(val, encoding='utf-8'))
        key = ha.hexdigest()
        return key
小试牛刀
# -*- coding: utf-8 -*-
import scrapy
from scrapy.selector import Selector
from scrapy.http import Request

class XiaohuarSpider(scrapy.Spider):
    name = 'xiaohuar'  #爬虫名称
    allowed_domains = ['xiaohuar.com'] #允许的域名
    start_urls = ['http://www.xiaohuar.com/hua/'] #起始URL

    def parse(self, response):
        # 访问起始URL并获取结果后的回调函数

        #处理返回的字符串,转换成标签
        hxs = Selector(response=response)

        user_list = hxs.xpath('//div[@class="item masonry_brick"]')
        for item in user_list:
            price = item.xpath('.//span[@class="price"]/text()').extract_first()
            url = item.xpath('//div[@class="item_t"]/div[@class="img"]//a/@href').extract_first()
            print(price, url)

        # 跨页获取
        result = hxs.xpath('.//a[re:test(@href,"http://www.xiaohuar.com/list-1-d+.html")]/@href')
        print(result)

        # 规则,固定写法,在配置文件中指定递归层数
        for url in result:
            yield Request(url=url, callback=self.parse)
校花网,跨页循环打印名字和url地址

  注意:Request是一个封装用户请求的类,请求回来都会调用参数为 callback 的回调函数,默认为None,我们可以通过callback = 函数名 指定请求回来执行某个函数。在回调函数中需要使用 yield该对象 表示才继续访问。在请求过程中我们不知道什么时候请求返回信息,所以我们就需要利用回调函数去响应!

  yield 的意义及在于,把当前需要继续请求的函数交给爬虫引擎,再次放入队列执行。

注意:settings.py中设置DEPTH_LIMIT =n n来指定“递归”的层数。

四、自定义scrapy框架方法

1、自定义起始URL 执行的回调函数

  当我们创建一个爬虫文件,都会在类下生成一个parse函数,只要是执行调用爬虫就会默认执行这个parse函数的方法。我们知道创建的爬虫类继承的是 Spider类的方法,那这里边是不是有相关默认执行的操作呢?我们查看源码发现,确实是有个函数 start_requests ,用于指定起始URL默认执行的回调函数。所以我们可以重写这个函数,以实现自定义起始URL,执行我们定义的回调函数。代码如下:

import scrapy
from scrapy.http import Request

class ChoutiSpider(scrapy.Spider):
	name = 'chouti'
	allowed_domains = ['chouti.com']
	start_urls = ['http://chouti.com/']

	def start_requests(self):
		"""
		自定义起始url
		"""
		for url in self.start_urls:
			yield Request(url, dont_filter=True,callback=self.parse1)

	def parse1(self, response):
		"""
		自定义执行的回调函数
		"""
		pass

2、POST请求,请求头相关问题

关于请求体数据类型的转换:把字典类型的数据转换成 k1=v1&k2=v2....

import urllib.parse
data = urllib.parse.urlencode({'k1':'v1','k2':'v2'}) 


GET请求:
	url, 
	method='GET', 
	headers={}, 
	cookies={}, cookiejar

POST请求:
	url, 
	method='GET', 
	headers={}, 
	cookies={}, cookiejar
	body=None,
	
		请求头:application/x-www-form-urlencoded; charset=UTF-8
		要传递数据类型:"phone=86155fa&password=asdf&oneMonth=1"  
			form_data = {
				'user':'alex',
				'pwd': 123
			}
			import urllib.parse
			data = urllib.parse.urlencode({'k1':'v1','k2':'v2'}) 	 
		
		请求头:application/json; charset=UTF-8
		要传递数据类型:"{k1:'v1','k2':'v2'}"  
			json.dumsp()		

2.5、硬插一杠,获取cookies

  我们知道在对网站请求的时候,需要携带cookies,那在scrapy框架返回的结果中怎么获取cookies呢?如下方法:

from scrapy.http.cookies import CookieJar  #引入
#转换成cookies的对象
cookies_jar = CookieJar() #对象,内部封装了cookies,scrapy框架可以直接使用,打印的话需要如下转换
cookies_jar.extract_cookies(response,response.request) #去响应中获取cookies

#可视化字典类型的cookies
cookie_dict = {}
for k, v in cookies_jar._cookies.items():
	for i, j in v.items():
		for m, n in j.items():
			cookie_dict[m] = n.value
			
#注意:在scrapy框架中,Request方法可以使用 cookies的对象,也可以使用字典类型的cookies
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request
from scrapy.selector import Selector
from scrapy.http.cookies import CookieJar
import urllib.parse as urlps

class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['chouti.com']
    start_urls = ['http://chouti.com/']

    #获取cookies
    cookie_dict = {}

    def start_requests(self):
        """
        自定义起始url,执行访问主页
        :return:
        """
        for url in self.start_urls:
            yield Request(url, dont_filter=True,callback=self.parse_login)

    def parse_login(self, response):
        #访问登录页面,登录并拿到新的cookies
        cookies_jar = CookieJar() #对象,内部封装了cookies,scrapy框架可以直接使用,打印的话需要如下转换
        cookies_jar.extract_cookies(response,response.request) #去响应中获取cookies

        for k, v in cookies_jar._cookies.items():
            for i, j in v.items():
                for m, n in j.items():
                    self.cookie_dict[m] = n.value

        url_info = "http://dig.chouti.com/login"
        post_dict = {
            'phone':'8613581945998',
            'password':'zyj13032025071',
            'oneMonth':1,
        }

        #POST登录
        yield Request(
            url=url_info,
            method="POST",
            cookies=self.cookie_dict,
            body=urlps.urlencode(post_dict),
            headers={"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8",},
            callback=self.get_message,
        )

    def get_message(self,response):
        # 获取消息列表
        print(response.text)
        yield Request(
            url="http://dig.chouti.com/",
            method="GET",
            cookies=self.cookie_dict,
            callback=self.parse_UP,
        )

    def parse_UP(self,response):
        #找到标签,点赞
        #找div,class = part2, 获取share-linkid
        print(11111)
        hxs = Selector(response)
        link_id_list = hxs.xpath('//div[@class="part2"]/@share-linkid').extract()
        print(link_id_list)

        for link_id in link_id_list:
            #获取每一个ID去点赞(仅在第一页点赞)
            base_url = "http://dig.chouti.com/link/vote?linksId=%s"%(link_id,)
            yield Request(
                url=base_url,
                method="POST",
                cookies=self.cookie_dict,
                callback=self.return_infos,
            )

        #对所有的页面点赞
        page_list = hxs.xpath('//div[@id="dig_lcpage"]//a/@href').extract()
        for page in page_list:
            page_url = "http://dig.chouti.com%s"%(page,)
            yield Request(url=page_url,method="GET",dont_filter=False,callback=self.parse_UP)
            page_list.remove(page)

    def return_infos(self,response):
        #点赞之后,获取返回信息
        print(response.text)
练习:自动登录抽屉并点赞

3、持久化

  上述实例只是简单的处理,所以可以在parse方法中直接处理。如果对于想要获取更多的数据处理保存在本地,则就要利用Scrapy的items将数据格式化,然后统一交由pipelines来处理持久化,保存在本地。

需要先在item.py文件中结构化,然后再通过pipeline持久化

item.py文件的类下可以自定义字段。
	class Sp1Item(scrapy.Item):
		# define the fields for your item here like:
		# name = scrapy.Field()
		"""
			注意只有Field字段
		"""
		name = scrapy.Field()
		url = scrapy.Field()
		
pipeline执行的前提:
	- spider爬虫中要 yield Items对象
	- settings中注册
		ITEM_PIPELINES = {
		   'sp2.pipelines.Sp2Pipeline': 300, #数字代表优先级,越小优先级越大
		   'sp2.pipelines.Sp3Pipeline': 100,
		}
	# 每行后面的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内。
	
编写pipeline 持久化操作

	"""
	检测 Sp2Pipeline类中是否有 from_crawler方法:
	如果有:
		obj = 类.from_crawler()
	如果没有:
		obj = 类()
	实例化完对象之后,会调用以下方法:
	obj.open_spider()  #爬虫开始执行调用
	
	然后会循环
		爬虫运行,并且执行parse各种各样的方法,yield item
		obj.process_item() #把获取的信息写入文件或是数据库
	循环结束,关闭	
	obj.close_spider() #爬虫关闭时调用
	注意:pipeline在爬虫开始初始化执行的时候,除了process_item()类下的其他方法都已经执行完成【实例化对象,连接数据库或是打开文件】,
        而process_item()会被爬虫代码循环调用执行写入信息,直至爬虫代码执行结束!
	"""
# -*- coding: utf-8 -*-
import scrapy
from scrapy.selector import Selector
from scrapy.http import Request
from items import Sp1Item

class JandanSpider(scrapy.Spider):
    name = 'jandan'
    allowed_domains = ['jandan.net']
    start_urls = ['http://jandan.net/ooxx/']

    def start_requests(self):
        for url in self.start_urls:
            yield Request(url=url,dont_filter=True,callback=self.parse1)

    def parse1(self, response):

        hxs = Selector(response)
        text_list = hxs.xpath('//div[@class="indexs"]/h2/a/text()').extract()
        for item in text_list:
            yield Sp1Item(text=item,name="jiandan")
spider/jandan.py
import scrapy
class Sp1Item(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    """
        注意只有Field字段
    """
    name = scrapy.Field()
    text = scrapy.Field()
items.py
class Sp2Pipeline(object):
    def __init__(self):
        self.f = None
        self.val = None
        
    def process_item(self, item, spider):
        """

        :param item:  爬虫中yield回来的对象
        :param spider: 爬虫对象 obj = JianDanSpider()
        :return:
        """
        print(item)
        self.f.write('....')
        #将item 传递给下一个pipeline 的 process_item 方法
        return item  #把对象传递给下一个pipeline
        
        # 如果不想让下一个pipline中process_item方法不执行,使用如下,抛出异常不再执行
        # from scrapy.exceptions import DropItem
        # raise DropItem()  下一个pipeline的process_item方法不在执行

    @classmethod
    def from_crawler(cls, crawler):
        """
        初始化时候,用于创建pipeline对象
        :param crawler: 爬虫相关的所有信息,可以去配置文件取值
        :return:
        """
        #从配置文件中取值,实例化传给__init__方法。【一般是通过这种方式连接数据库】
        # val = crawler.settings.get('MMMM')
        print('执行pipeline的from_crawler,进行实例化对象')
        return cls()

    def open_spider(self,spider):
        """
        爬虫开始执行时,调用
        :param spider: 爬虫对象
        :return:
        """
        print('打开爬虫')
        
        #打开文件
        self.f = open('a.log','a+') 
        
        #连接数据库
        self.val = 数据库连接操作
        
    def close_spider(self,spider):
        """
        爬虫关闭时,被调用
        :param spider:
        :return:
        """
        #关闭文件
        self.f.close()
        
        #关闭数据库
Pipeline.py(自定义持久化的方法,力荐)

注意:
  0、items结构化的文件可以自定义编写;但是只有一个字段名
  1、Pipeline类内最多可以构建5个方法,如下:__init__ ,open_spider,close_spider,from_crawler,process_item。
  2、PipeLine.py文件内,可以定义多个类,但是爬虫会顺序执行不同类内 process_item 这个方法。
    执行原因:process_item方法下,如果最后有个 return item ,就会把对象传递给下一个pipeline。
      #将item 传递给下一个pipeline 的 process_item 方法
      return item #把对象传递给下一个pipeline


      # 如果不想让下一个pipline中process_item方法不执行,使用如下,抛出异常不再执行
      # from scrapy.exceptions import DropItem
      # raise DropItem() 下一个pipeline的process_item方法不在执行

  3、多个执行process_item方法的的好处就是可以把数据保存到多地;
  4、PipeLine是全局生效,所有爬虫都可以调用执行;调用方法:【yield Item对象】。
  5、如果个别爬虫需要做特殊操作:
    可以在process_item方法下,通过spider.name获取爬虫的名字,通过这个名字进行判断单独的应用在这个爬虫上,从而做特殊的操作!

4、自定义去重规则(避免重复访问)

  我们都知道scrapy框架内部是有去重规则的,调用方式默认是在setting.py文件中存在的。
  scrapy默认使用 scrapy.dupefilter.RFPDupeFilter 进行去重,相关配置有:

DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
DUPEFILTER_DEBUG = False
JOBDIR = "保存范文记录的日志路径,如:/root/"  # 最终路径为 /root/requests.seen

我们自定义的话,需要先在当前项目下创建一个py文件,剩下的就两点:
  - 定义一个类;
  - 配置文件中指定
    DUPEFILTER_CLASS = 'sp2.rep.RepeatUrl' #文件的相对路径+类名!

class RepeatUrl:
    def __init__(self):
        #可以把要检测的url写在数据库 or 放在另一台服务器的内存,或是缓存
        self.visited_url = set()#结合 #

    @classmethod
    def from_settings(cls, settings):
        """
        初始化时,调用
        :param settings:配置文件,可以取东西
        :return:
        """
        return cls()

    def request_seen(self, request):
        """
        检测当前请求是否已经被访问过
        :param request:请求
        :return: True表示已经访问过;False表示未访问过
        """
        if request.url in self.visited_url:
            return True
        self.visited_url.add(request.url)
        return False

    def open(self):
        """
        开始爬去请求时,调用
        :return:
        """
        print('open replication')

    def close(self, reason):
        """
        结束爬虫爬取时,调用
        :param reason:
        :return:
        """
        print('close replication')

    def log(self, request, spider):
        """
        记录日志
        :param request:
        :param spider:
        :return:
        """
        print('repeat', request.url)
自定义去重操作

5、自定义扩展【基于信号】

  自定义扩展时,利用信号在指定位置注册制定操作。

  应用的话有两点:
    1、需要在当前项目下创建一个py文件。在文件中写入自定义扩展的操作。
    2、需要在配置文件中注册
      EXTENSIONS = {
        # 'scrapy.extensions.telnet.TelnetConsole': None,
        # '文件路径': 数字代表优先级,
      }

from scrapy import signals

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

    @classmethod
    def from_crawler(cls, crawler):
        val = crawler.settings.getint('MMMM')
        ext = cls(val)

        # 在scrapy中注册信号: ext.opened 注册触发信号时执行的函数,爬虫执行自动触发
        crawler.signals.connect(ext.opened, signal=signals.spider_opened)
        # 在scrapy中注册信号: 同上
        crawler.signals.connect(ext.closed, signal=signals.spider_closed)
        
        return ext
        
    #自定义触发信号时执行的函数
    def opened(self, spider):
        print('open')

    def closed(self, spider):
        print('close')
自定义扩展代码

  应用:在执行爬虫,若想实现其他的功能或是操作,不用多想,此时就需要自定义扩展!

 6、中间件

  我们在学习Django框架已经学习了中间件,爬虫的中间件功能与其一致,也是为了实现批量处理。爬虫的中间件分为:爬虫中间件(作用于爬虫文件与引擎之间)、下载中间件(作用于引擎与下载器之间)。在中间件中我们可以自定义方法,对所有要执行的爬虫或是在下载之前,进行处理。

- 下载中间件
	在process_request方法中,可以修改 请求方法 或是 请求头信息;加入代理。
	注意:我们可以在这个方法内,自定义请求地址 获取下载的信息,然后放入response响应体中,不管爬虫爬取的是哪个网站的数据返回的结果都是我们自定义的下载信息。
	在process_response方法中,可以自定义响应信息的处理方式。
	
	 - 引用的话,需要在配置文件中注册:
		# Enable or disable spider middlewares
		# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
		SPIDER_MIDDLEWARES = {
		   'sp2.middlewares.Sp2SpiderMiddleware': 543,
		}
	
- 爬虫中间件
	- start_requests 可迭代对象
	
	- 引用的话,需要在配置文件中注册:
	# Enable or disable downloader middlewares
	# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
	DOWNLOADER_MIDDLEWARES = {
	   'sp2.middlewares.MyCustomDownloaderMiddleware': 543,
	}
			
返回的响应信息中,存在发起请求的request信息,获取的方法:response.request
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
爬虫中间件
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
        """
        """
        #自定义请求方式,添加或是修改请求头
        from scrapy.http import Request
        # print(request)
        # request.method = "POST"
        request.headers['proxy'] = "{'ip_port': '111.11.228.75:80', 'user_pass': ''},"
        return None
        """
        """
        #自定义请求地址,返回响应信息
        from scrapy.http import Response
        import requests
        v = request.get('http://www.baidu.com')
        data = Response(url='xxxxxxxx',body=v.content,request=request)
        return data
         """
        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')
        #在响应回来时,我们可以对响应体进行某些处理
        # from scrapy.http import Response
        # response.encoding = 'utf-8'
        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
下载中间件

7、自定义命令

 - 让所有爬虫都开始工作

  • 在spiders同级创建任意目录,如:commands
  • 在其中创建 crawlall.py 文件 (此处文件名就是自定义的命令)
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()
crawlall.py
  • 在settings.py 中添加配置 COMMANDS_MODULE = '项目名称.目录名称'
  • 在项目目录执行命令:scrapy crawlall   可以让所有的爬虫开始工作

8、scrapy 配置文件

# -*- 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'
settings
"""
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/
        }
        
    例如:我们可以在自定义命令中引入,可以在爬虫执行的时候,使用代理IP。
        from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware
        import os

        os.environ['http_proxy'] = "http://root:woshiniba@192.168.11.11:9999/"
        os.environ['https_proxy'] = "http://192.168.11.11:9999/"
        os.environ['xx_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

"""
HTTPS 证书相关
"""
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,
# }
中间件及系统默认中间件

补充:

  爬虫也支持telnet终端操作,telnet 连接上 正在处理的爬虫,可以通过est() 查看正在执行的所有操作列表。

telnet localhost 6023     est()

更对文档请参考:官方文档

原文地址:https://www.cnblogs.com/zh605929205/p/7458997.html