爬虫之scrapy框架(二)

2020-03-19 15:41:51 浏览数 (1)

目录

  • 一、使用scrapy整站爬取网站信息
    • scrapy的真正起始爬取方法
    • 使用scrapy整站爬取cnblogs网站信息大致思路
  • 二、scrapy的请求传参
  • 三、提升scrapy爬取数据的效率
  • 四、scrapy的下载中间件
    • 下载中间件使用代理池
  • scrapy settings详细介绍
  • 五、selenium在scrapy中的使用流程
  • 六、scrapy框架去重规则
  • 七、scrapy-redis分布式爬虫
  • 八、常见反扒措施

一、使用scrapy整站爬取网站信息

scrapy的真正起始爬取方法

当我们启动spider.py文件时,会执行我们设置好的start_urls,但是源码真正是如何处理的呢?我们进入scrapy.Spider查看源码,Spider类下有如下代码:

代码语言:javascript复制
    def start_requests(self):
        cls = self.__class__#当前实例对象的类
        if method_is_overridden(cls, Spider, 'make_requests_from_url'):
            warnings.warn(
                "Spider.make_requests_from_url method is deprecated; it "
                "won't be called in future Scrapy releases. Please "
                "override Spider.start_requests method instead (see %s.%s)." % (
                    cls.__module__, cls.__name__
                ),
            )
            for url in self.start_urls:#循环遍历start_urls
                yield self.make_requests_from_url(url)#返回一个request对象,并将url作为request的参数
        else:
            for url in self.start_urls:
                yield Request(url, dont_filter=True)

看到这里我们可以知道真正调用request发送请求的是start_requests,我们可以在重写start_request来控制首次请求的网址。

使用scrapy整站爬取cnblogs网站信息大致思路

在爬取的过程中使用yield item对象去保存爬取的信息,使用yield Request去爬取数据。

爬取思路:

1.首先爬取网站首页,将首页(包括所有的下一页)信息全都爬取出来

2.获取到每页中所有的博客园url连接

3.对每个url里面的内容进行爬取

cnblogs.py

代码语言:javascript复制
class CnblogsSpider(scrapy.Spider):
    name = 'cnblogs'
    allowed_domains = ['www.cnblogs.com']
    start_urls = ['https://www.cnblogs.com/']

    def parse(self, response):
        # print(response.text)
        div_list=response.css('div.post_item')
        for div in div_list:
            item=ArticleItem()
            title=div.xpath('./div[2]/h3/a/text()').extract_first()
            # print(title)
            item['title']=title
            author=div.xpath('./div[2]/div/a/text()').extract_first()
            # print(author)
            item['author'] = author
            desc=div.xpath('./div[2]/p/text()').extract()[-1]
            # print(desc)
            item['desc'] = desc
            url=div.xpath('./div[2]/div/span[2]/a/@href').extract_first()
            # print(url)
            item['url'] = url

            ##******接下来要做两件事
            # 第一件,深度爬取爬当前页中每个url的文章信息
            # 第二件:广度爬取
            # yield item对象会去保存,yield Request对象会去爬取
            # callback 回调,数据请求回来,去哪做解析,默认调用parse
            # 将item对象通过meta参数存入Request,就可以在response中获取到,以方便在parse_detail中继续向item对象中添加信息
            yield Request(url=url,callback=self.parse_detail,meta={'item':item})

        # css选择器取属性::attr(属性名)
        next_url='https://www.cnblogs.com' response.css('div.pager>a:last-child::attr(href)').extract_first()#广度爬取下一页
        
        # 获取到next_url后将其给Request,并且回调parse方法,对请求到的内容进行解析,由于Request默认callback函数就是self.parse,所以回调可以省去不写
        # yield Request(url=next_url,callback=self.parse)
        yield Request(url=next_url)


    def parse_detail(self,response):
        # print('-----',response)

        item=response.meta.get('item')
        print(item)

        content=response.css('#post_detail').extract_first()
        item['content']=str(content)
        # print(str(content))
        yield item

爬虫深度优先和广度优先参考文章

items.py

代码语言:javascript复制
import scrapy

class ArticleItem(scrapy.Item):
    title = scrapy.Field()
    author = scrapy.Field()
    desc = scrapy.Field()
    content = scrapy.Field()
    url = scrapy.Field()

piplines.py

代码语言:javascript复制
import pymysql

