当前位置: 首页 > news >正文

Python Selenium模拟登录带验证码网站的实战攻防指南

1. 这不是“自动化登录”而是和网站对抗的实时攻防战很多人第一次看到“用Selenium模拟登录带验证码的网站”时下意识觉得不就是填个账号密码、点个登录按钮吗写几行代码的事。我当年也是这么想的——直到在某电商后台系统上卡了整整三天反复提交表单却始终被拦在登录页外控制台里滚动着一串又一串ElementNotInteractableException和TimeoutException而页面右下角那个小小的滑块验证码像在无声嘲笑我的天真。真相是现代带验证码的登录流程本质是一场浏览器环境与反爬策略之间的实时博弈。它早已不是简单的HTTP请求模拟而是涉及JavaScript执行上下文、DOM动态渲染、Canvas像素级行为采集、鼠标轨迹拟真、时间戳指纹、WebGL渲染特征、甚至本地字体枚举等多维度对抗。Selenium WebDriver之所以还能站在这条战线上不是因为它“能驱动浏览器”而是因为它唯一能复现真实用户操作链路的可控入口——从鼠标移动加速度到键盘输入间隔从页面加载时机到元素可见性判断每一步都在和前端埋点做精度拉锯。你真正要解决的从来不是“怎么点登录按钮”而是“如何让这段代码在目标网站眼里看起来就是一个活生生的人在凌晨两点、用一台刚重启过的Windows笔记本、带着咖啡渍未干的指纹完成了这次登录”。关键词就藏在标题里Python、Selenium、WebDriver、模拟登录、验证码——这五个词共同框定了一个极其具体的技术切口不是泛泛而谈的“爬虫”不是纯接口调用而是基于真实浏览器实例的、带交互行为的、需绕过人机识别的端到端登录闭环。这篇文章适合三类人一是刚学完Selenium基础、正准备实战却被验证码卡住的初学者二是已能处理简单登录但面对极验、腾讯云、阿里云盾等主流验证码平台频频失败的中级开发者三是需要将登录流程嵌入定时任务或数据采集流水线、对稳定性有硬性要求的工程实践者。我会跳过pip install selenium这种入门步骤直接切入你在真实项目中一定会遇到、文档里却从不提、Stack Overflow答案永远模棱两可的核心战场验证码类型识别逻辑、WebDriver环境指纹净化、滑块轨迹生成算法、OCR失败后的降级策略、以及最关键的——为什么你写的“完美代码”在本地跑通一上服务器就秒变403。这不是教程是我在过去三年里为6个不同行业客户教育SaaS后台、政务服务平台、金融风控系统、跨境电商ERP、医疗预约系统、本地生活聚合平台落地登录自动化时从27次失败重试、147份报错日志、3个被封IP段中熬出来的实操手册。现在我们从第一行真正有用的代码开始。2. 验证码不是“障碍”而是网站发给你的“能力测试卷”绝大多数人把验证码当成一个待“破解”的黑盒拼命搜索“极验识别API”“滑块自动识别开源库”却忽略了最根本的前提网站设计验证码首要目的不是阻止所有自动化而是区分“低质量脚本”和“高仿真行为”。它像一场考试题目分三类——选择题点选文字/图标、填空题OCR识别数字字母、操作题滑动拼图/轨迹验证。你得先看懂考卷结构才能决定是背答案、抄作业还是自己解题。2.1 三类主流验证码的底层机制与识别成本谱系验证码类型典型代表前端实现核心识别难度1-5推荐应对策略关键风险点文字点选型百度UAE、网易易盾Canvas绘制扭曲文字随机干扰线背景噪声3本地OCRPaddleOCR坐标映射校准Canvas像素抗锯齿导致OCR误识文字位置动态偏移滑块拼图型极验Geetest v3/v4、腾讯云TCAPTCHASVG/Canvas生成缺口图滑块图前端轨迹采集JS4行为轨迹生成非匀速加速度微调缺口定位模板匹配边缘检测滑动距离计算误差3px即触发失败鼠标抬起时机被JS监听行为验证型阿里云盾、Cloudflare Turnstile全页面行为采集滚动、悬停、点击热区、鼠标移动路径5环境指纹净化真实行为模拟非脚本式点击仅靠Selenium默认配置必失败需注入自定义JS覆盖检测逻辑提示别迷信“打码平台”。我实测过5家商用打码服务对极验v4的识别成功率在68%-82%之间波动且平均响应延迟达2.3秒——这意味着你的登录流程从2秒变成4.3秒而超时重试会显著增加被风控概率。真正的工程化方案永远是“能本地解决的绝不外包能规则判断的绝不依赖AI”。2.2 如何30秒内精准识别目标网站验证码类型别打开开发者工具盲翻Network标签页。直接在Console执行这段诊断脚本// 复制粘贴到目标网站登录页的浏览器控制台回车 (() { const checkGeetest () { if (window.geetest || document.querySelector(div[data-typegeetest])) return 极验Geetest; if (window.TencentCaptcha) return 腾讯云TCAPTCHA; if (window.AliyunCaptcha) return 阿里云盾; return null; }; const checkCanvas () { const canvases document.querySelectorAll(canvas); for (let c of canvases) { const ctx c.getContext(2d); if (ctx ctx.getImageData) { const data ctx.getImageData(0,0,1,1).data; if (data.length 4) return Canvas渲染型点选/拼图; } } return null; }; const result { geetest: checkGeetest(), canvas: checkCanvas(), forms: document.querySelectorAll(form).length, inputs: document.querySelectorAll(input[typetext], input[typepassword]).length }; console.table(result); return result; })();这段代码会输出结构化诊断结果。我拿它扫过83个含登录页的网站准确率96.4%。关键洞察在于验证码类型决定了你整个技术栈的选型边界。比如发现是极验v4你就必须放弃pytesseract转而研究cv2.matchTemplate在HSV色彩空间下的缺口定位发现是腾讯云就得立刻检查是否启用了--disable-blink-featuresAutomationControlled这个致命参数——它会让腾讯云JS直接返回{success:false, reason:browser_automation}。2.3 为什么你写的“滑块自动拖动”永远失败根源在时间精度陷阱这是最常被忽略的致命细节。看这段看似完美的滑块拖动代码# ❌ 危险示范匀速直线拖动 action ActionChains(driver) slider driver.find_element(By.CLASS_NAME, geetest_slider_button) action.click_and_hold(slider).move_by_offset(260, 0).release().perform()它失败的根本原因不是距离算错而是时间维度完全失真。真实人类拖动滑块的典型行为是0~0.2秒手指按压确认位移≈00.2~0.5秒加速启动加速度约120px/s²0.5~0.8秒匀速滑动速度约300px/s0.8~1.1秒减速制动减速度约-80px/s²1.1~1.3秒微调校准位移±5px抖动而Selenium默认的move_by_offset是毫秒级原子操作整个过程在10ms内完成相当于“瞬移”。网站前端JS通过performance.now()采集鼠标事件时间戳一眼就能识别出这是机器行为。我在某政务平台日志中抓到过明确证据当event.timeStamp序列出现连续3个间隔5ms的事件后端直接返回{code:401,msg:behavior_unreal}。注意不要用time.sleep()硬等这只会让时间戳更假。正确解法是使用ActionChains的pause()方法插入毫秒级停顿并配合贝塞尔曲线生成非线性位移序列——这部分我会在第4章给出可直接复用的轨迹生成器。3. WebDriver不是“浏览器”而是需要深度手术的“仿生躯体”很多开发者以为装好ChromeDriver、启动webdriver.Chrome()就万事大吉。实际上未经处理的WebDriver实例自带一套暴露身份的“数字胎记”。这些胎记被前端反爬JS扫描后会生成独一无二的设备指纹成为验证码系统优先拦截的目标。你不是在调试登录逻辑而是在给一个机器人做整容手术。3.1 必须清除的5大WebDriver原生指纹指纹位置检测方式前端JS清除方案验证效果navigator.webdriverif(navigator.webdriver) block()启动参数--disable-blink-featuresAutomationControlled JS注入覆盖从true变为undefinedwindow.chromeif(window.chrome) block()启动参数--disable-dev-shm-usage 删除window.chrome属性window.chrome返回undefinedpermissions.querynavigator.permissions.query({name:notifications}).then(...)启动参数--disable-notifications返回{state:denied}而非{state:prompt}webgl.vendorgl.getParameter(gl.VENDOR)启动参数--use-glswiftshader从Google Inc.变为ANGLE (SwiftShader Direct3D11)fonts.enumeratedocument.fonts.check(12px Arial)启动参数--font-render-hintingnone字体列表从127个缩减至32个符合真实低端设备这些参数不是随便堆砌的。我做过对照实验只加--disable-blink-features验证码通过率从12%升至41%再加--use-glswiftshader升至67%五项全开后稳定在89%。关键在于顺序和组合——比如--disable-blink-features必须放在参数列表最前否则会被后续参数覆盖。3.2 如何验证你的WebDriver指纹已“洗白”别信网上那些零散的检测网站。用这段自检脚本它会模拟目标网站最常用的12种检测手段def verify_webdriver_fingerprint(driver): 执行前端指纹检测并返回详细报告 checks { navigator.webdriver: return navigator.webdriver, window.chrome: return window.chrome, permissions.notifications: return navigator.permissions.query({name:notifications}).then(rr.state).catch(()null), webgl.vendor: const canvas document.createElement(canvas); const gl canvas.getContext(webgl) || canvas.getContext(experimental-webgl); return gl ? gl.getParameter(gl.VENDOR) : no-webgl; , mediaDevices.enumerate: return navigator.mediaDevices.enumerateDevices().then(dd.length).catch(()0), plugins.length: return navigator.plugins.length, mimeTypes.length: return navigator.mimeTypes.length, userAgent: return navigator.userAgent, platform: return navigator.platform, hardwareConcurrency: return navigator.hardwareConcurrency, deviceMemory: return navigator.deviceMemory, maxTouchPoints: return navigator.maxTouchPoints } report {} for name, script in checks.items(): try: result driver.execute_script(script) report[name] str(result)[:50] # 截断长字符串 except Exception as e: report[name] fERROR: {str(e)[:30]} # 输出为表格便于比对 print(\n{*60}) print(WebDriver指纹自检报告) print(f{*60}) for k, v in report.items(): status ✅ if ERROR not in v and len(v) 40 else ❌ print(f{status} {k:25} → {v}) print(f{*60}) return report # 调用示例 driver get_cleaned_webdriver() # 你的初始化函数 verify_webdriver_fingerprint(driver)运行后你会看到类似这样的输出 WebDriver指纹自检报告 ✅ navigator.webdriver → None ✅ window.chrome → None ✅ permissions.notifications → denied ✅ webgl.vendor → ANGLE (SwiftShader Direct3D11) ✅ mediaDevices.enumerate → 0 ✅ plugins.length → 0 ✅ mimeTypes.length → 0 ✅ userAgent → Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36... ✅ platform → Win32 ✅ hardwareConcurrency → 8 ✅ deviceMemory → 8 ✅ maxTouchPoints → 0 提示maxTouchPoints: 0是重要信号真实触摸屏设备该值≥1但绝大多数服务器环境无触摸硬件设为0反而更真实。我见过因强行设为1导致被识别为“虚拟机触摸屏”的离谱案例。3.3 为什么“无头模式”在验证码场景下是自杀行为几乎所有教程都教你加--headlessnew提升效率。但在验证码领域这是重大误区。原因有三Canvas渲染失效无头模式下Chrome无法调用GPUCanvas内容渲染为纯色块导致OCR和图像匹配完全失效鼠标事件缺失MouseEvent.movementX/Y在无头模式下恒为0而极验v4正是通过此属性检测鼠标是否“悬浮于滑块上方”字体渲染差异无头模式使用系统默认字体与GUI模式的ClearType渲染效果不同影响文字点选型验证码的字符分割。实测数据同一套代码GUI模式验证码通过率89%无头模式降至23%。解决方案不是放弃无头而是用Xvfb虚拟帧缓冲在Linux服务器上模拟GUI环境# Ubuntu服务器上安装Xvfb sudo apt-get install xvfb # 启动虚拟显示:99端口 Xvfb :99 -screen 0 1920x1080x24 # 在Python中指定display import os os.environ[DISPLAY] :99 # 启动WebDriver此时仍为GUI模式但无物理屏幕 options webdriver.ChromeOptions() options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) driver webdriver.Chrome(optionsoptions)这套方案让服务器端也能享受GUI模式的完整渲染能力通过率回归87%。代价是内存占用增加120MB但换来的是稳定性和可维护性——值得。4. 验证码攻防的终极武器行为轨迹生成器与降级熔断机制当你搞定环境指纹下一步就是直面验证码核心——行为模拟。这里没有银弹只有精密的工程妥协用数学建模逼近人类行为用多层熔断保障流程不死。我把它拆解为三个不可分割的模块缺口定位、轨迹生成、失败熔断。4.1 滑块缺口定位抛弃OpenCV拥抱HSV色彩空间鲁棒匹配网上90%的教程教你在RGB空间用cv2.matchTemplate找缺口结果在光照变化、压缩失真、抗锯齿下频繁失效。真实项目中我全部切换到HSV色彩空间因为H色调对亮度变化不敏感能稳定识别“缺口区域”的灰度特征S饱和度可过滤掉背景噪声验证码背景通常S值极低V明度提供边缘强度信息增强轮廓提取鲁棒性。以下是经过23个网站实测的缺口定位核心代码import cv2 import numpy as np from PIL import Image def locate_gap_position(slider_img_path, bg_img_path): 在HSV色彩空间精确定位滑块缺口X坐标 slider_img_path: 滑块图片路径PNG透明背景 bg_img_path: 背景图片路径JPG带干扰线 返回: 缺口中心X坐标像素值 # 读取图片并转换为HSV bg cv2.imread(bg_img_path) slider cv2.imread(slider_img_path, cv2.IMREAD_UNCHANGED) # 提取滑块不透明区域Alpha通道 if slider.shape[2] 4: alpha slider[:,:,3] slider_rgb slider[:,:,:3] # 二值化Alpha通道获取滑块轮廓 _, mask cv2.threshold(alpha, 127, 255, cv2.THRESH_BINARY) else: slider_rgb slider mask np.ones(slider.shape[:2], dtypenp.uint8) * 255 # 转换为HSV并提取H通道 bg_hsv cv2.cvtColor(bg, cv2.COLOR_BGR2HSV) slider_hsv cv2.cvtColor(slider_rgb, cv2.COLOR_BGR2HSV) # 定义滑块区域的HSV范围适应多数验证码 # H: 0-180, S: 30-255, V: 40-255 —— 过滤掉低饱和度背景 lower_hsv np.array([0, 30, 40]) upper_hsv np.array([180, 255, 255]) # 对背景图应用HSV掩膜 bg_mask cv2.inRange(bg_hsv, lower_hsv, upper_hsv) # 对滑块图应用相同掩膜获取其HSV特征 slider_mask cv2.inRange(slider_hsv, lower_hsv, upper_hsv) # 使用归一化互相关匹配NCORR比TM_CCOEFF更抗形变 result cv2.matchTemplate(bg_mask, slider_mask, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc cv2.minMaxLoc(result) # 验证匹配置信度阈值0.65经实测平衡精度与召回 if max_val 0.65: raise ValueError(f缺口匹配失败置信度{max_val:.3f}低于阈值0.65) # 返回缺口中心X坐标考虑滑块宽度的一半偏移 slider_w slider_mask.shape[1] gap_x max_loc[0] slider_w // 2 return int(gap_x) # 调用示例 try: gap_x locate_gap_position(slider.png, background.jpg) print(f检测到缺口位置X{gap_x}px) except ValueError as e: print(f定位失败{e})这段代码的关键创新点在于用HSV掩膜替代RGB阈值用NCORR匹配替代CCOEFF用置信度阈值替代固定坐标。在某教育平台实测中传统RGB匹配失败率38%HSV方案降至4.2%。4.2 行为轨迹生成器贝塞尔曲线驱动的非线性位移引擎这才是让验证码系统“相信你是人”的灵魂模块。我们不用匀速直线而用三次贝塞尔曲线生成符合人体运动学的位移序列import math import random class HumanTrajectory: def __init__(self, start_x, end_x, duration_ms1200): self.start_x start_x self.end_x end_x self.duration_ms duration_ms # 控制点模拟人类加速-匀速-减速过程 self.p0 (start_x, 0) # 起点 self.p1 (start_x (end_x-start_x)*0.2, 0.3) # 加速控制点 self.p2 (end_x - (end_x-start_x)*0.2, 0.7) # 减速控制点 self.p3 (end_x, 1) # 终点 def bezier_point(self, t): 计算贝塞尔曲线上t时刻的点t∈[0,1] u 1 - t tt t*t uu u*u uuu uu * u ttt tt * t p [ uuu * self.p0[0] 3 * uu * t * self.p1[0] 3 * u * tt * self.p2[0] ttt * self.p3[0], uuu * self.p0[1] 3 * uu * t * self.p1[1] 3 * u * tt * self.p2[1] ttt * self.p3[1] ] return p def generate_trajectory(self, step_ms50): 生成位移轨迹点列表每step_ms毫秒一个点 points [] total_steps self.duration_ms // step_ms for i in range(total_steps 1): t i / total_steps point self.bezier_point(t) # 添加微小随机抖动±2px模拟手部不稳定性 jitter_x random.randint(-2, 2) jitter_y random.randint(-1, 1) points.append({ x: int(point[0]) jitter_x, y: int(point[1] * 20) jitter_y, # Y轴微调滑块垂直方向 t: i * step_ms }) return points # 生成从X100到X360的轨迹260px缺口 trajectory HumanTrajectory(100, 360, duration_ms1100) points trajectory.generate_trajectory(step_ms40) print(f生成{len(points)}个轨迹点总耗时{points[-1][t]}ms)生成的轨迹点可直接喂给Seleniumdef move_slider_humanlike(driver, slider_element, trajectory_points): 按人类轨迹拖动滑块 action ActionChains(driver) # 按下鼠标左键 action.click_and_hold(slider_element).perform() # 逐点移动注意必须用move_to_element_with_offset模拟相对移动 for i, point in enumerate(trajectory_points): if i 0: # 第一点移动到起始位置 action.move_to_element_with_offset(slider_element, point[x]-100, 0) else: # 后续点相对上一点的偏移 dx point[x] - trajectory_points[i-1][x] dy point[y] - trajectory_points[i-1][y] action.move_by_offset(dx, dy) # 插入毫秒级暂停关键 action.pause(0.04) # 40ms对应step_ms # 最终释放 action.release().perform() # 调用 move_slider_humanlike(driver, slider_elem, points)注意action.pause(0.04)是成败关键。小于0.02s会被识别为机器大于0.06s会导致超时。这个0.04s是我在17个网站上反复压测得出的黄金值。4.3 熔断降级机制当验证码失败时让系统优雅地“装死”再完美的方案也有失败时刻。此时硬刚只会触发风控。我的经验是建立三层熔断L1熔断单次失败捕获ElementClickInterceptedException立即刷新页面重试最多3次L2熔断连续失败3次失败后启动“人工介入模式”——截图保存、发送邮件告警、暂停任务L3熔断全局失败24小时内失败超15次自动切换备用账号或通知运维。以下是生产环境使用的熔断控制器import time import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart class CaptchaCircuitBreaker: def __init__(self, max_failures15, window_hours24): self.failures [] self.max_failures max_failures self.window_seconds window_hours * 3600 def record_failure(self, site_name, error_msg): 记录一次失败事件 now time.time() # 清理过期记录 self.failures [f for f in self.failures if now - f[time] self.window_seconds] self.failures.append({ time: now, site: site_name, error: error_msg[:100] }) print(f[熔断器] {site_name}失败记录{error_msg[:50]}...) def should_break(self): 判断是否触发L3熔断 return len(self.failures) self.max_failures def send_alert(self, driver, site_name): 发送告警邮件并保存现场 # 截图存档 timestamp int(time.time()) screenshot_path f/var/log/captcha_failures/{site_name}_{timestamp}.png driver.save_screenshot(screenshot_path) # 发送邮件 msg MIMEMultipart() msg[Subject] f 验证码熔断告警{site_name} msg[From] alertyourdomain.com msg[To] opsyourdomain.com body f 验证码系统触发L3熔断 - 站点{site_name} - 当前失败次数{len(self.failures)} - 最近失败{self.failures[-1][error]} - 现场截图{screenshot_path} - 建议操作检查账号状态/更换IP/人工介入 msg.attach(MIMEText(body, plain)) try: server smtplib.SMTP(localhost) server.send_message(msg) server.quit() print(f[告警] 已发送熔断邮件至运维团队) except Exception as e: print(f[告警失败] {e}) # 全局熔断器实例 breaker CaptchaCircuitBreaker(max_failures15) # 在登录主流程中调用 def login_with_captcha(driver, username, password, site_name): try: # ... 执行登录逻辑 ... if not solve_captcha(driver): raise Exception(验证码识别失败) return True except Exception as e: breaker.record_failure(site_name, str(e)) if breaker.should_break(): breaker.send_alert(driver, site_name) raise RuntimeError(f熔断触发{site_name}连续失败{len(breaker.failures)}次) else: print(f尝试重试...剩余重试次数{3-len(breaker.failures)%3}) time.sleep(random.uniform(2, 5)) # 随机退避 return login_with_captcha(driver, username, password, site_name)这套机制让我们的登录服务在遭遇大规模验证码升级时从未发生过雪崩式故障。运维同事反馈“以前半夜报警电话响个不停现在每月就1-2次还能从容喝杯咖啡再处理”。5. 工程化落地从脚本到服务的四步封装写完能跑通的脚本只是起点。在真实业务中你需要把它变成可监控、可配置、可灰度、可审计的服务。我总结出四步封装法已在多个客户生产环境稳定运行超18个月。5.1 步骤一配置驱动化——把魔法数字变成YAML拒绝硬编码账号密码和XPath。创建config.yaml# config.yaml sites: - name: edu_platform url: https://admin.edu-system.com/login credentials: username: adminclient.com password: ENC(Q2F0Y2hQYXNzd29yZA) # AES加密 selectors: username_input: #username password_input: #password login_button: button[typesubmit] captcha_container: .geetest_panel slider_element: .geetest_slider_button timeout: 30 retry: 3 - name: gov_portal url: https://portal.gov-service.cn/auth credentials: username: serviceagency.gov password: ENC(RGVmYXVsdFBhc3N3b3Jk) selectors: username_input: input[nameaccount] password_input: input[namepassword] login_button: #login-btn captcha_container: #tcaptcha slider_element: .tcaptcha-slider timeout: 45 retry: 5 # 全局WebDriver配置 webdriver: headless: false chrome_options: - --disable-blink-featuresAutomationControlled - --use-glswiftshader - --disable-dev-shm-usage download_dir: /tmp/downloads # 熔断策略 circuit_breaker: max_failures: 10 window_hours: 12解析配置的代码需支持密码解密import base64 from cryptography.fernet import Fernet def decrypt_password(encrypted: str) - str: 解密配置中的密码需预置KEY key byour-32-byte-key-here-123456789012 # 生产环境应从KMS获取 f Fernet(key) encrypted_bytes base64.b64decode(encrypted.replace(ENC(, ).replace(), )) return f.decrypt(encrypted_bytes).decode() # 使用示例 config load_yaml_config(config.yaml) for site in config[sites]: plain_pass decrypt_password(site[credentials][password]) print(f站点{site[name]}密码长度{len(plain_pass)})5.2 步骤二模块化分层——分离关注点构建清晰的三层架构Driver层webdriver_manager.py—— 封装WebDriver生命周期、指纹清洗、异常重连Page层pages/edu_platform.py—— 封装特定网站的页面对象模型POM包含login()、solve_captcha()等方法Service层services/login_service.py—— 编排流程、调用熔断器、记录审计日志。以pages/edu_platform.py为例from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class EduPlatformLoginPage: def __init__(self, driver, config): self.driver driver self.config config self.wait WebDriverWait(driver, config[timeout]) def open_login_page(self): self.driver.get(self.config[url]) # 等待关键元素出现 self.wait.until(EC.presence_of_element_located( (By.CSS_SELECTOR, self.config[selectors][username_input]) )) def enter_credentials(self, username, password): user_input self.wait.until(EC.element_to_be_clickable( (By.CSS_SELECTOR, self.config[selectors][username_input]) )) user_input.clear() user_input.send_keys(username) pass_input self.driver.find_element( By.CSS_SELECTOR, self.config[selectors][password_input] ) pass_input.clear() pass_input.send_keys(password) def click_login_button(self): login_btn self.wait.until(EC.element_to_be_clickable( (By.CSS_SELECTOR, self.config[selectors][login_button]) )) login_btn.click() def solve_captcha(self): 统一调用验证码解决方案 from captcha.solvers import GeetestSolver solver GeetestSolver(self.driver, self.config) return solver.solve() def login(self, username, password): self.open_login_page() self.enter_credentials(username, password) self.click_login_button() return self.solve_captcha()5.3 步骤三可观测性注入——让每一次失败都可追溯在关键节点埋点输出结构化日志import logging import json from datetime import datetime # 配置JSON格式日志 logging.basicConfig( levellogging.INFO, format%(asctime)s %(levelname)s %(message)s, handlers[ logging.FileHandler(/var/log/login_service.json), logging.StreamHandler() ] ) def log_login_attempt(site_name, status, duration_ms, detailsNone): 记录结构化登录日志 log_entry { timestamp: datetime.utcnow().isoformat(), site: site_name, status: status, # success, captcha_failed, timeout, network_error duration_ms: duration_ms, details: details or {} } logging.info(json.dumps(log_entry)) # 在登录方法中调用 start_time time.time() try: success page.login(username, password) duration int((time.time() - start_time) * 1000) log_login_attempt(edu_platform, success, duration) except Exception as e: duration int((time.time() - start_time) * 1000) log_login_attempt(edu_platform, captcha_failed, duration, {error: str(e)})日志样例{ timestamp: 2023-10-05T08:23:41.123Z, site: edu_platform, status: success, duration_ms:
http://www.gsyq.cn/news/1388513.html

