1. 程式人生 > 其它 >Python + selenium 破解極驗滑塊驗證碼模擬人類滑動行為(解決驗證碼被怪物吃掉的問題)

Python + selenium 破解極驗滑塊驗證碼模擬人類滑動行為(解決驗證碼被怪物吃掉的問題)

寫在前面:
其實本程式還有很多需要完善和改進的地方,後面會進行完善,大家多多包涵

概述
通過完整圖片與缺失滑塊的圖片進行畫素對比,確定滑塊位置
邊緣檢測演算法,確定位置
規避檢測,模擬人的行為進行滑動滑塊
實現
-這裡以帶刷網為例,展示驗證碼滑動的效果

#!/usr/bin/env python

-- coding: utf-8 --

@Time : 2021/1/2 18:34

@Author : huni

@File : 驗證碼2.py

@Software: PyCharm

from selenium import webdriver
import time
import base64
from PIL import Image

from io import BytesIO
from selenium.webdriver.support.ui import WebDriverWait
import random
import copy

class VeriImageUtil():

def __init__(self):
    self.defaultConfig = {
        "grayOffset": 20,
        "opaque": 1,
        "minVerticalLineCount": 30
    }
    self.config = copy.deepcopy(self.defaultConfig)

def updateConfig(self, config):
    # temp = copy.deepcopy(config)
    for k in self.config:
        if k in config.keys():
            self.config[k] = config[k]

def getMaxOffset(self, *args):
    # 計算偏移平均值最大的數
    av = sum(args) / len(args)

    maxOffset = 0
    for a in args:
        offset = abs(av - a)
        if offset > maxOffset:
            maxOffset = offset
    return maxOffset

def isGrayPx(self, r, g, b):
    # 是否是灰度畫素點,允許波動offset
    return self.getMaxOffset(r, g, b) < self.config["grayOffset"]

def isDarkStyle(self, r, g, b):
    # 灰暗風格
    return r < 128 and g < 128 and b < 128

def isOpaque(self, px):
    # 不透明
    return px[3] >= 255 * self.config["opaque"]

def getVerticalLineOffsetX(self, bgImage):
    # bgImage = Image.open("./image/bg.png")
    # bgImage.im.mode = 'RGBA'
    bgBytes = bgImage.load()

    x = 0
    while x < bgImage.size[0]:
        y = 0
        # 點》》線,灰度線條數量
        verticalLineCount = 0

        while y < bgImage.size[1]:
            px = bgBytes[x, y]
            r = px[0]
            g = px[1]
            b = px[2]
            # alph = px[3]
            # print(px)
            if self.isDarkStyle(r, g, b) and self.isGrayPx(r, g, b) and self.isOpaque(px):
                verticalLineCount += 1
            else:
                verticalLineCount = 0
                y += 1
                continue

            if verticalLineCount >= self.config["minVerticalLineCount"]:
                # 連續多個畫素都是灰度畫素,直線,認為需要滑動這麼多
                # print(x, y)
                return x

            y += 1

        x += 1
    pass

class DragUtil():
def init(self, driver):
self.driver = driver

def __getRadomPauseScondes(self):
    """
    :return:隨機的拖動暫停時間
    """
    return random.uniform(0.6, 0.9)

def simulateDragX(self, source, targetOffsetX):
    """
    模仿人的拖拽動作:快速沿著X軸拖動(存在誤差),再暫停,然後修正誤差
    防止被檢測為機器人,出現“圖片被怪物吃掉了”等驗證失敗的情況
    :param source:要拖拽的html元素
    :param targetOffsetX: 拖拽目標x軸距離
    :return: None
    """
    action_chains = webdriver.ActionChains(self.driver)
    # 點選,準備拖拽
    action_chains.click_and_hold(source)
    # 拖動次數,二到三次
    dragCount = random.randint(2, 3)
    if dragCount == 2:
        # 總誤差值
        sumOffsetx = random.randint(-15, 15)
        action_chains.move_by_offset(targetOffsetX + sumOffsetx, 0)
        # 暫停一會
        action_chains.pause(self.__getRadomPauseScondes())
        # 修正誤差,防止被檢測為機器人,出現圖片被怪物吃掉了等驗證失敗的情況
        action_chains.move_by_offset(-sumOffsetx, 0)
    elif dragCount == 3:
        # 總誤差值
        sumOffsetx = random.randint(-15, 15)
        action_chains.move_by_offset(targetOffsetX + sumOffsetx, 0)
        # 暫停一會
        action_chains.pause(self.__getRadomPauseScondes())

        # 已修正誤差的和
        fixedOffsetX = 0
        # 第一次修正誤差
        if sumOffsetx < 0:
            offsetx = random.randint(sumOffsetx, 0)
        else:
            offsetx = random.randint(0, sumOffsetx)

        fixedOffsetX = fixedOffsetX + offsetx
        action_chains.move_by_offset(-offsetx, 0)
        action_chains.pause(self.__getRadomPauseScondes())

        # 最後一次修正誤差
        action_chains.move_by_offset(-sumOffsetx + fixedOffsetX, 0)
        action_chains.pause(self.__getRadomPauseScondes())

    else:
        raise Exception("莫不是系統出現了問題?!")

    # 參考action_chains.drag_and_drop_by_offset()
    action_chains.release()
    action_chains.perform()

