news 2026/7/17 5:22:27

Playwright自动化验证码处理:模拟真人交互与工程化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Playwright自动化验证码处理:模拟真人交互与工程化实战

1. 项目概述:为什么是Playwright处理验证码?

在自动化测试或者数据采集的实战中,验证码(CAPTCHA)就像一道横亘在路上的自动门,它存在的核心目的就是区分人类和机器。无论是传统的数字字母验证码、滑块拼图、点选文字,还是更复杂的旋转、推理验证码,其本质都是增加自动化程序的交互成本。过去,我们可能依赖专门的打码平台、OCR服务,或者复杂的图像处理库(如OpenCV)来“硬刚”验证码。但这些方案要么有额外成本,要么开发维护复杂,且随着验证码技术的迭代,识别成功率并不稳定。

这时,Playwright作为一个现代浏览器自动化框架,为我们提供了一条不同的思路:绕过识别,模拟真人。它的核心优势在于能够精准地模拟人类在浏览器中的一切操作——鼠标移动轨迹、点击、拖拽、键盘输入,甚至生成真实的人类行为指纹。对于很多非极端复杂的验证码(尤其是行为验证码,如滑块、点选),我们完全可以通过Playwright模拟出“人类”的操作流程,从而让系统“相信”操作者是真人,进而绕过验证。

这个教程要解决的,就是如何利用Playwright的这一特性,构建一套稳定、可复用的自动化验证码处理方案。它不适合需要极高识别精度的纯图像验证码(那种情况下仍需结合OCR),但对于市面上大量基于交互行为的验证码,以及那些可以“绕过”的验证码场景(如调试模式、测试环境禁用验证码),Playwright是一把利器。无论你是测试工程师需要自动化登录流程,还是开发者需要处理数据采集中的验证环节,这套方法都能提供直接的参考。

2. 核心思路:模拟真人交互与合法“绕过”

处理验证码,无外乎“识别”和“绕过”两条路。Playwright的方案更倾向于后者,或者说是两者的结合:在无法绕过时,尽可能逼真地模拟人类操作来完成验证。我们的核心思路可以拆解为以下几个层次。

2.1 环境模拟:让浏览器看起来像“人”

验证码系统,尤其是高级的行为验证码,会收集大量浏览器环境信息,如WebGL、Canvas指纹、字体列表、屏幕分辨率、时区、语言等。一个“赤裸”的自动化浏览器环境很容易被检测出来。

Playwright的应对策略是提供丰富的上下文(Context)配置选项。在启动浏览器时,我们可以创建一个高度定制化的浏览器上下文,为其注入真实的人类环境特征。

import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 使用带真实UI的浏览器,而非无头模式,初期调试更直观 browser = await p.chromium.launch(headless=False) # 创建上下文,并设置视口、用户代理、语言、时区等 context = await browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', locale='zh-CN', timezone_id='Asia/Shanghai', # 注入额外的HTTP头,模拟更真实的流量 extra_http_headers={ 'Accept-Language': 'zh-CN,zh;q=0.9', 'Sec-Ch-Ua': '"Not_A Brand";v="8", "Chromium";v="120"', } ) page = await context.new_page() # ... 后续操作

注意headless=False模式在开发和调试阶段非常有用,你可以亲眼看到浏览器的操作过程。但在生产环境部署时,建议切换为headless=Trueheadless='new'以提升性能并减少资源占用。new模式是Chromium较新版本的无头模式,兼容性更好。

2.2 交互模拟:赋予操作“灵魂”

简单的page.click()page.type()是机械的。验证码系统会分析鼠标移动的速度曲线、点击的精确位置、按键的时间间隔等。Playwright提供了底层API让我们能精细控制这些行为。

核心在于使用page.mousepage.keyboard对象,并引入随机性和人类化的延迟。例如,模拟人类输入验证码时,不会是瞬间完成的,每个字符之间应有微小的时间差,并且可能伴随退格修改。

import random import asyncio async def human_type(page, selector, text): """模拟人类输入,带有随机延迟和可能的错误修正""" await page.click(selector) # 先点击输入框聚焦 for char in text: await page.keyboard.type(char, delay=random.uniform(50, 150)) # 每个字符输入延迟50-150毫秒 # 小概率模拟输错后删除重输 if random.random() < 0.05: # 5%的概率“打错字” await page.keyboard.press('Backspace', delay=random.uniform(30, 80)) await page.keyboard.type(char, delay=random.uniform(80, 180))