相关文章:

  • 从USB识别到成功联网:在Tina5.0上调试RTL8188FU WiFi驱动的完整流程与实战日志分析
  • ARMv8/v9架构中AArch64与AArch32寄存器映射机制详解
  • Java类型转换运算符
  • parse-skill-to-json
  • 华为突然发表「韬定律」,一个让台积电和ASML都沉默的问题出现了
  • 告别裸奔寄存器:手把手教你用设备树为IMX6ULL开发板编写LED驱动
  • 从按键消抖到实时响应:AT89S52外部中断的两种触发方式实战解析
  • OnlyOffice保存失败根因:JWT签名与X-Frame-Options权限断点解析
  • Jetson Nano/Orin避坑指南:手把手解决Realsense D435i IMU数据丢失和realsense-viewer黑屏问题
  • USB PD 3.1协议消息头详解:手把手教你用逻辑分析仪抓包并解读关键字段
  • DeepSeek LeetCode 2642. 设计可以求最短路径的图类 Java实现
  • 终极百度网盘下载速度破解指南:深度解析真实链接获取技术
  • 【技术判断力:法则一】2、架构必败根源:90%的架构活动,死在“没有唯一正确目标”
  • ARM AArch32内存管理架构与MMU实现详解
  • LVGL移植避坑指南:搞定Keil工程下的文件管理、栈溢出和屏幕撕裂(实测HC32F460)
  • 手把手教你用逻辑分析仪抓取SPI/IIC波形:从时序图到代码调试的完整实战(附Saleae使用教程)
  • 保姆级教程:在Debian 11上搞定PulseAudio 14.2与UCM2音频路由(以RK809/ES8388为例)
  • 2026年亲测有效:3种高效降论文AIGC率的方法 - 降AI实验室
  • JMeter高并发压测脚本设计范式:可伸缩、可观测、可诊断
  • 从零实现五子棋AI:极小化极大算法与Alpha-Beta剪枝实战
  • 低空经济规模化落地前置刚需:产业赛道全景+低空安防技术体系深度解析
  • Claude Code in Cursor:代理式AI编程的可审查实践
  • 一篇看懂Linux下的IIC驱动
  • Tims天好中国股权曝光:腾讯持股12% 2025年净亏4亿 资金流动性趋紧
  • 震坤行第一季营收21亿 2026目标是全年盈利
  • 2026年昭通市本地上门黄金回收门店指南 彩金+铂金+金条+白银回收门店联系方式推荐 - 大熊猫898989
  • 2026年肇庆市本地上门黄金回收门店指南 彩金+铂金+金条+白银回收门店联系方式推荐 - 大熊猫898989
  • 2026 SSH工具怎么选:多台 VPS 管理时,什么类型更省心?
  • 告别被动抢修!AI才是设备运维的正确打开方式
  • 探寻靠谱省煤器锅炉部件生产商,为你的生产节能添助力!