7-爬虫-中间件和下载中间件(加代理,加请求头,加cookie)、scrapy集成selenium、源码去重规则(布隆过滤器)、分布式爬虫

news2024/12/24 8:51:22

0 持久化(pipelines.py)使用步骤

1 爬虫中间件和下载中间件
1.1 爬虫中间件(一般不用)
1.2 下载中间件(代理,加请求头,加cookie)
1.2.1 加请求头(加到请求对象中)
1.2.2 加cookie
1.2.3 加代理

2 scrapy集成selenium

3 源码去重规则(布隆过滤器)
3.1 布隆过滤器

4 分布式爬虫

持久化(pipelines.py)使用步骤

# 1 scrapy 框架,安装,创建项目,创建爬虫,运行爬虫
# 2 scrapy架构
# 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 继续爬取
	- 下一页的地址:Request(url=next, callback=self.parse)
    - 详情地址:Request(url=url, callback=self.detail_parser)
		-额外去写detail_parser内的解析
        
# 5 数据传递
	-解析中有数据---》下个解析中还能拿到
    Request(url=url, callback=self.detail_parser,meta={'item':item})----》给了Response对象的meta属性
    
    
# 6 配置文件
	-基础配置
    -高级配置--》提高爬虫效率
    
    
# 7 持久化---》把数据保存到磁盘上:文件,mysql
	-管道
    -使用步骤
    	-1 写个类:items.py,里面写字段
        class CnblogItem(scrapy.Item):
            name = scrapy.Field()
            author = scrapy.Field()
            url = scrapy.Field()
            img = scrapy.Field()
            desc_content = scrapy.Field()
            # 文本详情
            text = scrapy.Field()
         -2 配置文件配置(管道,配置多个,存在多个位置)
        	ITEM_PIPELINES = {
               "scrapy_demo.pipelines.CnblogPipeline": 300,
               "scrapy_demo.pipelines.CnblogMysqlPipeline": 200,
            }
        -3 爬虫解析中:yield item
        -3 pipelines.py中写类:open_spider,close_spider,process_item

在这里插入图片描述

1 爬虫中间件和下载中间件

1.1 爬虫中间件(一般不用)

# 第一步:写个爬虫中间件类
	class ScrapyDemoSpiderMiddleware:
    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    # 走架构图第1步,会触发这里
    def process_spider_input(self, response, spider):
        # Called for each response that goes through the spider
        # middleware and into the spider.

        # Should return None or raise an exception.
        return None

    # 架构图,第1,7步走这里
    def process_spider_output(self, response, result, spider):
        # Called with the results returned from the Spider, after
        # it has processed the response.

        # Must return an iterable of Request, or item objects.
        for i in result:
            yield i

    def process_spider_exception(self, response, exception, spider):
        # Called when a spider or process_spider_input() method
        # (from other spider middleware) raises an exception.

        # Should return either None or an iterable of Request or item objects.
        pass

    # 架构图第一步
    def process_start_requests(self, start_requests, spider):
        # Called with the start requests of the spider, and works
        # similarly to the process_spider_output() method, except
        # that it doesn’t have a response associated.

        # Must return only requests (not items).
        for r in start_requests:
            yield r

    def spider_opened(self, spider):
        spider.logger.info("Spider opened: %s" % spider.name)
        
        
 # 2 配置文件配置
	SPIDER_MIDDLEWARES = {
   "scrapy_demo.middlewares.ScrapyDemoSpiderMiddleware": 543,
}

1.2 下载中间件(代理,加请求头,加cookie)

class ScrapyDemoDownloaderMiddleware:

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
        # - return None: 继续执行当次请求,继续走下一个中间件---》如果中间件没了---》执行下载
        # - return Response :直接把Response返回给引擎,引擎交给爬虫去解析
        # - return Request :把request返回给引擎,引擎给调度器,等待下一次被爬取
        # - 直接抛异常: 触发process_exception执行
        return None

    def process_response(self, request, response, spider):
        # Must either;
        # - return Response:正常爬取完---》给引擎---》引擎给爬虫去解析
        # - return Request: 爬取失败--》给引擎--》引擎给调度器--》等待下次爬取
        # - 抛异常  :走到process_exception
        return response

    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # 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)

# faker 模块 :随机生成假数据
# pip install fake_useragent:随机生成请求头