class MysqlArticlePipeline(object):
    def open_spider(self, spider):
        self.conn = pymysql.connect(host='127.0.0.1', user='root', password="zgh123",
                                    database='cnblogs', port=3306)

    def process_item(self, item, spider):
        cursor = self.conn.cursor()
        sql = "insert into article (title,author,url,`desc`,content) values ('%s','%s','%s','%s','%s')"%(item['title'],item['author'],item['url'],item['desc'],item['content'])
        cursor.execute(sql)
        self.conn.commit()
        return item
    #将爬取的数据写入数据库然后将item return出去继续爬取数据,每爬取一篇文章就向数据库写入一次

    def close_spider(self, spider):
        self.conn.close()

二、scrapy的请求传参

scrapy请求需要传入的参数如下,callback是回调函数,该函数是request爬完数据后的数据解析函数;meta是传入Request的参数,我们可以用它来传递数据。

代码语言:javascript复制
#scrapy的Request源码,
class Request(object_ref):

    def __init__(self, url, callback=None, method='GET', headers=None, body=None,
                 cookies=None, meta=None, encoding='utf-8', priority=0,
                 dont_filter=False, errback=None, flags=None, cb_kwargs=None):
        @property
    def meta(self):
        if self._meta is None:
            self._meta = {}
        return self._meta#将传入的参数存入字典,我们需要传入一个键值对

实际使用:

代码语言:javascript复制
1 yield Request(url=url,callback=self.parse_detail,meta={'item':item})  # meta=字典

#获取meta中的item
2 response.meta.get('item')   #上面传过来的

三、提升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

四、scrapy的下载中间件

代码语言:javascript复制
-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:会被引擎调度,放到调度器,等待下一次爬取

使用举例:

代码语言:javascript复制
#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
代码语言:javascript复制
#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':'zgh','age':18}

        # 使用代理(使用代理池)
        # print(request.meta)
        # request.meta['proxy'] = 'http://117.27.152.236:1080'
        return None
代码语言:javascript复制
  #  process_response 的使用
  def process_response(self, request, response, spider):
                
        return response

下载中间件使用代理池

为了不被服务器发现我们在爬数据,我们需要设置随机user-agent、cookie池、ip代理进行伪装。

代码语言:javascript复制
import random
class Scrapy02DownloaderMiddleware(object):
    user_agent_list = [
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
        "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
        "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6"
        "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
        "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
        "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
    ]
        def process_request(self, request, spider):
        # 请求头
        print(request.headers)
        request.headers['User-Agent']=random.choice(self.user_agent_list)#将随机的user-agent传入

        # 设置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':'zgh','age':18}

        # 使用代理(使用代理池)
        print(request.meta)
        request.meta['proxy'] = 'http://117.27.152.236:1080'
        return None

scrapy settings详细介绍

代码语言:javascript复制
#==>第一部分:基本配置<===
#1、项目名称,默认的USER_AGENT由它来构成,也作为日志记录的日志名
BOT_NAME = 'Amazon'

#2、爬虫应用路径
SPIDER_MODULES = ['Amazon.spiders']
NEWSPIDER_MODULE = 'Amazon.spiders'

#3、客户端User-Agent请求头
#USER_AGENT = 'Amazon ( http://www.yourdomain.com)'

#4、是否遵循爬虫协议
# Obey robots.txt rules
ROBOTSTXT_OBEY = False

#5、是否支持cookie,cookiejar进行操作cookie,默认开启
#COOKIES_ENABLED = False

#6、Telnet用于查看当前爬虫的信息,操作爬虫等...使用telnet ip port ,然后通过命令操作
#TELNETCONSOLE_ENABLED = False
#TELNETCONSOLE_HOST = '127.0.0.1'
#TELNETCONSOLE_PORT = [6023,]

#7、Scrapy发送HTTP请求默认使用的请求头
#DEFAULT_REQUEST_HEADERS = {
#   'Accept': 'text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8',
#   'Accept-Language': 'en',
#}



#===>第二部分:并发与延迟<===
#1、下载器总共最大处理的并发请求数,默认值16
#CONCURRENT_REQUESTS = 32

#2、每个域名能够被执行的最大并发请求数目,默认值8
#CONCURRENT_REQUESTS_PER_DOMAIN = 16

#3、能够被单个IP处理的并发请求数,默认值0,代表无限制,需要注意两点
#I、如果不为零,那CONCURRENT_REQUESTS_PER_DOMAIN将被忽略,即并发数的限制是按照每个IP来计算,而不是每个域名
#II、该设置也影响DOWNLOAD_DELAY,如果该值不为零,那么DOWNLOAD_DELAY下载延迟是限制每个IP而不是每个域
#CONCURRENT_REQUESTS_PER_IP = 16

#4、如果没有开启智能限速,这个值就代表一个规定死的值,代表对同一网址延迟请求的秒数
#DOWNLOAD_DELAY = 3

