首页 > 数据库 >爬取的数据存mysql中、加代理,cookie,header,加入selenium、布隆过滤器、scrapy-redis实现分布式爬虫

爬取的数据存mysql中、加代理,cookie,header,加入selenium、布隆过滤器、scrapy-redis实现分布式爬虫

时间:2023-04-17 16:23:18浏览次数:39  
标签:return redis self selenium request spider 爬取 item scrapy

上节回顾

# 1 scrapy架构
	-爬虫:写的一个个类
    -引擎:
	-调度器:排队,去重
    -下载器
    -pipline
    -下载中间件
    -爬虫中间件
    
    
# 2 命令
	-scrapy startproject 项目名
    -scrapy gensipder 爬虫名 网址
    -scrapy crawl 爬虫名字
    -run.py
    
    
# 3 解析  parse 方法   response对象
	response.text
    response.status
    response.css
    	-div.post-item-text>a::text
        -div.post-item-text img::attr(src)
    response.xpath
   		-'.//div/a/text()'
        -'//div//img/@src'
        
    extract_first()
    extract()
    
# 4 解析方法就一个,会有多种页面,样子不一样,再写解析方法
	-Request(url=url,callback=self.parser_detail,meta={'item':item})
	-parser_detail

# 5 meta的作用:做 Request对象和Response对象传值的


# 6 scrapy 配置文件
	-基础配置
    	-爬虫中间件配置
        -下载中间件配置
        -pipline配置(讲了)
    -提供爬虫效率的配置
    
    
# 7 持久化
	-两种
    -使用pipline
    	-1 items.py  写类
        -2 爬虫中,实例化得到对象(必须放在for内部),放到对象中数据,yield item对象
        -3 pipline中写 类中3个方法
        	open_spider :打开资源
            close_spider:关闭资源
            process_item
       - 4 配置文件中配置

今日内容

  • 爬取的数据,存到mysql中

  • 加代理,cookie,header,加入selenium

  • 去重规则源码分析(布隆过滤器)

  • scrapy-redis实现分布式爬虫

1 爬取的数据,存到mysql中

# 存到mysql中
class FirstscrapyMySqlPipeline:
    def open_spider(self, spider):
        print('我开了')
        self.conn = pymysql.connect(
            user='root',
            password="",
            host='127.0.0.1',
            database='cnblogs',
            port=3306)
        self.cursor = self.conn.cursor()

    def close_spider(self, spider):
        print('我关了')

        self.cursor.close()
        self.conn.close()

    # 这个很重要
    def process_item(self, item, spider):
        sql = '''INSERT INTO aritcle (title,author_img,author_name,`desc`,url,content) VALUES(%s,%s,%s,%s,%s,%s);'''
        print(len(item['content']))  # 可能会超长
        # print('-----')
        self.cursor.execute(sql,
                            args=[item['title'], item['author_img'], item['author_name'], item['desc'], item['url'],item['content']
                                  ])
        self.conn.commit()
        return item

2 爬虫和下载中间件

# 爬虫中间件:处于爬虫和引擎之间的
# 下载中间件:处于引擎和下载器之间的

# 咱们主要用的是下载中间件,爬虫中间件,了解即可
	-进来的时候是个Request对象
    -出去的时候是个Response对象
    
    
    
# 爬虫中间件
class FirstscrapySpiderMiddleware:
	# 进入到爬虫时,触发它
    def process_spider_input(self, response, spider):
        return None
	# 出爬虫时,触发它
    def process_spider_output(self, response, result, spider):
        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)
        
        
 # 下载中间件

class FirstscrapyDownloaderMiddleware:
	# 请求来了,从引擎进入到下载器会触发,这里有request对象
    def process_request(self, request, spider):
        # 必须返回以下情况:
        # - return None:继续下一个中间件
        # - return a Response object:结束掉,这个请求就不爬取了,回到引擎中
        # - return a Request object :把请求对象给引擎,引擎把它再次放到调度器中,等待下一次调度
        # - raise IgnoreRequest: 就会执行process_exception() 
        return None

    def process_response(self, request, response, spider):
        # 请求下载完成,回来经过:request请求对象, response响应对象
        # - return a Response object:继续走下一个中间件的process_response
        # - return a Request object :把请求对象给引擎,引擎把它再次放到调度器中,等待下一次调度
        # - raise IgnoreRequest:就会执行process_exception() 
        return response

    def process_exception(self, request, exception, spider):
     	中间件中抛异常会走到
        pass

 # 下载中间件的process_request,因为有request对象,就是要爬取的对象
	-修改请求头
    -加cookie
    -加代理
    --------
    -集成selenium

3 加代理,cookie,header,加入selenium

3.1 加代理

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