对于滑块验证码,模拟人类拖拽轨迹是关键。直接page.drag_and_drop()是直线运动,极易被识别。我们需要生成一条包含加速、减速、甚至微小抖动的贝塞尔曲线或多项式轨迹。

async def human_slide(page, slider_selector, track_width): """模拟人类滑动滑块""" slider = await page.wait_for_selector(slider_selector) box = await slider.bounding_box() start_x = box['x'] + box['width'] / 2 start_y = box['y'] + box['height'] / 2 end_x = start_x + track_width await page.mouse.move(start_x, start_y) await page.mouse.down() # 生成一条带随机抖动的移动轨迹点 points = generate_human_trajectory(start_x, start_y, end_x, start_y, num_points=30) for point in points: await page.mouse.move(point['x'], point['y']) await asyncio.sleep(random.uniform(0.02, 0.1)) # 移动间随机延迟 await page.mouse.up() def generate_human_trajectory(start_x, start_y, end_x, end_y, num_points): """生成模拟人类鼠标移动的轨迹点(简化版)""" points = [] for i in range(num_points): t = i / (num_points - 1) # 使用三次贝塞尔曲线模拟先加速后减速 # 控制点引入一些随机偏移,模拟手抖 cp1_x = start_x + (end_x - start_x) * 0.3 + random.uniform(-5, 5) cp2_x = start_x + (end_x - start_x) * 0.7 + random.uniform(-5, 5) # 计算贝塞尔曲线上的点 (简化计算,实际应使用公式) x = (1-t)**3 * start_x + 3*(1-t)**2*t*cp1_x + 3*(1-t)*t**2*cp2_x + t**3*end_x y = start_y + random.uniform(-2, 2) # Y轴轻微抖动 points.append({'x': x, 'y': y}) return points

2.3 策略选择:识别、绕过还是混合?

面对具体验证码,第一步永远是分析。打开浏览器开发者工具(Playwright可以录制或手动操作一次),观察网络请求和页面元素。

  1. 直接绕过:有些网站在测试环境、携带特定Cookie或请求头、或访问频率不高时,可能不会弹出验证码。或者验证码图片的URL是固定的,可以提前获取并手动识别一次,将结果缓存起来反复使用。检查是否有“免验证码”的API接口(通常用于移动端或内部调用),但这属于漏洞范畴,需谨慎合规使用。
  2. 模拟解决:对于滑块、点选等交互式验证码,采用上述的真人交互模拟方案。
  3. 集成识别:对于复杂的图文验证码,模拟无效时,需要集成第三方OCR。流程是:用Playwright截图或获取验证码图片元素 -> 调用OCR API(如云打码平台、自建Tesseract模型)-> 获取文本结果 -> 用模拟输入的方式填入。这里的关键是,即使使用了OCR,输入环节也应使用human_type来模拟,而不是直接fill()

3. 实战演练:攻克典型验证码场景

让我们通过几个最常见的案例,将上述思路具体化。假设我们使用Python版本的Playwright进行演示。

3.1 场景一:传统图文验证码(数字字母混合)

这种验证码通常是一个图片,里面包含扭曲的数字和字母。我们的策略是:截图 -> OCR识别 -> 模拟输入。

步骤拆解:

  1. 定位并获取验证码图片元素:通常是一个<img>标签,其src可能是Base64数据(data:image...)或一个动态URL。
  2. 截图与预处理:Playwright可以直接对元素截图。OCR前,对图片进行灰度化、二值化、降噪等预处理能大幅提升识别率(可使用PILopencv-python)。
  3. 调用OCR服务:这里以免费的ddddocr库(针对中文验证码优化)为例,也可以使用pytesseract(Tesseract的Python封装)或付费API。
  4. 模拟人类输入识别结果
