反爬虫详解


了解反爬虫

我们现在处于一个信息爆炸的大数据时代,数据在互联网上的传播和呈现方式多种多样,越来越多的公司开始重视保护自己的数据了,他们研发反爬虫技术,让爬虫不在可以随便的去爬取获取他们的信息。

如果你想要更好的获取数据,那么反爬虫的知识也需要有一定程度上的研究,当你掌握反爬虫的知识后,将会让你的爬虫更好的绕过反爬虫。

反爬虫的概念和定义

反爬虫概念的理解是:一切限制爬虫程序从服务器获取数据的方式都属于反爬虫。它有多种限制手段,如:限制请求头、限制登陆、验证码检验、限制访问频率等。

从这些限制手段出发,可以将反爬虫分为主动型和被动型,两种类型。

开发者使用一些技术手段来区分正常用户和爬虫被称为主动型反爬虫,通常有限制请求头、限制访问频率、限制登陆等方式。

为了提高用户体验或节约资源,用一些技术间接提高爬虫访问难度被称为被动型反爬虫,通常有Ajax数据加载、点击切换标签页等方式。

当然,也可以从其他方面对反爬虫进行更加细化的划分,我们这里不在做详细的讲述,需要注意的是,同一种限制方式,可能属于不同的反爬虫技术类型,比如由JS加载的数据,我们可以说成动态渲染反爬虫,也可以说成是信息检验型反爬虫,所以我们在学习的时候,遇到这类情况时,知道怎么回事即可。

爬虫和反爬虫的对立关系

我们通过下图来了解爬虫和反爬虫之间即针锋相对,又互相进步的关系。(从左至右,从上至下发展)

反爬虫不仅要会技术,同时也需要金钱进行支撑。我们有时需要购买代理IP或者开通VIP账户等,如果爬取数据量较大时,这些将是一笔不菲的开支。同时,在研究如何破解反爬虫时,还会消耗我们大量的时间,这也是需要计算的成本。

信息校验型

签名验证反爬虫的原理和绕过方法

信息校验反爬虫的方式主要是服务器对请求发起者的身份或者合法性进行判断,从而来进行反爬限制。只要我们在发送请求时将自己伪装好,即可绕过这类反爬虫。

定义

签名是一个根据数据源进行计算或者加密的过程,用户经过签名后会一个具有一致性和唯一性的字符串,它就是你访问服务器的身份象征。由它的一致性和唯一性这两种特性,从而可以有效的避免服务器端,将伪造的数据或被篡改的数据当初正常数据处理。

用于签名验证的信息通常被放在请求正文中发送到服务器端。如下图所示:

原理

签名验证反爬虫有多种实现方式,但是它的实现原理都是相同的,都是由客户端生成一些,随机值和不可逆的MD5加密字符串,在发起请求的同时,将这些值一同发送到服务器端。然后服务器端使用相同的方式对随机值进行计算和MD5加密,如果服务器端得到的MD5值与前端提交的MD5值相等,就代表是正常的请求,否则报错。部分代码如下:

import hashlib

def Hex5(value):
   # 使用 MD5 加密值并返回加密后的字符串

   manipulator = hashlib.md5()
   manipulator.update(value.encode('utf-8'))
   return manipulator.hexdigest()

def Comparison(, actions, tim, randstr, sign):
   # 根据 MD5 加密值,并与客户端提交的 MD5 值进行比对

   value = actions + tim + randstr
   hexs = Hex5(value)
   if sign == hexs:
       returnTrue
   returnFalse

绕过

现在签名验证反爬虫已经被广泛应用于Web领域,当我们在发起请求时,发现正文中含有一些随机的字符串时,我们就要考虑目标网站可能采用了签名验证反爬虫。

大部分签名验证反爬虫的正文信息,都是由JavaScript进行计算的,我们在遇到它时,去JS页面中寻找相应的JS文件,从而发现它的加密方式,进而来绕过它。

小结

  1. 签名验证是一种较为高级的反爬虫方式,主要是通过JavaScript来生成随机字符串、多字段组合等方式来对请求进行加密。

  2. 个人感觉它是Ajax异步加载的更高级形式。绕过它不仅要在XHR中寻找请求信息,也要在JS中寻找加密的方式。

  3. 目前这种反爬虫方法已经被各类大型网站所应用,所以大家要掌握这种反爬虫的绕过方法。

User-Agent反爬虫的原理和绕过方法

定义

User-Agent是一种请求头,服务器可以从User-Agent对应的值中来识别用户端使用的操作系统、浏览器、浏览器引擎、操作系统语言等等。

浏览器User-Agent通常由浏览器标识、渲染引擎标识、版本信息这三部分来构成。我们可以在这个位置来查看我们的User-Agent请求头值。

原理

我们通过浏览器来获取数据的方式是这样的:

我们通过爬虫来获取数据的方式是这样的:

所以,我们的爬虫程序,实际上是模拟浏览器对服务器发送请求来获取数据的。

在向服务器发送的网络请求中,User-Agent是客户端用于表明身份的一种标识。所以,反爬虫工程师可以将一些爬虫程序的请求头放入服务器访问的黑名单中,当有网络请求时通过检测User-Agent请求头域值来判断客户端的类型,将其与黑名单中数据进行比对,一致时则拒绝其访问,从而有效的限制一些爬虫程序。

绕过

User-Agent请求头域的值是可以修改的。Requests.get()请求中有一个headers参数,可以将我们修改后的User-Agent加入,然后将爬虫程序伪装成浏览器,以此来骗过反爬虫程序。伪装代码如下:

