最近在研究反反爬,拿腾讯滑块验证下手

以下是官方给的 Demo:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web 前端接入示例</title>
<!-- 验证码程序依赖(必须)。请勿修改以下程序依赖,如使用本地缓存,或通过其他手段规避加载,会导致验证码无法正常更新,对抗能力无法保证,甚至引起误拦截。 -->
<script src="https://ssl.captcha.qq.com/TCaptcha.js"></script>
</head>

<body>
<button id="CaptchaId" type="button">验证</button>
</body>

<script>

// 定义回调函数
function callback(res) {
// 第一个参数传入回调结果,结果如下:
// ret Int 验证结果,0:验证成功。2:用户主动关闭验证码。
// ticket String 验证成功的票据,当且仅当 ret = 0 时 ticket 有值。
// CaptchaAppId String 验证码应用ID。
// bizState Any 自定义透传参数。
// randstr String 本次验证的随机串,后续票据校验时需传递该参数。
console.log('callback:', res);


// res(用户主动关闭验证码)= {ret: 2, ticket: null}
// res(验证成功) = {ret: 0, ticket: "String", randstr: "String"}
// res(请求验证码发生错误,验证码自动返回terror_前缀的容灾票据) = {ret: 0, ticket: "String", randstr: "String", errorCode: Number, errorMessage: "String"}
// 此处代码仅为验证结果的展示示例,真实业务接入,建议基于ticket和errorCode情况做不同的业务处理
// if (res.ret === 0) {
// // 复制结果至剪切板
// var str = '【randstr】->【' + res.randstr + '】 【ticket】->【' + res.ticket + '】';
// var ipt = document.createElement('input');
// ipt.value = str;
// document.body.appendChild(ipt);
// ipt.select();
// document.execCommand("Copy");
// document.body.removeChild(ipt);
// }
}

// 定义验证码js加载错误处理函数
function loadErrorCallback() {
var appid = ''
// 生成容灾票据或自行做其它处理
var ticket = 'terror_1001_' + appid + Math.floor(new Date().getTime() / 1000);
callback({
ret: 0,
randstr: '@' + Math.random().toString(36).substr(2),
ticket: ticket,
errorCode: 1001,
errorMessage: 'jsload_error'
});
}

// 定义验证码触发事件
window.onload = function () {
document.getElementById('CaptchaId').onclick = function () {
try {
// 生成一个验证码对象
// CaptchaAppId:登录验证码控制台,从【验证管理】页面进行查看。如果未创建过验证,请先新建验证。注意:不可使用客户端类型为小程序的CaptchaAppId,会导致数据统计错误。
//callback:定义的回调函数
var captcha = new TencentCaptcha('123', callback, {});
// 调用方法,显示验证码
captcha.show();
} catch (error) {
// 加载异常,调用验证码js加载错误处理函数
loadErrorCallback();
}
}
}
</script>

</html>

官方做了不少限制:机器滑动识别、吻合度、成功拖动时间、IP 限制……

直接上代码:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# -*- coding: utf-8 -*-
"""
@Author: Kang
@Version 1.0
@File: Demo.py
@CreateTime: 2022/9/26 14:10
@Software: PyCharm
@Description: TODO:破解腾讯滑块验证
"""

from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium import webdriver
import cv2
import requests
import random
import time


class TCaptcha:
"""
识别腾讯验证码
"""

def __init__(self, url):
"""
初始化浏览器配置

:param url: 要登录的网站地址
"""
# profile = webdriver.FirefoxOptions() # Firefox 配置无头
# profile.add_argument('-headless')
# 初始化 WebDriver
self.browser = webdriver.Edge()
# WebDriver 实例延时
self.wait = WebDriverWait(self.browser, 20)
# 实例化 url
self.url = url

def tx_code(self):
"""
主要部分,函数入口

:return:
"""

# 等待 iframe
WebDriverWait(self.browser, 20, 0.5).until(
expected_conditions.presence_of_element_located((By.ID, 'tcaptcha_iframe')))
# 加载 iframe
self.browser.switch_to.frame(
self.browser.find_element(By.ID, 'tcaptcha_iframe'))
# 等待 0.5 秒加载完成
time.sleep(0.5)
# 获取图片链接
bk_block = self.browser.find_element(By.ID, 'slideBg').get_attribute('src')
print('正在保存图像……')
# 保存图片,判断是否写入成功
if self.save_img(bk_block):
# 获取缺口位置信息
dex = self.get_pos()
if dex:
# 获取轨迹列表
track_list = self.get_track(dex)
time.sleep(0.5)
# 获取滑块控制选择器
slid_ing = self.browser.find_element(By.ID, 'tcaptcha_drag_thumb')
# 点按滑块控制器
ActionChains(self.browser).click_and_hold(on_element=slid_ing).perform()
time.sleep(0.2)
print('轨迹', track_list)
# 遍历轨迹列表
for track in track_list:
# 移动滑块控制选择器
ActionChains(self.browser).move_by_offset(
xoffset=track, yoffset=0).perform()
time.sleep(1)
# 释放滑块控制器
ActionChains(self.browser).release(
on_element=slid_ing).perform()
time.sleep(1)
# 识别图片
return True
else:
self.start()
else:
print('缺口图片捕获失败')
return False