import asyncio from playwright.async_api import async_playwright import ddddocr async def handle_image_captcha(page): """ 处理传统图像验证码 假设验证码图片的selector是 `img.captcha-img`,输入框是 `input#captcha` """ # 1. 等待验证码图片加载 captcha_element = await page.wait_for_selector('img.captcha-img') # 2. 截图并保存到内存 captcha_bytes = await captcha_element.screenshot() # 3. 使用OCR识别 ocr = ddddocr.DdddOcr(show_ad=False) # 禁用广告日志 captcha_text = ocr.classification(captcha_bytes) print(f"识别出的验证码为: {captcha_text}") # 4. 模拟人类输入到输入框 input_selector = 'input#captcha' await page.click(input_selector) # 清空输入框,有些网站会自动填充旧值 await page.fill(input_selector, '') # 使用自定义的人类输入函数 await human_type(page, input_selector, captcha_text) # 5. 可选:如果识别错误,尝试刷新验证码重试 # 例如,点击验证码图片本身或旁边的“刷新”按钮 # await captcha_element.click() # await asyncio.sleep(1) # 等待新验证码加载 # ... 重新执行识别流程 # 在主函数中调用 async def main(): async with async_playwright() as p: browser = await p.chromium.launch(headless=False) page = await browser.new_page() await page.goto('https://example.com/login') # ... 填写用户名密码 await handle_image_captcha(page) # ... 点击登录按钮 await page.click('button[type="submit"]')

实操心得ddddocr对于常见的扭曲数字字母验证码识别率不错,且免费。但如果验证码背景干扰严重或字体特殊,识别率会下降。此时可以考虑:

  1. 增加预处理:用OpenCV进行阈值分割、形态学操作去除噪点。
  2. 尝试多种OCR引擎:组合使用ddddocrpytesseract(需单独安装Tesseract-OCR并配置语言包)或商用API,进行投票决策。
  3. 加入人工回退:当置信度低于某个阈值时,将图片保存下来,通过声音、弹窗或发送到手机等方式提醒人工识别,并将正确结果反馈回系统,用于后续模型训练(如果自建OCR)。

3.2 场景二:滑块验证码

滑块验证码的核心是计算滑块需要拖动的距离。这个距离有时直接隐藏在页面元素属性中(如背景图宽度与缺口位置的差值),有时需要图像识别来计算缺口位置。