3.2 加cookie,修改请求头,随机生成UserAgent

   #### 加cookie
    def process_request(self, request, spider):
        print(request.cookies)
        request.cookies['name']='lqz'
        return None
    # 修改请求头
    def process_request(self, request, spider):
        print(request.headers)
        request.headers['referer'] = 'http://www.lagou.com'
        return None
    # 动态生成User-agent使用
    def process_request(self, request, spider):
        # fake_useragent模块
        from fake_useragent import UserAgent
        ua = UserAgent()
        request.headers['User-Agent']=str(ua.random)
        print(request.headers)

        return None

3.3 集成selenium

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

# 第一步:在爬虫类中写
from selenium import webdriver
class CnblogsSpider(scrapy.Spider):
    bro = webdriver.Chrome(executable_path='./chromedriver.exe')
    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

4 去重规则源码分析(布隆过滤器)

# 调度器可以去重,研究一下,如何去重的
要爬取的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?name=lqz&age=19
    -它俩是一样的,返回的数据都是一样的,就应该是一条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)
# 总结:scrapy的去重规则
	-根据配置的去重类RFPDupeFilter的request_seen方法,如果返回True,就不爬了,如果返回False就爬
    -后期咱们可以使用自己定义的去重类,实现去重
    
    
 # 更小内存实现去重
	-如果是集合:存的数据库越多,占内存空间越大,如果数据量特别大,可以使用布隆过滤器实现去重

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

	# 原理: BloomFilter 会开辟一个m位的bitArray(位数组),开始所有数据全部置 0 。当一个元素(www.baidu.com)过来时,能过多个哈希函数(h1,h2,h3....)计算不同的在哈希值,并通过哈希值找到对应的bitArray下标处,将里面的值 0 置为 1 。

    
    
    
   
# Python中使用布隆过滤器
# 测试布隆过滤器
# 可以自动扩容指定错误率,底层数组如果大于了错误率会自动扩容
# 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)
# bloom.add(url2)
# print(url in bloom)
# print(url2 in bloom)

from pybloom_live import BloomFilter

bf = BloomFilter(capacity=10)
url = 'www.baidu.com'
bf.add(url)
bf.add('aaaa')
bf.add('ggg')
bf.add('deww')
bf.add('aerqaaa')
bf.add('ae2rqaaa')
bf.add('aerweqaaa')
bf.add('aerwewqaaa')
bf.add('aerereweqaaa')
bf.add('we')


print(url in bf)
print("wa" in bf)



# 如果有去重的情况,就可以使用集合---》但是集合占的内存空间大,如果到了亿级别的数据量,想一种更小内存占用,而去重的方案----》布隆过滤器
# 布隆过滤器:通过不同的hash函数,加底层数组实现的极小内存去重
# python中如何使用:pybloom_live  
	-指定错误率
    -指定大小

    
    
# 使用redis实现布隆过滤器
	-编译redis---》把第三方扩展布隆过滤器编译进去,才有这个功能
	-https://zhuanlan.zhihu.com/p/94668736
# 重写scrapy的过滤类

5 scrapy-redis实现分布式爬虫

# 什么是分布式爬虫
	-集群:一个项目,在多个机器上部署,每个机器完成完整的功能,称之为集群
	-原来使用一台机器爬取cnblogs整站
    -现在想使用3台机器爬取cnblogs整站
    	- 每台机器爬取数据是不一样的
        - 最终组装成完整的数据
# 如果变成分布式,面临的问题
	-1 去重集合,我们要使用同一个----》redis集合
    -2 多台机器使用同一个调度器:Scheduler,排队爬取,使用同一个队列
    
    
# scrapy-redis 已经解决这个问题了,我只需要在我们单机基础上,改动一点,就变成了分布式爬虫
# 使用步骤
	第一步:安装scrapy-redis  ---》pip3 install scrapy-redis
    第二步:改造爬虫类
    from scrapy_redis.spiders import RedisSpider
    class CnblogSpider(RedisSpider):
        name = 'cnblog_redis'
        allowed_domains = ['cnblogs.com']
        # 写一个key:redis列表的key,起始爬取的地址
        redis_key = 'myspider:start_urls'
        
    第三步:配置文件配置
    # 分布式爬虫配置
    # 去重规则使用redis
    REDIS_HOST = 'localhost'                            # 主机名
    REDIS_PORT = 6379                                   # 端口
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" #看了源码
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"   # 先进先出:队列,先进后出:栈
    # 持久化:文件,mysql,redis
    ITEM_PIPELINES = {
       'cnblogs.pipelines.CnblogsFilePipeline': 300,
       'cnblogs.pipelines.CnblogsMysqlPipeline': 100,
       'scrapy_redis.pipelines.RedisPipeline': 400,  #简单看
    }
    第四步:在多台机器上启动scrapy项目,在一台机器起了多个scrapy爬虫进程,就相当于多台机器
    
    第五步:把起始爬取的地址放到redis的列表中
    lpush mycrawler:start_urls http://www.cnblogs.com/
    