#===>第三部分:智能限速/自动节流:AutoThrottle extension<===
#一:介绍
from scrapy.contrib.throttle import AutoThrottle #http://scrapy.readthedocs.io/en/latest/topics/autothrottle.html#topics-autothrottle
设置目标:
1、比使用默认的下载延迟对站点更好
2、自动调整scrapy到最佳的爬取速度,所以用户无需自己调整下载延迟到最佳状态。用户只需要定义允许最大并发的请求,剩下的事情由该扩展组件自动完成

#二:如何实现?
在Scrapy中,下载延迟是通过计算建立TCP连接到接收到HTTP包头(header)之间的时间来测量的。
注意,由于Scrapy可能在忙着处理spider的回调函数或者无法下载,因此在合作的多任务环境下准确测量这些延迟是十分苦难的。 不过,这些延迟仍然是对Scrapy(甚至是服务器)繁忙程度的合理测量,而这扩展就是以此为前提进行编写的。

#三:限速算法
自动限速算法基于以下规则调整下载延迟
#1、spiders开始时的下载延迟是基于AUTOTHROTTLE_START_DELAY的值
#2、当收到一个response,对目标站点的下载延迟=收到响应的延迟时间/AUTOTHROTTLE_TARGET_CONCURRENCY
#3、下一次请求的下载延迟就被设置成:对目标站点下载延迟时间和过去的下载延迟时间的平均值
#4、没有达到200个response则不允许降低延迟
#5、下载延迟不能变的比DOWNLOAD_DELAY更低或者比AUTOTHROTTLE_MAX_DELAY更高

#四:配置使用
#开启True,默认False
AUTOTHROTTLE_ENABLED = True
#起始的延迟
AUTOTHROTTLE_START_DELAY = 5
#最小延迟
DOWNLOAD_DELAY = 3
#最大延迟
AUTOTHROTTLE_MAX_DELAY = 10
#每秒并发请求数的平均值,不能高于 CONCURRENT_REQUESTS_PER_DOMAIN或CONCURRENT_REQUESTS_PER_IP,调高了则吞吐量增大强奸目标站点,调低了则对目标站点更加”礼貌“
#每个特定的时间点,scrapy并发请求的数目都可能高于或低于该值,这是爬虫视图达到的建议值而不是硬限制
AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0
#调试
AUTOTHROTTLE_DEBUG = True
CONCURRENT_REQUESTS_PER_DOMAIN = 16
CONCURRENT_REQUESTS_PER_IP = 16



#===>第四部分:爬取深度与爬取方式<===
#1、爬虫允许的最大深度,可以通过meta查看当前深度;0表示无深度
# DEPTH_LIMIT = 3

#2、爬取时,0表示深度优先Lifo(默认);1表示广度优先FiFo

# 后进先出,深度优先
# DEPTH_PRIORITY = 0
# SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue'
# SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue'
# 先进先出,广度优先

# DEPTH_PRIORITY = 1
# SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
# SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue'

#3、调度器队列
# SCHEDULER = 'scrapy.core.scheduler.Scheduler'
# from scrapy.core.scheduler import Scheduler

#4、访问URL去重
# DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl'



#===>第五部分:中间件、Pipelines、扩展<===
#1、Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
#    'Amazon.middlewares.AmazonSpiderMiddleware': 543,
#}

#2、Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
   # 'Amazon.middlewares.DownMiddleware1': 543,
}

#3、Enable or disable extensions
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
#EXTENSIONS = {
#    'scrapy.extensions.telnet.TelnetConsole': None,
#}

#4、Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   # 'Amazon.pipelines.CustomPipeline': 200,
}



#===>第六部分:缓存<===
"""

1. 启用缓存
   目的用于将已经发送的请求或相应缓存下来,以便以后使用

   from scrapy.downloadermiddlewares.httpcache import HttpCacheMiddleware
   from scrapy.extensions.httpcache import DummyPolicy
   from scrapy.extensions.httpcache import FilesystemCacheStorage
   """
   # 是否启用缓存策略
   # HTTPCACHE_ENABLED = True

# 缓存策略:所有请求均缓存,下次在请求直接访问原来的缓存即可
# HTTPCACHE_POLICY = "scrapy.extensions.httpcache.DummyPolicy"
# 缓存策略:根据Http响应头:Cache-Control、Last-Modified 等进行缓存的策略
# HTTPCACHE_POLICY = "scrapy.extensions.httpcache.RFC2616Policy"

# 缓存超时时间
# HTTPCACHE_EXPIRATION_SECS = 0

# 缓存保存路径
# HTTPCACHE_DIR = 'httpcache'