headers = {
   'Connection': 'keep-alive',
   'Cache-Control': 'max-age=0',
   'sec-ch-ua': '"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"',
   'sec-ch-ua-mobile': '?0',
   'Upgrade-Insecure-Requests': '1',
   'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36',
   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
   'Sec-Fetch-Site': 'same-origin',
   'Sec-Fetch-Mode': 'navigate',
   'Sec-Fetch-User': '?1',
   'Sec-Fetch-Dest': 'document',
   'Referer': 'https://bj.ke.com/',
   'Accept-Language': 'zh-CN,zh;q=0.9',
}
response = requests.get(url, headers=headers)

小结

  1. User-Agent检验是一种最为初级的反爬虫方式,主要是通过服务器黑名单来限制爬虫的反爬虫方式。

  2. 对于有一定基础的小伙伴来说,这种方式基本不能够限制大家。

  3. 现在很多网站基于User-Agent检验上又增加了访问频率限制,即同一User-Agent在单位时间内访问频率过高,也会默认是爬虫,从而拉入黑名单,所以我们如果要大量需要的话,可以尝试搭建一个User-Agen池,来更好的伪装自己。

文本混淆

文本混淆反爬虫大多是利用CSS来进行限制爬虫,这类反爬虫使得我们无法借助一些渲染工具来获取目的数据。遇到这类反爬虫,我们需要去理解它的反爬原理,写出爬虫绕过逻辑,并用代码来实现这个逻辑,从而破解这类反爬虫。

SVG映射反爬虫的原理和破解方法

SVG是用于描述二维矢量图形的一种图形格式。它基于XML描述图形,对图形进行放大或缩小操作都不会影响图形质量。

  • SVG图形

这种反爬虫手段是通过映射的方式,来使SVG图形代替具体的文字。这并不会影响用户的正常阅读,但是爬虫程序因为没有映射关系,所以无法像网页一样正常获取SVG图形中的内容,从而导致无法获取网页数据。

从上面我们知道,这种反爬虫手段是通过映射的方式建立的,那么这种映射关系是通过怎样的方式进行建立的呢?那这就得学习一下SVG反爬虫的原理了。

原理

这种反爬虫主要是网站开发者利用CSS样式的特性,使得HTML标签和SVG图形之间形成一种独特的映射关系,如下图:

然后根据这种映射关系,使得网页中的HTML标签,从SVG图形中取出相应的数据。

这样就使得网站开发者进行网页设计时,只需要使用HTML标签进行占位即可,不需要将真正的数据放到页面中去。这样,爬虫程序如果不知道这种映射关系的话,就无法从SVG图形中获取正确的数据,从而实现反爬虫。

破解

破解这类反爬虫的关键是找到HTML标签与SVG图形之间的映射关系,只要找到了映射关系,那么我们就可以用Python实现映射算法,进而通过该算法,获取到真正的数据。

1. 观察HTML标签

我们按F12打开开发者模式来对网页进行观察,我们发现占位的标签是标签,并通过class的值来进行映射的,如下图:

2. 寻找映射关系

从原理中,我们知道映射关系是利用CSS样式来构造的,并且我们在对HTML标签观察时已经发现了占位标签和映射值的样式,所以我们在开发者模式中对CSS样式进行查看,最终在food.css文件中找到了映射关系,如下图:

3.下载SVG图形

在上面,我们通过对网页和CSS样式的观察,成功的找到了映射关系,那么现在需要把SVG图形给下载下来。

我们找到它映射的背景图片是在一个food.svg文件中,那我们把该图形下载下来,如下图:

4. 构建映射算法

在上面我们已经找到了,映射关系和SVG图形,那么我们现在就可以开始用Python来构建映射算法,从而使得爬虫可以获取一个正确的数据。