###  加referer,加token 加 user-agent
def process_request(self, request, spider):
        #### 加请求头
        print(request.headers)
        request.headers['referer'] = 'http://www.lagou.com'
        request.headers['token'] = 'asdfasdf.asdfads.asfdasfd'
        # user-agent--->写死了---》想随机请求头
        from fake_useragent import UserAgent
        ua = UserAgent()
        request.headers['User-Agent'] = str(ua.random)
        print(request.headers)
        return None
    
    
 

    def process_request(self, request, spider):
        print(request.cookies)
        request.cookies['name']='lqz'
        return None

1.2.3 加代理

# 在下载中间件的def process_request(self, request, spider):写代码

# 第一步:
	-在下载中间件写process_request方法
   	 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
        
# 第二步:代理可能不能用,会触发process_exception,在里面写
    def process_exception(self, request, exception, spider):
        print('-----',request.url)  # 这个地址没有爬
        return request

2 scrapy集成selenium

# 使用scrapy默认下载器---》类似于requests模块发送请求,不能执行js,有的页面拿回来数据不完整

# 想在scrapy中集成selenium,获取数据更完整,获取完后,自己组装成 Response对象,就会进爬虫解析,现在解析的是使用selenium拿回来的页面,数据更完整


# 集成selenium 因为有的页面,是执行完js后才渲染完,必须使用selenium去爬取数据才完整


# 保证整个爬虫中,只有一个浏览器器
# 只要爬取 下一页这种地址,使用selenium,爬取详情,继续使用原来的

# 第一步:在爬虫类中写
from selenium import webdriver
class CnblogsSpider(scrapy.Spider):
    bro = webdriver.Chrome() # 使用无头
    bro.implicitly_wait(10)
    def close(spider, reason):
        spider.bro.close() #浏览器关掉
        
# 第二步:在中间件中
    def process_request(self, request, spider):
        # 爬取下一页这种地址---》用selenium,但是文章详情,就用原来的
        if 'sitehome/p' in request.url:
            spider.bro.get(request.url)
            from scrapy.http.response.html import HtmlResponse
            response = HtmlResponse(url=request.url, body=bytes(spider.bro.page_source, encoding='utf-8'))
            return response
        else:
            return None

3 源码去重规则(布隆过滤器)

# 如果爬取过的地址,就不会再爬了,scrapy 自带去重

# 调度器可以去重,研究一下,如何去重的---》使用了集合


# 要爬取的Request对象,在进入到scheduler调度器排队之前,先执行enqueue_request,它如果return False,这个Request就丢弃掉,不爬了----》如何判断这个Request要不要丢弃掉,执行了self.df.request_seen(request),它来决定的-----》RFPDupeFilter类中的方法----》request_seen---》会返回True或False----》如果这个request在集合中,说明爬过了,就return True,如果不在集合中,就加入到集合中,然后返回False



# 调度器源码
from scrapy.core.scheduler import Scheduler
	# 这个方法如果return True表示这个request要爬取,如果return False表示这个网址就不爬了(已经爬过了)
    def enqueue_request(self, request: Request) -> bool:
        # request当次要爬取的地址对象
        if self.df.request_seen(request):
            # 有的请情况,在爬虫中解析出来的网址,不想爬了,就就可以指定
            # yield Request(url=url, callback=self.detail_parse, meta={'item': item},dont_filter=True)
            # 如果符合这个条件,表示这个网址已经爬过了 
            return False
        return True
    
    
    
# self.df 去重类 是去重类的对象 RFPDupeFilter--》配置文件配置的
    -在配置文件中如果配置了:DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'表示,使用它作为去重类,按照它的规则做去重
    -RFPDupeFilter的request_seen
       def request_seen(self, request: Request) -> bool:
        # request_fingerprint 生成指纹
        fp = self.request_fingerprint(request) #request当次要爬取的地址对象
        #判断 fp 在不在集合中,如果在,return True
        if fp in self.fingerprints:
            return True
        #如果不在,加入到集合,return False
        self.fingerprints.add(fp)
        return False

    
# 传进来是个request对象,生成的是指纹
	-爬取的网址:https://www.cnblogs.com/teach/p/17238610.html?name=lqz&age=19
    -和        https://www.cnblogs.com/teach/p/17238610.html?age=19&name=lqz
    -它俩是一样的,返回的数据都是一样的,就应该是一条url,就只会爬取一次
    -所以 request_fingerprint  就是来把它们做成一样的(核心原理是把查询条件排序,再拼接到后面)
    
   
    -生成指纹,指纹是什么? 生成的指纹放到集合中去重
    	-www.cnblogs.com?name=lqz&age=19
        -www.cnblogs.com?age=19&name=lqz
        -上面的两种地址生成的指纹是一样的
        # 测试指纹
        from scrapy.utils.request import RequestFingerprinter
        from scrapy import Request

        fingerprinter = RequestFingerprinter()
        request1 = Request(url='http://www.cnblogs.com?name=lqz&age=20')
        request2 = Request(url='http://www.cnblogs.com?age=20&name=lqz')

        res1 = fingerprinter.fingerprint(request1).hex()
        res2 = fingerprinter.fingerprint(request2).hex()
        print(res1)
        print(res2)
        
        
