# 1 scrapy 框架 架构 -爬虫:我们写爬取起始地址,解析数据的位置 -引擎:控制数据流向 -调度器:控制爬取的先后 -下载器:负责下载,建立在twisted 之上 -pipline:持久化 # 2 目录结构 -创建爬虫命令:scrapy gensipder 名字 网址 -运行爬虫:scrapy crawl 爬虫名字 # 3 解析数据 1 response对象有css方法和xpath方法 -css中写css选择器 response.css('') -xpath中写xpath选择 response.xpath('') 2 重点1: -xpath取文本内容 './/a[contains(@class,"link-title")]/text()' -xpath取属性 './/a[contains(@class,"link-title")]/@href' -css取文本 'a.link-title::text' -css取属性 'img.image-scale::attr(src)' 3 重点2: .extract_first() 取一个 .extract() 取所有 # 4 整站爬取cnblogs - 在 cnblogs 的解析中 -网址: yield Request 对象 -数据: yield Item 对象 -两个解析 -首页,下一页: parse -下一个地址:Request - 详情地址:Request+item对象 -把item传递到 下一次的解析中 -Request(url=地址,call_banck=解析的方法,meta={}) -详情:parse_detail -response.meta 中取出传递的对象(item) # 5 持久化 -1 在Item.py中写个类 -2 在爬虫中解析出的数据---》组装到Item的类中 -3 yield 对象 -4 配置文件中配置项目管道 -数字--》优先级 ITEM_PIPELINES = { "myfirstscrapy.pipelines.MyfirstscrapyFilePipeline": 300, "myfirstscrapy.pipelines.MyfirstscrapyMysqlPipeline": 100, } -5 pipelines.py 写保存 MyfirstscrapyFilePipeline -open_spider -close_spider -process_item MyfirstscrapyMysqlPipeline # 6 配置文件 -基础配置:请求头,是否遵循爬虫协议,日志级别。。 -高级配置:提高爬虫效率
1 爬虫中间件和下载中间件
# 引擎和下载器之间的叫:下载中间件
# 爬虫和引擎之间的叫:爬虫中间件
爬虫中间件(用的少,了解)
1 写一个类:middlewares.py MyfirstscrapySpiderMiddleware 2 在配置文件中注册 SPIDER_MIDDLEWARES = { "myfirstscrapy.middlewares.MyfirstscrapySpiderMiddleware": 543, } 3 中间件类中得方法 class MyfirstscrapySpiderMiddleware: # 内部自动触发,不需要我们手动调用 @classmethod def from_crawler(cls, crawler): s = cls() # 信号 crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_spider_input(self, response, spider): # 走架构图第6步,会触发这里 return None def process_spider_output(self, response, result, spider): # 架构图,第1,7步走这里 for i in result: yield i def process_spider_exception(self, response, exception, spider): # 出异常会走 pass def process_start_requests(self, start_requests, spider): # 第一爬取起始地址时会走 for r in start_requests: yield r def spider_opened(self, spider): # 爬虫开启时会走 spider.logger.info("Spider opened: %s" % spider.name)
下载中间件
1 写一个类:middlewares.py MyfirstscrapyDownloaderMiddleware 2 在配置文件中注册 DOWNLOADER_MIDDLEWARES = { "myfirstscrapy.middlewares.MyfirstscrapyDownloaderMiddleware": 543, } 3 中间件类中得方法 class MyfirstscrapyDownloaderMiddleware: # 自动会触发,不需要我们手动调用 @classmethod def from_crawler(cls, crawler): s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_request(self, request, spider): # 请求来了--》执行它--》架构图 第 4 步 # 必须返回 return 以下之一: # - return None: 继续下一个中间件 # - return a Response:直接返回给引擎 Response---》引擎把Response给爬虫 # - return a Request:直接把返回给引擎---》引擎会把Request给调度器--》等待下次调度 # - raise 异常: process_exception() 的执行 return None def process_response(self, request, response, spider): # 请求走了--》执行它---》架构图 第 5 步 # - return Response:正常继续往后走--》给引擎---》引擎给爬虫去解析 # - return a Request :给引擎--》引擎放到调度器--》等待下次调度 # - raise IgnoreRequest:抛异常 return response def process_exception(self, request, exception, spider): # 执行中间件出异常 # Must either: # - return None: continue processing this exception # - return a Response object: stops process_exception() chain # - return a Request object: stops process_exception() chain pass def spider_opened(self, spider): spider.logger.info("Spider opened: %s" % spider.name) # 4 重点是: process_request process_response
修改请求头的user-agent,referer,Authorization...
#### 随机生成user-agent # pip3 install fake-useragent from fake_useragent import UserAgent ua=UserAgent() # print(ua.random) # print(ua.firefox) # print(ua.chrome) print(ua.edge) def process_request(self, request, spider): # 修改请求头中得user-agent print('-----',request.headers) # if 'userinfo' in request.url: # 当访问查询用户信息地址,再改referer,改useragent,加登录信息 # 1 上次访问的域 request.headers['referer'] = 'http://www.lagou.com' # 2 登录认证的信息 request.headers['Authorization']='asfasf.asfas.asfas' # 3 客户端类型--》fake_useragent--》随机生成客户端类型 user-agent from fake_useragent import UserAgent ua = UserAgent() # print(ua.random) request.headers['User-Agent'] = ua.random return None
加cookie
def process_request(self, request, spider): print('-----',request.cookies) # cnblogs-->cookie 文件 request.cookies['name'] = 'lqz' return None
加代理(MyfirstscrapyDownloaderMiddleware)
def get_proxy(self): import requests res = requests.get('http://127.0.0.1:5010/get/').json() if res.get('https'): return 'https://' + res.get('proxy') else: return 'http://' + res.get('proxy') def process_request(self, request, spider): # request.meta['proxy'] = self.get_proxy() request.meta['proxy'] = 'http://192.168.11.11:8888' return None
scrapy集成selenium
# scrapy 下载的内容,是不包含执行完js后的数据--》等同于requests发送请
# 有的网站---》需要执行完js,数据才完整(分离项目)
# 对于前后端分离项目--》使用scrapy发送请求会导致数据不完整--》需要执行完js后才能解析数据
# 在scrapy中集成 selenium---》使用selenium去加载网页-->数据是执行完js后,完整的数据
使用步骤
1 在爬虫类中写 开启浏览器 class CnblogsSpider(scrapy.Spider): ###### 爬虫开启---》打开浏览器--->类属性--》对象可以取到 from selenium.webdriver.edge.service import Service ser = Service() ser.path = r'.\myfirstscrapy\chromedriver.exe' bro = webdriver.Chrome(service=ser) 2 爬虫结束,关闭浏览器,爬虫类中写 ## 爬虫结束---》关闭浏览器 def close(spider, reason): # spider.bro.close() spider.bro.quit() 3 在中间件中 def process_request(self, request, spider): from scrapy.http.response.html import HtmlResponse spider.bro.get(request.url) response=HtmlResponse(url=request.url,body=spider.bro.page_source.encode('utf-8')) return response 4 区分不同的地址,选择使用selenium或scrapy原生爬取 -1 通过 url区分 -2 通过在request对象的meta中加入标志 # 爬虫类中 yield Request(url=url, callback=self.parse_detail, meta={'item': item,'is_selenium':True}) #中间件中: if request.meta.get('is_selenium'): # 使用scrapy效率低
源码去重规则(布隆过滤器)
scrapy 的调度器,会自动去重
源码去重原理
# 要爬取的Request对象,在进入到scheduler调度器排队之前,先执行enqueue_request,它如果return False,这个Request就丢弃掉,不爬了----》如何判断这个Request要不要丢弃掉,执行了self.df.request_seen(request),它来决定的-----》RFPDupeFilter类中的方法----》request_seen---》会返回True或False----》如果这个request在集合中,说明爬过了,就return True,如果不在集合中,就加入到集合中,然后返回False # 本质原理,通过集合--》根据爬取的地址--》去重的 -爬取路径一样---》post--》请求体不一样---》不要去重 -如果使用默认去重规则---》这个不会提交多次---》只会提交一次--》有问题 # 源码分析--->scrapy.Spider ##### 起始爬取的地址 # 1 类属性中写了起始位置: start_urls = ["https://www.cnblogs.com"] # 2 爬虫运行时,会执行类中得start_requests def start_requests(self) -> Iterable[Request]: for url in self.start_urls: yield Request(url, dont_filter=True) ######### 去重位置---from scrapy.core.scheduler import Scheduler # 默认配置文件中有: DUPEFILTER_CLASS = "scrapy.dupefilters.RFPDupeFilter" class Scheduler: # 在排队之前---》先执行enqueue_request--》完成去重 def enqueue_request(self, request: Request) -> bool: # self 是调度器 #self.df 是去重类 RFPDupeFilter 的对象 # 调用RFPDupeFilter中得方法request_seen,会返回True或False if self.df.request_seen(request): #返回True说明集合中有了 return False # 说明不再爬取了 dqok = self._dqpush(request) return True class RFPDupeFilter: def request_seen(self, request: Request) -> bool: # 16 进制字符串 fp = self.request_fingerprint(request) #self.fingerprints 是 set() 集合 if fp in self.fingerprints: return True # 如果在集合中,就返回True # 加入到集合中,返回False self.fingerprints.add(fp) return False def request_fingerprint(self, request: Request) -> str: return self.fingerprinter.fingerprint(request).hex() # 16进制 字符串 # request_fingerprint 生成指纹 # 请求回来的数据是完全一致的---》如果仅仅使用地址区分-->他们不是一个 # 但是被request_fingerprint执行完后,它生成的16进制是一样的 -www.cnblogs.com/?name=lqz&age=19 -www.cnblogs.com/?age=19&name=lqz # 总结: 1 去重是使用集合去重 -高级在(生成的指纹): -get请求,参数如果一样,就是一样的 -post请求,请求体不一样,就不一样 # 代码 from scrapy.utils.request import RequestFingerprinter from scrapy.http import Request f=RequestFingerprinter() res1=Request(url='http://www.cnblogs.com/',method='POST',body='name=lqz&age=19') res2=Request(url='http://www.cnblogs.com/',method='POST',body='name=lqz&age=20') res1_hex=f.fingerprint(res1) res2_hex=f.fingerprint(res2) print(res2_hex) print(res1_hex)
布隆过滤器
# https://zhuanlan.zhihu.com/p/94668361 # 布隆过滤器是什么? bloomfilter:是一个通过多哈希函数映射到一张表的数据结构,能够快速的判断一个元素在一个集合内是否存在,具有很好的空间和时间效率。(典型例子,爬虫url去重) # 布隆过滤器原理 原理: BloomFilter 会开辟一个m位的bitArray(位数组),开始所有数据全部置 0 。当一个元素过来时,通过多个哈希函数(h1,h2,h3....)计算不同的在哈希值,并通过哈希值找到对应的bitArray下标处,将里面的值 0 置为 1 # 布隆过滤器可能存在误差 -误差大小:由数组长度和hash函数绝对的 -允许出现误差 # python 字典的key值必须可以 hash -不可变数据类型---》数字和字符串---》布尔,元组,对象 -元组可以作为字典key # 使用布隆过滤器 安装:pip3 install pybloom_live # 布隆过滤器---》可以自动扩容---》规定错误率 # from pybloom_live import ScalableBloomFilter # # bloom = ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH) # # url = "www.cnblogs.com" # # url2 = "www.liuqingzheng.top" # # bloom.add(url) # # print(url in bloom) # # print(url2 in bloom) # 布隆过滤器---》定长,不扩容 # BloomFilter 是定长的 from pybloom_live import BloomFilter bf = BloomFilter(capacity=1000) url = 'www.baidu.com' bf.add(url) print(url in bf) print("www.liuqingzheng.top" in bf) ### redis可以实现布隆过滤器 ## 布隆过滤器可以做什么? 1 爬虫去重 2 垃圾邮件过滤 3 黑白名单 4 布隆过滤器避免缓存击穿
自定义去重规则(通过布隆过滤器)
# 集合去重,随着数据量越来越大---》很占空间 sad 3个bytes aaa 3个bytes 1个bytes就有8个格 00100000 00100000 00100000 00000000 00000000 00000000 # 使用布隆过滤器实现去重 from scrapy.dupefilters import BaseDupeFilter from scrapy.utils.request import RequestFingerprinter from pybloom_live import ScalableBloomFilter class MyPDupeFilter(BaseDupeFilter): fingerprints = ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH) fingerprinter = RequestFingerprinter() def request_seen(self, request): print('zoule') fp = self.request_fingerprint(request) if fp in self.fingerprints: return True self.fingerprints.add(fp) return False def request_fingerprint(self, request) -> str: return self.fingerprinter.fingerprint(request).hex()
写去重类
from scrapy.dupefilters import BaseDupeFilter from scrapy.utils.request import RequestFingerprinter from pybloom_live import ScalableBloomFilter from scrapy.dupefilters import RFPDupeFilter class MyPDupeFilter(BaseDupeFilter): fingerprints = ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH) fingerprinter = RequestFingerprinter() def request_seen(self, request): print('zoule--------------') fp = self.request_fingerprint(request) if fp in self.fingerprints: return True self.fingerprints.add(fp) return False def request_fingerprint(self, request) -> str: return self.fingerprinter.fingerprint(request).hex()
配置文件
DUPEFILTER_CLASS = "myfirstscrapy.MyPDupeFilter.MyPDupeFilter"
分布式爬虫
# 第三方的scrapy-redis 帮助我们实现分布式爬虫 # 什么是分布式爬虫 10条数据要爬取 一台机器爬10条 3台机器爬10条数据 1 台1--3条 2 台4--7条 3 台8--10条 # 实现分布式爬虫的核心 #1、共享队列 schudler 多台机器共享同一个队列 #2、同一套去重规则 # #### 使用步骤#### 0 下载:pip3 install scrapy-redis 1 把之前爬虫类,继承class CnblogsSpider(RedisSpider): 2 去掉起始爬取的地址,加入一个类属性 redis_key = 'myspider:start_urls' # redis列表的key,后期我们需要手动插入起始地址 3 配置文件中配置 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # scrapy redis去重类,使用redis的集合去重 # 不使用原生的调度器了,使用scrapy_redis提供的调度器,它就是使用了redis的列表 SCHEDULER = "scrapy_redis.scheduler.Scheduler" REDIS_HOST = 'localhost' # 主机名 REDIS_PORT = 6379 # 端口 ITEM_PIPELINES = { 'mysfirstscrapy.pipelines.MyCnblogsMySqlPipeline': 301, 'scrapy_redis.pipelines.RedisPipeline': 400, } 4 在不同多台机器上运行scrapy的爬虫,就实现了分布式爬虫:在一台机器上开启多个程序即可 5 写入到redis的列表中起始爬取的地址:列表key:myspider:start_urls rpush myspider:start_urls https://www.cnblogs.com # 执行报错 scrapy-redis(没有做好兼容)和scrapy(很新)的版本要对应 # 版本对应关系--》改scrapy-redis源码
标签:return,06days,self,request,spider,爬虫,scrapy From: https://www.cnblogs.com/wzh366/p/18034439