10_selenium在scrapy中使用&分布式&增量式
selenium在scrapy中的使用
-
https://news.163.com/
-
爬取网易新闻中的国内、国际、军事、航空、无人机这五个板块下所有的新闻数据(标题+内容)
-
分析
- 首页没有动态加载的数据
- 爬取五个板块对应的url
- 每一个板块对应的页面中的新闻标题是动态加载
- 爬取新闻标题+详情页的url(***)
- 每一条详情页的数据不是动态加载
- 爬取的新闻内容
- 首页没有动态加载的数据
-
使用流程
- 在爬虫类中实例化一个浏览器对象,将其作为爬虫类的一个属性
- 在中间件中实现浏览器自动化相关的操作
- 在爬虫类中重写closed(self,spider),在其内部关闭浏览器对象
案例:网易新闻爬取
#wangyi.py
class WangyiSpider(scrapy.Spider):
name = 'wangyi'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://news.163.com/']
model_urls = []
#实例化了一个全局的浏览器对象
bro = webdriver.Chrome(executable_path=r'D:\pycharmCode\spider2020Learn\day08\wangyiPro\wangyiPro\chromedriver.exe')
def parse(self, response):
li_list = response.xpath('//*[@id="js_festival_wrap"]/div[3]/div[2]/div[2]/div[2]/div/ul/li')
indexs = [3,4,6,7,8]
for index in indexs:
model_li = li_list[index]
#print(model_li)
model_url = model_li.xpath('./a/@href').extract_first()
self.model_urls.append(model_url)
#对每一个板块的url发起请求
for url in self.model_urls:
#print(url)
yield scrapy.Request(url=url,callback=self.parse_model)
#数据解析:新闻标题+新闻详情页的url(动态加载的数据)
def parse_model(self,response):
#直接对response解析新闻标题数据是无法获取该数据的(动态加载的数据)
#response是不满足当下需求的response,需要将其变成满足需求的response
#满足需求的response就是包含了动态加载数据的response
#满足需求的response和不满足的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_detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()
if new_detail_url:
item = WangyiproItem()
item['title'] = title
#对新闻详情页的url发起请求
yield scrapy.Request(url=new_detail_url,callback=self.parse_new_detail,meta={'item':item})
def parse_new_detail(self,response):
#解析新闻内容
content = response.xpath('//*[@id="content"]/div[2]/p/text()').extract()
content = ''.join(content)
item = response.meta['item']
item['content'] = content
yield item
#爬虫类父类的方法,该方法是在爬虫结束最后一刻执行
def closed(self,spider):
self.bro.quit()
#middlewares.py
class WangyiproDownloaderMiddleware:
#拦截所有的响应对象
#整个工程发起的请求:1+5+n,相应也会有1+5+n个响应
#只有指定的5个响应对象是不满足需求
#只将不满足需求的5个指定的响应对象的响应数据进行篡改
def process_response(self, request, response, spider):
#将拦截到的所有的响应对象中指定的5个响应对象找出
if request.url in spider.model_urls:
bro = spider.bro
#response表示的就是指定的不满足需求的5个响应对象
#篡改响应数据:首先先获取满足需求的响应数据,将其篡改到响应对象中即可
#满足需求的响应数据就可以使用selenium获取
bro.get(request.url) #对五个板块的url发起请求
sleep(2)
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(1)
#...还可以点击
#捕获到了板块页面中加载出来的全部数据(包含了动态加载的数据)
page_text = bro.page_source
#1. 篡改响应数据
# response.text = page_text
# return response #5
#2. 返回一个新的响应对象,新的对象替换原来不满足需求的旧的响应对象
return HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
else:
return response #1+n
分布式
- 实现方式:scrapy+redis(scrapy结合着scrapy-redis组件)
-
原生的scrapy框架是无法实现分布式
-
什么是分布式?
需要搭建一个分布式的机群,然后让机群中的每一台电脑执行同一组程序,让其对同一种资源进行联合且分布的数据爬取。
-
为什么原生的scrapy框架无法实现分布式?
- 调度器无法被分布式机群共享
- 管道无法被共享
-
如何实现分布式:使用scrapy-redis组件
-
scrapy-redis组件的作用:
- 可以给原生的scrapy框架提供共享的管道和调度器
-
-
pip install scrapy-redis
-
实现流程
-
修改爬虫文件
-
导包:
from scrapy_redis.spiders import RedisCrawlSpider
-
修改当前爬虫类的父类为:
RedisCrawlSpider
class FbsSpider(RedisCrawlSpider):
-
将start_url替换成redis_key的属性,属性值为任意字符串
-
redis_key = ‘xxx’:表示的是可以被共享的调度器队列的名称,最终是需要将起始的url手动放置到redis_key表示的队列中
redis_key = 'sunQueue' #可以被共享的调度器队列的名称 #稍后我们是需要将一个起始的url手动地添加到redis_key表示的队列中
-
-
将数据解析的操作补充完整即可
-
-
对settings.py进行配置
-
指定调度器
#增加了一个去重容器类的配置,作用使用Redis的set集合来存储请求的指纹数据,从而实现请求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" #使用scrapy-redis组件自己的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" #配置调度器是否持久化,也就是当爬虫结束了,要不要清空Redis中请求队列和去重指纹的set。如果是Ture,就表示要持久化存储,就不清空数据,否则清空数据 SCHEDULER_PERSIST = True
-
指定管道
ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 }
- 特点:该种管道只可以将item写入redis
-
指定redis
REDIS_HOST = 'redis服务的ip地址' #127.0.0.1 REDIS_PORT = 6379 #编码格式 REDIS_ENCODING = 'utf-8' #用户名和密码 REDIS_PARAMS = {'password':'123456'}
-
-
配置redis的配置文件(redis.windows-service.conf)
- 解除默认绑定
- 56行:注释bind 127.0.0.1
- 关闭保护模式
- 75行:protected-mode 改成no
- 解除默认绑定
-
启动redis服务和客户端
-
执行scrapy工程(不要在配置文件中加入LOG_LEVEL)
- 程序会停留在listening位置:等待起始url加入
-
向redis_key表示的队列中添加起始url
- 需要在redis的客户端执行如下指令:(调度器队列是存在于redis中)
- lpush sunQueue http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1
-
增量式
- 概念:检测网站数据更新的情况,以便于爬取到最新更新出来的数据。
- 实现核心:去重
- 实战中去重的方式:记录表
- 记录表需要记录什么?记录的一定是爬取过的相关信息。
- 爬取过的相关信息:每一部电影详情页的url
- 只需要使用某一组数据,该组数据如果可以作为该部电影的唯一标识即可,刚好电影详情页的url就可以作为电影的唯一标识。只要可以表示电影唯一标识的数据我们统称为数据指纹。
- 记录表需要记录什么?记录的一定是爬取过的相关信息。
- 去重的方式对应的记录表:
- python中的set集合(不可以)
- set集合无法持久化存储
- redis中的set(可以,案例用这种方式)
- 可以持久化存储
- python中的set集合(不可以)
- 数据指纹一般是经过加密的
- 当前案例的数据指纹没有必要加密
- 什么情况数据指纹需要加密?
- 如果数据的唯一标识表示的内容数据量比较大,可以使用hash将数据加密成32位的密文
- 目的是为了节省空间。
- 如果数据的唯一标识表示的内容数据量比较大,可以使用hash将数据加密成32位的密文
案例:爬取电影名称和简介
#zls.py
class ZlsSpider(CrawlSpider):
name = 'zls'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://www.4567kan.com/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/1.html']
conn = Redis(host='127.0.0.1',port=6379)
rules = (
Rule(LinkExtractor(allow=r'page/\d+\.html'), 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:
title = li.xpath('./div/a/@title').extract_first()
detail_url = 'http://www.4567kan.com'+li.xpath('./div/a/@href').extract_first()
#sadd为set(集合),插入成功为1,插入失败(表示有重复)为0
ex = self.conn.sadd('movie_urls',detail_url)
#ex==1插入成功,ex==0插入失败
if ex == 1: #detail_url表示的电影没有存在于记录表当中
#爬取电影数据:发起请求
print('有新数据更新,正在爬取新数据......')
item = ZlsproItem()
item['title'] = title
yield scrapy.Request(url=detail_url,callback=self.parse_detail,meta={'item':item})
else: #存在于记录表中
print('暂无数据更新!')
def parse_detail(self,response):
#解析电影简介
desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[3]/text()').extract_first()
item = response.meta['item']
item['desc'] = desc
yield item
#pipelines.py
class ZlsproPipeline:
def process_item(self, item, spider):
conn = spider.conn #redis的链接对象
conn.lpush('movieData',item)
return item
结果图:
cmd:
-
第一次 scrapy crawl zls
有新数据更新,正在爬取新数据…
-
第二次 scrapy crawl zls
暂无数据更新!