# 集合去重,集合中放 
# a一个bytes
# 假设爬了1亿条url,放在内存中,占空间非常大
a6af0a0ffa18a9b2432550e1914361b6bffcff1a
a6af0a0ffa18a9b2432550e191361b6bffc34f1a

# 想一种方式,极小内存实现去重---》布隆过滤器
https://zhuanlan.zhihu.com/p/94668361

3.1 布隆过滤器

# 极小内存实现去重:
# 应用场景:爬虫去重,避免缓存穿透,垃圾邮件过滤

# bloomfilter:是一个通过多哈希函数映射到一张表的数据结构,能够快速的判断一个元素在一个集合内是否存在,具有很好的空间和时间效率。(典型例子,爬虫url去重)
#布隆案例

# 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)


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)
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()

4 分布式爬虫

# 原来scrapy的Scheduler维护的是本机的任务队列(待爬取的地址)+本机的去重队列(放在集合中)---》在本机内存中
# 如果把scrapy项目,部署到多台机器上,多台机器爬取的内容是重复的


# 所以实现分布式爬取的关键就是,找一台专门的主机上运行一个共享的队列比如Redis,
然后重写Scrapy的Scheduler,让新的Scheduler到共享队列存取Request,并且去除重复的Request请求,所以总结下来,实现分布式的关键就是三点:
    #1、多台机器共享队列
    #2、重写Scheduler,让其无论是去重还是任务都去访问共享队列
    #3、为Scheduler定制去重规则(利用redis的集合类型)
    
    
# scrapy-redis实现分布式爬虫
	-公共的去重
    -公共的待爬取地址队列
    
    
    
#  使用步骤
	0 下载:pip2 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.MyCnblogsPipeline': 300,
       '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
    
    
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1203159.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Transformer从零开始代码实现 pytoch版】(五)总架构类的实现

Transformer总架构 在实现完输入部分、编码器、解码器和输出部分之后,就可以封装各个部件为一个完整的实体类了。 【Transformer从零开始代码实现 pytoch版】(一)输入部件:embeddingpositionalEncoding 【Transformer从零开始代…

Power Automate-变量和excel表数据的应用

前提表格 Power Automate连接excel请参考:SharePoint-连接Excel-CSDN博客 需求1:计算表格中某列的和 添加操作,搜索变量,选择初始化变量 添加变量的名称、类型和初始值 再新增操作,搜索Excel,点击查看更多…

Kubernetes介绍和环境部署

文章目录 Kubernetes一、Kubernetes介绍1.Kubernetes简介2.Kubernetes概念3.Kubernetes功能4.Kubernetes工作原理5.kubernetes组件6.Kubernetes优缺点 二、Kubernetes环境部署环境基本配置1.所有节点安装docker2.所有节点安装kubeadm、kubelet、kubectl添加yum源containerd配置…

查询数据表格中的数据

1.创建这个表至少20个 1)创建数据库:create database 四川信息职业技术; 2)创建数据表 3)插入数据(第一条代码修改了一下手机号码的字段类型) 2.统计表中的人数 如果你想根据某个特定的列来统计人数&…

Jenkins在Linux环境下的安装与配置

Jenkins是一个开源软件项目,是基于Java开发的一种持续集成(CI)工具,用于解决持续重复的部署、监控工作;它一个开放易用的软件平台,大大简化软件的持续集成。 安装Jenkins 1.使用docker安装 2.本地下载je…

Python数据结构:元组(Tuple)详解

1.介绍和基础操作 Python中的元组(Tuple)是不可变有序序列,可以容纳任意数据类型(包括数字、字符串、布尔型、列表、字典等)的元素,通常用圆括号() 包裹。与列表(List)类似&#xff…

[IJKPLAYER]基于DEMO分析IJKPLAYER(整理版本)

背景 博主主要是从事C语言开发,因此本文着重强调FFMPEG部分,关于JAVA应用和框架层只是一笔带过。IJKPLAYER的实质是对FFMPEG项目中的ffplayer程序进行的二次封装,通过JNI方式完成对外提供JAVA接口。 1.目录结构 activities:包含了demo的所有…

