20200228 scrapy高级使用及分布式
目录
昨日回顾
1 安装 pip3 install scrapy
2 创建项目:scrapy startproject 项目名字
3 创建爬虫:scrapy genspider 爬虫名字 地址
4 运行爬虫:scrapy crawl 爬虫名字
5 目录结构
6 setting的配置
7 css选择和xpath选择(extract_first(),extract())
8 持久化:pipline方式
scrapy高级使用及分布式
1.爬虫件参数
yield Request(url=url,callback=self.parse,meta={'item':item})
# 参数
url 再次请求的地址
callback 回调函数(处理数据)
meta 请求传参
2.提升scrapy爬取的效率
- 在配置文件中进行相关的配置即可:(默认还有一套setting)
#1 增加并发:
默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
#2 降低日志级别:
在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’
# 3 禁止cookie:
如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
# 4禁止重试:
对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
# 5 减少下载超时:
如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s
3.scrapy的中间件(下载中间件)
-DownloaderMiddleware
-process_request
-retrun None/Respnose/Request/raise
-None:表示继续处理
-Respnose:会被引擎调度,进入爬虫
-Request:会被引擎调度,放到调度器,等待下一次爬取
-raise:process_exception触发执行
-process_response
-Response:继续处理,会被引擎调度,放到爬虫中解析
-Request:会被引擎调度,放到调度器,等待下一次爬取
-raise:process_exception触发执行
-process_exception
-None:表示继续处理
-Respnose:会被引擎调度,进入爬虫
-Request:会被引擎调度,放到调度器,等待下一次爬取
process_exception
def process_exception(self, request, exception, spider):
from scrapy.http import Request
print('xxxx')
# request.url='https://www.baidu.com/'
request=Request(url='https://www.baidu.com/')
return request
process_request
def process_request(self, request, spider):
# 请求头
# print(request.headers)
# request.headers['User-Agent']=random.choice(self.user_agent_list)
# 设置cookie(并不是所有的请求,都需要带cookie,加一个判断即可)
# 可以使用cookie池
# print(request.cookies)
# # import requests # 如果自己搭建cookie池,这么写
# # ret=requests.get('127.0.0.1/get').json()['cookie']
# # request.cookies=ret
# request.cookies={'name':'lqz','age':18}
# 使用代理(使用代理池)
# print(request.meta)
# request.meta['proxy'] = 'http://117.27.152.236:1080'
return None
process_response
def process_response(self, request, response, spider):
#详见下面
return response
4.scrapy中使用selenium
1 在爬虫中启动和关闭selenium
-启动
bro = webdriver.Chrome('./chromedriver')
-关闭
def closed(self,spider):
print("爬虫结束,会走我关了")
self.bro.close()
2 在下载中间件中写
def process_response(self, request, response, spider):
from scrapy.http import Response,HtmlResponse
# 因为向该地址发请求,不能执行js,现在用selenium执行js,获取执行完的结果,再返回response对象
url=request.url
spider.bro.get(url)
page_source=spider.bro.page_source
new_response=HtmlResponse(url=url,body=page_source,encoding='utf-8',request=request)
return new_response
5.去重规则
如何自己写去重规则,布隆过滤器
from scrapy.dupefilter import RFPDupeFilter
from scrapy.core.scheduler import Scheduler
# 整个去重规则是通过RFPDupeFilter中的request_seen控制
# 在调度器Scheduler中的enqueue_request调用,如果dont_filter=True就不过滤了
# scrapy中如下两个地址,是同一个地址,通过request_fingerprint处理了
# http://www.baidu.com/?name=lqz&age=18
# http://www.baidu.com/?age=18&name=lqz
res1=Request(url='http://www.baidu.com/?name=lqz&age=18')
res2=Request(url='http://www.baidu.com/?age=18&name=lqz')
print(request_fingerprint(res1))
print(request_fingerprint(res2))
# 有更省空间的方式
bitmap方式:比特位:计算机的存储单位 1bit byte=8个比特位 1kb=1024b
布隆过滤器:BloomFilter:原理
# 自己重写去重规则
# 1 在setting中配置
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' #默认的去重规则帮我们去重,去重规则在内存中
# 写一个类,继承BaseDupeFilter,重写方法,主要重写request_seen,如果返回True表示有了,False表示没有
6.分布式爬虫
# scrapy-redis
# 概念:整站爬取,假设有9w条连接地址,一台机器一天只能爬3w条,爬3天
# 现在想用3台机器爬一天
# scrapy项目部署在3台机器上,三台机器重复的爬9w条,3台机器共享爬取的地址,
# 3台机器都去一个队列中取地址爬取
#scrapy-redis 重写了Scheduler和pipline
pip3 install scrapy-redis
#https://github.com/rmax/scrapy-redis :整个源码总共不超过1000行
使用
安装scrapy-redis
pip install scrapy_redis
1 在setting中配置
# 分布式爬虫的配置
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# Ensure all spiders share same duplicates filter through redis.
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300
}
2 修改爬虫类
class CnblogsSpider(RedisSpider): # 继承RedisSpider
name = 'cnblogs'
allowed_domains = ['www.cnblogs.com']
redis_key = 'myspider:start_urls' # 原来的start_ulr去掉,携写成这个
3 项目部署
把项目部署到不同的机器上,或者同一个机器开启多个?
4 redis中写去启动url
# 启动cmd或者redis服务
- redis-cli
- lpush myspider:start_urls https://www.cnblogs.com/(初始网址)
代码
全站爬取博客园
run.py启动文件
#-*- coding: utf-8 -*-
#!/usr/bin/env python3
' a test module '
__author__ = 'Fwzzz'
from scrapy.cmdline import execute
# execute(['scrapy','crawl','cnblogs','--nolog'])
# execute(['scrapy','crawl','cnblogs'])
# 分布式爬虫
execute(['scrapy','crawl','cnblogs_redis','--nolog'])
爬虫文件cnblogs.py
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request
# 导入items文件类
from cnblog_pa.items import CnblogPaItem
# 了解: 究竟真正的起始爬取的方法在哪 start_requests()
class CnblogsSpider(scrapy.Spider):
name = 'cnblogs'
allowed_domains = ['www.cnblogs.com']
# start_urls = ['https://www.cnblogs.com//']
def start_requests(self):
# # 深度爬取
# # 获取所有的页码,进行url拼接 https://www.cnblogs.com/#p2
# html = Request(url='https://www.cnblogs.com/')
# last_p = html.css('div[p_200 last]').extract()
# print(last_p)
# 需要yield
yield Request(url='https://www.cnblogs.com/',callback=self.parse)
def parse(self, response):
# 获取文章块
div_list = response.css('div.post_item')
for div in div_list:
# 声明item
item = CnblogPaItem()
# 文章标题
title = div.xpath('./div[2]/h3/a/text()').extract_first()
# 文章链接
url=div.xpath('./div[2]/div/span[2]/a/@href').extract_first()
# 用户名
author = div.xpath('./div[2]/div/a/text()').extract_first()
# 文章简介
desc = div.xpath('./div[2]/p/text()').extract()[-1].replace('\r\n','').replace(' ','')
# print(title_url,user,desc)
# 持久化的准备传参设置
item['title'] = title
item['url'] = url
item['author'] = author
item['desc'] = desc
# 深度爬取 # 广度爬取
# yield item对象会去保存 ,request对象会去爬取 # callback回调函数
yield Request(url=url,callback=self.parse_detail,meta={'item':item})
# 传参使用meta
# 广度爬取下一页
# 解析下一页的url css选择器取属性
next_url = 'https://www.cnblogs.com' + response.css('div.pager>a:last-child::attr(href)').extract_first()
# print(1111,next_url)
yield Request(url=next_url,callback=self.parse)
# 文章详情
def parse_detail(self,response):
# 获取传递的item对象
item = response.meta.get('item')
# print(item)
# 深度爬取文章详情
# 获取文章的html页面
content = response.css('#post_detail').extract_first()
item['content'] = str(content)
# 存入数据库 (直接跳转到pipeline文件中进行持久化)
yield item
items.py持久化字段设置
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
# 数据库持久化
class CnblogPaItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
author = scrapy.Field()
desc = scrapy.Field()
content = scrapy.Field()
url = scrapy.Field()
pipelines.py数据库持久化文件
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
# 数据库持久化
class CnblogPaItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
author = scrapy.Field()
desc = scrapy.Field()
content = scrapy.Field()
url = scrapy.Field()
settings文件
BOT_NAME = 'cnblog_pa'
SPIDER_MODULES = ['cnblog_pa.spiders']
NEWSPIDER_MODULE = 'cnblog_pa.spiders'
ROBOTSTXT_OBEY = False
CONCURRENT_REQUESTS = 32
# 普通使用
ITEM_PIPELINES = {
'cnblog_pa.pipelines.CnblogPaPipeline': 300,
}
# 分布式爬虫的设置
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# Ensure all spiders share same duplicates filter through redis.
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300
}
# redis连接的配置 (不配置,默认即可)
补充
## 后续
redis高级(持久化rdb和aof,附近的人:地理位置信息,独立用户统计,发布订阅,一主多从,高可用(哨兵)),mongodb,es(集群搭建),docker(镜像,容器,私服搭建,项目部署,dockerfile),mysql主从做读写分离,django中做读写分离,go语言
## redis
缓存雪崩,缓存穿透
## 反扒
0.检测浏览器header
1 User-Agent,
2 cookie
3. ip 封禁
4. 图片验证码(打码平台,手动)
5 图片懒加载()
6 js加密,混淆:pip install PyExecJS 动态执行js
7 css加密
8 图片防盗链