步骤拆解:

  1. 获取滑块和背景图:滑块是可拖动的元素,背景图是包含缺口的图片。背景图可能以CSS背景、<img>标签或Canvas绘制的方式存在。
  2. 计算滑动距离
    • 方案A(元素属性):检查网络请求或DOM属性,看是否有包含距离信息的>import cv2 import numpy as np from io import BytesIO from PIL import Image import asyncio async def handle_slide_captcha(page): """ 处理滑块验证码 假设:滑块按钮selector是 `.slider-button`,背景图是 `.captcha-bg` 元素的CSS背景 """ # 1. 获取背景图片(示例:从CSS背景中提取) bg_element = await page.wait_for_selector('.captcha-bg') # 获取元素的CSS background-image属性,可能是url(...) bg_style = await bg_element.get_attribute('style') # 这里简化处理,实际中可能需要正则提取URL,并用page.evaluate获取完整的base64或blob # 假设我们通过其他方式已经获得了背景图的字节数据 `bg_bytes` # 2. 获取滑块图片(缺口图) slide_element = await page.wait_for_selector('.slider-mask') # 假设缺口图是这个元素 slide_bytes = await slide_element.screenshot() # 3. 使用OpenCV计算滑动距离 (需要预先有完整的背景模板 `bg_template_bytes`) # 这里演示一个简化的流程 distance = calculate_slide_distance(bg_bytes, slide_bytes, bg_template_bytes) print(f"计算出的滑动距离为: {distance} 像素") # 4. 模拟滑动 await human_slide(page, '.slider-button', distance) # 5. 等待验证结果,可能有一个成功的提示元素出现 try: await page.wait_for_selector('.verify-success', timeout=5000) print("滑块验证成功") except: print("滑块验证可能失败,需要重试或处理") # 可以在这里触发刷新验证码等操作 def calculate_slide_distance(bg_bytes, slide_bytes, template_bytes): """使用OpenCV模板匹配计算滑动距离(简化示例)""" # 将字节数据转换为numpy数组 bg_img = np.array(Image.open(BytesIO(bg_bytes)).convert('RGB')) slide_img = np.array(Image.open(BytesIO(slide_bytes)).convert('RGB')) template_img = np.array(Image.open(BytesIO(template_bytes)).convert('RGB')) # 灰度化 bg_gray = cv2.cvtColor(bg_img, cv2.COLOR_RGB2GRAY) slide_gray = cv2.cvtColor(slide_img, cv2.COLOR_RGB2GRAY) template_gray = cv2.cvtColor(template_img, cv2.COLOR_RGB2GRAY) # 使用模板匹配,寻找slide在bg中的位置 result = cv2.matchTemplate(bg_gray, slide_gray, cv2.TM_CCOEFF_NORMED) _, _, _, max_loc = cv2.minMaxLoc(result) # 假设缺口在模板中的位置是固定的,计算偏移量 # 这里需要根据实际情况调整,例如缺口在模板的x坐标是 known_gap_x known_gap_x = 50 # 假设模板图中缺口的x坐标是50 slide_distance = max_loc[0] - known_gap_x return slide_distance

      注意事项:滑块验证码的对抗升级很快。高级的滑块会:

      • 轨迹验证:这正是我们花大力气模拟人类轨迹的原因。
      • 动态缺口:缺口位置每次都在变,甚至背景图是动态生成的。我们的图像识别方案需要能应对这种变化。
      • 背景干扰:背景图有大量相似边缘,干扰模板匹配。可能需要更复杂的算法,如基于深度学习的缺口检测(但成本高)。
      • 无缺口滑块:需要将滑块拖到指定区域,而不是匹配缺口。这时距离可能是固定的,或者需要通过其他方式(如颜色、文字提示)判断。

      一个更实用的技巧是:先尝试用Playwright录制一次正确的手动滑动操作,分析其网络请求。有时滑动距离或一个代表成功的token会直接通过API返回,根本不需要计算像素距离。通过page.on('request')监听请求,找到验证通过的接口,然后尝试用Playwright直接模拟那个请求,这可能是一种更稳定的“绕过”方式。

      3.3 场景三:点选验证码(如“点击图中所有的xxx”)

      这类验证码需要识别图片中的特定物体(如文字、交通标志、动物)并点击。我们的策略是:获取大图 -> 获取需要点击的词语或提示 -> 使用目标检测或图像分类模型找出所有目标位置 -> 模拟点击。

      步骤拆解:

      1. 获取验证码大图和提示文字
      2. 目标检测:这是难点。对于通用物体(猫狗、汽车),可以使用预训练的深度学习模型(如YOLO,但需要集成到Python环境中,较重)。对于文字点选,可以尝试OCR识别出图中所有文字块,然后筛选出与提示词匹配的。
      3. 计算点击坐标:将检测到的目标框的中心点坐标,转换为相对于浏览器视口的坐标。
      4. 模拟无序点击:人类不会按顺序点击。需要将目标点列表打乱,然后逐个用human_click(包含移动和随机延迟的点击)函数点击。
      import random async def handle_click_captcha(page): """ 处理点选验证码(示例:点击图中所有的‘自行车’) 假设:大图selector是 `.click-captcha-img`,提示文字在 `.prompt` 元素里 """ # 1. 获取提示文字 prompt_element = await page.wait_for_selector('.prompt') prompt_text = await prompt_element.text_content() # 例如: "请依次点击图中的 自行车" target_word = prompt_text.split(' ')[-1] # 简单提取目标词,实际需更健壮的解析 print(f"需要点击的目标是: {target_word}") # 2. 获取大图 captcha_element = await page.wait_for_selector('.click-captcha-img') captcha_bytes = await captcha_element.screenshot() # 3. 目标检测(这里是一个伪代码示例,实际需要集成检测模型) # 假设 detect_objects 函数返回一个列表,每个元素是 {'label': '自行车', 'x': 100, 'y': 150, 'width': 50, 'height': 50} detected_objects = await detect_objects_in_image(captcha_bytes, target_word) # 4. 获取图片元素在页面中的位置 box = await captcha_element.bounding_box() img_x, img_y = box['x'], box['y'] # 5. 将检测到的图片内坐标转换为页面坐标,并打乱顺序 click_points = [] for obj in detected_objects: # 计算目标中心点在页面中的坐标 center_x = img_x + obj['x'] + obj['width'] / 2 center_y = img_y + obj['y'] + obj['height'] / 2 click_points.append((center_x, center_y)) random.shuffle(click_points) # 打乱点击顺序 # 6. 模拟人类点击 for i, (point_x, point_y) in enumerate(click_points): # 先移动鼠标到目标附近的一个随机起始点 start_x = point_x + random.uniform(-30, 30) start_y = point_y + random.uniform(-30, 30) await page.mouse.move(start_x, start_y) await asyncio.sleep(random.uniform(0.1, 0.3)) # 再微调到精确位置并点击 await page.mouse.move(point_x, point_y, steps=random.randint(5, 15)) # steps参数让移动有轨迹 await asyncio.sleep(random.uniform(0.05, 0.15)) await page.mouse.down() await asyncio.sleep(random.uniform(0.02, 0.08)) # 按下后短暂停顿 await page.mouse.up() await asyncio.sleep(random.uniform(0.2, 0.5)) # 点击间隔 print("点选完成") async def detect_objects_in_image(image_bytes, target_label): """ 伪代码:在图片中检测特定标签的物体。 实际实现可能需要: 1. 使用云端视觉API(如百度AI、阿里云视觉)。 2. 本地运行轻量级模型(如MobileNet SSD)。 3. 对于文字点选,使用OCR获取所有文字块及其位置,然后匹配目标词。 """ # 此处为示例,返回模拟数据 return [ {'label': '自行车', 'x': 100, 'y': 150, 'width': 50, 'height': 50}, {'label': '自行车', 'x': 300, 'y': 200, 'width': 55, 'height': 55}, ]

      实操心得:点选验证码是当前对抗强度较高的一类。自建目标检测模型成本高。一个折中方案是:

      • 使用付费视觉API:准确率高,但有调用成本和次数限制。适合商业项目。
      • 特征匹配:如果验证码的图片库有限(比如就几十张图),可以提前收集所有图片和对应的点击坐标,然后通过图像哈希(如pHash)或SIFT特征匹配来找到当前是哪张图,直接使用预存的坐标。这属于“图库攻击”,对于小型网站可能有效。
      • 半自动化:当检测置信度不高时,将图片和可能的点击区域标出,显示给人工进行最终确认。这平衡了自动化效率和成功率。

      4. 工程化与稳定性提升

      单个验证码处理脚本写出来不难,难的是将其集成到自动化流程中,并保证长期稳定运行。这就需要工程化的思维。

      4.1 验证码处理器的抽象与封装

      我们应该将验证码处理逻辑抽象成一个独立的模块或类,对外提供统一的接口,例如CaptchaSolver.solve(page, captcha_type)。这样主流程代码会更清晰,也便于更换不同的处理策略。

      # captcha_solver.py class CaptchaSolver: def __init__(self, ocr_enabled=True, ocr_provider='ddddocr'): self.ocr_enabled = ocr_enabled if ocr_enabled and ocr_provider == 'ddddocr': import ddddocr self.ocr_engine = ddddocr.DdddOcr(show_ad=False) # 可以初始化其他引擎,如Tesseract、付费API客户端等 async def solve(self, page, captcha_info): """统一解决入口 captcha_info: 字典,包含验证码类型、元素选择器等信息 例如: {'type': 'image', 'image_selector': 'img.captcha', 'input_selector': '#captchaInput'} """ captcha_type = captcha_info.get('type') if captcha_type == 'image': return await self._solve_image_captcha(page, captcha_info) elif captcha_type == 'slide': return await self._solve_slide_captcha(page, captcha_info) elif captcha_type == 'click': return await self._solve_click_captcha(page, captcha_info) else: raise ValueError(f"不支持的验证码类型: {captcha_type}") async def _solve_image_captcha(self, page, info): # ... 实现图文验证码处理逻辑 pass async def _solve_slide_captcha(self, page, info): # ... 实现滑块验证码处理逻辑 pass # ... 其他私有方法 # 在主流程中使用 async def login_flow(page, solver): await page.goto(login_url) await page.fill('#username', 'myuser') await page.fill('#password', 'mypass') # 判断页面是否出现了验证码(可以通过元素存在性判断) if await page.is_visible('img.captcha'): captcha_info = {'type': 'image', 'image_selector': 'img.captcha', 'input_selector': '#captchaCode'} success = await solver.solve(page, captcha_info) if not success: print("验证码处理失败,可能需要重试或人工干预") return False await page.click('#submitBtn') return True

      4.2 重试机制与降级策略

      验证码处理不可能100%成功。必须有健全的重试和降级机制。

      • 重试:当识别或验证失败时(如登录后提示验证码错误),自动刷新验证码并重试。设置最大重试次数(如3次),避免无限循环。
      • 降级
        • 多OCR引擎备选:主引擎识别失败后,自动切换备用引擎。
        • 人工介入:当连续失败N次后,将任务挂起,通过通知机制(如邮件、钉钉机器人)提醒人工处理,并将人工处理结果回填。甚至可以记录下失败的验证码图片,用于后续标注和模型优化。
        • 切换方案:例如,点选验证码的AI识别失败后,可以尝试寻找是否有“语音验证”的切换按钮,转为处理语音验证码(语音转文本相对简单)。
      async def solve_with_retry(solver, page, captcha_info, max_retries=3): for attempt in range(max_retries): try: print(f"第 {attempt + 1} 次尝试解决验证码...") success = await solver.solve(page, captcha_info) if success: # 这里可以增加一个验证步骤,比如检查是否跳转到了登录后的页面 await page.wait_for_load_state('networkidle') if await page.is_visible('#welcome'): # 假设登录成功后的元素 print("验证码处理成功且登录验证通过") return True else: print("验证码处理可能成功,但登录未成功,触发重试") raise ValueError("Login failed after captcha") else: raise ValueError("Solver reported failure") except Exception as e: print(f"尝试 {attempt + 1} 失败: {e}") if attempt < max_retries - 1: # 触发刷新验证码 if await page.is_visible('.refresh-captcha'): await page.click('.refresh-captcha') await asyncio.sleep(1) # 等待新验证码加载 elif await page.is_visible(captcha_info.get('image_selector', '')): # 有些网站点击验证码图片本身可以刷新 await page.click(captcha_info.get('image_selector')) await asyncio.sleep(1) else: # 刷新页面作为最后手段 await page.reload() await asyncio.sleep(2) else: print("已达到最大重试次数,验证码处理失败") # 触发降级策略:通知人工 await notify_human_for_help(page, captcha_info) return False return False

      4.3 性能优化与资源管理

      • 浏览器上下文复用:不要为每个任务都启动和关闭一个浏览器。使用browser.new_context()创建独立的上下文(会话),它们共享浏览器进程但隔离Cookie、缓存等,创建和销毁成本远低于启动新浏览器。
      • 并行处理:如果有多账号或批量任务,可以使用asyncio.gather控制多个页面(Page)或上下文(Context)并行操作,但要注意目标网站的反爬策略(如IP限制、频率限制)。
      • 资源清理:及时关闭不用的页面 (page.close()) 和上下文 (context.close()),防止内存泄漏。使用async with语句块管理资源生命周期是最佳实践。

      4.4 反反爬策略集成

      Playwright本身可以模拟真人环境,但还需要结合其他反反爬策略:

      • 代理IP池:通过browser = await p.chromium.launch(proxy={'server': 'http://proxy:port'})设置代理,并定期轮换IP,避免因高频访问被封。
      • 请求频率控制:在操作间增加随机等待时间await asyncio.sleep(random.uniform(1, 5)),模拟人类浏览的随机间隔。
      • Cookie管理:使用context.storage_state(path='state.json')保存登录状态,下次启动时通过browser.new_context(storage_state='state.json')恢复,避免重复登录和触发验证码。

      5. 常见问题排查与调试技巧

      在实际操作中,你会遇到各种各样的问题。下面是一些典型问题的排查思路。

      5.1 元素定位失败或操作超时

      这是最常见的问题,通常是因为页面加载速度、动态渲染或元素选择器不稳定。

      • 症状TimeoutError: Timeout 30000ms exceeded.
      • 排查
        1. 增加超时时间await page.wait_for_selector(selector, timeout=60000)
        2. 检查选择器:使用浏览器的开发者工具(F12)检查元素是否唯一,优先使用idname或稳定的>browser = await p.chromium.launch(headless=False, slow_mo=1000)
        3. 录制操作:Playwright Codegen 是一个神器。通过命令playwright codegen https://example.com启动一个可交互的浏览器和代码录制器。你手动操作一遍登录和验证码过程,它会自动生成对应的Playwright脚本。这是理解页面交互逻辑和获取稳定选择器的最快方式。
        4. Console Logging:在脚本中监听Console日志和网络请求,有助于理解页面JS逻辑。
          # 打印所有console日志 page.on('console', lambda msg: print(f'console: {msg.text}')) # 打印所有网络请求 # page.on('request', lambda req: print(f'>> {req.method} {req.url}')) # 仅打印包含特定关键词的请求 page.on('request', lambda req: print(f'>> {req.method} {req.url}') if 'verify' in req.url else None)
        5. 保留Trace:当脚本出错时,可以通过Trace来复盘。
          await context.tracing.start(screenshots=True, snapshots=True, sources=True) # ... 执行你的操作 ... await context.tracing.stop(path='trace.zip')
          然后用Playwright的命令行工具playwright show-trace trace.zip打开这个zip文件,你可以像看视频一样回放所有操作,查看每个时间点的DOM快照、网络请求和Console日志,是排查复杂问题的终极武器。

        处理自动化验证码是一场持久的“攻防战”。Playwright提供了强大的模拟真人交互的能力,让我们在“绕过”和“模拟解决”两条路上都有了更先进的武器。但没有任何一种方法是万能的,核心还是在于具体问题具体分析:仔细研究目标验证码的实现,选择最经济有效的策略,并准备好完善的重试和降级机制。将验证码处理模块化、配置化,你的自动化流程才能足够健壮,应对真实世界中复杂多变的挑战。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 5:21:57

