中间件:
概念和django的中间件概念很相似,当有响应由下载器传到引擎的时候都会先经过中间件,而当请求从引擎到下载器的时候也会先经过中间件,这样一来我们就可以在中间件处对请求和响应进行我们想要效果的修改。(虽然在spider与引擎中间同样存在中间件,但是我们更多的是使用下载中间件)
(1)引擎将请求传递给下载器过程中, 下载中间件可以对请求进行一系列处理。比如设置请求的 User-Agent,设置代理等
(2)在下载器完成将Response传递给引擎中,下载中间件可以对响应进行一系列处理。比如进行gzip解压等。
scrapy与selenium:
在获取动态加载数据的时候我们是无法直接从请求对应的响应中拿到数据的,单是浏览器可以动态的加载这些数据,所以在爬取这方面的数据的时候,我们需要模拟浏览器开启,这就要使用到我们之前的selenium模块了。
案例:爬取网易新闻相应模块的相应内容
spider:
# -*- coding: utf-8 -*- import scrapy from selenium import webdriver from wangyiPro.items import WangyiproItem class WangyiSpider(scrapy.Spider): name = 'wangyi' # allowed_domains = ['www.xxx.com'] start_urls = ['https://news.163.com/'] model_urls = [] #五个板块对应的url #实例化好了浏览器对象 bro = webdriver.Chrome(executable_path=r'C:/Users/Administrator/Desktop/爬虫/chromedriver.exe') #解析出五个板块对应的url def parse(self, response): #解析五个板块对应的url li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li') model_index = [3,4,6,7,8] for index in model_index: li_tag = li_list[index] model_url = li_tag.xpath('./a/@href').extract_first() self.model_urls.append(model_url) #需要每一个板块的url发起请求(注意:板块url请求到的页面中有动态加载数据(新闻数据)) for url in self.model_urls: yield scrapy.Request(url=url,callback=self.parse_model) #用来解析每一个板块中的新闻数据(新闻的标题和新闻详情页的url) def parse_model(self,response): div_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div/div/ul/li/div/div') for div in div_list: title = div.xpath('./div/div[1]/h3/a/text()').extract_first() new_url = div.xpath('./div/div[1]/h3/a/@href').extract_first() item = WangyiproItem() item['title'] = title if new_url: #对新闻的详情页url发起请求 yield scrapy.Request(new_url,callback=self.parse_new,meta={'item':item}) #用来解析新闻内容 def parse_new(self,response): item = response.meta['item'] content = response.xpath('//*[@id="content"]//text()').extract() content = ''.join(content) item['content'] = content yield item def closed(self,spider): print('整个爬虫结束!') self.bro.quit()
middleware:
# -*- coding: utf-8 -*- # Define here the models for your spider middleware # # See documentation in: # https://doc.scrapy.org/en/latest/topics/spider-middleware.html from scrapy import signals from time import sleep from scrapy.http import HtmlResponse class WangyiproDownloaderMiddleware(object): def process_request(self, request, spider): return None def process_response(self, request, response, spider): model_urls = spider.model_urls bro = spider.bro if request.url in model_urls: #response就是我们想要拦截到的五个板块对应的响应对象 #将response的响应数据进行修改 bro.get(request.url) sleep(2) page_text = bro.page_source return HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request) else: return response def process_exception(self, request, exception, spider): pass
crawlapider:
crawlspider是spider的一个子类,除了有与spider类似的功能之外,他还具有自己更为强大的特性与功能,其中较为显著的是LinkExtractors链接提取器,spider只会向start_url中的链接发起请求,而crawlspider则有相匹配的rule进行url跟进
创建的时候与spider文件的创建有所不同
实例:
spider文件
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class SunSpider(CrawlSpider): name = 'sun' # allowed_domains = ['www.xxx.com'] start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4&page='] #连接提取器:可以根据指定规则(allow=正则)进行连接的提取 link = LinkExtractor(allow=r'id=1&page=\d+') # link = LinkExtractor(allow=r'') 可以将网站中所有的连接都获取 rules = ( #规则解析器:接收link的连接且对其进行请求发送,然后根据指定形式的规则(callback)对其进行数据解析 Rule(link, callback='parse_item', follow=True), ) def parse_item(self, response): print(response)
#链接提取器参数详解 LinkExtractor( allow=r'Items/',# 满足括号中“正则表达式”的值会被提取,如果为空,则全部匹配。 deny=xxx, # 满足正则表达式的则不会被提取。 restrict_xpaths=xxx, # 满足xpath表达式的值会被提取 restrict_css=xxx, # 满足css表达式的值会被提取 deny_domains=xxx, # 不会被提取的链接的domains。 )
基于scrapy的分布式爬虫:
我们通过分析,已经基本得出scrapy的底层结构和运行原理。那么大家可以尝试真分析一下scrapy能够实现分布式爬虫么?
如果只靠scrapy自己的话是无法做到的,首先在不同的服务器上每一个scrapy都有自己的引擎,而引擎之间有时无法互相通信的,这就导致了请求状态无法统一,其次不同的引擎导致了在进行管道化持久化存储的时候无法做到统一存储。
相信有朋友看出我的语义了,如果只靠scrapy自己是无法完成分布式爬虫的,但是当我们将其与redis结合使用的时候就可以完成相应功能了。
scrapy-redis组件中为我们封装好了可以被多台机器共享的调度器和管道,我们可以直接使用并实现分布式数据爬取
实现方式:
1.基于该组件的RedisSpider类
2.基于该组件的RedisCrawlSpider类
首先安装上我们的依赖
pip install scrapy-redis
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
}
之后再在settings中开启调度器
然后就是spider中的内容,我们需要将父类修改为RedisSpider,在配置文件中开启redis
如果你是新安装的redis,那么配置文件需要做一些更改,主要问题在于访问和操作,具体流程大家可以去网上查找一下相关内容。
# 使用scrapy-redis组件的去重队列 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis组件自己的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 是否允许暂停 SCHEDULER_PERSIST = True #进行连接 REDIS_HOST = 'redis服务的ip地址' REDIS_PORT = 6379 REDIS_ENCODING = ‘utf-8’ REDIS_PARAMS = {‘password’:’123456’}
同时基于scrapy-redis也可以完成增量式爬虫
-
将爬取过程中产生的url进行存储,存储在redis的set中。当下次进行数据爬取时,首先对即将要发起的请求对应的url在存储的url的set中做判断,如果存在则不进行请求,否则才进行请求。
-
对爬取到的网页内容进行唯一标识的制定,然后将该唯一表示存储至redis的set中。当下次爬取到网页数据的时候,在进行持久化存储之前,首先可以先判断该数据的唯一标识在redis的set中是否存在,在决定是否进行持久化存储。