0%

爬虫之破解极验验证(滑动验证码)

概述

说实话,本文代码主要是参考网上资料的。

极验验证的滑动验证码如下:

其实网上有许多破解极验验证的教程,主要分为两种,一种是手动分析各种请求,另一种是直接使用 Selenium 模拟浏览器。前者,破解过程繁琐,开发久,但是运行速度快;后者,开发快,但是运行很慢。。考虑到极验验证一直在更换各种请求 URL,参数等等(毕竟人家靠这个吃饭的啊),手动分析的方法时效性不强。所以,本文主要参考使用 Selenium 破解验证码的方法。

本文所使用的验证码例子如下:

极验验证-验证例子

破解过程如下:

  1. 访问该页面,点击 “滑动行为验证”,点击 “点击按钮进行验证”,使得滑动验证码出现。
  2. 获取验证码图像和带缺口的验证码图像
  3. 比对两张图像,获得滑块位移
  4. 模拟滑块运动,进行验证

本文代码使用的库如下:

1
2
3
4
5
6
7
8
9
from PIL import Image
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait as Wait
from selenium.webdriver.support import expected_conditions as Expect
import new.anti.easing as easing
import time
import cv2


1. 滑动验证码的出现

这个比较简单啦,首先使用 Selenium 访问网页:

1
2
3
4
5
6
7
8
9
10
browser = webdriver.Chrome(
executable_path="e:/chromedriver.exe",
chrome_options=chrome_options
)
browser.get('http://www.geetest.com/type/')

Wait(browser, 60).until(
Expect.visibility_of_element_located((By.CLASS_NAME, "products-content"))
)
time.sleep(1.2)

接着,点击 “滑动行为验证”:

1
2
3
4
5
slide_captcha = browser.find_element_by_xpath('//div[@class="products-content"]/ul/li[2]')
slide_captcha.click()
Wait(browser, 60).until(
Expect.visibility_of_element_located((By.CLASS_NAME, "geetest_radar_tip_content"))
)

然后,点击 “点击按钮进行验证”,就出现验证图片了:

1
2
3
validate = browser.find_element_by_xpath('//span[@class="geetest_radar_tip_content"]')
validate.click()
time.sleep(0.5)


2. 获取验证码图像和带缺口的验证码图像

这里用到 Selenium 的截图功能。Selenium 对元素截图会出错(原因我也不知,网上这样说的。。)。所以,我们要全屏截图,然后裁剪出验证码图像。在这里,我们要截取的元素,是重叠的。对于重叠的元素,我们先执行 JS 脚本,移除部分元素或者使得元素不可见,再截图。获取图像的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def get_pictures(browser):
browser.execute_script('''
var x = document.getElementsByClassName("geetest_canvas_slice geetest_absolute");
x[0].style.display="none";
var y = document.getElementsByClassName("geetest_canvas_fullbg geetest_fade geetest_absolute");
y[0].style.display="none";
''')
time.sleep(0.8)

# 带缺口的验证码图像的全屏截图
browser.save_screenshot('img1.jpg')
# 裁剪验证码图像
crop_picture('img1.jpg')

browser.execute_script('''
var x = document.getElementsByClassName("geetest_canvas_bg geetest_absolute");
x[0].style.display="none";
var y = document.getElementsByClassName("geetest_canvas_fullbg geetest_fade geetest_absolute");
y[0].style.display="block";
''')
time.sleep(0.8)
# 不带缺口的验证码图像的全屏截图
browser.save_screenshot('img2.jpg')
# 裁剪验证码图像
crop_picture('img2.jpg')

# 使元素恢复成可见的
browser.execute_script('''
var x = document.getElementsByClassName("geetest_canvas_bg geetest_absolute");
x[0].style.display="block";
var y = document.getElementsByClassName("geetest_canvas_slice geetest_absolute");
y[0].style.display="block";
''')

其中,裁剪函数如下:

1
2
3
4
5
6
7
8
9
def crop_picture(filename):
left = 830
top = 335
right = left + 260
bottom = top + 160

im = Image.open(filename)
im = im.crop((left, top, right, bottom))
im.save(filename)

260 和 160 是验证码图像的宽度和高度。830 和 335 是根据屏幕的高和宽以及验证码图像的高和宽计算出来的。对于不同的浏览器和不同的显示器,这些数值可能不一样。合理的话,应该使用程序获取这些高度和宽度并计算的。但是我懒得写了。。


3. 比对两张图像,获得滑块位移

这里,是对两张图片逐个像素进行匹配,如果某个像素点不匹配,该像素点就是滑块缺口的左上顶点。滑块的位移是水平方向的,初始滑块左上顶底横坐标大约是 12,所以 offset = j - 12

但是实际上,我们截图的时候,截的验证码位置可能有误差,所以计算位移要考虑到误差的影响。在我这里,设置为 offset = j

1
2
3
4
5
6
7
8
9
10
11
def get_offset(image1, image2, threshold):
offset = -1
img1 = cv2.imread(image1)
img2 = cv2.imread(image2)

for j in range(260):
for i in range(160):
if not match(img1[i][j], img2[i][j], threshold):
offset = j
return offset
return offset

匹配函数如下(注意 imread 函数读到的像素值是 uint8 类型,相减之后可能溢出,所以要转为 int):

1
2
3
4
5
6
def match(i1, i2, t):
p1 = [int(p) for p in i1]
p2 = [int(p) for p in i2]
if abs(p1[0] - p2[0]) < t and abs(p1[1] - p2[1]) < t and abs(p1[2] - p2[2]) < t:
return True
return False

有个问题,就是,为什么我们要设置一个阈值呢?显而易见的原因是,图像之间可能存在少许误差嘛。不过,实际上,这个阈值设置得蛮大的(我设置为 50),这是因为,有些验证码图像加入了一些阴影干扰。

所以,像素之间差距小于阈值的,我们可以认为是跟原图像一样的,或者是阴影干扰项。


4. 模拟滑块运动,进行验证

注意,得到位移后,不能进行简单的匀速拖拽,因为极验验证会识别是否是机器操作的。所以,拖拽的的过程要精心设计。这里,我啥都没干。。哈哈。直接 copy 了网上的代码,成功率还是蛮高的。验证的代码如下:

1
2
3
4
def do_crack(browser, offset):
knob = browser.find_element_by_class_name("geetest_slider_button")
fake_drag(browser, knob, offset)
return

里面调用的函数,可以去参考资料 [2] 那里看


参考资料

[1] (极验滑动验证最新破解实践(18年1月底))[https://zhuanlan.zhihu.com/p/31995134]

[2] (使用 Python + Selenium 破解滑块验证码)[http://www.aneasystone.com/archives/2018/03/python-selenium-geetest-crack.html]