目录
- 爬虫07 /scrapy图片爬取、中间件、selenium在scrapy中的应用、CrawlSpider、分布式、增量式
- 1. scrapy图片的爬取/基于管道类实现
- 2. 中间件的使用
- 3. selenium在scrapy中的应用
- 4. CrawlSpider
- 5. 分布式
- 5. 增量式
爬虫07 /scrapy图片爬取、中间件、selenium在scrapy中的应用、CrawlSpider、分布式、增量式
1. scrapy图片的爬取/基于管道类实现
- 爬取流程:
- 爬虫类中将解析到的图片地址存储到item,将item提交给指定的管道
- 在管道文件中导包:from scrapy.pipelines.images import ImagesPipeline
- 基于ImagesPipeline父类,自定义一个管道类
- 重写管道类中的如下三个方法:
指定文件路径
def file_path(self,request,response=None,info=None): # 可以接收get_media_requests传递过来的meta,获取图片名称 request.meta # 在配置文件设置 # IMAGES_STORE=‘./imgs’
对图片地址发送请求
def get_media_requests(self,item,info): yield scrapy.Request(url,meta) # 将item中存储的图片地址进行get请求发送 # meta就会传递给file_path方法
返回item,交由下一个管道类处理
def item_completed(self,request,item,info): return item
- 代码示例
items.py
import scrapy class ImgproItem(scrapy.Item): img_src = scrapy.Field() img_name = scrapy.Field()
imgDemo.py
# -*- coding: utf-8 -*- import scrapy from imgPro.items import ImgproItem class ImgdemoSpider(scrapy.Spider): name = 'imgDemo' start_urls = ['http://www.521609.com/daxuemeinv/'] def parse(self, response): li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li') for li in li_list: img_src = 'http://www.521609.com'+li.xpath('./a[1]/img/@src').extract_first() img_name = li.xpath('./a[2]/b/text() | ./a[2]/text()').extract_first()+'.jpg' print(img_name) item = ImgproItem() item['img_src'] = img_src item['img_name'] = img_name yield item
pipelines.py
# -*- coding: utf-8 -*- import scrapy from scrapy.pipelines.images import ImagesPipeline class ImgproPipeline(ImagesPipeline): # 指定文件存储的目录(文件名) def file_path(self,request,response=None,info=None): # 接收meta item = request.meta['item'] return item['img_name'] # 对指定资源进行请求发送 def get_media_requests(self,item,info): # meta可以传递给file_path yield scrapy.Request(item['img_src'],meta={'item':item}) # 用于返回item,将item传递给下一个即将被执行的管道类 def item_completed(self,request,item,info): return item
settings.py
ITEM_PIPELINES = { 'imgPro.pipelines.ImgproPipeline': 300, } # 指定文件存储的目录 IMAGES_STORE = './imgs'
2. 中间件的使用
- scrapy中间件:/在爬虫中一般使用下载中间件
下载中间件/DownloaderMiddleware:位于引擎和下载器之间;
爬虫中间件/SpiderMiddleware:位于引擎和Spider之间
- 作用:
拦截所有的请求和响应
- 拦截请求:
process_request拦截正常的请求
process_exception拦截异常的请求,必须有return request返回值,对异常请求重新发送
篡改请求的头信息:
process_request request.headers['User-Agent'] = 'chorme'
设置代理:
process_exception: request.meta['proxy'] = 'http://ip:port' # 是字符串不是字典
注意:process_exception,return request的作用,将修正后的请求重新发送
- 拦截响应:
篡改响应数据:
- 不满足需求的响应数据对应的一定是不满足需求的响应对象,动态加载的数据就是不满足需求的对象
- 直接更换响应对象
- 代码示例:
配置文件settings.py:将中间件配置打开
DOWNLOADER_MIDDLEWARES = { 'middlePro.middlewares.MiddleproDownloaderMiddleware': 543, }
爬虫文件middle.py:
# -*- coding: utf-8 -*- import scrapy class MiddleSpider(scrapy.Spider): name = 'middle' start_urls = ['http://www.521609.com/daxuemeinv/'] def parse(self, response): li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li') for li in li_list: img_src = 'http://www.123'+li.xpath('./a[1]/img/@src').extract_first() yield scrapy.Request(img_src)
中间件middlewares.py
# -*- coding: utf-8 -*- from scrapy import signals import random class MiddleproDownloaderMiddleware(object): # 拦截正常请求 # 参数request:拦截到的请求 user_agent_list = ['UA池'] def process_request(self, request, spider): print('proces_request!!!') # UA伪装 request.headers['User-Agent'] = random.choice(self.user_agent_list) return None # 拦截所有的响应 def process_response(self, request, response, spider): return response # 拦截发生异常的请求,目的就是为了将异常的请求进行修正,然后将修正之后的正常的请求进行重新发送 def process_exception(self, request, exception, spider): # 代理操作 request.meta['proxy'] = 'http://ip:port' print('i am exception!!!') return request
3. selenium在scrapy中的应用
- selenium在scrapy中的编码流程:
- 在爬虫类中实例化浏览器对象:将实例化出来的浏览器作为爬虫类的一个属性;属性可以交互给中间件类
- 编写自动化操作:写在中间件的process_response中
- 关闭浏览器: 写在爬虫类的closed(self)方法中,只会在关闭的时候执行一次
- 需求:爬取网易新闻中国内,国际,军事,航工,无人机这五个板块下所有的新闻标题和内容
分析:
- 通过中间件更换不满足需求的响应对象
- 在scrapy中应用selenum
- 每一个板块中显示的新闻标题是动态加载的
代码实现:
settings.py
# 打开中间件配置 DOWNLOADER_MIDDLEWARES = { 'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543, }
wangyi.py:爬虫文件
# -*- coding: utf-8 -*- import scrapy from selenium import webdriver from wangyiPro.items import WangyiproItem class WangyiSpider(scrapy.Spider): name = 'wangyi' start_urls = ['https://news.163.com/'] # 整个项目中涉及的响应对象个数:1+5+n # 解析:解析五个新闻板块对应的url five_model_urls = [] bro = webdriver.Chrome(executable_path=r'chromedriver.exe') # 方法只会被调用一次 def closed(self,spider): self.bro.quit() def parse(self, response): li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li') model_indexs = [3,4,6,7,8] for index in model_indexs: li_tag = li_list[index] # 解析出了每一个板块对应的url model_url = li_tag.xpath('./a/@href').extract_first() self.five_model_urls.append(model_url) # 对每一个板块的url进行手动的请求发送 yield scrapy.Request(model_url,callback=self.parse_model) # 解析:每一个板块中的新闻标题和新闻详情页的url(两个值都是动态加载出来的) def parse_model(self,response): # 遇到了不满足需求的响应对象就是当前方法中的response参数 div_list = response.xpath('/html/body/div/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() detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first() item = WangyiproItem() item['title'] = title if detail_url: yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item}) # 爬取新闻详情 def parse_detail(self,response): item = response.meta['item'] content = response.xpath('//*[@id="endText"]//text()').extract() content = ''.join(content) item['content'] = content yield item
middlewares.py
# -*- coding: utf-8 -*- from scrapy import signals from scrapy.http import HtmlResponse from time import sleep class WangyiproDownloaderMiddleware(object): def process_request(self, request, spider): return None # 拦截所有的响应(1+5+n),只有5个响应不满足需求,动态加载数据只能爬取少量数据 def process_response(self, request, response, spider): # request.url:每一个响应对应的url # spider.five_model_urls:5个板块对应的url if request.url in spider.five_model_urls: # 满足if条件的response就是5个板块对应的response spider.bro.get(request.url) # 对每一个板块对应的url进行get请求发送 sleep(3) spider.bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(2) page_text = spider.bro.page_source new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request) return new_response else: return response def process_exception(self, request, exception, spider): pass # 流程总结: # 1.将拦截到所有的响应中的指定5个不满足需求的响应对象找出 # 2.将这5个响应对象删除,实例化5个新的响应对象 # 3.保证5个新的响应对象中包含动态加载出来的新闻标题数据 # 4.将满足需求的5个新的响应对象返回
4. CrawlSpider
- 作用:
是Spider的一个子类,可以实现全站数据爬取
- 实现流程:
- 创建工程:scrapy startproject proName
- 切换到工程目录:cd proName
- 创建爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com
- 关键点:
LinkExtracor链接提取器:可以根据指定的规则(allow=正则)进行链接的提取
Rule规则解析器:将链接提取器提取到的链接进行请求发送,然后根据指定的规则(callback)进行数据解析
参数follow=True:将链接提取器 继续作用 到链接提取器提取到的链接 所对应的页面源码中
- 代码示例:CrawlSpider实现深度爬取
items.py :
import scrapy class SunproItem(scrapy.Item): title = scrapy.Field() status = scrapy.Field() class SunproItem_content(scrapy.Item): content = scrapy.Field()
sun.py/爬虫文件:
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from sunPro.items import SunproItem_content,SunproItem class SunSpider(CrawlSpider): name = 'sun' start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page='] # 实例化了一个链接提取器对象 # 作用:可以根据指定的规则(allow=(正则))进行链接的提取,筛选链接 link = LinkExtractor(allow=r'type=4&page=\d+') # 提取页码链接 link_detail = LinkExtractor(allow=r'question/\d+/\d+\.shtml') rules = ( # 规则解析器 # 作用:规则解析器可以将链接提取器提取到的链接进行请求发送且进行指定规则(callback)的数据解析 Rule(link, callback='parse_item', follow=False), Rule(link_detail,callback='parse_detail') ) # 该方法调用的次数请求的个数 def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/@title').extract_first() status = tr.xpath('./td[3]/span/text()').extract_first() item = SunproItem() item['title'] = title item['status'] = status yield item def parse_detail(self,response): content = response.xpath('/html/body/div[9]/table[2]//tr[1]').extract() content = ''.join(content) item = SunproItem_content() item['content'] = content yield item # link:实例化了一个链接提取器对象 # 作用:可以根据指定的规则(allow=(正则))进行链接的提取 # Rule:规则解析器 # 作用:规则解析器可以将链接提取器提取到的链接进行请求发送且进行指定规则(callback)的数据解析
pipelines.py/管道:
class SunproPipeline(object): def process_item(self, item, spider): if item.__class__.__name__ == 'SunproItem_content': print(item['content']) else: print(item['title'],item['status']) return item # 获取对象从属的父类的名称:.__class__.__name__
5. 分布式
- 概念:
可以使用多台电脑组件一个分布式机群,让其执行同一组程序,对同一组网络资源进行联合爬取。
- scrapy实现分布式:
原生的scrapy是无法实现分布式:调度器无法被共享,管道无法被共享
基于scrapy+redis(scrapy&scrapy-redis组件)实现分布式
- scrapy-redis组件:
作用:提供可被共享的管道和调度器
环境安装:
pip install scrapy-redis
编码流程:
1.创建工程:scrapy startproject proName
2.切换到工程目录:cd proName
3.创建crawlspider的爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com
4.修改一下爬虫类:
- 导包:from scrapy_redis.spiders import RedisCrawlSpider
- 修改当前爬虫类的父类:RedisCrawlSpider
- allowed_domains和start_urls删除
- 添加一个新属性:redis_key = ‘xxxx‘可以被共享的调度器队列的名称
5.修改配置settings.py
&指定管道
ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 }
&指定调度器
# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis组件自己的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据 SCHEDULER_PERSIST = True
&指定redis数据库
REDIS_HOST = 'redis服务的ip地址' REDIS_PORT = 6379
6.配置redis数据库(redis.windows.conf)
# 关闭默认绑定 56Line:#bind 127.0.0.1 # 关闭保护模式 75line:protected-mode no
7.启动redis服务(携带配置文件)和客户端
redis-server.exe redis.windows.conf redis-cli
8.执行工程
scrapy runspider spider.py # 先执行工程,让其产生调度器
9.将起始的url扔入到可以被共享的调度器的队列(sun)中
在redis-cli中操作:lpush sun www.xxx.com
10.redis
redis_key:items:存储的就是爬取到的数据
- 代码示例:
fbs.py:爬虫文件
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from scrapy_redis.spiders import RedisCrawlSpider from fbsPro.items import FbsproItem class FbsSpider(RedisCrawlSpider): name = 'fbs' # allowed_domains = ['www.xxx.com'] # start_urls = ['http://www.xxx.com/'] redis_key = 'sun' # 可以被共享的调度器队列的名称 link = LinkExtractor(allow=r'type=4&page=\d+') rules = ( Rule(link, callback='parse_item', follow=True), ) def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/@title').extract_first() status = tr.xpath('./td[3]/span/text()').extract_first() item = FbsproItem() item['title'] = title item['status'] = status yield item
settings.py
#指定管道 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 } #指定调度器 # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis组件自己的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据 SCHEDULER_PERSIST = True #指定redis REDIS_HOST = '192.168.16.64' REDIS_PORT = 6379
5. 增量式
- 概念
监测网站数据更新的情况。爬取到最新更新出来的数据。
- 关键点:
核心:去重,利用set
记录表:记录爬取过的信息,需要持久化存储,redis中使用set进行持久化存储
- 需求:url=https://www.4567tv.tv/frim/index1.html,爬取其电影的名称以及详情页的简介
分析:
- 对于深度爬取:一般采用url作为唯一标识进行存储;
比如:先在首页爬取链接,再根据这些链接爬取相应的内容,爬取图片
- 对于非深度爬取:一般通过数据指纹作为数据的唯一标识
比如:没有动态加载的数据,所有数据都在本页
- 数据指纹:一组数据的唯一标识
- 对于深度爬取:一般采用url作为唯一标识进行存储;
- 代码示例:
movie.py:爬虫文件
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from redis import Redis from moviePro.items import MovieproItem class MovieSpider(CrawlSpider): name = 'movie' start_urls = ['https://www.4567tv.tv/frim/index1.html'] conn = Redis(host='127.0.0.1',port=6379) link = LinkExtractor(allow=r'frim/index1-\d+\.html') #提取页码链接 rules = ( Rule(link, callback='parse_item', follow=False), ) def parse_item(self, response): # 电影名称+详情页的url li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li') for li in li_list: name = li.xpath('./div/a/@title').extract_first() item = MovieproItem() item['name'] = name detail_url = 'https://www.4567tv.tv'+li.xpath('./div/a/@href').extract_first() ex = self.conn.sadd('movie_record',detail_url) if ex == 1: # 这部电影之前没有存在于记录表中 print('有最新更新的数据!!!!!!') yield scrapy.Request(url=detail_url,callback=self.parse_detail,meta={'item':item}) else: print('暂无新数据的更新......') def parse_detail(self,response): item = response.meta['item'] desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first() item['desc'] = desc yield item
items.py
import scrapy class MovieproItem(scrapy.Item): name = scrapy.Field() desc = scrapy.Field()
pipelines.py
class MovieproPipeline(object): def process_item(self, item, spider): conn = spider.conn conn.lpush('movieData',item) return item
- 增量式总结:
对于深度爬取,可以在爬取之前对比记录表中是否已经有该url;
对于非深度爬取,只能是在爬下来之后,加过加密校验后,判断校验值是否相同,进而确定是否保存到数据库/记录表
原文地址:https://www.cnblogs.com/liubing8/p/12020237.html