如何绕过反爬虫技术分析

对于大型网站的爬取我们经常会面临网站设定的反爬技术封锁,比如输入图片验证码、识别图中汉字,甚至直接禁止你的 ip 等。这样我们的爬虫可能刚开始运行不久就会遭受严重打击,无法进行下去。如何才能伪装的更像正常的请求是我们本节关注的重点。

1. 反爬封锁应对措施

面对网站的反爬封锁,我们往往有如下几个应对措施,这些基本的方法已经能应对大部分网站的封锁。更为深入的拟人技术还需要读者自行去探索,多加实战。

1.1 修改请求头

我们前面爬虫的第一步都是在请求头中添加正常浏览器的 user-agent,但是请求头的参数不止这些,还有一个值也会经常被检查到,那就是 referer 字段,它会告诉服务器该请求是从哪个页面链接过来的,服务器基此可以获得一些信息用于处理,因此为了更好的防止请求被封,我们也要尽量在请求 header 中加上referer 字段以及其正确的值。

图片描述
另外,user-agent 是每个反爬网站必查的字段,如果大量请求重复使用固定的 user-agent 值,在某些程序中也会被判定为爬虫。因此我们可以对所有的请求采用随机的 user-agent 值来避免这种反爬检测。Scrapy 中的中间件 UserAgentMiddleware 就是专门用来设置请求头中 user-agent 值的。

1.2 降低请求频率

正常情况下,我们的每次请求都会被记录,如果我们请求太快,比如1秒内发送10次请求,这明显不是手动实现的,很容易被识别然后采取相应措施。我们不管是在普通的爬虫代码还是 scrapy 中都要设置请求的间隔时间,防止被检测出来。在普通的爬虫程序中,我们直接使用 python 的 time 模块,调用 time.sleep() 方法进行延时调用,而在 Scrapy 中,我们只需要在 settings.py 中设置 DOWNLOAD_DELAY 的值即可。例如:

DOWNLOAD_DELAY = 2

上述的配置表示我们将两次请求的间隔设置为 2 秒,这已经是一个符合人正常操作的时间间隔了。不过太过于规律的请求同样会被更为智能的程序察觉,为了避免出现这样的情况,在 Scrapy 框架的配置中提供了 RANDOMIZE_DOWNLOAD_DELAY 参数。当它设置为 True 时 ,Scrapy 会在两次下载请求间设置一个 0.5 * DOWNLOAD_DELAY1.5 * DOWNLOAD_DELAY 之间的一个随机延迟时间。

1.3 禁用Cookie

有些网站会通过 Cookie 来发现爬虫的踪迹,因此,我们在进行普通网站数据爬取时可以考虑关闭 Cookie:

COOKIES_ENABLED = False

不过,对于一些需要登录后进行操作的,比如我们之前爬取起点用户书架以及删除书架上数据的爬虫,则需要使用 COOKIES 的情况下,我们不能禁用 Cookie。所以,特定场景需要特定的配置,不能一概而论。

1.4 验证码突破

许多做的非常好的网站都会有验证码校验,比如京东、淘宝的登录。更为复杂的还有12306网站那个让人头晕的识图验证等等。目前而言,验证码技术从原来的简单数字、字母识别,到滑块拖动、拼图认证以及最新的图片识别、汉字倒立等,已经越来越复杂和难辨。很多基于机器学习以及深度学习的高难度识别算法应运而生,但这些对于普通程序员而言,难以企及。我们唯有两方面突破:

  • 花钱买服务:网上有不少专门的验证码识别服务提供商,比如几年前比较流行的若快平台 (目前官网无法访问,似乎已经凉了)等;
  • 开源项目:如果舍不得花钱买服务的,我们只能寄希望于部分开源工具。好在还是有不少大神愿意将他们的研究代码、工具进行开源,这也使得我们能有机会去学习和使用这些工具去突破验证码的限制;

图片描述

京东的拼图验证

图片描述

12306的识图验证

1.5 使用 ip 代理

对于一些比较狠的网站,会对一些爬虫的程序客户端 ip 进行封杀,这样我们至少在一段时间内无法在运行该程序去爬取网站数据。此时,我们可以使用代理 ip 去隐藏真实的请求 ip,这样又可以访问网站并爬取数据。如果这个代理 ip 被封了怎么办?那就需要有多个可用的代理 ip,一旦发现该 ip 被封,我们立马换下一个 ip 代理继续请求数据。假设我们有十万代理 ip,每个代理能支撑我们爬取 1分钟数据,那么我们至少能顺利爬取2个多月,且一般1-2天左右,原来被封的 ip 又会被解除禁封。这样,只要我们有大量的 ip 代理,我们就不怕网站的封杀,能源源不断获取相应的网站数据。那么到哪里去获取这样免费的代理 ip 呢?同样有两种途径:

  • 网络上的免费 ip 代理:免费、不稳定且大部分不可用;
  • 付费 ip 代理池:略贵、大部分稳定可用;

图片描述

在 github 上也有许多维护和获取免费代理服务器地址的项目,我们也可以直接使用这些免费的项目帮我们抓取并维护可用的代理 ip 池。

2. 伪装成随机浏览器

我们来看看 Scrapy 给我们提供的、用于伪装 User-Agent 字段的中间件:UserAgentMiddleware 。其定义位于 scrapy/downloadermiddlewares/useragent.py 文件中,我们来看看其具体内容:

class UserAgentMiddleware:
    """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)

从上面的代码我们可以看到,该中间件会从 settings.py 中取得 USER_AGENT 参数值, 然后进行实例化:

o = cls(crawler.settings['USER_AGENT'])

在处理请求的核心方法 process_request() 会将该值赋给请求头中的 User-Agent 字段。注意该中间件属于下载中间件,在 Scrapy 中默认被启用,如下图所示:

图片描述

我们来看看如何在这个中间件的基础上实现随机的 User-Agent 请求:

  • 编写一个基于 UserAgentMiddleware 的中间件类,可以放到 scrapy 项目的 middlewares.py 文件中。这里我们使用 fake-useragent 模块来帮我们生成各种各样的 user-agent 值,这样避免我们手工维护一个 user-agent 的值列表。该模块的使用非常简单:

    (scrapy-test) [root@server china_pub]# python 
    Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
    [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from fake_useragent import UserAgent
    >>> ua = UserAgent()
    >>> ua.random
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36'
    >>> ua.random
    'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36'
    >>> ua.random
    'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36'
    >>> ua.random
    'Mozilla/5.0 (Windows NT 6.2; rv:21.0) Gecko/20130326 Firefox/21.0'
    >>> 
    

    注意:使用这个模块需要联网,根据相应的版本要请求网站的相应接口,获取相应数据。例如我这里的版本是0.1.11,于是请求的 URL 及其接口数据如下:

图片描述

来看我们自定义的中间件代码如下:

# 写入位置:scrapy项目的middlewares.py文件中

from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
from fake_useragent import UserAgent

# ...

class MyUserAgentMiddleware(UserAgentMiddleware):
    def process_request(self, request, spider):
        ua = UserAgent()
        user_agent = ua.random
        if user_agent:
            request.headers.setdefault(b'User-Agent', user_agent)
        return None
  • 另外,我们这里继承了 UserAgentMiddleware 中间件,那么原来的这个中间件就失去了意义、因此,在 settings.py 中,我们要启用新的设置 User-Agent 的中间件且关闭原来的中间件:

    # 代码位置:scrapy项目的settings.py文件中
    
    DOWNLOADER_MIDDLEWARES = {
       '项目名称.middlewares.MyUserAgentMiddleware': 500,
       'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
    }
    

3. 使用代理

在 Scrapy 项目中使用代理是非常简单的一件事情,我们只需要在发送的 Request 请求中添加 meta 参数即可实现代理功能:

yield Request(url, callback=回调方法, errback=错误回调, meta={"proxy": proxy, "download_timeout": 10})

上面生成的 Request 请求带上了 meta 参数,该参数中又设置了代理服务器的地址以及相应的超时时间。我们可以简单来看看代理服务器的使用:

  • 首先我们来自己搭建一个 Nginx 服务,具体的搭建过程可以参考这个教程: Nginx入门手册

  • 接着我们使用9999这个端口做为代理转发端口,相关的配置如下:

    # nginx.conf
    
    server {
         resolver 114.114.114.114;
         resolver_timeout 5s;
    
         listen 9999;
    
         location / {
             proxy_pass $scheme://$host$request_uri;
             proxy_set_header Host $http_host;
    
             proxy_buffers 256 8k;
             proxy_max_temp_file_size 0;
    
             proxy_connect_timeout 30;
    
             proxy_cache_valid 200 302 10m;
             proxy_cache_valid 301 1h;
             proxy_cache_valid any 1m;
        }
    }
    
  • 启动 nginx 服务后,我们这个代理服务就搞定了。我们可以使用 requests 测试下这个代理服务,看看是不是生效了:

    import requests
    
    proxies = {
      "http": "http://180.76.152.113:9999",
    }
    
    response = requests.get("http://www.china-pub.com/browse/", proxies=proxies)
    response.encoding = 'gbk'
    print(response.text)
    

    上述代码位于另一台云服务器,请求的是互动出版物的图书分类页面,这次我们会在 requests 请求中加上我们刚刚配置的代理,使用 nginx 代理转发请求,请看视频演示:

往往在 Scrapy 中,我们往往会采用这样的方式去使用代理服务:

  • 准备好一个 redis 和 web 服务。其中 web 服务往往会使用 django 或者 flask 等 web 框架开发,用于爬取免费的代理 ip,同时会对爬取到的 ip:port 进行校验。如果代理 ip 有效则将其缓存至 redis 服务中,形成有效的 ip 代理池;
  • web 服务会定期检查 ip 代理池的所有数据,对于无效的 ip 及时清除。同时,也会定时爬取新的有效的代理 ip 并保存到 redis 中;
  • web 服务会提供一个获取当前 ip 池内有效 HTTP 代理地址的接口,这样外部应用只需要请求这个接口就能获取一个有效的代理地址;

此外,在 Scrapy 的项目中,我们往往会按照如下的思路进行代理爬取:

图片描述

限于篇幅,这里不再对整个流程做案例演示,有兴趣的读者可以下去后一步步完成从 ip 代理池服务的开发到最后对接 Scrapy 框架的整个过程,推荐测试的网站为新浪微博,这也是 github 上许多案例的测试靶场。

4. 小结

本小节中我们主要讲述了三方面的内容:

  • 详细介绍了常见的突破反爬封锁措施;
  • 完成一个 Scrapy 中间件实现伪装随机浏览器功能;
  • 介绍代理技术与 Scrapy 项目结合的方式;

当然这些对于淘宝、微博等大厂的反爬技术而言还远远不够。后续读者可以技术深入研究如何突破各种验证码的校验、模拟人点击的轨迹,真正实现模拟用户浏览器请求操作,才能实现无所不爬的 Spider。

图片描述