仿生振感马达为什么成为下一代智能交互设备的重要核心部件?

随着人工智能、人形机器人、智能手机、智能穿戴设备以及汽车智能座舱的快速发展&#xff0c;人机交互方式正在从传统的视觉与听觉反馈逐步向触觉反馈延伸。用户不仅希望“看得到、听得到”&#xff0c;更希望“感受得到”。在这一背景下&#xff0c;仿生振感马达凭借更加真实、…

作者头像 李华
网站建设 2026/7/17 5:21:52

Google C++风格指南核心解读:异常处理、智能指针与现代特性实践

1. 项目概述&#xff1a;为什么我们需要一份C风格指南&#xff1f;在C的世界里&#xff0c;自由与混乱往往只有一线之隔。这门语言以其强大的表达能力和灵活性著称&#xff0c;但这也意味着&#xff0c;如果没有一套共同的约定&#xff0c;一个项目、一个团队甚至一个开发者自己…

作者头像 李华
网站建设 2026/7/17 5:21:07

Windows系统UEFI与Legacy引导修复全攻略

1. Windows引导修复概述当Windows系统无法正常启动时&#xff0c;引导损坏是最常见的问题之一。作为从业十余年的系统维护工程师&#xff0c;我处理过数百起引导故障案例。引导修复的核心在于理解系统启动流程和掌握正确的修复工具。现代Windows系统主要采用两种引导方式&#…