@staticmethod
def save_img(bk_block):
"""
保存图片

:param bk_block: 图片url
:return: bool类型,是否被保存
"""
try:
# 请求图片 url 内容
img = requests.get(bk_block).content
with open('bg.jpeg', 'wb') as f:
f.write(img)
print('保存成功!')
return True
except:
print('保存失败!')
return False

@staticmethod
def get_pos():
"""
识别缺口,网页上显示的图片为缩放图片,缩放 50% 所以识别坐标需要 0.5

:return: 缺口位置
"""
# 读取图片
image = cv2.imread('bg.jpeg')
# 高斯滤波,(5, 5)表示高斯矩阵的长与宽都是5,标准差取0
blurred = cv2.GaussianBlur(image, (5, 5), 0)
# 边缘检测
canny = cv2.Canny(blurred, 200, 400)
# 轮廓检测
contours, hierarchy = cv2.findContours(
canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 遍历轮廓
for i, contour in enumerate(contours):
m = cv2.moments(contour)
if m['m00'] == 0:
cx = cy = 0
else:
cx, cy = m['m10'] / m['m00'], m['m01'] / m['m00']
# 判断轮廓的面积和周长范围
if 6000 < cv2.contourArea(contour) < 8000 and 370 < cv2.arcLength(contour, True) < 390:
# 判断轮廓的宽
if cx < 400:
continue
x, y, w, h = cv2.boundingRect(contour) # 外接矩形返回四个值,分别是x,y,w,h;x,y是矩阵左上点的坐标,w,h是矩阵的宽和高
# 绘制矩形框做标记,<待处理图片, 左上坐标, 右下坐标, 颜色, 线性宽度[, 线性]>
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
# cv2.imshow('image', image) # 显示识别结果
print('缺口识别: {x}px'.format(x=x / 2))
return x / 2 + 40
return 0

@staticmethod
def get_track(distance):
"""
轨迹方程

:param distance: 距缺口的距离
:return: 位移列表
"""
distance -= 75 # 初始位置
# 初速度
v = 0
# 单位时间为0.3s来统计轨迹,轨迹即0.3内的位移
t = 0.3
# 位移/轨迹列表,列表内的一个元素代表0.2s的位移
tracks = []
# 当前的位移
current = 0
# 到达mid值开始减速
mid = distance * 4 / 5

distance += 10 # 先滑过一点,最后再反着滑动回来
# a = random.randint(1,3)
while current < distance:
if current < mid:
# 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细
# a = random.randint(2, 4) # 加速运动
a = 3
else:
# a = -random.randint(3, 5) # 减速运动
a = -2
# 初速度
v0 = v
# 0.3秒时间内的位移
s = v0 * t + 0.5 * a * (t ** 2)
# 当前的位置
current += s
# 添加到轨迹列表
tracks.append(round(s))

# 速度已经达到v,该速度作为下次的初速度
v = v0 + a * t

# 反着滑动到大概准确位置
for i in range(4):
tracks.append(-random.randint(2, 3))
for i in range(4):
tracks.append(-random.randint(1, 3))
return tracks

def move_to(self, index):
"""
移动滑块

:param index:
:return:
"""
pass

def start(self):
"""
开始,执行打开网页操作

:return: None
"""
# 打开网页
self.browser.get(url=self.url)
# 点击显示验证码
showCaptcha = self.b.find_element(By.ID, 'CaptchaId')
showCaptcha.click()
# 处理验证
self.tx_code()

def end(self):
"""
结束后退出,可选

:return:
"""
self.browser.quit()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding: utf-8 -*-
"""
@Author: Kang
@Version 1.0
@File: Test.py
@CreateTime: 2022/9/26 14:32
@Software: PyCharm
@Description: TODO:测试样例
"""
from Demo import TCaptcha


if __name__ == '__main__':
# url 链接
url = 'file:///C:/Users/47771/Desktop/TCaptcha.html'
# 实例化
tCaptcha = TCaptcha(url)
# 开始
tCaptcha.start()

下一步针对 IP 限制做出解决方案。