美文网首页scrapy爬虫框架职业成长之路
scrapy框架-反爬虫与绕过方法+setting动态配置

scrapy框架-反爬虫与绕过方法+setting动态配置

作者: 中乘风 | 来源:发表于2018-07-13 13:49 被阅读73次

    反爬虫与绕过方法

    反爬虫的技术越来越丰富,种类也越来越多,以下归纳爬虫与反爬虫的应对措施和绕过方法。


    小蜘蛛

    甲.对网站感兴趣,分析网络请求并写爬虫进行数据爬取

    乙.监控发现某时间段访问增大,IP相同,user-agent都是python,判断是爬虫,直接限制访问(不是封IP)

    甲.随机切换user-agent,比如火狐 chrome 360浏览器 ie等,并且用IP代理手段继续爬取。

    乙.发现IP变化,直接要求登录才可以访问(知乎就是)

    甲.注册账号,每次请求带上cookie或者token。

    乙.健全账号体系,限制只能访问好友信息等(facebook就是这样)

    甲.注册多个账号,养着。多个账号联合爬取。

    乙.单个账号请求过于固定或者频繁,限定IP访问频率

    甲.模仿人类的请求,放慢请求速率

    乙.偶尔弹出验证码,识别验证码后才能操作

    甲.各种手段识别验证码

    乙.增加动态网站,数据通过js动态加载,增加网络分析复杂度;发现大量请求只请求html,而不请求image/css/js。

    甲.通过seleniumh和phantomjs完全模拟浏览器操作

    乙.考虑成本太高的问题,可能放弃反爬虫


    自动切换User-Agent

    考虑到需要绕过反爬虫对user-agent的识别和限制,实际爬取过程中需要自动切换user-agent以继续爬取网站数据。

    简单的,先设置一个user-agent池,里面预先放置各种浏览器的agent,通过随机数取到随机的agent。在每一次发起请求都需要带上这个随机获得的agent。例如:

    user_agent_list = [
        "Mozilia firefox xxx…",
        "Mozilia chrome xxx…",
        Mozilia ie xxx…
    ]
    
    random_index = randmo.randint(0,len(user_agent_list))
    random_agent = user_agent_list[random_index]
    self.headers["User-Agent"] = random_agent
    
    

    这是最简单的形态,但是不符合代码开发需求。一个是因为这样太麻烦,每一次请求都必须这样带上;二是代码分离性不好,一旦要更改就很麻烦,代码重用性也不高;三是耦合性太高,难以分离。

    根据scrapy的数据流图

    Scrapy架构图

    可以发现第4、5步经过的MIddleware是全局的,可以在这里面写

    在scrapy里面负责下载的中间件DownloadMiddleware里面进行编写,这样才能够达到高重用性/低耦合性和自动切换的需求。

    开启配置

    在settings.py中找到DOWNLOADER_MIDDLEWARES,并且取消掉它的注释(scrapy默认不开启,所以是注释着的)。

    查看源码

    接着到[项目目录jobbole-test\Lib\site-packages\scrapy\downloadermiddlewares\useragent.py]中可以看到,scrapy默认设置了user_agent,里面也包含了很多的方法:

    
    from scrapy import signals
    
    
    class UserAgentMiddleware(object):
        """This middleware allows spiders to override the user_agent"""
    
        def __init__(self, user_agent='Scrapy'):
            self.user_agent = user_agent
    
        @classmethod
        def from_crawler(cls, crawler):
            o = cls(crawler.settings['USER_AGENT'])
            crawler.signals.connect(o.spider_opened, signal=signals.spider_opened)
            return o
    
        def spider_opened(self, spider):
            self.user_agent = getattr(spider, 'user_agent', self.user_agent)
    
        def process_request(self, request, spider):
            if self.user_agent:
                request.headers.setdefault(b'User-Agent', self.user_agent)
    

    init中写明scrapy的UserAgent是Scrapy
    from_crawler方法会读取settings.py中的USER_AGENT配置,如果有的话就用配置,如果没有的话就默认用Scrapy,这里我可以在setting里面新增配置:

    """ 自定义 UserAgent 配置 """
    USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0"
    

    根据文档编写

    根据官方文档中对下载中间件的介绍,如果要开启自己的Middleware,则需要将后面的值设置变大,将默认的Middleware值设置为None(因为数值越大,执行越靠后,这样才会在执行的时候选择我们自己编写的Middleware配置)
    在settings.py中:

    DOWNLOADER_MIDDLEWARES = {
        'jobbolecvs.middlewares.MyCustomDownloaderMiddleware': 543,
        'scrapy.downloadermiddleware.useragent.UserAgentMiddleware':None,
    }
    

    下方还有一个函数:

        def process_request(self, request, spider):
            if self.user_agent:
                request.headers.setdefault(b'User-Agent', self.user_agent)
    

    它就会在每次请求的时候都带上配置的UserAgent,如果没有配置就带上"Scrapy"

    同时,在官方文档中下面部分有介绍:

    下载器中间件(Downloader Middleware)

    下载器中间件是介于Scrapy的request/response处理的钩子框架。 是用于全局修改Scrapy request和response的一个轻量、底层的系统。

    并且根据文档中描述[激活下载器中间件]:

    DOWNLOADER_MIDDLEWARES = {
        'myproject.middlewares.CustomDownloaderMiddleware': 543,
        'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,
    }
    

    除了把自己编写的Middleware数值定义较大,而且还需要关闭默认的,那就是需要到settings中进行配置。

    由于它是request/response双向钩子,所以重载的时候如果是处理request就重载request相关的方法,如果是response则只重载response相关的方法。

    编写自己的Middleware需要重载几个方法process_request、process_response、process_exception

    在建立scrapy项目的时候,目录中自动建立了一个middlewares.py文件(与setting、item、pipelin同目录),我们在里面编写自己的middleware就行了(这里random还没写):

    
    class RandomUserAgentMiddleware(object):
        """
            自定义随机切换UserAgent
        """
        def __inti__(self, crawler):
            """ 主要获取settings配置中的user_agent_list """
            super(RandomUserAgentMiddleware, self).__init__()
            self.user_agent_list = crawler.settings.getr("user_agent_list",[])
    
        @classmethod
        def from_crawler(cls, crawler):
            return cls(crawler)
        
        def process_request(self, request, spider):
            """
            这里未完成的是需要编写一个随机取值,从user_agent_list里面随机取1个 
            设置请求的时候每次都带上一个User-Agent """
            request.headers.setdefault('User-Agent', random())
    
    

    然后在settings中配置:

    user_agent_list = ["Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0",
                       "Mozilla/5.0 (X10; Windows x86_64; rv:55.0) Gecko/20100101 Firefox/57.0"]
    

    这样的一个列表即可。(写完后记得在setting开启RandomUserAgentMiddleware配置)。


    使用fake外部包

    自定义UserAgent有一个缺点,就是浏览器的版本很多、平台也很多,有些会淘汰、新的一直在升级。所以用这个的话还得自己长期维护版本号。所以可以用网上的fake-useragent包,它有github网友维护,我拿来安装后根据代码提示,集成到scrapy即可。

    首先 安装fake-useragent包,然后到middleware.py中新增代码:

    from fake_useragent import UserAgent
    
    class RandomUserAgentMiddleware(object):
        """
        自定义下载中间件 以达到每次请求都会随机切换user-agent的目的
        通过安装fake-useragent包,来获取浏览器版本号(自己写个list也可以,但是没有网上的这个那么丰富齐全)
        为了增强可选择性,比如选择随机ie或者随机firefox浏览器的版本号,所以增加了ua_type和get_ua的一些代码
        """
    
        def __init__(self, crawler):
            super(RandomUserAgentMiddleware, self).__init__()
            self.ua = UserAgent()
            # 从settings.py中读取RANDOM_UA_TYPE配置 如果没有则默认值为random  达到可配置的目的
            # 默认是random随机选择,但是可以在配置指定ie或者firefox、chrome等浏览器的不同版本
            self.ua_type = crawler.settings.get("RANDOM_UA_TYPE", "random")
    
        @classmethod
        def from_crawler(cls, crawler):
            return cls(crawler)
    
        def process_request(self, request, spider):
            def get_ua():
                """
                函数中的函数 闭包
                读取上面的ua_type设置 让process_request直接调用本get_ua
                """
                return getattr(self.ua, self.ua_type)
    
            request.headers.setdefault('User-Agent', get_ua())
    
    

    然后在settings.py中新增代码:

    RANDOM_UA_TYPE = "random"
    

    然后在配置中开启:

    DOWNLOADER_MIDDLEWARES = {
        'jobbolecvs.middlewares.RandomUserAgentMiddleware': 543,
        'scrapy.downloadermiddleware.useragent.UserAgentMiddleware':None,
    }
    

    即可实现随机切换User-Agent的功能。


    自动切换ip地址

    在爬虫爬取的过程中,不仅对User-Agent有识别和限制,很多时候也对IP地址进行判断和限制,比如拉勾网在爬虫爬取的过程中返回的警告信息:

    2017-12-29 17:01:35 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://passport.lagou.com/login/login.html?msg=validation&uStatus=2&clientIp=113.106.97.12> (referer: https://www.lagou.com/)
    2017-12-29 17:01:35 [scrapy.core.scraper] DEBUG: Scraped from <200 https://passport.lagou.com/login/login.html?msg=validation&uStatus=2&clientIp=113.106.97.12>
    

    就限制了爬虫的继续爬取。

    所以IP代理就派上了用场。IP代理实际上是利用了代理服务器在中间作梗:

    默认访问形式:我->浏览器->目标服务器

    代理访问形式:我->浏览器->代理服务器<->目标服务器

    实际上后面变成代理服务器与目标之间的沟通,我自己的ip则隐藏起来了,不会被禁止。而ip代理服务器由于提供了很多的ip,也不怕被封。所以可以用这个办法绕过对IP限制的反爬虫手段。


    免费的ip代理

    做IP代理的服务很多,也有人专门从事爬虫IP代理服务,百度搜一下就出来很多。这里用西刺代理做演示.打开西刺选择一个ip地址,记得复制端口号。


    实现简单的ip代理

    在刚才的middlewares.py中写了一个RandomUserAgentMiddleware类,里面最后一个方法是process_request,在方法里面,最底部加上一句代码:

        def process_request(self, request, spider):
            def get_ua():
                return getattr(self.ua,self.ua_type)
            request.headers.setdefault('User-Agent',get_ua())
            request.meta["proxy"] = "http://101.68.73.54:53281"
    

    代码里的ip和端口就是西刺上面复制下来的。
    保存运行就可以实现ip代理访问了。再次爬取拉勾网,则发现比用自己的IP地址顺畅很多,虽然也有限制,但是不像之前爬取2个就要被关闭。


    外部包+实现IP代理池

    用上面的方法实现了IP代理,但是还没有达到自动切换IP地址的方法。实现自动切换的方法可以爬取免费IP代理网站的IP/端口/http类型(有些是https)还有有效时间等,可以爬取多网站。

    最好的IP代理插件包是scrapy官方提供的scrapy-crawlera包,在github上有,但是收费的。

    然而穷就要用免费的,在github上搜索scrapy-proxies就能找到对应的包,用pycharm进行安装即可。

    根据上面文档的介绍,安装好后只需要在settings.py中进行配置即可:

    # Retry many times since proxies often fail
    RETRY_TIMES = 10
    # Retry on most error codes since proxies fail for different reasons
    RETRY_HTTP_CODES = [500, 503, 504, 400, 403, 404, 408]
    
    DOWNLOADER_MIDDLEWARES = {
        'scrapy.downloadermiddlewares.retry.RetryMiddleware': 90,
        'scrapy_proxies.RandomProxy': 100,
        'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,
    }
    
    # Proxy list containing entries like
    # http://host1:port
    # http://username:password@host2:port
    # http://host3:port
    # ...
    PROXY_LIST = '/path/to/proxy/list.txt'
    
    # Proxy mode
    # 0 = Every requests have different proxy
    # 1 = Take only one proxy from the list and assign it to every requests
    # 2 = Put a custom proxy to use in the settings
    PROXY_MODE = 0
    
    # If proxy mode is 2 uncomment this sentence :
    #CUSTOM_PROXY = "http://host1:port"
    

    这是原文,里面的'/path/to/proxy/list.txt'就是存放ip的文件。所以我在settings.py中空白地方实际添加的代码是:

    
    """ 通过scrapy-proxies实现ip切换 """
    # Retry many times since proxies often fail
    RETRY_TIMES = 10
    # Retry on most error codes since proxies fail for different reasons
    RETRY_HTTP_CODES = [500, 503, 504, 400, 403, 404, 408]
    # Proxy list containing entries like
    # http://host1:port
    # http://username:password@host2:port
    # http://host3:port
    # ...
    project_dir = os.path.abspath(os.path.dirname(__file__))
    proxys_path = os.path.join(project_dir, 'utils/proxys.txt')
    PROXY_LIST = proxys_path
    # Proxy mode
    # 0 = Every requests have different proxy
    # 1 = Take only one proxy from the list and assign it to every requests
    # 2 = Put a custom proxy to use in the settings
    PROXY_MODE = 0
    # If proxy mode is 2 uncomment this sentence :
    #CUSTOM_PROXY = "http://host1:port"
    

    然后在DOWNLOADER_MIDDLEWARES中新增:

        # 以下三个是实现IP动态切换
        'scrapy.downloadermiddlewares.retry.RetryMiddleware': 90,
        'scrapy_proxies.RandomProxy': 100,
        'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,
    

    这样就完成了配置。但是还没有设置存放ip的文件。我在utils目录下新建proxys.py。里面存放了网上复制下来的IP:

    http://27.46.74.52:9999
    http://187.87.76.25:3128
    http://202.179.189.18:3128
    http://95.68.244.254:3128   
    http://178.32.213.128:80    
    http://103.76.175.88:8080
    

    然后就可以实现每次请求都切换IP地址(开关可设置,在使用说明里写有)

    ip代理的问题

    一般来讲,市面上免费的IP代理服务都是比较差的,要么是服务器不稳定,要么是请求太慢。所以其实用IP代理能解决被封的问题,但是速度会变慢。

    收费的IP代理能够有效的解决这个问题,但是穷。

    还有一个办法,就是在自己的电脑上使用VPN来进行IP切换,但是要放慢速度爬取,,免得VPN的地址也被封。

    商用ip代理的实现

    这个就需要参考我另一篇文章了《目标反爬虫怎么办?实践出真知-scrapy集成动态ip代理(以阿布云为例)》


    识别验证码(商用)

    验证码识别方法有:

    1.编码实现(tesseract-ocr)这是谷歌开源的一个工具,但是已经不适用了,而且很麻烦,需要很多时间。

    2.在线打码,识别率较高90%,云平台识别

    3.人工打码,认为识别打码,最高识别率

    这里使用云打码平台。注册两个用户,一个是普通用户:

    yundamas
    123456789
    

    另一个是开发者:

    wanliruhu
    123456789
    

    云打码平台提供的开发文档中的PythonHTTP示例下载,下载后有python3的示例代码。

    根据示例代码和普通用户中心/开发者中心需要填写普通用户的用户名密码以及开发者的ID和密钥:

    # 用户名
    username    = 'yundamas'
    
    # 密码
    password    = '123456789'                            
    
    # 软件ID,开发者分成必要参数。登录开发者后台【我的软件】获得!
    appid       = 4342                                     
    
    # 软件密钥,开发者分成必要参数。登录开发者后台【我的软件】获得!
    appkey      = '2e02326f1d782de768b784eaabc287' 
    
    codetype = 5000
    
    filename = 'getimage.jpg'
    

    codetype = 5000是指不知道验证码的格式是纯数字 英文或者中文等类型。

    filename = 'getimage.jpg'是具体识别的图片。实际爬取中遇到验证码,需要下载下来,然后调用代码对验证码图片进行识别,返回值。


    禁用cookie

    有些网站会根据cookie识别是否爬虫,不需要登录也能访问的网站生效,如果是需要登录的网站如果禁用则会无法登陆。

    在settings.py中将

    COOKIES_ENABLED = False
    

    设置为False即可实现禁用cookie


    自动限速

    默认是一直下载的,下载延迟为0.但是有可能被目标发现,所以需要进行限速下载。在官方文档中有个关于自动限速AutoThrottle扩展的介绍,里面包括扩展的实现,算法等。这个扩展会自动调节限制的速度。

    文档里的参数都是在settings.py中存在的,去取消注释或者改变状态即可开启。比如启用扩展就在settings.py中:

    AUTOTHROTTLE_ENABLED = True
    

    即可开启扩展。

    然后初始下载延迟(单位:秒)的设置就是:

    AUTOTHROTTLE_START_DELAY = 3
    

    还有DOWNLOAD_DELAY下载器在下载同一个网站下一个页面前需要等待的时间。该选项可以用来限制爬取速度, 减轻服务器压力。同时也支持小数:

    DOWNLOAD_DELAY = 0.5
    

    就是0.5秒后开启下一个页面的爬取


    实现动态settings配置

    在同一个工程项目中,会存在多个spider,但settings.py却只有一个。比如需要爬取jobbole和知乎的时候,知乎需要开启cookie用于登录,Jobbole则可能会通过我们提交的cookie判断是否是爬虫,所以需要在settings.py中实现动态设置,给每个spider分配不同的配置

    打开scrapy源码(路径是项目/Lib/site-packages/scrapy/spiders/init.py)中的Spider类。里面有

        custom_settings = None
    

    如果设置有值的话,就可以自动覆盖默认的custom_settings。所以要实现动态设置就是 在settings.py文件中保持默认配置,然后 在具体的spider文件的类中 添加如下代码:

    custom_settings = {
        "COOKIES_ENABLED":True,
    }
    

    就可以实现单个spider开启个性化配置。就是知乎可以在spiders目录下的zhihu.py中的class ZhihuSpider类里面添加这么一句代码即可。

    其他spider类似。

    相关文章

      网友评论

        本文标题:scrapy框架-反爬虫与绕过方法+setting动态配置

        本文链接:https://www.haomeiwen.com/subject/zdkdpftx.html