我们现在有两种方法来进行构建:

  • 程序构建法

    此方法适用于映射数量较多且映射关系较为复杂的网页。

    有些网站,因为子页面较多,页面排版不一样,所以它会使用多个CSS样式和SVG图形,依次来方便页面排版,但是它们的映射算法是不变的,此时使用程序来计算获取每个class值对应的数字是最为方便的。

    那么如何来计算映射算法呢,我给大家总结了一个公式,大家只要牢记这个公式就可以很快的构建好映射算法了。

  • SVG图形的x轴坐标计算方法:字体大小/2+该字符的x轴起点位置

  • 代码如下:

  • # 求svg转化成css的x坐标
        svg_x_css = []
        for j in range(len(svg_x_y[0][0].split())):
            svg_x_css.append(7+j*14)
        ```
  • SVG图形的y轴坐标计算方法:(字体大小-该字符的y轴起点位置-字体大小)/2+该字符的y轴起点位置+字体大小/2

  • 代码如下:

  • # 求svg转化成css的y坐标
        a = 0# a的作用是为了存储上一个y坐标的值
        svg_y_css = []
        for i in svg_x_y:
            j = float(i[1])
            svg_y_css.append((j-14-a)/2+a+7)
            a = j
        ```
  • 然后将SVG图形转化的坐标与CSS样式的坐标进行比对,即可求出映射值:

  • # 让css和svg转化的坐标进行比对,求出css对应svg中的位置。
       for i in css_x_y:
           a=b=0# a是x坐标轴对应的位置,b是y坐标轴对应的位置
           x = int(i[1])
           y = int(i[2])
           for j in svg_x_css:
               if x >= j:
                   a+=1
               else:
                   break
           for k in svg_y_css:
               if y >= k:
                   b+=1
               else:
                   break
           if b == 0:
               b=1
       ```
  • 页面构建法

    此方法适用于映射数量较少且映射关系较为简单的网页。

    当网页页面较少时,CSS样式和SVG图形就会较少,甚至只有一个,此时,我们可以将SVG图形下载下来,并将CSS样式copy到本地,然后将背景图形替换成下载的SVG图形,从而找出映射关系,进行构建。

小结

  1. 本节详细介绍了如何破解SVG映射反爬虫,这种反爬虫即使使用一些自动化软件或者渲染工具也无法获得真正的数据。

  2. 这类反爬虫的破解需要掌握前端的知识,所以对爬虫工程师的综合能力要求较高。

  3. CSS样式的x、y轴的正方向与SVG图形的x、y轴正方向是相反的,大家在实战中就明白这个定义的用处了。

CSS偏移反爬虫的原理和破解方法

原理

在搭建网页的时候,我们需要用CSS来控制各类字符的位置,也正是如此,我们可以利用CSS来将浏览器中显示的文字,在HTML中以乱序的方式存储,从而来限制爬虫。如下图,我们发现浏览器中实际显示的是1226,但是HTML中显示的是1262。

接下来,我们通过一个例子来了解绕过CSS偏移反爬虫的方法。

绕过

从下图中我们看出机票价格的数据被包裹在一个<span>标签中,标签大小是64px。如下图所示:

进一步对网页进行观察发现,<span>标签中有五个<b>标签,它们每一对<b>标签都有特定的样式。如下图所示:

我们用这些CSS样式来进行分析的话,我们发现,第一对<b>标签中的四对<i>标签刚好占满了<span>标签的位置。如下图所示:

浏览器中,本应显示的是5377,因为第二、三、四、五对<b>标签中有值,所以我们还需要看一下他们的位置。

第二对的<b>标签的位置样式是left:-64px,所以第二对<b>标签中的1就会覆盖第一对<b>标签中第一对<i>标签中的5;第三对的<b>标签的位置样式是left:-32px,第三对<b>标签中的2会覆盖第一对<b>标签中第三对<i>标签中的7。依次类推,第四对<b>标签中的6会覆盖第一对<b>标签中第四对<i>标签中的7,第五对<b>标签中的2会覆盖第一对<b>标签中第二对<i>标签中的3。

通过上面的分析,我们发现,CSS偏移反爬虫其实就是让几个数据由CSS控制位置,来对原数据进行覆盖,从而实现反爬效果。

那么当我们遇到这类反爬虫的时候,首先就需要对页面的CSS进行观察分析,找到偏移量的计算规律,然后提取出每个标签中left的值,根据规律来排列出真实的数据。

小结

  1. CSS偏移反爬虫实质上是通过CSS样式来控制数据在页面中显示的位置,从而将乱序的数据以正常的形式展示给用户。

  2. 破解这种反爬虫的难度并不大,主要是找到位置偏移的计算方法,而且代码书写可能较为繁琐,读者们可以提前写好流程图,然后在进行书写。

  3. 目前这种反爬虫方法主要是针对于数字数据的反爬。

图片伪装反爬虫的原理和破解方法

定义

现在许多大型网站的反爬虫方式是将图片与文字混合在一起,放到页面上进行展示。这种混合展示的方式并不会影响用户的正常阅读,但是却可以限制爬虫程序获取这些内容。如下图:

原理

这种反爬虫的原理十分简单,就是将本应是普通文本内容的部分在前端页面中用图片来进行替换,从而达到“鱼目混珠“的效果。

破解

因为这种反爬虫方式是将内容进行替换,所以我们无法进行绕过,只能破解它来获取我们想要的内容。

破解的方法也比较简单,我们只需要将图片下载下来然后对里面的内容进行提取即可。提取图片中的文字有很多方式,我用的是百度AI来进行提取。代码如下:

from aip import AipOcr

APP_ID = '你的APPID'
API_KEY = 'API Key'
SECRET_KEY = '你的Secret Key'
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
with open(img,'rb') as f:
  image = f.read()
word = client.basicGeneral(image)

小结

  1. 图片伪装反爬虫的本质就是用图片替换了原来的内容,从而让爬虫程序无法正常获取,我们只要将里面的内容识别、提取出来就可以破解这种反爬虫。

  2. 破解这种反爬虫的难度并不大,但是代码书写可能较为繁琐,读者们可以提前写好流程图,然后在进行书写。

  3. 目前这种反爬虫方法已经被各类大型网站所应用,所以大家要掌握这种反爬虫的绕过方法。

字体反爬虫的原理和破解方法

原理

在之前,网站开发者在设计网页时只能使用公用的字体来展示网页中的数据。

但是,随着CSS样式的深入开发,网站开发者可以将自己的字体放到服务器中。当用户在访问Web界面时,对应的字体就会被浏览器自动下载到用户的计算机中,然后通过CSS样式进行调用。

之后,通过一种映射关系,使得网页中的源数据变为真正的数据进行展示。

通过这种方式,使得这样就使得网站开发者进行网页设计时,只需要使用特殊字符进行占位即可,不需要将真正的数据放到页面中去。这样,爬虫程序如果不知道这种映射关系的话,就无法从字体中获取正确的数据,从而实现反爬虫。

破解

破解这类字体反爬虫有以下几步。

1. 下载字体woff文件

从上面我们知道,字体是在服务器上进行存储,并通过浏览器下载到我们的电脑上的,那么我们就可以在网站上找到加载的字体文件,下载下来。

下载下来之后,打开它进行观察,这里给大家分享一个再点字体编译器网站,使用它可以很方便打开woff文件。网址:http://font.qqe2.com/index-en.html

打开字体文件之后,我们发现,每个数字都对应一个字符串,如7对应的是$E9C7。

2. 寻找映射关系

通过对源网页中的占位数据和字体进行比对,我们发现将源数据中的&#x替换成$,然后将字符串首字母大写,就变成了字体对应的字符串了。

3. 构建映射算法

在上面我们已经找到了字体之间映射关系,那么我们现在就可以开始用Python来构建映射算法,从而使得爬虫可以获取一个正确的数据。

构建代码如下:

data = {
   '&#xe9c7' : 7,
   '&#xf57b' : 1,
   '&#xe7df' : 2,
   '&#xe339' : 6,
   '&#xe624' : 9,
   '&#xea16' : 5,
   '&#xf19a' : 3,
   '&#xee76' : 0,
   '&#xf593' : 4,
   '&#xefd4' : 8,
}

之后,我们即可对网页进行爬取,然后将对应的源数据与data进行比如,从而获得正确数据。

小结

  1. 本节详细介绍了如何破解字体反爬虫,由于这种反爬虫是使用CSS进行加载和映射的,所以即使使用一些自动化软件或者渲染工具也无法获得真正的数据。

  2. 这类反爬虫的破解只需要将woff文件中的字体与页面数据之间的对应关系找到,构建好即可。

  3. 找到woff文件进行下载是关键。

特征识别

特征识别反爬虫是利用爬虫程序的一些特殊性,来将其与正常用户的请求进行区分,从而来限制爬虫,但是现在爬虫程序的特性是可以被改变的,我们可以通过将爬虫程序的特性改变成正常用户,从而来绕过这类反爬虫。

Python搭建IP代理池,轻松破解请求频率限制反爬虫

我们所写的爬虫,它对服务器发出的网络请求频率要比正常用户的高的多,从而开发者可以将请求频率过高的用户视为爬虫程序,从而来限制爬虫程序。

原理

因为客户端的IP地址是唯一的,所以开发者便将IP地址作为客户端的身份标识。

服务器可以根据客户端的IP的访问次数来标识记录,从而计算出它的请求频率。然后,对于请求频率过高的客户端进行反爬虫限制。

破解

其实破解请求频率限制反爬虫是十分简单的,因为Requests库中就有一个proxies参数,就是专门为使用IP来准备的,具体使用方法如下:

import requests
proxies = {
 "http": "http://10.10.1.10:3128",
 "https": "https://10.10.1.10:1080",
}
requests.get("http://example.org", proxies=proxies)

搭建IP代理池

搭建一个IP代理池分为三个模块,分别是爬取模块、检测模块、存储模块。下面让我们来看看这三个模块要怎么写吧。

1. 爬取模块

我们此次是在百度上搜索的一个免费的IP代理网站对其代理IP进行爬取。

我们打开开发者模式,然后输入对网页进行观察,我们发现数据存储在源网页中。

既然我们已经发现数据的存储位置和存储形式了,那么就可以发起请求,提取数据了,代码如下:

import requests
import re
headers = {
   'Connection': 'keep-alive',
   'sec-ch-ua': '"Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"',
   'sec-ch-ua-mobile': '?0',
   'Upgrade-Insecure-Requests': '1',
   'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36',
   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
   'Sec-Fetch-Site': 'same-origin',
   'Sec-Fetch-Mode': 'navigate',
   'Sec-Fetch-User': '?1',
   'Sec-Fetch-Dest': 'document',
   'Referer': 'https://www.kuaidaili.com/free/inha/1/',
   'Accept-Language': 'zh-CN,zh;q=0.9',
}
for page in range(1,50):
   response = requests.get(f'https://www.kuaidaili.com/free/inha/{page}/', headers=headers, cookies=cookies)
   ip_list = re.findall('data-title="IP">(.*?)</td>',response.text)

2. 检测模块

因为我们是爬取的免费的IP,所以我们要对其进行检测,看看是否失效了,毕竟便宜没好货,好货不便宜么~ 检测代码如下:

list = []
for ip in ip_list:
   try:
       response = requests.get('https://www.baidu.com', proxies=ip, timeout=2)
       if response.status_code == 200:
           list.append(ip)
   except:
       pass
   else:
       print(ip, '检测通过')

3. 存储模块

我这里是将检测出来可以使用的IP代理存到了csv文件中去,大家也可以尝试使用其他类型的存储,代码如下:

import csv
with open('ip.csv','a',newline='') as f:
   writer = csv.writer(f)
   writer.writerow(list)

小结

  1. 本节详细介绍了如何破解请求频率限制的反爬虫,并教大家搭建一个自己的IP代理池。

  2. 使用代理IP来进行爬虫是当前一种非常流行的方式,因为每个用户端的IP是唯一的,一旦被认为是爬虫给限制或者是封禁了,那么对于用户来说会造成很大的损失。

  3. 免费的IP代理质量不如付费的,如果有大量的需求还是需要购买一下专业的。

WebDriver识别反爬虫的原理和破解方法

有时候我们在爬取动态网页的时候,会借助渲染工具来进行爬取,这个“借助”实际上就是通过使用相应的浏览器驱动(即WebDriver)向浏览器发出命令。

但是有时候使用浏览器驱动来爬取网页时,会遇到这种情况

原理

开发者在开发网页的时候,通过JavaScript设置一个事件,然后让这个事件来调用Navigator对象的webdriver属性,从而来判断客户端是否使用WebDriver驱动浏览器。

如果检测到客户端有webdriver属性,则会返回True,此时反爬虫就会认为这是一个爬虫程序,从而进行限制;如果没有检测到,则会返回False或者Undefined,此时反爬虫不会运行。

检测代码如下:

webdriver = window.navigator.webdriver;
if(webdriver){
    console.log('请不要使用自动化测试工具访问网页') 
} 
else{  
  console.log('正常浏览器')
 }

破解

通过学习上面的WebDriver识别反爬虫原理,我们知道反爬虫机制是根据webdriver属性的返回值来判断是否是爬虫程序访问的。

也就是说,当我们用浏览器驱动来爬取网页时,只要我们能够将它的特征给隐藏,让Navigator对象检测不到浏览器驱动,从而使得webdriver属性返回的值为False或者Undefined,此时就破解WebDriver识别反爬虫了。

上面已经分析出来解决方案了,那我们现在就可以书写代码来实现这个方案了,隐藏浏览器驱动特征的代码如下:

driver = webdriver.Chrome()
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
option.add_experimental_option('useAutomationExtension', False)
option.add_argument(
   'user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36')
option.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=option)
with open('stealth.min.js') as f:
   js = f.read()
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
   'source': js
})

让我们来看看效果:

此时,文章内容就被正确显示出来了,没有再出现被反爬冲限制。

小结

  1. 本节详细介绍了如何破解特征识别反爬虫之WebDriver识别反爬虫。

  2. 现在很多网站对Selenium套件有着明显的限制,所以破解WebDriver识别反爬虫是一个爬虫工程师必备的技能之一。

隐藏链接反爬虫的原理和破解方法

如下图所示,网站将访问详情页的URL放到网页标签中去,所以我们在爬取时,首先要先定位到这些存在URL的标签,然后将URL提取出来,对其发送请求,才能够访问详情页,并提取其数据。

提取这个标签中的URL,相对于我们现在的水平来说,应该是一个十分简单的事情,但是当书写代码来实现时,却显示被反爬了, 重新观察网页,寻找反爬虫的机制,终于在存储URL的标签中发现了问题。

原来,在写爬虫的时候,我们观察并实验了前几个URL后,发现能够正常获取数据,就默认全局都应该这样写,但实际上并不是,这里面存储了隐藏链接用于反爬虫,如下图:

这就是典型的隐藏链接反爬虫,将真假数据混在一起,当访问假数据时,默认为爬虫程序,典型例子是微博评论

原理

由于反爬虫机制是存在标签样式中的,并不存在页面中,所以用户在正常访问时是不会触碰到它的。

但是爬虫程序就不同了,因为它是从网页标签中获取数据的,一旦爬虫工程师没有注意到隐藏在标签列表中的特殊URL,就会导致爬虫程序触发反爬虫机制,从而被反爬限制。

破解

这类反爬虫主要存在于有许多标签列表的网页中。

因为爬虫工程师在分析网页时,通常只会分析一个点,然后由点及面,毕竟即使爬虫就是为了减少工作量,如果全部观察了,那用爬虫也没必要了。

隐藏链接反爬虫主要是通过封禁IP来进行反爬,所以爬虫工程师在爬取时可以加上IP代理池,在节中我们已经详细介绍了搭建IP代理池的方法,有兴趣的读者可以看看。

当我们在爬取标签列表较多或者数据分页较多的网站时,遇到分析的网页可以正常爬取,但是后面的数据无法正常爬取时,我们就要考虑自己是否是遇到了隐藏链接反爬虫了。

此时我们要加大对数据分析的范围,找到隐藏链接和真正链接之间的区别,然后在程序中进一步甄别这两种链接之后,就破解这类反爬虫了~

小结

  1. 本节详细介绍了如何破解隐藏链接反爬虫,这类反爬虫要存在于标签列表较多的网站中,或者网页数据较多的网站
  2. 当我们遇到分析的部分可以正常爬取,但是后面却被反爬,这时候就要考虑是否是否是遇到了隐藏链接反爬虫了。
  3. 隐藏链接反爬虫属于一类非常简单的反爬虫,它主要是利用了爬虫工程师的粗心,只要我们足够细心,那么这类反爬虫对我们来说是轻而易举的。

验证码

验证码反爬虫可谓是现在最为流行的反爬虫手段之一,它的高速发展可谓是爬虫促进的,想要破解这类反爬虫,就一定要学好图像识别技术,利用图像识别,可以很轻松的破解一般的验证码反爬虫。

文字点选验证码的破解方法

基本思路

  1. 获取图像
  2. 对图像进行二值化处理
  3. 识别图中轮廓
  4. 识别轮廓文字
  5. 构建待选文字图像
  6. 比较识别文字与待选文字图像
  7. 返回点选结果

本节仅提供基本思路,具体应用需根据各类型点选验证码自行修改。

实现过程

0. 导入依赖

import base64
import json
import cv2
import numpy as np
import requests
from PIL import Image, ImageDraw, ImageFont

1. 获取图像

第一步是获取图像,有一些验证码图像是以二进制形式返回的,本节测试的图像是以base64编码字符串的形式返回,因此需要对其进行解码。

def getData(capsession: requests.Session):
    resp = s.post("验证码获取url")
    return resp.json()["repData"]

def getImageFromBase64(b64):
    buffer = base64.b64decode(b64)
    nparr = np.frombuffer(buffer, np.uint8)
    image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
    return image

2. 对图像进行二值化处理

为了方便识别文字轮廓,我们对图像进行二值化处理。

def normalizeImage(img):
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    _, img = cv2.threshold(img, 1, 255, cv2.THRESH_BINARY)
    img = cv2.bitwise_not(img)
    return img

3. 识别图中轮廓

在识别图中轮廓时,为了提高效率和准确度,我们对轮廓按长宽比和面积进行筛选,尽可能保证轮廓能够满足文字识别需要。

def findContour(img):
    contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL,
                                   cv2.CHAIN_APPROX_SIMPLE)
    def find_if_close(cnt1, cnt2):
        row1, row2 = cnt1.shape[0], cnt2.shape[0]
        for i in range(row1):
            for j in range(row2):
                dist = np.linalg.norm(cnt1[i] - cnt2[j])
                if abs(dist) < 5:
                    returnTrue
                elif i == row1 - 1and j == row2 - 1:
                    returnFalse
    LENGTH = len(contours)
    status = np.zeros((LENGTH, 1))
    for i, cnt1 in enumerate(contours):
        x = i
        if i != LENGTH - 1:
            for j, cnt2 in enumerate(contours[i + 1:]):
                x = x + 1
                dist = find_if_close(cnt1, cnt2)
                if dist == True:
                    val = min(status[i], status[x])
                    status[x] = status[i] = val
                else:
                    if status[x] == status[i]:
                        status[x] = i + 1
    unified = []
    maximum = int(status.max()) + 1
    for i in range(maximum):
        pos = np.where(status == i)[0]
        if pos.size != 0:
            cont = np.vstack([contours[i] for i in pos])
            hull = cv2.convexHull(cont)
            unified.append(hull)
    cnt = list(filter(aspectRatio,unified))
    cnt.sort(key=cv2.contourArea,reverse=True)
    return cnt[:4]

def aspectRatio(cnt):
    _,_,w,h = cv2.boundingRect(cnt)
    return (0.6<float(w)/h<1.7) and (cv2.contourArea(cnt)>200.0)

4. 识别轮廓文字

我们对图中文字轮廓进行识别,返回文字轮廓与相应的坐标位置。

def extractCharContour(img, contour):
    mult = 1.2
    ret = []
    point = []
    for cnt in contour:
        rect = cv2.minAreaRect(cnt)
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        W = rect[1][0]
        H = rect[1][1]
        Xs = [i[0] for i in box]
        Ys = [i[1] for i in box]
        x1 = min(Xs)
        x2 = max(Xs)
        y1 = min(Ys)
        y2 = max(Ys)
        rotated = False
        angle = rect[2]
        if angle < -45:
            angle += 90
            rotated = True
        center = (int((x1 + x2) / 2), int((y1 + y2) / 2))
        size = (int(mult * (x2 - x1)), int(mult * (y2 - y1)))
        try:
            M = cv2.getRotationMatrix2D((size[0] / 2, size[1] / 2), angle,
                                        1.0)
            cropped = cv2.getRectSubPix(img, size, center)
            cropped = cv2.warpAffine(cropped, M, size)
            croppedW = W ifnot rotated else H
            croppedH = H ifnot rotated else W
            croppedRotated = cv2.getRectSubPix(
                    cropped,
                    (int(croppedW * mult), int(croppedH * mult)),
                    (size[0] / 2, size[1] / 2),
            )
            im = cv2.resize(croppedRotated, (20, 20))
            kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]],
                                  np.float32)
            im = cv2.filter2D(im, -1, kernel=kernel)
            ret.append(im)
            point.append((rect[0][0], rect[0][1]))
        except:
            pass
    return ret, point

5. 构建待选文字图像

将获取的待选文字转化为图像。

def genCharacter(ch, size):
    img = Image.new("L", size, 0)
    font = ImageFont.truetype("simsun.ttc", min(size))
    draw = ImageDraw.Draw(img)
    draw.text((0, 0), ch, font=font, fill=255)
    return np.asarray(img)

6. 比较识别文字与待选文字图像

我们将识别文字轮廓与待选文字图像进行比较,获得对应位置,并根据验证码要求依次添加点选坐标。

def compareCharImage(words, chars, point, word_list) 
    scores = []
    for i, word in enumerate(words):
        for j, char in enumerate(chars):
            scores.append(((i, j), cv2.bitwise_xor(char, word).sum()))
    scores.sort(key=lambda x: x[1])
    word_set = set()
    char_set = set()
    answers = {}
    for score in scores:
        if (score [0][0] notin word_set) and (score [0][1] notin char_set):
            continue
        word_set.add(score[0][0])
        char_set.add(score[0][1])
        answers[word_list[score[0][0]]] = point[score[0][1]]
    return [{
        "x": int(answers[word][0]),
        "y": int(answers[word][1])
    } for word in word_list]

7. 返回点选结果

最后,将得到的点选结果返回给服务器进行验证。

def checkCaptcha(captchaSession: requests.Session, data, point):
    enc = encrypt(json.dumps(point).replace(" ", ""), data["secretKey"])
    resp = captchaSession.post(
        "验证码提交url",
        json={
            "token": data["token"],
            "pointJson": enc,
        },
    )
    return resp.json()

滑动拼图验证码的原理和破解方法

原理

滑动拼图验证码是在滑块验证码的基础上增加了一个随机滑动距离,用户需要将滑块滑到拼图的缺口处,使拼图完整,才能通过校验。如下图所示:

破解

其实破解滑动拼图验证码的原理和滑块验证码的是一样的,就是找到滑动距离,然后让滑块按照该距离进行滑动即可。

但是滑动拼图验证码,它的滑动距离是随机的,所以我们不能像对滑块验证码一样,通过直接观察滑块和滑轨的长度来确定滑动距离。

我们打开开发者模式,对网页进行观察,果然从中找到了一些线索。如下图所示:

从图中可以看出,当我们点击滑块后,拼图和缺角的CSS代码就会展示出来。

并且我们发现,滑块移动的距离就是缺口CSS样式中的left值减去拼图CSS样式中的值。

确定滑动距离

好了,通过上面的分析,我们已经找到了获取滑块滑动距离的思路了,那么现在就是来将思路转化成代码实现即可。

1. 获取点击滑块后的网页代码

因为点击滑块后,缺口和拼图的CSS样式才会显示出来,获取代码如下:

from selenium import webdriver
import time
driver = webdriver.Chrome(r'chromedriver.exe')
driver.get(url)
jigsawCircle = driver.find_element_by_css_selector('#jigsawCircle')
action = webdriver.ActionChains(driver)
action.click_and_hold(jigsawCircle).perform()
time.sleep(2)
html = driver.page_source

2. 提取CSS样式中的left值

采用的用BS4库进行提取,提取代码如下:

from bs4 import BeautifulSoup
import re
soup = BeautifulSoup(html,'lxml')
qidian = soup.find_all(id='missblock')[0]['style']
zhongdian = soup.find_all(id='targetblock')[0]['style']
left_hua = re.findall('left:(.*?)px',qidian)
left_que = re.findall('left:(.*?)px',zhongdian)

3. 计算滑动距离

计算代码如下:

distance = float(left_que[0])-float(left_hua[0])

模拟滑动

因为在计算滑动距离的时候,我们就已经将鼠标定在滑块位置了,那么我们现在只需移动鼠标到缺口处即可完成校验。代码如下:

action.move_by_offset(distance,0)
action.release().perform()

小结

  1. 本节详细介绍了滑动拼图验证码反爬虫的原理和破解方法。

  2. 滑动拼图验证码是现在非常流行的一种验证码反爬虫,所以大家如果想成为一名爬虫工程师,那这绝对是一个必备技能。

  3. 破解滑动拼图验证码的关键是要找到拼图和缺口这两者之间距离的联系,只要找到随机移动的距离,那模拟移动,想必对大家来说都不是什么事~

滑块验证码的原理和破解方法

原理

网站开发者们认为可以从行为方面来区别人类和计算机。例如:人们可以自由的完成拖拽、按下和释放鼠标等操作,但是计算机却不可以。

破解

观察滑块和轨道的长度

我们首先要打开网页的开发者模式,来看一下滑块和轨道的长度分别是多少。

滑块长度

轨道长度

从上面我们知道了滑块的长度是50,轨道的长度是390,也就是说,滑块需要滑动的距离是340。

模拟滑动

在上面我们已经知道滑块要滑动的距离了,那么我们现在就可以开始尝试模拟滑动了。这里我们使用Selenium来完成滑块滑动的工作。

模拟滑动主要分为以下两步:

1. 将鼠标定位到滑块位置

我们打开开发者模式,对滑块进行检查发现,滑块的class属性是hover,所以定位代码如下:

from selenium import webdriver
driver = webdriver.Chrome(r'chromedriver.exe')
url = 'http://www.porters.vip/captcha/sliders.html#'
driver.get(url)
hover = driver.find_element_by_css_selector('.hover')

2. 移动鼠标到轨道终点

Selenium库中有一个ActionChains模块,它可以模拟鼠标按住滑块进行移动,然后进行释放等操作,很符合我们的需求。代码如下:

from selenium import webdriver
import time
move = webdriver.ActionChains(driver)
move.click_and_hold(hover).perform()
time.sleep(1)
move.move_by_offset(340,0)
time.sleep(1)
move.release().perform()

小结

  1. 本节详细介绍了滑块验证码反爬虫的原理和破解方法。

  2. 滑块验证码是现在比较流行的一种验证码反爬虫,所以大家如果想成为一名爬虫工程师,那这是一个必备技能。

  3. 破解滑块验证码主要分两步:一、计算出滑块要滑动的距离;二、让滑块进行移动。

计算型验证码反爬虫的原理和破解方法

原理

计算型验证码其实是一种特殊的字符型验证码,只不过在它的基础上增加了数字运算。

计算型验证码在将人类视觉和计算机视觉的差异作为区分用户和电脑的依据的同时,还加上了逻辑运算,从而来增加机器识别的难度。

破解

以下面这张验证码为例给大家讲一下如何用用图像识别的方式破解字符验证码。

我们换一个新方式来破解验证码。此次使用的是Ddddocr库。这个库的使用非常的便捷,可以通过下面命令进行安装:

pip install ddddocr

它的参数说明

我们现在书写一下代码,来实战一下,看看效果

import ddddocr
ocr = ddddocr.DdddOcr()
with open('6.jpg', 'rb') as f:
   img_bytes = f.read()
res = ocr.classification(img_bytes)
print(res)

成功的将式子识别了出来。然后我们就可以根据式子来进行算数了,代码如下:

if'+'in res:
   zhi =  int(res.split('+')[0])+int(res.split('+')[1][:-1])
   print(zhi)
if'-'in res:
   zhi =  int(res.split('+')[0])-int(res.split('+')[1][:-1])
   print(zhi)
if'*'in res:
   zhi =  int(res.split('+')[0])*int(res.split('+')[1][:-1])
   print(zhi)
if'/'in res:
   zhi =  int(res.split('+')[0])/int(res.split('+')[1][:-1])
   print(zhi)

结果成功的计算了出来。

制作代码

from PIL import Image,ImageDraw,ImageFont
import random
def getRandomColor():
   # 获取一个随机的rgb格式颜色
   r = random.randint(0, 255)
   g = random.randint(0, 255)
   b = random.randint(0, 255)
   return (r,g,b)
def getRandomStr():
   # 获取一个随机字符串,其中每个字符也是随机的
   num_random = str(random.randint(1,50))
   return num_random
text =''
for i in range(3):
   if i != 1:
       k = getRandomStr()+' '
   else:
       k = random.choice(['+','-','*','/'])+' '
   text += k
text = text+' = '
# 获取一个Image对象,参数分别是:RGB格式,宽120,高40,随机颜色
image = Image.new('RGB',(160,40),(255,255,255))
# 获取一个画笔对象,将图片对象传过去
draw = ImageDraw.Draw(image)
# 获取一个font字体对象,参数是ttf的字体文件的目录,以及字体的大小
font = ImageFont.truetype(r'K:\msyh.ttc',size=24)
# 在图片上写东西,参数是定位、字符串、颜色和字体
draw.text((10,10),text,getRandomColor(),font=font)
image.save('6.jpg')

小结

  1. 本节详细介绍了计算型验证码反爬虫的原理和破解方法,并教大家如何做一款自己的计算型验证码。

  2. 因为计算型验证码是一种特殊的字符型验证码,所以Ddddocr库其实也适用于破解字符型验证码。

  3. 相比较于Pytesseract库,Ddddocr库的使用更加便捷且不需要进行环境配置。

字符型验证码反爬虫的原理和破解方法

原理

字符验证码的反爬虫原理很简单,它利用数字、字母、汉字和标点符号等字符做成一张图片,用人类和计算机对这张图片视觉上的差异作为区分用户身份的依据。

随着当前社会识别技术的发展,字符验证码也也在不断的改进,它通过添加干扰线、添加噪点以及增加字符的黏连程度和旋转角度来增加机器识别的难度。

破解

以下面这张验证码为例给大家讲一下如何用用图像识别的方式破解字符验证码。

它有在线和离线两种方式,我们下面来分别进行介绍。

离线破解

我们离线破解验证码主要是使用的PIL库和Pytesseract库,在使用Pytesseract库时,需要先安装Tesseract-OCR。

验证码识别一共分为这四步:

  1. 灰度化

  2. 二值化

  3. 识别

首先使用PIL库将彩色图像转化成灰色的图像,代码如下:

from PIL import Image
im= Image.open('5.jpg')
imgry = im.convert('L')
imgry.save('gray-'+'5.jpg')

灰度化图像:

紧跟着二值化处理,让字符颜色跟背景颜色反差更为明显,代码如下:

threshold = 200
Table = []
for j in range(256):
   if j < threshold:
       Table.append(0)
   else:
       Table.append(1)
out = imgry.point(Table,'1')
out.save('123'+'5.jpg')

二值化图像:

尝试识别一下,代码如下:

import pytesseract
pytesseract.image_to_string(out)

在线识别

其实字符验证码就是一张图片,我们可以借助百度AI来进行在线识别

from aip import AipOcr
APP_ID = '你的APP_ID'
API_KEY = '你的API_KEY'
SECRET_KEY = '你的SECRET_KEY'
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
with open('5.jpg','rb') as f:
   a = f.read()
client.basicGeneral(a)

制作验证码

from PIL import Image,ImageDraw,ImageFont
import random
def getRandomColor():
   r = random.randint(0, 255)
   g = random.randint(0, 255)
   b = random.randint(0, 255)
   return (r,g,b)
def getRandomStr():
   num_random = str(random.randint(1,9))
   random_upper_alpha = chr(random.randint(65,90))
   random_char = random.choice([num_random,random_upper_alpha])
   return random_char
image = Image.new('RGB',(120,40),(255,255,255))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(r'K:\msyh.ttc',size=24)
for i in range(4):
   draw.text((10+i*30,10),getRandomStr(),getRandomColor(),font=font)
width = 120
height = 40
for i in range(5):
   x1 = random.randint(0,width)
   x2 = random.randint(0,width)
   y1 = random.randint(0,height)
   y2 = random.randint(0,height)
   draw.line((x1,x2,y1,y2),fill=getRandomColor())
for i in range(20):
   draw.point([random.randint(0,width),random.randint(0,height)],fill=getRandomColor())
   x = random.randint(0,width)
   y = random.randint(0,height)
   draw.arc((x,y,x+5,y+5),0,90,fill=getRandomColor())
image.save('5.jpg')

小结

  1. 本节详细介绍了字符验证码反爬虫的原理和破解方法。

  2. 字符型验证码是一种比较常规的验证码反爬虫,破解方法较为简单

鼠标轨迹检测反爬虫的原理和破解方法

Selenium库经过WebDriver伪装之后,就可以对大部分的动态加载网页进行网络爬虫爬取,但是最近又出了一种鼠标轨迹检测反爬虫,它对伪装后的Selenium又开始有了限制,那么这时候怎么解决它呢?

原理

用户正常用鼠标进行点击按钮,轨迹如下图,它的轨迹线路是不规则的。

但是,当用户使用Selenium时,它就没有轨迹了,而是直接定位到指定位置进行点击。

找了一个鼠标检测网站来让大家更加直观的进行体会。这个网站分三层,绿色的是我们看到正常访问界面,黄色的是我们鼠标在绿色区域内移动的轨迹,白色的是鼠标移动的坐标。

用户正常访问页面时,页面的鼠标轨迹和坐标变化,如下图:

我们接下来看看用Selenium库进行点击first时,鼠标的轨迹和坐标变化。

通过上面两个图,我们可以更加直观的感受到用Selenium来对网页进行爬取与正常用户浏览的不同,而网站开发者也恰恰是抓住这一点来建立反爬虫机制。

开发者在对网页开发时,对页面加入鼠标运行轨迹检测,如果只有一个点击坐标,没有移动坐标的话,那就默认这是爬虫进行访问网页的,此时要对此访问进行限制。

破解

我们通过上面的原理已经知道了网站的反爬虫机制是通过对鼠标的移动坐标进行检测,来进行反爬的。

那也就是说只要我们能让Selenium爬取网页时,它的移动轨迹不再是直接点击坐标,而是和正常用户一样的,就可以绕过这类反爬虫~

在对Selenium库的文档进行查阅时发现,它有一个move_by_offset()参数,可以控制鼠标进行上下移动,那我们现在来书写代码,来看一下,它是否可以模拟真实鼠标轨迹,代码如下:

from selenium import webdriver
browser = webdriver.Chrome()
browser.get(url)
first = browser.find_element_by_class_name('button1')
action = webdriver.ActionChains(browser)
action.click_and_hold(first).perform()
action.move_by_offset(10, 3)
action.move_by_offset(5, 2)
action.move_by_offset(10, -1)
action.move_by_offset(30, 3)
action.move_by_offset(55, -2)
action.move_by_offset(10, 1)
action.move_by_offset(30, 3)
action.move_by_offset(20, -1)
action.move_by_offset(10, -4)
action.move_by_offset(10, 2)
action.move_by_offset(10, -6)
action.release().perform()

让我们来看看效果:

从轨迹上看,这次的访问和正常的用户访问十分相似。此时,反爬虫已经不能再从轨迹这个地方来进行限制我们了。

小结

  1. 本节详细介绍了鼠标轨迹检测反爬虫的原理和破解它的方法。

  2. 使用这种限制的网站不是主流


文章作者: 杰克成
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 杰克成 !
评论
  目录