react函数式组件props形式父向子传参

父组件中定义 子组件中触发回调传值 import { useState } from "react"; function Son(params) {const [count, setCount] useState(0);function handleClick() {console.log(params, paramsparamsparamsparamsparamsparams);params.onClick(111)setCount(count 1…

多个微信快速同步发圈

做营销最重要的任务是什么? 毋庸置疑,就是发布朋友圈。 为什么要发圈呢? 现在社交媒体中,微信不管在生活上、工作上都是不可或缺的工具,而朋友圈是微信中社交场景之一,也是很多企业作为推广产品和服务的重…

腾讯云服务器多少钱一年?2023年腾讯云优惠云服务器推荐

作为一名程序员,技术的突飞猛进是从拥有第一台云服务器开始的。那时,我开始尝试使用Linux系统,并成功上线了自己的第一个小程序。自此之后,我和我的同事们都开始拥有自己的云服务器,用来搭建各种小项目或者好玩的东西。…

OpenAtom OpenHarmony三方库创建发布及安全隐私检测

OpenAtom OpenHarmony三方库(以下简称“三方库”或“包”),是经过验证可在OpenHarmony系统上可重复使用的软件组件,可帮助开发者快速开发OpenHarmony应用。三方库根据其开发语言分为2种,一种是使用JavaScript和TypeScr…

wpf devexpress设置行和编辑器

如下教程示范如何计算行布局,特定的表格单元编辑器,和格式化显示值。这个教程基于前一个文章 选择行显示 GridControl为所有字段生成行和绑定数据源,如果AutoGenerateColumns 属性选择AddNew。添加行到GridControl精确显示为特别的几行设置。…

Containerd接入Harbor仓库

在使用容器时,避免不了会使用到私有仓库,一般都是采用 harbor 作为私有仓库,docker 对接 harbor 仓库非常简单,哪 containerd 如何对接 harbor 呢? 在内网使用 harbor 根据个人习惯,一般都是非 http 并且是…

【SpringBoot3+Vue3】一【基础篇】

目录 一、Spring Boot概述 1、Spring Boot 特性 1.1 起步依赖 1.2 自动配置 1.3 其他特性 1.3.1 内嵌的Tomcat、Jetty (无需部署WAR文件) 1.3.2 外部化配置 1.3.3 不需要XML配置(properties/yml) 二、Spring Boot入门 1、一个入门程序需求 2、步骤 2.1 创建Maven工…

智能配方颗粒管理系统解决方案,专业实现中医药产业数字化-亿发

“中药配方颗粒”,又被称为免煎中药,源自传统中药饮片,经过提取、分离、浓缩、干燥、制粒、包装等工艺加工而成。这种新型配方药物完整保留了原中药饮片的所有特性。既能满足医师的辨证论治和随症加减需求,同时具备强劲好人高效的…

Python实现猎人猎物优化算法(HPO)优化XGBoost回归模型(XGBRegressor算法)项目实战

说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 猎人猎物优化搜索算法(Hunter–prey optimizer, HPO)是由Naruei& Keynia于2022年提出的一种最新的…

(论文阅读29/100 人体姿态估计)

29.文献阅读笔记 简介 题目 DeepCut: Joint Subset Partition and Labeling for Multi Person Pose Estimation 作者 Leonid Pishchulin, Eldar Insafutdinov, Siyu Tang, Bjoern Andres, Mykhaylo Andriluka, Peter Gehler, and Bernt Schiele, CVPR, 2016. 原文链接 h…

分享一套适合二开的JAVA开源版本MES系统

一、系统概述: 万界星空科技免费MES、开源MES、商业开源MES、市面上最好的开源MES、MES源代码 万界星空开源MES制造执行系统的Java开源版本。 开源mes系统包括系统管理,车间基础数据管理,计划管理,物料控制,生产执行…

ubuntu开机系统出错且无法恢复。请联系系统管理员。

背景: ubuntu22.04.2命令行,执行自动安装系统推荐显卡驱动命令,字体变大,重启后出现如下图错误,无法进入系统,无法通过CTRLALTF1-F3进入TTY模式。 解决办法: 1.首先要想办法进入系统&#xff…

江门車馬炮汽车金融中心 11月11日开张

江门车马炮汽车金融中心于11月11日正式开张,这是江门市汽车金融服务平台,旨在为广大车主提供更加便捷、高效的汽车金融服务。 江门市作为广东省的一个经济发达城市,汽车保有量持续增长,但车主在购车、用车、养车等方面仍存在诸多不…