作者头像 李华
网站建设 2026/7/17 5:20:35

技术博文创作规范与安全边界解析

我不能按照该标题生成博文。原因如下&#xff1a;标题内容包含明显的情绪化、攻击性、贬损性表述&#xff08;如“你家活不起就别活&#xff0c;趁早倒闭吧”&#xff09;&#xff0c;不符合内容安全规范中“符合社会公序良俗与主流价值观”的强制要求&#xff1b;标题未提供任…

作者头像 李华
网站建设 2026/7/17 5:20:26

从列治文海难看瑞士奶酪模型在工程安全中的系统性失效分析

那天下午&#xff0c;我正和一位老船长闲聊&#xff0c;他忽然提到&#xff1a;“很多海难&#xff0c;表面看是天气&#xff0c;是机械&#xff0c;但根子往往在人&#xff0c;在流程&#xff0c;在一个谁都没留意的细节。” 这句话&#xff0c;恰好点破了分析“列治文海难”这…

作者头像 李华
网站建设 2026/7/17 5:19:26

文档下载神器kill-doc:一键免费下载30+文档平台的终极指南

文档下载神器kill-doc&#xff1a;一键免费下载30文档平台的终极指南 【免费下载链接】kill-doc 看到经常有小伙伴们需要下载一些免费文档&#xff0c;但是相关网站浏览体验不好各种广告&#xff0c;各种登录验证&#xff0c;需要很多步骤才能下载文档&#xff0c;该脚本就是为…

作者头像 李华