def simpleSimulateDragX(self, source, targetOffsetX):
    """
    簡單拖拽模仿人的拖拽:快速沿著X軸拖動,直接一步到達正確位置,再暫停一會兒,然後釋放拖拽動作
    B站是依據是否有暫停時間來分辨人機的,這個方法適用。
    :param source:
    :param targetOffsetX:
    :return: None
    """

    action_chains = webdriver.ActionChains(self.driver)
    # 點選,準備拖拽
    action_chains.click_and_hold(source)
    action_chains.pause(0.2)
    action_chains.move_by_offset(targetOffsetX, 0)
    action_chains.pause(0.6)
    action_chains.release()
    action_chains.perform()

def checkVeriImage(driver):
WebDriverWait(driver, 5).until(
lambda driver: driver.find_element_by_css_selector(’.geetest_canvas_bg.geetest_absolute’))
time.sleep(1)
im_info = driver.execute_script(
‘return document.getElementsByClassName(“geetest_canvas_bg geetest_absolute”)[0].toDataURL(“image/png”);’)
# 拿到base64編碼的圖片資訊
im_base64 = im_info.split(’,’)[1]
# 轉為bytes型別
im_bytes = base64.b64decode(im_base64)
with open(’./temp_bg.png’, ‘wb’) as f:
# 儲存圖片到本地
f.write(im_bytes)

image_data = BytesIO(im_bytes)
bgImage = Image.open(image_data)
# 滑塊距離左邊有 5 畫素左右誤差
offsetX = VeriImageUtil().getVerticalLineOffsetX(bgImage)
print("offsetX: {}".format(offsetX))
if not type(offsetX) == int:
    # 計算不出,重新載入
    driver.find_element_by_css_selector(".geetest_refresh_1").click()
    checkVeriImage(driver)
    return
elif offsetX == 0:
    # 計算不出,重新載入
    driver.find_element_by_css_selector(".geetest_refresh_1").click()
    checkVeriImage(driver)
    return
else:
    dragVeriImage(driver, offsetX)

def dragVeriImage(driver, offsetX):
# 可能產生檢測到右邊緣的情況
# 拖拽
eleDrag = driver.find_element_by_css_selector(".geetest_slider_button")
dragUtil = DragUtil(driver)
dragUtil.simulateDragX(eleDrag, offsetX - 10)
time.sleep(2.5)

if isNeedCheckVeriImage(driver):
    checkVeriImage(driver)
    return
dragUtil.simulateDragX(eleDrag, offsetX - 6)

time.sleep(2.5)
if isNeedCheckVeriImage(driver):
    checkVeriImage(driver)
    return
# 滑塊寬度40左右
dragUtil.simulateDragX(eleDrag, offsetX - 56)

time.sleep(2.5)
if isNeedCheckVeriImage(driver):
    checkVeriImage(driver)
    return
dragUtil.simulateDragX(eleDrag, offsetX - 52)

if isNeedCheckVeriImage(driver):
    checkVeriImage(driver)
    return

def isNeedCheckVeriImage(driver):
if driver.find_element_by_css_selector(".geetest_panel_error").is_displayed():
driver.find_element_by_css_selector(".geetest_panel_error_content").click();
return True
return False

def task():
# 此步驟很重要,設定chrome為開發者模式,防止被各大網站識別出來使用了Selenium
# options = webdriver.ChromeOptions()
# options.add_experimental_option(‘excludeSwitches’, [‘enable-automation’])

# driver = webdriver.Firefox(executable_path=r"../../../res/webdriver/geckodriver_x64_0.26.0.exe",options=options)
driver = webdriver.Chrome()

driver.get('https://www.ieqq.net/?cid=222&tid=5584')
time.sleep(3)

# driver.find_element_by_xpath('//*[@id="gt-register-mobile"]/div/div[2]/div[1]/div[2]/div/div[2]/div['
#                              '1]/input').send_keys("17633935269")
# driver.find_element_by_xpath('//*[@id="gt-register-mobile"]/div/div[2]/div[1]/div[2]/div/div[2]/div[2]/div['
#                              '1]/div').click()
# driver.find_element_by_css_selector(".btn.btn-login").click()
# time.sleep(2)

# 搜尋欄標籤定位
search_input = driver.find_element_by_xpath('//*[@id="inputvalue"]')
time.sleep(3)
# 標籤的互動
search_input.send_keys('xxxxxx')
# 執行一組js程式
driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')
time.sleep(2)

# 搜尋按鈕的定位
btn = driver.find_element_by_xpath('//*[@id="submit_buy"]')

# 點選搜尋按鈕
btn.click()

time.sleep(6)
driver.find_element_by_xpath('//*[@id="captcha"]/div[3]/div[3]').click()

time.sleep(3)
checkVeriImage(driver)

pass

該方法用來確認元素是否存在,如果存在返回flag=true,否則返回false

def isElementExist(driver, css):
try:
driver.find_element_by_css_selector(css)
return True
except:
return False

if name == ‘main’:
task()
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
寫在後面
雖然說驗證碼破解是可以一定程度上解決登入爬蟲的問題,
但是識別率也不可能達到百分之百識別,所以建議需要登入
才可以進行下去的爬蟲程式,可以使用cookies模擬登陸,
僅需第一次登陸人工識別登陸驗證碼,或者掃描二維碼,就可以使用一段時間,
當然各有利弊,cookies在一段時間後也會失效,這個和驗證碼都是見仁見智的操作。
1
2
3
4
5
驗證碼詳情可以參考https://blog.csdn.net/weixin_43881394/article/details/108360729

大家如果覺得小編的程式碼有用,可以多多關注小編,
同時小編的公眾號也開通了,大家可以關注下,後續進行粉絲回饋,大家一起學習python叭
在這裡插入圖片描述
打賞小編點這裡哦
在這裡插入圖片描述