Python自动化测试破解极验滑块验证码:Selenium+OpenCV实战
1. 项目概述:当自动化测试遇上滑块验证码
做自动化测试或者爬虫的朋友,对“极验滑块验证码”这个名字一定不陌生。它就像横亘在自动化流程面前的一道智能闸门,核心逻辑就是判断操作者是人还是机器。传统的验证码,比如输入扭曲的字母数字,已经被OCR技术轻松破解。而滑块验证码,尤其是极验这种头部服务商提供的,通过要求用户将一块拼图滑动到缺口处,并全程监测滑动的轨迹、加速度、停顿等行为特征,极大地提高了机器模拟的难度。
那么,我们做自动化测试,如果被测系统集成了极验,难道就只能束手无策,或者永远依赖人工点选吗?显然不是。这个项目的核心目标,就是深入极验滑块验证码的防御逻辑,用Python来模拟出无限接近真实人类的滑动行为,从而让我们的自动化脚本能够顺畅地通过这道验证。这不仅仅是“破解”,更是一种在对抗中深入理解前端交互、行为分析和图像处理技术的绝佳实践。对于测试工程师而言,掌握这套方法,意味着你能对带有强验证的登录、注册等关键流程进行全自动化覆盖,极大提升测试效率和场景深度。对于开发者,理解其原理也有助于设计出更安全的交互验证。
接下来,我将从一个实践者的角度,拆解如何用Python实现这一过程。我们会从环境搭建、缺口识别、轨迹生成、模拟执行到问题排查,一步步构建一个健壮的解决方案。请注意,所有技术讨论均基于学习与研究目的,旨在提升自动化测试能力,请确保你的所有操作符合目标网站的服务条款与法律法规。
2. 核心思路与技术选型解析
面对滑块验证码,一个完整的自动化模拟流程可以拆解为几个关键环节: 环境启动 -> 获取验证码图片 -> 识别缺口位置 -> 生成人类滑动轨迹 -> 驱动滑块执行轨迹 -> 验证结果 。每个环节的技术选型都直接决定了最终的成功率和稳定性。
2.1 为什么选择Selenium作为执行核心?
首先,我们需要一个能驱动真实浏览器、执行JavaScript、并捕捉页面元素和事件的工具。这里, Selenium 是几乎唯一的选择。它支持多种浏览器(Chrome, Firefox, Edge等),提供了完整的WebDriver协议来控制浏览器。相比一些轻量级的HTTP请求库(如requests),Selenium能完美渲染由JavaScript动态生成的验证码组件,并允许我们获取到完整的图片资源和滑块元素。Pyppeteer或Playwright也是现代且强大的替代品,但Selenium的生态更成熟,社区资源(特别是针对各类反检测的解决方案)更丰富,对于这个特定场景更稳妥。
注意:极验等验证码服务会检测WebDriver的特征。直接使用未经修饰的Selenium很容易被识别。因此,我们的第一步往往不是写滑动代码,而是如何“隐藏”Selenium的自动化特征。
2.2 缺口识别:图像处理 vs. AI模型
这是最关键的技术点之一。我们需要从两张图片(一张是完整的背景图,一张是带有缺口和滑块的背景图)中,找到缺口的位置(即滑块需要移动到的水平距离)。
1. 传统图像处理方案(OpenCV) 这是最常用且高效的方案。核心是利用 模板匹配 或 边缘检测 。
- 模板匹配 :将小的滑块拼图块作为模板,在带缺口的背景图中滑动匹配,找到最相似的位置。但极验的图片通常经过混淆(如随机噪点、干扰线条),直接匹配效果可能不佳。
- 边缘检测 :这是更可靠的方法。对两张图片都进行边缘检测(如使用Canny算法),然后进行 图像差分 。因为缺口处的边缘在完整图和缺口图中是不同的,差分后,缺口位置会呈现出一个明显的边缘差异区域。通过寻找这个差异区域轮廓的左侧边界,就能计算出滑动距离。
- 优点 :速度快,不依赖网络,逻辑清晰。
- 缺点 :对于干扰强烈的图片可能需要调整参数,泛化能力稍弱。
2. 深度学习方案 可以使用目标检测模型(如YOLO)直接定位缺口位置。这需要收集数据、标注、训练模型。
- 优点 :泛化能力强,可能应对更复杂的图片变换。
- 缺点 :成本高,需要机器学习知识,部署复杂,运行速度慢。
对于自动化测试场景,追求的是稳定、快速和可维护性。因此, OpenCV边缘检测差分法 是首选。它足以应对绝大多数极验验证码的变体,且整个处理过程在本地毫秒级完成。
2.3 轨迹生成:模拟人性的关键
简单地用 move_to_element_with_offset 将滑块瞬间移动到终点,100%会被识别为机器。极验会监测整个滑动过程的鼠标事件序列,包括每个事件的时间戳、坐标。因此,我们必须生成一条“像人”的移动轨迹。
一条人类滑动轨迹通常包含以下特征:
- 初始加速 :手指按下后,会有一个短暂的加速过程。
- 中间匀速或轻微波动 :主体滑动阶段,速度相对均匀,但会有极微小的、无意识的抖动。
- 末端减速与回拉 :接近目标时,会减速。并且,人类很难一次对准,通常会有小幅度的回拉调整动作。
- 随机停顿 :滑动过程中可能有非常短暂的停顿(毫秒级)。
- 总时间 :通常在1到3秒之间,太短像机器,太长也不自然。
我们将用物理学中的 匀加速和匀减速运动公式 来模拟速度变化,并叠加 随机扰动 来模拟抖动。最终生成一个包含数百个 (x_offset, y_offset, time_interval) 的轨迹点列表。
2.4 执行驱动:ActionChains的精细控制
Selenium的 ActionChains 提供了对鼠标、键盘低级动作的链式编程接口。我们需要用它来分解我们的轨迹:
click_and_hold(slider_element):按住滑块。- 循环轨迹列表,不断调用
move_by_offset(x_delta, y_delta),模拟微小移动。但这里有个陷阱:move_by_offset是相对 当前光标位置 的移动,且ActionChains的perform()是批量执行,无法在中间插入延迟。 - 因此,更精准的做法是:放弃链式操作,使用
driver.execute_script直接注入JavaScript,动态触发鼠标事件(mousedown,mousemove,mouseup),并精确控制每个事件的时间戳。这能绕过Selenium高级API的一些限制,实现更底层的控制。
综合以上,我们的技术栈确定为: Selenium(用于浏览器驱动和环境隐藏) + OpenCV(用于缺口识别) + NumPy(用于轨迹计算) + 原生JavaScript事件注入(用于精确模拟滑动) 。
3. 环境准备与反检测配置
工欲善其事,必先利其器。一个不被识别为自动化的浏览器环境是成功的第一步。
3.1 基础环境搭建
首先,安装必要的Python库:
pip install selenium opencv-python numpy
下载与你Chrome浏览器版本匹配的 ChromeDriver ,并将其所在目录添加到系统PATH,或者将可执行文件放在项目目录下。
3.2 ChromeOptions 高级配置:抹去自动化痕迹
这是至关重要的一步。一个默认的Selenium驱动Chrome,会在 navigator.webdriver 属性上暴露自己。我们需要通过 ChromeOptions 添加一系列实验性参数来隐藏这些特征。
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
def get_stealth_driver():
chrome_options = Options()
# 1. 隐藏“Chrome正受到自动测试软件控制”的提示栏
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)
# 2. 修改navigator.webdriver属性为undefined (核心反检测)
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
# 3. 其他常用伪装参数
chrome_options.add_argument('--disable-gpu') # 某些环境下可增加稳定性
chrome_options.add_argument('--no-sandbox') # Linux环境下可能需要
chrome_options.add_argument('--disable-dev-shm-usage') # 解决共享内存问题
chrome_options.add_argument('--start-maximized') # 最大化窗口,更像真人
# 4. 可以加载用户数据目录,使用真实的浏览器指纹(可选,但更有效)
# chrome_options.add_argument(r"user-data-dir=C:\Path\To\Your\Chrome\User\Data")
driver = webdriver.Chrome(options=chrome_options)
# 5. 执行JavaScript,进一步覆盖可能被检测的属性
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
"""
})
return driver
实操心得 :仅靠
--disable-blink-features=AutomationControlled有时还不够。结合CDP命令(execute_cdp_cmd)在页面加载前注入脚本,直接修改navigator.webdriver属性,是更彻底的方法。此外,使用真实的user-data-dir(一个已登录、有历史记录的Chrome用户配置文件)能极大地提升伪装成功率,因为你的浏览器指纹(插件、字体、屏幕分辨率等)与普通用户无异。
4. 缺口位置识别实战(OpenCV篇)
假设我们已经用Selenium打开了页面,并定位到了验证码区域。极验通常有两张背景图,通过CSS背景定位(background-position)来显示完整图和缺口图。我们需要下载这两张图。
4.1 获取图片资源
import time
import requests
from io import BytesIO
import cv2
import numpy as np
def download_bg_images(driver, bg_full_element, bg_gap_element):
"""
从页面元素中提取背景图URL并下载。
通常,背景图URL藏在元素的style属性中,如 `background-image: url('...')`。
"""
# 获取完整背景图URL
full_style = bg_full_element.get_attribute('style')
full_url = full_style.split('url("')[1].split('")')[0] # 简单提取,实际情况可能更复杂
# 获取带缺口背景图URL
gap_style = bg_gap_element.get_attribute('style')
gap_url = gap_style.split('url("')[1].split('")')[0]
# 下载图片并转换为OpenCV格式
headers = {'User-Agent': 'Mozilla/5.0'} # 模拟浏览器请求头
full_resp = requests.get(full_url, headers=headers)
gap_resp = requests.get(gap_url, headers=headers)
full_img_array = np.asarray(bytearray(full_resp.content), dtype=np.uint8)
gap_img_array = np.asarray(bytearray(gap_resp.content), dtype=np.uint8)
img_full = cv2.imdecode(full_img_array, cv2.IMREAD_COLOR)
img_gap = cv2.imdecode(gap_img_array, cv2.IMREAD_COLOR)
return img_full, img_gap
4.2 使用边缘检测与图像差分定位缺口
这是核心的图像处理函数:
def get_slide_distance(img_full, img_gap):
"""
通过边缘检测和图像差分计算滑块需要移动的距离(像素)。
"""
# 1. 转换为灰度图
gray_full = cv2.cvtColor(img_full, cv2.COLOR_BGR2GRAY)
gray_gap = cv2.cvtColor(img_gap, cv2.COLOR_BGR2GRAY)
# 2. 高斯模糊,降噪
blurred_full = cv2.GaussianBlur(gray_full, (5, 5), 0)
blurred_gap = cv2.GaussianBlur(gray_gap, (5, 5), 0)
# 3. Canny边缘检测
# 阈值需要根据图片情况微调
edges_full = cv2.Canny(blurred_full, 50, 150)
edges_gap = cv2.Canny(blurred_gap, 50, 150)
# 4. 图像差分:找出缺口图中独有的边缘
# 注意:这里用‘缺口图’减‘完整图’,因为缺口处,完整图有边缘而缺口图没有,相减后得到负值,取绝对值。
diff = cv2.absdiff(edges_gap, edges_full)
# 5. 二值化,强化差异区域
_, thresh = cv2.threshold(diff, 20, 255, cv2.THRESH_BINARY)
# 6. 闭运算,填充细小空洞,连接相邻区域
kernel = np.ones((5, 5), np.uint8)
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# 7. 寻找轮廓
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
# 假设最大的轮廓就是缺口区域
max_contour = max(contours, key=cv2.contourArea)
# 获取该轮廓的外接矩形
x, y, w, h = cv2.boundingRect(max_contour)
# 缺口位置通常在矩形左侧,我们返回左侧的x坐标
# 注意:这个距离是缺口左侧边缘在图片中的位置。
# 实际滑块移动距离,可能需要根据页面缩放比例和滑块初始位置进行换算。
return x
else:
raise Exception("未能识别到缺口轮廓")
注意事项 :
cv2.findContours在不同OpenCV版本中返回值个数不同(2个或3个),请根据你的版本调整代码。另外,计算出的x是缺口在 下载的图片 中的像素位置。网页上的验证码图片可能被CSS缩放,因此最终需要换算到网页中的实际像素距离。一个简单的方法是:获取网页中验证码容器的实际宽度,与图片原始宽度的比例,用这个比例乘以计算出的x。
5. 生成拟人化滑动轨迹
有了滑动距离 distance (单位:像素),我们需要生成一条时间-位移轨迹。
import random
import math
def generate_track(distance):
"""
生成滑动轨迹。
返回一个列表,每个元素是 (time_offset, x_offset, y_offset)。
time_offset: 从滑动开始到当前点的累计时间(毫秒)
x_offset: 当前点的水平位移(像素)
y_offset: 当前点的垂直位移(像素,模拟手抖)
"""
track = []
current_x = 0
current_time = 0
# 运动参数
total_time = random.uniform(1800, 2800) # 总时间在1.8~2.8秒
mid = distance * random.uniform(0.6, 0.8) # 减速点设在总距离的60%-80%处
# 加速度和减速度
a1 = random.uniform(1.5, 3.0) # 加速段加速度
a2 = -random.uniform(1.0, 2.5) # 减速段加速度(负值)
v = 0 # 初始速度
t_interval = 10 # 每10毫秒记录一个点
while current_x < distance:
# 判断当前阶段
if current_x < mid:
a = a1 # 加速阶段
else:
a = a2 # 减速阶段
# 计算当前速度 (v = v0 + a*t),这里t_interval单位是毫秒,需转换为秒
v = v + a * (t_interval / 1000)
# 计算位移增量 (s = v0*t + 0.5*a*t^2)
s = v * (t_interval / 1000) * 1000 # 转换为像素,粗略估算
# 添加随机扰动:水平方向微小波动,垂直方向模拟手抖
s += random.uniform(-0.5, 0.5)
y_offset = random.uniform(-1, 1) # 垂直抖动范围
current_x += s
current_time += t_interval
# 防止溢出
if current_x > distance:
current_x = distance
track.append((int(current_time), int(current_x), int(y_offset)))
# 最后,模拟人类的“回拉”调整
if random.random() > 0.3: # 70%的概率有回拉
back_distance = random.uniform(-5, -1) # 回拉1-5像素
back_time = random.randint(30, 80) # 回拉时间30-80毫秒
track.append((int(current_time + back_time), int(current_x + back_distance), 0))
return track
轨迹生成逻辑详解 :
- 分段运动 :将滑动分为加速段和减速段,更符合人体力学。
- 随机参数 :总时间、加速度、减速点都是在一定范围内随机选取,避免每次轨迹雷同。
- 离散化记录 :以10毫秒为间隔,计算每个时间点的位移,形成密集的点序列。
- 添加噪声 :在位移上添加微小随机扰动,在垂直方向添加抖动,模拟肌肉的微小震颤。
- 回拉模拟 :在终点附近增加一个微小的反向移动,这是人类操作的精髓细节之一。
6. 使用JavaScript事件精确模拟滑动
有了轨迹,我们不能直接用Selenium的 ActionChains 去移动,因为它难以精确控制每个点的时间间隔。我们将通过注入JavaScript,直接触发DOM事件。
def drag_slider_with_js(driver, slider_element, track):
"""
使用JavaScript事件模拟滑动。
:param driver: WebDriver实例
:param slider_element: 滑块WebElement
:param track: 轨迹列表,[(t1, x1, y1), (t2, x2, y2), ...]
"""
# 获取滑块在页面中的位置和大小
location = slider_element.location
size = slider_element.size
# 计算滑块中心点坐标
start_x = location['x'] + size['width'] / 2
start_y = location['y'] + size['height'] / 2
# 构建JavaScript代码字符串
# 首先,在滑块上触发mousedown事件
js_down = """
var slider = arguments[0];
var rect = slider.getBoundingClientRect();
var startX = rect.left + rect.width / 2;
var startY = rect.top + rect.height / 2;
var downEvent = new MouseEvent('mousedown', {
clientX: startX,
clientY: startY,
bubbles: true,
cancelable: true
});
slider.dispatchEvent(downEvent);
"""
driver.execute_script(js_down, slider_element)
time.sleep(0.05) # 短暂停顿,模拟按下动作
# 然后,按轨迹依次触发mousemove事件
for t_offset, x_offset, y_offset in track:
# 计算绝对坐标
move_x = start_x + x_offset
move_y = start_y + y_offset # 加入垂直抖动
js_move = """
var moveEvent = new MouseEvent('mousemove', {
clientX: arguments[0],
clientY: arguments[1],
bubbles: true
});
document.dispatchEvent(moveEvent);
"""
driver.execute_script(js_move, move_x, move_y)
# 关键:按照轨迹中计算的时间间隔进行等待
# 由于循环和JS执行本身有时间消耗,这里用一个简化的等待。
# 更精确的做法是记录每个事件的实际触发时间。
time.sleep(t_offset / 1000.0) # 转换为秒
# 最后,触发mouseup事件
js_up = """
var upEvent = new MouseEvent('mouseup', {
bubbles: true,
cancelable: true
});
document.dispatchEvent(upEvent);
"""
driver.execute_script(js_up)
time.sleep(1) # 等待验证结果
核心技巧 :这里没有直接对滑块元素触发
mousemove,而是对document触发。因为在实际的极验监听中,它很可能是在document级别监听鼠标移动事件。同时,事件对象的clientX和clientY属性必须使用 视口坐标 ,所以我们通过getBoundingClientRect()获取初始位置,并在此基础上累加偏移量。
7. 整合与完整流程示例
现在,我们把所有模块串联起来,形成一个完整的自动化测试函数。
def crack_geetest_slider(url):
"""主函数:破解极验滑块验证码流程"""
driver = None
try:
# 1. 获取伪装后的浏览器驱动
driver = get_stealth_driver()
driver.get(url)
time.sleep(2) # 等待页面加载
# 2. 定位相关元素(需要根据目标网站具体结构调整选择器)
# 例如,触发验证码的按钮
# trigger_btn = driver.find_element(By.ID, 'some-button')
# trigger_btn.click()
# time.sleep(1) # 等待验证码弹出
# 假设验证码已显示,定位滑块和背景图元素
slider = driver.find_element(By.CLASS_NAME, 'geetest_slider_button') # 示例类名
bg_full = driver.find_element(By.CLASS_NAME, 'geetest_bg') # 完整背景图元素
bg_gap = driver.find_element(By.CLASS_NAME, 'geetest_slice') # 带缺口背景图元素(可能类名不同)
# 3. 下载并处理图片,获取缺口距离
img_full, img_gap = download_bg_images(driver, bg_full, bg_gap)
# 注意:这里获取的是图片像素距离,可能需要换算
pixel_distance = get_slide_distance(img_full, img_gap)
# 4. 换算为网页实际滑动距离(假设图片原始宽400px,网页显示宽300px)
# scale_factor = 300 / 400
# actual_distance = pixel_distance * scale_factor
# 为简化,假设无需换算
actual_distance = pixel_distance
# 5. 生成拟人化轨迹
track = generate_track(actual_distance)
print(f"生成的轨迹点数:{len(track)}, 总距离:{actual_distance}")
# 6. 执行滑动
drag_slider_with_js(driver, slider, track)
# 7. 验证结果(根据页面变化判断)
time.sleep(2)
# 例如,检查成功提示元素是否出现
# success_element = driver.find_element(By.XPATH, "//div[contains(text(),'验证成功')]")
# if success_element.is_displayed():
# print("滑块验证通过!")
# else:
# print("验证可能失败,需重试或调整参数。")
print("滑动操作执行完毕。")
except Exception as e:
print(f"过程中出现错误:{e}")
# 可以在这里截图保存,便于调试
# driver.save_screenshot('error.png')
finally:
if driver:
driver.quit()
# 使用示例
if __name__ == '__main__':
target_url = "https://你的测试网站登录页" # 请替换为实际地址
crack_geetest_slider(target_url)
8. 常见问题排查与优化技巧实录
在实际操作中,你几乎一定会遇到各种问题。下面是我踩过坑后总结的排查清单和优化点。
8.1 缺口识别不准
- 症状 :计算出的距离偏差很大,导致滑块对不准。
- 排查 :
- 保存图片 :在
download_bg_images函数后,将img_full和img_gap保存到本地,用图片查看器打开,确认是否下载正确。 - 检查图片尺寸 :确保两张图尺寸完全一致。
- 调整OpenCV参数 :重点调整
cv2.Canny()的阈值(threshold1,threshold2)和cv2.threshold()的阈值。可以写一个简单的GUI或循环来测试不同参数下的差分效果。 - 干扰线 :极验的干扰线可能影响边缘检测。可以尝试在边缘检测前,先使用形态学操作(如开运算)去除细小的干扰线。
- 保存图片 :在
- 优化 :采用 多尺度模板匹配+边缘检测投票 机制。用滑块小图在多尺度下进行模板匹配,得到一个可能的位置范围,再用边缘检测在这个小范围内精确定位,提高鲁棒性。
8.2 滑动后被判定为机器
- 症状 :滑块成功滑动到终点,但页面提示“验证失败”或要求重新验证。
- 排查 :
- 轨迹检查 :打印出生成的轨迹,观察速度曲线是否平滑且符合“慢-快-慢”的人体特征。检查是否有速度突变。
- 事件监听 :极验可能监听更底层的事件,如
MouseEvent的detail、screenX等属性。确保你生成的JavaScript事件对象属性尽可能完整和真实。 - 环境检测 :你的Selenium隐藏措施可能被新的检测方法绕过。尝试更新ChromeDriver和浏览器版本,或使用更高级的隐藏工具如
undetected-chromedriver。 - 行为模式 :不要用完全相同的轨迹重复尝试。增加轨迹参数的随机范围,并在每次验证前模拟一些随机鼠标移动(在页面非验证码区域)。
- 优化 :
- 轨迹多样化 :引入更多随机因素,如总时间、加速度曲线(可以用贝塞尔曲线模拟更自然的加速)、停顿点的位置和时长。
- 模拟前置行为 :在点击滑块前,让鼠标在页面上随机移动一小段路径,再移动到滑块上。
- 使用更真实的浏览器环境 :如前所述,加载真实的Chrome用户配置文件(
user-data-dir)是最有效的方法之一。
8.3 页面元素定位失败
- 症状 :
find_element抛出NoSuchElementException。 - 排查 :
- 等待时机 :验证码可能是动态加载的。使用 显式等待 (WebDriverWait)代替
time.sleep。
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait = WebDriverWait(driver, 10) slider = wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_slider_button')))- iframe问题 :极验验证码很可能被包裹在一个
<iframe>里。你需要先切换到正确的iframe中才能定位到里面的元素。
iframe = driver.find_element(By.TAG_NAME, 'iframe') # 可能需要更精确的选择器 driver.switch_to.frame(iframe) # 现在可以定位iframe内的元素了 # 操作完成后,切回主文档 driver.switch_to.default_content()- 类名/ID变化 :网站的类名可能会更新。准备多个可能的选择器,或者使用XPath通过部分文本来定位。
- 等待时机 :验证码可能是动态加载的。使用 显式等待 (WebDriverWait)代替
8.4 性能与稳定性优化
- 重试机制 :任何一步失败(如下载图片失败、识别距离偏差过大、验证失败),都应加入重试逻辑。但要注意,连续失败多次后应停止,避免被封IP。
- 日志记录 :详细记录每个步骤的结果、计算的像素距离、生成的轨迹等。当出现问题时,这些日志是宝贵的调试依据。
- 模块化与配置化 :将关键参数(如OpenCV阈值、轨迹生成参数、等待时间)提取到配置文件或函数参数中,便于针对不同网站进行调整,而无需修改核心代码。
- Headless模式 :在服务器上运行时可能需要无头模式。但请注意,无头模式更容易被检测。如果使用,务必启用
--no-sandbox和--disable-dev-shm-usage参数,并同样应用所有反检测措施。
这个项目就像一场精妙的攻防演练。成功的核心不在于找到某个一劳永逸的“万能代码”,而在于深刻理解验证码设计者的思路(行为特征分析),并在此基础上,用更细致、更拟真的方式去“表演”人类行为。每一次失败和调整,都会让你对Web自动化、图像处理和反反检测有更深的认识。在实际的自动化测试项目中,这套方法能帮你打通那些带有强验证码的关键业务流程,让全流程自动化成为可能。当然,也要时刻牢记,技术是把双刃剑,务必在合法合规和尊重对方服务器资源的前提下使用这些知识。
更多推荐

所有评论(0)