# 缓存忽略的Http状态码
# HTTPCACHE_IGNORE_HTTP_CODES = []

# 缓存存储的插件
# HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

#===>第七部分:线程池<===
REACTOR_THREADPOOL_MAXSIZE = 10

#Default: 10
#scrapy基于twisted异步IO框架,downloader是多线程的,线程数是Twisted线程池的默认大小(The maximum limit for Twisted Reactor thread pool size.)

#关于twisted线程池:
http://twistedmatrix.com/documents/10.1.0/core/howto/threading.html

#线程池实现:twisted.python.threadpool.ThreadPool
twisted调整线程池大小:
from twisted.internet import reactor
reactor.suggestThreadPoolSize(30)

#scrapy相关源码:
D:python3.6Libsite-packagesscrapycrawler.py

#补充:
windows下查看进程内线程数的工具:
    https://docs.microsoft.com/zh-cn/sysinternals/downloads/pslist
    或
    https://pan.baidu.com/s/1jJ0pMaM
    

​```
命令为:
pslist |findstr python

​```

linux下:top -p 进程id

#===>第八部分:其他默认配置参考<===
D:python3.6Libsite-packagesscrapysettingsdefault_settings.py

settings.py

五、selenium在scrapy中的使用流程

代码语言:javascript复制
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

六、scrapy框架去重规则

通过导入from scrapy.dupefilters import RFPDupeFilter我们可以查看scrapy的去重使用的类RFPDupeFilter,通过分析源码可以发现:

1.整个去重规则是通过RFPDupeFilter中的request_seen控制

2.在调度器Scheduler中的enqueue_request调用,如果dont_filter=True就不过滤了

scrapy采用的去重方式是现将请求的url都放入一个集合,利用集合的去重功能进行去重,但是在放入集合前scrapy会对url的携带参数进行切割,然后排序再放入集合,这样即使url后的携带参数排序普通,只要携带的参数一样也会被去重。

如:

代码语言:javascript复制
http://www.baidu.com/?name=zgh&age=18
http://www.baidu.com/?age=18&name=zgh

就会被去重。

代码语言:javascript复制
from scrapy.dupefilters import RFPDupeFilter
from scrapy.core.scheduler import Scheduler

# 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:原理

如果爬取的数据超过千万级为了提高爬取效率我们可以手写去重规则,采用bloomfilter(布隆过滤器)进行去重,具体的配置如下:

代码语言:javascript复制
#在setting中配置
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' #默认的去重规则帮我们去重,去重规则在内存中
# 写一个类,继承BaseDupeFilter,重写方法,主要重写request_seen,如果返回True表示有了,False表示没有

布隆过滤器的过滤方式是将调数据通过哈希算法算出三个比特位置为1,通过对比每条数据算出的比特位是否相同然后进行过滤,这种方式更加节省内存,但是有可能会出现某条数据本来和前面的不重复但是也会被过滤掉的情况。

布隆过滤器简介

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

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

关于多个哈希函数,它们计算出来的值必须 [0,m) 之中。

例子:

有这么一个网址

假设长度为 20的bitArray,通过 3 个哈希函数求值。如下图:

另外说明一下,当来查找对应的值时,同样通过哈希函数求值,再去寻找数组的下标,如果所有下标都为1时,元素存在。当然也存在错误率。(如:当数组全部为1时,那么查找什么都是存在的),但是这个错误率的大小,取决于数组的位数和哈希函数的个数。

七、scrapy-redis分布式爬虫

分布式爬虫就是让多台机器同时对某一网站进行爬虫,然后将爬去的数据放入同一个数据库中,从而提高爬虫的效率,但是对于小服务器最好不要采用分布式进行爬虫,容易把服务器搞崩掉。

scrapy实现分布式爬虫步骤:

代码语言:javascript复制
#1、共享队列
#2、重写Scheduler,让其无论是去重还是任务都去访问共享队列
#3、为Scheduler定制去重规则(利用redis的集合类型)

举例与配置:

代码语言:javascript复制
# 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行


# 使用
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中写入下面#这里是将start_urls写在redis中,scrapy会自动去redis中读取,多台机器的话,哪台机器先读取到,哪台机器就先爬
redis-cli
lpush myspider:start_urls https://www.cnblogs.com/

八、常见反扒措施

代码语言:javascript复制
0.检测浏览器header
1 User-Agent,
2 cookie
3. ip 封禁
4. 图片验证码(打码平台,手动)
5  图片懒加载()
6 js加密,混淆:pip install PyExecJS 动态执行js
7 css加密
8 图片防盗链

0 人点赞