5.1 拓展

# 原来的去重规则
class RFPDupeFilter(BaseDupeFilter):
   def request_seen(self, request):
        # 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


# scrapy-redis的去重规则
class RFPDupeFilter(BaseDupeFilter):
    def request_seen(self, request):
        fp = self.request_fingerprint(request)
        # This returns the number of values added, zero if already exists.
        added = self.server.sadd(self.key, fp)
        return added == 0
    
    
    
    
# 持久化
class RedisPipeline(object):

    def process_item(self, item, spider):
        return deferToThread(self._process_item, item, spider)
    def _process_item(self, item, spider):
        key = self.item_key(item, spider)
        data = self.serialize(item)
        self.server.rpush(key, data)
        return item
# 扩展
https://zhuanlan.zhihu.com/p/91643259
https://www.zhihu.com/column/c_1175438244715651072

标签:return,redis,self,selenium,request,spider,爬取,item,scrapy
From: https://www.cnblogs.com/super-xz/p/17326234.html

相关文章

  • 1 redis介绍 、2 redis linux下安装 、3 redis启动方式、4 redis典型场景 、5 redis通
    目录1redis介绍2redislinux下安装3redis启动方式3.1最简启动3.2动态参数启动3.3配置文件启动3.4客户端连接命令4redis典型场景5redis通用命令6数据结构和内部编码7redis字符串类型1redis介绍#特性Redis特性(8个)#速度快:10wops(每秒10w读写),数据存在内存中,c语言实现......
  • 记录selenium,python自动化测试中的chromedriver.exe地址和打开后自动关闭浏览器问题
    selenium的官方地址为:https://selenium-python.readthedocs.io/index.html镜像地址:https://npmmirror.com/#导入webdriverfromseleniumimportwebdriverfromselenium.webdriver.common.byimportBy#调用键盘按键操作时需要引入的Keys包fromselenium.webdriver.common.k......
  • mysql和redis测试
    Go单测从零到溜系列2—MySQL和Redis测试发布于2021/09/14,更新于2021/09/1422:31:17|Golang|总阅读量:480次这是Go语言单元测试从零到溜系列教程的第2篇,介绍了如何使用go-sqlmock和miniredis工具进行MySQL和Redis的mock测试。在上一篇《Go单测从零到溜系列1—网络测试》中,......
  • 聊聊Redis sentinel 机制
    Redis的哨兵机制自动完成了以下三大功能,从而实现了主从库的自动切换,可以降低Redis集群的运维开销:监控主库运行状态,并判断主库是否客观下线;在主库客观下线后,选取新主库;选出新主库后,通知从库和客户端。 一、为什么需要哨兵主从模式下,如果主库发生故障了,那就直接会影响到......
  • thinkphp: 用redis存储短信验证码(thinkphp v6.0.12LTS)
    一,配置redis1,编辑.env[REDIS0]TYPE=redisHOST=127.0.0.1PORT=6379PASSWORD=2,config/cache.php<?php//+----------------------------------------------------------------------//|缓存设置//+----------------------------------------------------......
  • Redis疑问
    为什么redis默认16个库,建议仅使用第一个库?如果Redis各个库都有在使用,会有以下影响或者问题:数据混乱:如果不同的应用程序使用同一个Redis的不同库,那么就可能出现键名冲突的情况,导致数据被覆盖或者错误地读取。同一个Redis不同库会导致数据被覆盖数据混乱的原因是:键名冲突:......
  • day02-Redis命令
    Redis命令1.Redis数据结构介绍Redis是一个key-value的数据库,key一般是String类型,value的类型多种多样,value常见的八种类型:Redis支持五种基本的数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sortedset,有序集合)。各个数据类型应用场景:类型简介特性场景S......
  • thinkphp:redis+lua实现短信发送频率限制(thinkphp v6.0.12LTS)
    一,配置:.env中[REDIS0]TYPE=redisHOST=127.0.0.1PORT=6379PASSWORD=二,php代码:1,lib\util\SmsRateUtil.php<?phpnamespaceapp\lib\util;//短信验证码发送频率classSmsRateUtil{//redis连接private$redis;//60秒内不允许重复发送pri......
  • Redis
    一、redis数据类型1.字符串类型2.哈希类型(适合存储对象)3.列表4.set(无序不重复元素)5.sortedset(有序不重复元素)二.redis常用命令......
  • redis 一般有用 看1
    redis和memcached什么区别?为什么高并发下有时单线程的redis比多线程的memcached效率要高?区别:1.mc可缓存图片和视频。rd支持除k/v更多的数据结构;2.rd可以使用虚拟内存,rd可持久化和aof灾难恢复,rd通过主从支持数据备份;3.rd可以做消息队列。原因:mc多线程模......