1. 项目概述为什么你的Playwright脚本总被“抓包”如果你用Python的Playwright做过网页自动化无论是数据采集、自动化测试还是RPA流程大概率都遇到过这个头疼的问题脚本运行得好好的突然就被目标网站拦截了弹出一个验证码或者干脆告诉你“检测到异常流量”。你可能会纳闷我明明模拟了点击、输入甚至加了随机延迟怎么还是被认出来了这背后是一场自动化脚本与网站反机器人检测系统之间持续不断的“军备竞赛”。简单来说现代网站的反爬虫和反自动化机制已经非常智能。它们不再仅仅检查你的请求频率而是会深入分析你的浏览器环境——也就是我们常说的“浏览器指纹”。当你用Playwright的默认配置启动一个浏览器尤其是无头模式时它会留下许多“非人类”的痕迹。比如navigator.webdriver属性会被设置为true用户代理User-Agent字符串里会包含“HeadlessChrome”字样屏幕分辨率、插件列表、字体集等数十个属性都可能与真实用户浏览器存在细微但可检测的差异。这些“指纹”就像你自动化脚本的“身份证”反机器人系统一扫描就知道来的是个程序。因此这个项目的核心目标不是教你写一个简单的点击脚本而是深入“敌后”拆解这些检测机制并分享一套经过实战检验的、用Playwright绕过机器人检测的进阶技巧。这些技巧不是简单的“加个延迟”而是从浏览器指纹伪装、网络行为模拟、执行轨迹混淆等多个维度系统性地提升你自动化脚本的“隐身”能力。无论你是需要稳定采集数据的开发者还是构建复杂自动化流程的工程师掌握这些技巧都能让你的脚本从“一用就封”变得“长期潜伏”。2. 核心思路从“模拟浏览器”到“伪装成真人”要成功绕过检测我们必须转变思路我们的目标不是“启动一个浏览器并控制它”而是“启动一个看起来、用起来都像真人操作的浏览器环境”。这需要我们在多个层面进行精细化的伪装和干预。2.1 理解检测的四个维度网站的反机器人检测通常从四个维度进行综合判断浏览器指纹Browser Fingerprinting这是最核心的维度。网站通过JavaScript查询浏览器暴露的大量属性和API生成一个近乎唯一的“指纹”。这包括HTTP头信息如User-Agent、Accept-Language、Sec-CH-UA用户代理客户端提示等。Navigator对象属性如webdriver、plugins、languages、hardwareConcurrency等。Screen与Window对象如屏幕分辨率、颜色深度、可用窗口大小等。WebGL与Canvas通过渲染特定图像来获取显卡和驱动的细微差异。字体列表通过测量字符宽度来推断系统已安装的字体。行为模式Behavioral Patterns真人操作是有随机性和不完美性的。检测系统会分析鼠标移动轨迹是否过于直线、匀速移动速度是否符合人类特征先快后慢的减速曲线点击与滚动点击位置是否过于精准总是元素的绝对中心滚动是否过于平滑匀速输入速度键盘输入的速度是否恒定得像机器是否有拼写纠正、删除重输等行为页面停留与跳转在页面上的停留时间是否过短页面跳转逻辑是否符合正常用户流网络请求特征Network Signature自动化工具发出的网络请求往往带有“签名”。请求头顺序与默认值Playwright等库发出的请求其HTTP头的顺序和默认值与真实浏览器可能有差异。TLS/SSL指纹客户端在SSL握手时提供的加密套件、扩展等信息构成了TLS指纹一些安全产品会据此识别自动化客户端。资源加载模式是否加载了所有资源如图片、样式表加载顺序和时间线是否异常环境一致性Environment Consistency各个维度的信息是否自洽例如User-Agent声明的浏览器版本是否与navigator.userAgentData、navigator.platform等信息匹配IP地址的地理位置是否与浏览器语言、时区设置匹配我们的五大实战技巧正是围绕破解这四个维度的检测而设计的。2.2 方案选型为什么是Playwright Stealth 自定义策略市面上有很多反检测方案比如直接使用付费的“防关联浏览器”或“爬虫浏览器”服务。它们固然省心但成本高且灵活性受限。对于开发者而言掌握一套基于开源工具Playwright的自定义方案是性价比最高、也最能应对变化的选择。我们选择以playwright-stealth插件为基础。它是一个非常优秀的开源项目移植自Puppeteer的puppeteer-extra-plugin-stealth专门用于修补Playwright默认暴露的自动化痕迹。它能处理掉大部分“低垂的果实”比如清除navigator.webdriver属性、修正无头模式的User-Agent等。但是playwright-stealth并非银弹。首先反检测技术日新月异单一插件不可能覆盖所有检测点。其次过于依赖一个广为人知的插件其模式本身也可能被加入特征库。因此我们的策略是以playwright-stealth为基石在其之上叠加我们自定义的、更深层次的伪装技巧。这样既能利用现成的优秀解决方案又能通过个性化定制提高脚本的独特性和生存能力。3. 实战技巧一深度伪装浏览器指纹这是绕过检测的第一道也是最重要的一道关卡。我们的目标是让Playwright控制的浏览器环境在指纹检测网站如bot.sannysoft.com,antoinevastel.com/bots上拿到“人类”的评分。3.1 基础设置正确使用playwright-stealth安装和使用playwright-stealth非常简单但有些细节需要注意。# 安装 pip install playwright playwright-stealthimport asyncio from playwright.async_api import async_playwright from playwright_stealth import stealth_async # 异步API async def main(): async with async_playwright() as p: # 关键点1建议以有头模式启动进行调试无头模式更易被检测 browser await p.chromium.launch(headlessFalse) # 调试时可设为True context await browser.new_context() page await context.new_page() # 关键点2必须在页面导航到任何网站之前应用stealth插件 await stealth_async(page) # 然后才能进行后续操作 await page.goto(https://bot.sannysoft.com) await page.screenshot(pathstealth_test.png) await browser.close() asyncio.run(main())注意stealth_async函数必须在对page对象执行任何导航goto或执行可能触发指纹检测的脚本之前调用。因为它需要向页面注入修改浏览器环境的JavaScript代码。如果先访问了检测页面再调用就为时已晚了。3.2 进阶伪装覆盖stealth插件的盲区playwright-stealth做了很多工作但我们可以做得更细致。以下是一些常见的、需要额外处理的指纹属性1. 修正WebGL Vendor和RendererCanvas和WebGL指纹是强力的检测手段。我们可以通过覆盖WebGLRenderingContext的原型方法来返回伪造的、更常见的显卡信息。async def enhance_stealth(page): await stealth_async(page) # 先应用基础stealth # 注入自定义JS覆盖WebGL参数 await page.add_init_script( () { const getParameter WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter function(parameter) { // 覆盖VENDOR和RENDERER使其看起来像常见的Intel/NVIDIA显卡 if (parameter 37445) { return Intel Inc.; } if (parameter 37446) { return Intel Iris OpenGL Engine; } // 对于UNMASKED_VENDOR_WEBGL和UNMASKED_RENDERER_WEBGL有些检测会查这个 if (parameter 37445) { return Google Inc. (NVIDIA); } if (parameter 37446) { return ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 Direct3D11 vs_5_0 ps_5_0); } return getParameter.apply(this, [parameter]); }; } )2. 伪装Plugins和MimeTypes真实浏览器通常有少量插件如PDF查看器。我们可以手动设置一个合理的列表。await page.add_init_script( () { // 覆盖navigator.plugins Object.defineProperty(navigator, plugins, { get: () [ {0: {type: application/pdf, suffixes: pdf, description: Portable Document Format}, name: Chrome PDF Viewer, filename: internal-pdf-viewer, length: 1}, {0: {type: application/x-google-chrome-pdf, suffixes: pdf, description: Portable Document Format}, name: Chrome PDF Viewer, filename: internal-pdf-viewer, length: 1}, ], configurable: true }); // 覆盖navigator.mimeTypes Object.defineProperty(navigator, mimeTypes, { get: () ({ application/pdf: {type: application/pdf, suffixes: pdf, description: Portable Document Format}, application/x-google-chrome-pdf: {type: application/x-google-chrome-pdf, suffixes: pdf, description: Portable Document Format}, }), configurable: true }); } )3. 设置合理的硬件并发数navigator.hardwareConcurrency返回CPU逻辑核心数。对于云服务器或虚拟机这个数可能很高如32、64显得不真实。可以将其设置为一个常见值如4或8。await page.add_init_script( () { Object.defineProperty(navigator, hardwareConcurrency, { get: () 4, configurable: true }); } )4. 使用真实的User-Agent并保持一致性不要使用Playwright默认的UA。从最新的真实浏览器中获取一个并确保所有相关属性一致。# 可以从类似 https://www.useragentstring.com/ 获取最新的UA realistic_ua Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 async with async_playwright() as p: browser await p.chromium.launch() # 在创建上下文时设置UA这比在页面级设置更彻底 context await browser.new_context(user_agentrealistic_ua) page await context.new_page() await stealth_async(page) # 同时也需要通过init script覆盖navigator.userAgent和navigator.platform确保JS读取的值一致 await page.add_init_script(f () {{ Object.defineProperty(navigator, userAgent, {{ get: () {realistic_ua}, configurable: true }}); // 根据UA推断平台例如Windows NT 10.0对应Win32 Object.defineProperty(navigator, platform, {{ get: () Win32, configurable: true }}); }} )实操心得指纹伪装是一场“猫鼠游戏”。最好的测试方法是定期用你的脚本访问bot.sannysoft.com这样的综合检测页并截图保存结果。对比伪装前后的差异重点关注还有哪些项被标红检测为机器人。然后针对这些项进行研究和修补。记住一致性是关键修改一个属性时要思考它是否会影响其他关联属性。4. 实战技巧二模拟人类交互行为模式即使指纹完美生硬的操作也会立刻暴露你。人类的行为是带有噪声和不确定性的。4.1 拟人化鼠标移动不要使用page.click()直接点击。而是先移动鼠标模拟一个带有加速度曲线的轨迹。import random import math async def human_like_click(page, selector): 模拟人类点击先移动再点击 element await page.wait_for_selector(selector) box await element.bounding_box() # 目标点元素中心加上一点随机偏移 target_x box[x] box[width] / 2 random.uniform(-5, 5) target_y box[y] box[height] / 2 random.uniform(-5, 5) # 从当前鼠标位置或屏幕某点开始移动。这里假设从屏幕左上角附近开始。 start_x, start_y random.randint(100, 300), random.randint(100, 300) await page.mouse.move(start_x, start_y) # 使用贝塞尔曲线或分段移动模拟人类轨迹 steps random.randint(30, 60) # 移动步数 for i in range(steps): # 一个简单的缓动函数开始快接近目标时慢 t i / steps # 使用三次缓动函数 (easeInOutCubic) ease_t t * t * (3 - 2 * t) if t 0.5 else 1 - math.pow(-2 * t 2, 3) / 2 current_x start_x (target_x - start_x) * ease_t random.uniform(-1, 1) # 加入微小随机抖动 current_y start_y (target_y - start_y) * ease_t random.uniform(-1, 1) await page.mouse.move(current_x, current_y) # 每步之间随机延迟 await page.wait_for_timeout(random.randint(5, 15)) # 在点击前可能有一个短暂的停顿 await page.wait_for_timeout(random.randint(50, 200)) await page.mouse.click(target_x, target_y)4.2 拟人化键盘输入不要用page.fill()瞬间填满输入框。模拟有节奏、有错误的输入。async def human_like_type(page, selector, text): 模拟人类打字包括随机延迟、退格纠错 await page.click(selector) # 先聚焦输入框 for char in text: await page.keyboard.type(char) # 随机延迟模仿思考或打字速度变化 delay random.randint(50, 150) # 毫秒 # 小概率触发“打错字然后删除” if random.random() 0.03: # 3%的概率打错 wrong_char random.choice(asdfjkl;) await page.keyboard.type(wrong_char) await page.wait_for_timeout(random.randint(100, 200)) await page.keyboard.press(Backspace) await page.wait_for_timeout(random.randint(100, 200)) await page.keyboard.type(char) # 输入正确的字符 await page.wait_for_timeout(delay)4.3 模拟滚动与阅读行为在获取数据时不要直接等待所有内容加载。模拟人类滚动阅读的模式。async def human_like_scroll(page, scroll_to_bottomTrue): 模拟人类滚动页面 viewport_height page.viewport_size[height] current_scroll 0 total_height await page.evaluate(document.body.scrollHeight) while current_scroll (total_height if scroll_to_bottom else viewport_height * 3): # 假设滚动3屏 # 每次滚动的距离随机 scroll_amount random.randint(int(viewport_height * 0.7), int(viewport_height * 1.2)) current_scroll scroll_amount await page.evaluate(fwindow.scrollTo(0, {current_scroll})) # 滚动后随机停留一段时间模仿阅读 await page.wait_for_timeout(random.randint(800, 2500)) # 偶尔可能向上滚动一点回看 if random.random() 0.1: back_scroll random.randint(100, 300) current_scroll - back_scroll await page.evaluate(fwindow.scrollTo(0, {current_scroll})) await page.wait_for_timeout(random.randint(500, 1500)) # 更新总高度动态加载的内容可能增加高度 total_height await page.evaluate(document.body.scrollHeight) if current_scroll total_height: break注意事项行为模拟的度需要根据具体网站调整。对于交互简单的网站过度模拟反而浪费时间和资源。核心原则是打破完美的机械节奏引入随机性和不完美性。可以先录制一段真人操作浏览器的鼠标移动和键盘事件分析其模式速度、加速度、停顿点然后在脚本中尽量复现。5. 实战技巧三管理网络请求与Cookie会话自动化脚本的网络请求特征也是检测重点。我们需要让请求流看起来更像来自一个真实的浏览器会话。5.1 精心配置HTTP请求头Playwright允许为每个上下文Context或请求设置请求头。不要只设置User-Agent。async with async_playwright() as p: browser await p.chromium.launch() # 准备一组完整的、看起来真实的请求头 headers { Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,image/apng,*/*;q0.8, Accept-Encoding: gzip, deflate, br, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Cache-Control: no-cache, Connection: keep-alive, Pragma: no-cache, Sec-Fetch-Dest: document, Sec-Fetch-Mode: navigate, Sec-Fetch-Site: none, Sec-Fetch-User: ?1, Upgrade-Insecure-Requests: 1, User-Agent: realistic_ua, # 使用之前定义的UA } context await browser.new_context(user_agentrealistic_ua, extra_http_headersheaders)提示Sec-Fetch-*系列头是现代浏览器为了安全而添加的表明请求的来源和目的。设置它们能显著增加请求的真实性。你可以用浏览器的开发者工具查看一个真实请求的Headers标签页复制这些值。5.2 持久化Cookie与本地存储许多检测系统会跟踪会话。如果你每次运行脚本都用一个全新的、无Cookie的浏览器行为会显得很可疑。应该像真人一样保持会话状态。import os import json COOKIE_FILE browser_cookies.json async def save_cookies(context): 保存当前上下文的Cookies到文件 cookies await context.cookies() with open(COOKIE_FILE, w) as f: json.dump(cookies, f) async def load_cookies(context): 从文件加载Cookies到上下文 if os.path.exists(COOKIE_FILE): with open(COOKIE_FILE, r) as f: cookies json.load(f) await context.add_cookies(cookies) return context async def main(): async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) context await browser.new_context() # 尝试加载之前的Cookie await load_cookies(context) page await context.new_page() await stealth_async(page) # ... 执行你的自动化操作 ... # 操作结束后保存当前的Cookie await save_cookies(context) await browser.close()实操心得对于需要登录的网站Cookie持久化至关重要。但要注意有些网站的Cookie有过期时间或与IP/User-Agent绑定。如果更换了IP或UA旧的Cookie可能失效甚至触发安全警报。最佳实践是固定使用一套伪装参数UA、视窗大小、时区等并将它们与Cookie一起持久化形成一个完整的“浏览器身份”。5.3 处理动态资源与XHR请求一些单页应用SPA主要靠XHRAjax加载数据。如果你的脚本只加载了HTML但没触发后续的API调用行为模式会显得异常。你需要监听网络请求并模拟。async def intercept_and_mimic_requests(page): 监听并可选地模拟页面发出的API请求 # 监听所有响应确保资源加载完整 async def handle_response(response): if response.request.resource_type in (xhr, fetch, script): # 可以在这里记录API请求用于后续分析或回放 pass page.on(response, handle_response) # 或者如果你知道特定API的调用模式可以直接用page.evaluate触发它们 # await page.evaluate(() { # // 模拟触发某个按钮点击从而发起XHR # document.querySelector(.load-more-btn).click(); # })6. 实战技巧四应对高级挑战验证码、WebSocket、TLS指纹当网站部署了更高级的防护时我们需要更专业的工具和策略。6.1 集成验证码解决服务遇到验证码时手动处理不现实。推荐集成第三方验证码识别服务如2Captcha、Anti-Captcha、CapSolver等。以下是一个使用2Captcha的示例流程import asyncio from playwright.async_api import async_playwright import requests CAPTCHA_SERVICE_KEY your_2captcha_api_key async def solve_recaptcha_v2(page, site_key, page_url): 解决Google reCAPTCHA v2 # 1. 获取页面上的site key (通常可以在iframe或div中找到) # 这里假设site_key已作为参数传入 # 2. 向2Captcha发送请求获取解决任务ID s requests.Session() params { key: CAPTCHA_SERVICE_KEY, method: userrecaptcha, googlekey: site_key, pageurl: page_url, json: 1 } resp s.post(http://2captcha.com/in.php, dataparams).json() if resp[status] ! 1: raise Exception(fFailed to create captcha task: {resp}) task_id resp[request] # 3. 轮询获取结果 for _ in range(120): # 最多轮询2分钟 await asyncio.sleep(5) result_params {key: CAPTCHA_SERVICE_KEY, action: get, id: task_id, json: 1} result_resp s.get(http://2captcha.com/res.php, paramsresult_params).json() if result_resp[status] 1: captcha_token result_resp[request] # 4. 将Token填入页面的g-recaptcha-response textarea await page.evaluate(f() {{ document.getElementById(g-recaptcha-response).innerHTML {captcha_token}; }}) # 或者如果页面是通过回调函数处理的则触发回调 await page.evaluate(f() {{ if (typeof ___grecaptcha_cfg ! undefined) {{ const callback ___grecaptcha_cfg.clients[0].callback; if (callback) callback({captcha_token}); }} }}) return True return False # 在脚本中使用 # 假设你检测到页面有reCAPTCHA # site_key await page.evaluate(() document.querySelector(.g-recaptcha).getAttribute(data-sitekey)) # if site_key: # success await solve_recaptcha_v2(page, site_key, page.url) # if success: # await page.click(#submit-button) # 然后提交表单6.2 处理WebSocket通信一些实时应用或游戏化反爬机制会使用WebSocket。Playwright可以监听WebSocket消息。async def handle_websocket(page): 监听和处理WebSocket消息 def on_websocket(ws): print(fWebSocket opened: {ws.url}) # 监听消息 ws.on(framereceived, lambda frame: print(fWS Received: {frame.text})) # 也可以发送消息 # ws.send({type:ping}) page.on(websocket, on_websocket)6.3 缓解TLS指纹识别高级这是最棘手的部分之一。TLS指纹识别通过分析SSL/TLS握手阶段的客户端Hello包中的特征如密码套件顺序、扩展列表等来识别客户端。Playwright底层使用的浏览器引擎Chromium有其特定的TLS指纹这可能被一些高级防火墙如Cloudflare的WAF识别。缓解方案使用真实浏览器配置文件通过user_data_dir参数让Playwright使用一个你已经用真实Chrome浏览器登录并浏览过网站的本地用户数据目录。这个目录里包含了浏览器完整的配置和TLS状态指纹更真实。context await browser.new_context(user_data_dir/path/to/your/chrome/profile)使用Playwright的“非无头”模式尽管效率低但有头模式headlessFalse的TLS指纹与真实浏览器几乎无异。考虑专业解决方案如果面对的是Cloudflare等企业级防护上述方法可能仍不够。这时需要考虑使用住宅代理或爬虫浏览器服务。这些服务提供真实住宅IP和经过深度修改的浏览器环境能有效绕过TLS和高级指纹检测。但这通常涉及付费服务超出了纯代码技巧的范畴。7. 实战技巧五构建健壮且可维护的自动化系统单个脚本能运行还不够我们需要一个能长期稳定运行、易于维护的系统。7.1 代理IP轮换与池化管理频繁从同一IP发起请求是自寻死路。必须使用代理IP。import aiohttp import asyncio from itertools import cycle class ProxyPool: def __init__(self, proxy_list): self.proxies cycle(proxy_list) # 循环使用 self.current None def get_next(self): self.current next(self.proxies) return self.current # 假设你有一个代理列表格式为 http://user:passhost:port 或 socks5://host:port proxy_list [ http://proxy1.example.com:8080, http://proxy2.example.com:8080, socks5://proxy3.example.com:1080, ] proxy_pool ProxyPool(proxy_list) async def create_browser_with_proxy(p): proxy proxy_pool.get_next() browser await p.chromium.launch( headlessTrue, proxy{server: proxy}, # Playwright的proxy配置 args[f--proxy-server{proxy}] ) return browser注意事项免费代理质量极差不稳定且可能被目标网站封禁。对于生产环境建议使用可靠的付费代理服务住宅代理或数据中心代理并确保代理的IP类型住宅、机房与你的“浏览器身份”如UA声称是家庭用户相匹配。7.2 错误处理与自动重试网络不稳定、元素加载失败、意外弹窗都会导致脚本中断。必须有健壮的错误处理和重试机制。import logging logging.basicConfig(levellogging.INFO) async def robust_operation(page, operation, selectorNone, max_retries3, **kwargs): 执行一个操作失败后重试 for attempt in range(max_retries): try: if operation goto: await page.goto(kwargs[url], timeout30000, wait_untilnetworkidle) elif operation click: elem await page.wait_for_selector(selector, timeout10000, statevisible) await elem.click() elif operation type: elem await page.wait_for_selector(selector, timeout10000, statevisible) await elem.fill(kwargs[text]) # ... 其他操作 logging.info(fOperation {operation} on {selector} succeeded.) return True except Exception as e: logging.warning(fAttempt {attempt 1} failed for {operation}: {e}) if attempt max_retries - 1: logging.error(fOperation {operation} failed after {max_retries} retries.) # 可以在这里保存错误页面截图便于调试 await page.screenshot(pathferror_attempt_{attempt1}.png) raise await asyncio.sleep(2 ** attempt) # 指数退避 return False # 使用示例 # await robust_operation(page, goto, urlhttps://example.com) # await robust_operation(page, click, selector#submit-btn)7.3 配置管理与环境隔离将伪装参数UA、视窗大小、代理列表等提取到配置文件如config.yaml或.env文件中便于管理和切换不同网站的配置。# config.yaml target_website: name: example_site base_url: https://www.example.com stealth: user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 viewport: { width: 1920, height: 1080 } timezone_id: Asia/Shanghai locale: zh-CN behavior: min_typing_delay: 50 max_typing_delay: 150 scroll_variation: true proxy: enabled: true list: - http://proxy1.com:8080 - http://proxy2.com:8080然后在代码中加载配置使你的脚本高度可配置化。8. 常见问题与排查技巧实录即使掌握了所有技巧在实际运行中还是会遇到各种问题。这里记录了一些典型问题的排查思路。8.1 问题速查表问题现象可能原因排查步骤与解决方案访问检测页如sannysoft仍被识别1.stealth_async调用时机不对。2. 指纹有遗漏项。3. 浏览器启动参数暴露。1. 确保在page.goto()前调用stealth_async。2. 访问检测页截图并逐一检查被标红的项。针对性地编写page.add_init_script进行修补。3. 检查启动参数避免使用--disable-blink-featuresAutomationControlled等已被广泛识别的标志。登录后立刻被踢出或要求二次验证1. Cookie或会话与浏览器指纹/IP不匹配。2. 行为模式异常如登录太快。3. 触发了风控规则。1. 确保每次运行使用相同的“浏览器身份”UA、分辨率、时区和IP如果可能。2. 在关键操作登录、提交表单前增加随机、较长的延迟。3. 尝试更换IP使用住宅代理并模拟从该IP所在地理位置出发的浏览行为设置对应时区、语言。页面元素找不到或点击无效1. 页面尚未加载完成。2. 元素在iframe内。3. 元素被动态生成选择器不对。1. 使用page.wait_for_selector并设置合理的timeout和statevisible, attached。2. 使用page.frame_locator()定位iframe内的元素。3. 使用更稳健的选择器如>脚本运行速度慢1. 不必要的延迟过多。2. 网络请求如图片、样式加载拖慢速度。3. 代理IP速度慢。1. 优化延迟只在必要步骤如跳转页面、提交表单后添加随机等待。2. 通过route功能拦截并中止不必要的资源请求如图片、字体、媒体。3. 测试代理IP的速度和稳定性建立优质代理池。遇到Cloudflare等5秒盾1. TLS指纹或浏览器指纹被识别。2. IP地址被标记。1. 尝试使用有头模式headlessFalse和真实的user_data_dir。2.这是最困难的情况。考虑使用playwright-stealth的更高阶配置或切换到专门绕过Cloudflare的库如undetected-playwright一个社区维护的、更激进的防检测Playwright分支。3. 终极方案使用前述的住宅代理爬虫浏览器服务。8.2 调试与取证技巧当脚本被阻断时不要盲目猜测。学会取证保存现场在page.goto()或关键操作后立即截图并保存HTML。await page.screenshot(pathdebug.png, full_pageTrue) html await page.content() with open(debug.html, w, encodingutf-8) as f: f.write(html)监听控制台和网络在启动上下文时开启record_har和监听控制台。context await browser.new_context( record_har_pathnetwork.har, viewport{width: 1920, height: 1080} ) page.on(console, lambda msg: print(fCONSOLE: {msg.type} - {msg.text}))分析network.har文件看是否有请求被重定向到验证码页面或者返回了特殊的拦截状态码如403、429、503。使用无头模式调试虽然无头模式易被检测但在初期调试行为模拟、监听网络时非常有用。可以结合slow_mo参数让操作慢放便于观察。browser await p.chromium.launch(headlessFalse, slow_mo100) # 每个操作延迟100毫秒8.3 最后的忠告绕过机器人检测没有一劳永逸的解决方案。这是一个持续对抗的过程。今天有效的方法明天可能因为网站更新检测算法而失效。因此你的自动化系统应该具备可观测性良好的日志记录和可适应性易于修改配置和策略。将你的伪装策略模块化定期例如每周用你的脚本去测试几个公开的机器人检测页面。保持对新技术如WebDriver BiDi、CDP协议的新特性和新的检测手段如基于机器学习的用户行为分析的关注。最终最可靠的“技巧”是尊重目标网站的robots.txt规则合理控制请求频率并将你的自动化行为尽可能模拟得与善意的人类用户别无二致。这既是技术挑战也是一场需要耐心和细心的游戏。
Playwright反检测实战:五大技巧伪装浏览器指纹与人类行为,绕过机器人检测
发布时间:2026/6/19 12:44:22
1. 项目概述为什么你的Playwright脚本总被“抓包”如果你用Python的Playwright做过网页自动化无论是数据采集、自动化测试还是RPA流程大概率都遇到过这个头疼的问题脚本运行得好好的突然就被目标网站拦截了弹出一个验证码或者干脆告诉你“检测到异常流量”。你可能会纳闷我明明模拟了点击、输入甚至加了随机延迟怎么还是被认出来了这背后是一场自动化脚本与网站反机器人检测系统之间持续不断的“军备竞赛”。简单来说现代网站的反爬虫和反自动化机制已经非常智能。它们不再仅仅检查你的请求频率而是会深入分析你的浏览器环境——也就是我们常说的“浏览器指纹”。当你用Playwright的默认配置启动一个浏览器尤其是无头模式时它会留下许多“非人类”的痕迹。比如navigator.webdriver属性会被设置为true用户代理User-Agent字符串里会包含“HeadlessChrome”字样屏幕分辨率、插件列表、字体集等数十个属性都可能与真实用户浏览器存在细微但可检测的差异。这些“指纹”就像你自动化脚本的“身份证”反机器人系统一扫描就知道来的是个程序。因此这个项目的核心目标不是教你写一个简单的点击脚本而是深入“敌后”拆解这些检测机制并分享一套经过实战检验的、用Playwright绕过机器人检测的进阶技巧。这些技巧不是简单的“加个延迟”而是从浏览器指纹伪装、网络行为模拟、执行轨迹混淆等多个维度系统性地提升你自动化脚本的“隐身”能力。无论你是需要稳定采集数据的开发者还是构建复杂自动化流程的工程师掌握这些技巧都能让你的脚本从“一用就封”变得“长期潜伏”。2. 核心思路从“模拟浏览器”到“伪装成真人”要成功绕过检测我们必须转变思路我们的目标不是“启动一个浏览器并控制它”而是“启动一个看起来、用起来都像真人操作的浏览器环境”。这需要我们在多个层面进行精细化的伪装和干预。2.1 理解检测的四个维度网站的反机器人检测通常从四个维度进行综合判断浏览器指纹Browser Fingerprinting这是最核心的维度。网站通过JavaScript查询浏览器暴露的大量属性和API生成一个近乎唯一的“指纹”。这包括HTTP头信息如User-Agent、Accept-Language、Sec-CH-UA用户代理客户端提示等。Navigator对象属性如webdriver、plugins、languages、hardwareConcurrency等。Screen与Window对象如屏幕分辨率、颜色深度、可用窗口大小等。WebGL与Canvas通过渲染特定图像来获取显卡和驱动的细微差异。字体列表通过测量字符宽度来推断系统已安装的字体。行为模式Behavioral Patterns真人操作是有随机性和不完美性的。检测系统会分析鼠标移动轨迹是否过于直线、匀速移动速度是否符合人类特征先快后慢的减速曲线点击与滚动点击位置是否过于精准总是元素的绝对中心滚动是否过于平滑匀速输入速度键盘输入的速度是否恒定得像机器是否有拼写纠正、删除重输等行为页面停留与跳转在页面上的停留时间是否过短页面跳转逻辑是否符合正常用户流网络请求特征Network Signature自动化工具发出的网络请求往往带有“签名”。请求头顺序与默认值Playwright等库发出的请求其HTTP头的顺序和默认值与真实浏览器可能有差异。TLS/SSL指纹客户端在SSL握手时提供的加密套件、扩展等信息构成了TLS指纹一些安全产品会据此识别自动化客户端。资源加载模式是否加载了所有资源如图片、样式表加载顺序和时间线是否异常环境一致性Environment Consistency各个维度的信息是否自洽例如User-Agent声明的浏览器版本是否与navigator.userAgentData、navigator.platform等信息匹配IP地址的地理位置是否与浏览器语言、时区设置匹配我们的五大实战技巧正是围绕破解这四个维度的检测而设计的。2.2 方案选型为什么是Playwright Stealth 自定义策略市面上有很多反检测方案比如直接使用付费的“防关联浏览器”或“爬虫浏览器”服务。它们固然省心但成本高且灵活性受限。对于开发者而言掌握一套基于开源工具Playwright的自定义方案是性价比最高、也最能应对变化的选择。我们选择以playwright-stealth插件为基础。它是一个非常优秀的开源项目移植自Puppeteer的puppeteer-extra-plugin-stealth专门用于修补Playwright默认暴露的自动化痕迹。它能处理掉大部分“低垂的果实”比如清除navigator.webdriver属性、修正无头模式的User-Agent等。但是playwright-stealth并非银弹。首先反检测技术日新月异单一插件不可能覆盖所有检测点。其次过于依赖一个广为人知的插件其模式本身也可能被加入特征库。因此我们的策略是以playwright-stealth为基石在其之上叠加我们自定义的、更深层次的伪装技巧。这样既能利用现成的优秀解决方案又能通过个性化定制提高脚本的独特性和生存能力。3. 实战技巧一深度伪装浏览器指纹这是绕过检测的第一道也是最重要的一道关卡。我们的目标是让Playwright控制的浏览器环境在指纹检测网站如bot.sannysoft.com,antoinevastel.com/bots上拿到“人类”的评分。3.1 基础设置正确使用playwright-stealth安装和使用playwright-stealth非常简单但有些细节需要注意。# 安装 pip install playwright playwright-stealthimport asyncio from playwright.async_api import async_playwright from playwright_stealth import stealth_async # 异步API async def main(): async with async_playwright() as p: # 关键点1建议以有头模式启动进行调试无头模式更易被检测 browser await p.chromium.launch(headlessFalse) # 调试时可设为True context await browser.new_context() page await context.new_page() # 关键点2必须在页面导航到任何网站之前应用stealth插件 await stealth_async(page) # 然后才能进行后续操作 await page.goto(https://bot.sannysoft.com) await page.screenshot(pathstealth_test.png) await browser.close() asyncio.run(main())注意stealth_async函数必须在对page对象执行任何导航goto或执行可能触发指纹检测的脚本之前调用。因为它需要向页面注入修改浏览器环境的JavaScript代码。如果先访问了检测页面再调用就为时已晚了。3.2 进阶伪装覆盖stealth插件的盲区playwright-stealth做了很多工作但我们可以做得更细致。以下是一些常见的、需要额外处理的指纹属性1. 修正WebGL Vendor和RendererCanvas和WebGL指纹是强力的检测手段。我们可以通过覆盖WebGLRenderingContext的原型方法来返回伪造的、更常见的显卡信息。async def enhance_stealth(page): await stealth_async(page) # 先应用基础stealth # 注入自定义JS覆盖WebGL参数 await page.add_init_script( () { const getParameter WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter function(parameter) { // 覆盖VENDOR和RENDERER使其看起来像常见的Intel/NVIDIA显卡 if (parameter 37445) { return Intel Inc.; } if (parameter 37446) { return Intel Iris OpenGL Engine; } // 对于UNMASKED_VENDOR_WEBGL和UNMASKED_RENDERER_WEBGL有些检测会查这个 if (parameter 37445) { return Google Inc. (NVIDIA); } if (parameter 37446) { return ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 Direct3D11 vs_5_0 ps_5_0); } return getParameter.apply(this, [parameter]); }; } )2. 伪装Plugins和MimeTypes真实浏览器通常有少量插件如PDF查看器。我们可以手动设置一个合理的列表。await page.add_init_script( () { // 覆盖navigator.plugins Object.defineProperty(navigator, plugins, { get: () [ {0: {type: application/pdf, suffixes: pdf, description: Portable Document Format}, name: Chrome PDF Viewer, filename: internal-pdf-viewer, length: 1}, {0: {type: application/x-google-chrome-pdf, suffixes: pdf, description: Portable Document Format}, name: Chrome PDF Viewer, filename: internal-pdf-viewer, length: 1}, ], configurable: true }); // 覆盖navigator.mimeTypes Object.defineProperty(navigator, mimeTypes, { get: () ({ application/pdf: {type: application/pdf, suffixes: pdf, description: Portable Document Format}, application/x-google-chrome-pdf: {type: application/x-google-chrome-pdf, suffixes: pdf, description: Portable Document Format}, }), configurable: true }); } )3. 设置合理的硬件并发数navigator.hardwareConcurrency返回CPU逻辑核心数。对于云服务器或虚拟机这个数可能很高如32、64显得不真实。可以将其设置为一个常见值如4或8。await page.add_init_script( () { Object.defineProperty(navigator, hardwareConcurrency, { get: () 4, configurable: true }); } )4. 使用真实的User-Agent并保持一致性不要使用Playwright默认的UA。从最新的真实浏览器中获取一个并确保所有相关属性一致。# 可以从类似 https://www.useragentstring.com/ 获取最新的UA realistic_ua Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 async with async_playwright() as p: browser await p.chromium.launch() # 在创建上下文时设置UA这比在页面级设置更彻底 context await browser.new_context(user_agentrealistic_ua) page await context.new_page() await stealth_async(page) # 同时也需要通过init script覆盖navigator.userAgent和navigator.platform确保JS读取的值一致 await page.add_init_script(f () {{ Object.defineProperty(navigator, userAgent, {{ get: () {realistic_ua}, configurable: true }}); // 根据UA推断平台例如Windows NT 10.0对应Win32 Object.defineProperty(navigator, platform, {{ get: () Win32, configurable: true }}); }} )实操心得指纹伪装是一场“猫鼠游戏”。最好的测试方法是定期用你的脚本访问bot.sannysoft.com这样的综合检测页并截图保存结果。对比伪装前后的差异重点关注还有哪些项被标红检测为机器人。然后针对这些项进行研究和修补。记住一致性是关键修改一个属性时要思考它是否会影响其他关联属性。4. 实战技巧二模拟人类交互行为模式即使指纹完美生硬的操作也会立刻暴露你。人类的行为是带有噪声和不确定性的。4.1 拟人化鼠标移动不要使用page.click()直接点击。而是先移动鼠标模拟一个带有加速度曲线的轨迹。import random import math async def human_like_click(page, selector): 模拟人类点击先移动再点击 element await page.wait_for_selector(selector) box await element.bounding_box() # 目标点元素中心加上一点随机偏移 target_x box[x] box[width] / 2 random.uniform(-5, 5) target_y box[y] box[height] / 2 random.uniform(-5, 5) # 从当前鼠标位置或屏幕某点开始移动。这里假设从屏幕左上角附近开始。 start_x, start_y random.randint(100, 300), random.randint(100, 300) await page.mouse.move(start_x, start_y) # 使用贝塞尔曲线或分段移动模拟人类轨迹 steps random.randint(30, 60) # 移动步数 for i in range(steps): # 一个简单的缓动函数开始快接近目标时慢 t i / steps # 使用三次缓动函数 (easeInOutCubic) ease_t t * t * (3 - 2 * t) if t 0.5 else 1 - math.pow(-2 * t 2, 3) / 2 current_x start_x (target_x - start_x) * ease_t random.uniform(-1, 1) # 加入微小随机抖动 current_y start_y (target_y - start_y) * ease_t random.uniform(-1, 1) await page.mouse.move(current_x, current_y) # 每步之间随机延迟 await page.wait_for_timeout(random.randint(5, 15)) # 在点击前可能有一个短暂的停顿 await page.wait_for_timeout(random.randint(50, 200)) await page.mouse.click(target_x, target_y)4.2 拟人化键盘输入不要用page.fill()瞬间填满输入框。模拟有节奏、有错误的输入。async def human_like_type(page, selector, text): 模拟人类打字包括随机延迟、退格纠错 await page.click(selector) # 先聚焦输入框 for char in text: await page.keyboard.type(char) # 随机延迟模仿思考或打字速度变化 delay random.randint(50, 150) # 毫秒 # 小概率触发“打错字然后删除” if random.random() 0.03: # 3%的概率打错 wrong_char random.choice(asdfjkl;) await page.keyboard.type(wrong_char) await page.wait_for_timeout(random.randint(100, 200)) await page.keyboard.press(Backspace) await page.wait_for_timeout(random.randint(100, 200)) await page.keyboard.type(char) # 输入正确的字符 await page.wait_for_timeout(delay)4.3 模拟滚动与阅读行为在获取数据时不要直接等待所有内容加载。模拟人类滚动阅读的模式。async def human_like_scroll(page, scroll_to_bottomTrue): 模拟人类滚动页面 viewport_height page.viewport_size[height] current_scroll 0 total_height await page.evaluate(document.body.scrollHeight) while current_scroll (total_height if scroll_to_bottom else viewport_height * 3): # 假设滚动3屏 # 每次滚动的距离随机 scroll_amount random.randint(int(viewport_height * 0.7), int(viewport_height * 1.2)) current_scroll scroll_amount await page.evaluate(fwindow.scrollTo(0, {current_scroll})) # 滚动后随机停留一段时间模仿阅读 await page.wait_for_timeout(random.randint(800, 2500)) # 偶尔可能向上滚动一点回看 if random.random() 0.1: back_scroll random.randint(100, 300) current_scroll - back_scroll await page.evaluate(fwindow.scrollTo(0, {current_scroll})) await page.wait_for_timeout(random.randint(500, 1500)) # 更新总高度动态加载的内容可能增加高度 total_height await page.evaluate(document.body.scrollHeight) if current_scroll total_height: break注意事项行为模拟的度需要根据具体网站调整。对于交互简单的网站过度模拟反而浪费时间和资源。核心原则是打破完美的机械节奏引入随机性和不完美性。可以先录制一段真人操作浏览器的鼠标移动和键盘事件分析其模式速度、加速度、停顿点然后在脚本中尽量复现。5. 实战技巧三管理网络请求与Cookie会话自动化脚本的网络请求特征也是检测重点。我们需要让请求流看起来更像来自一个真实的浏览器会话。5.1 精心配置HTTP请求头Playwright允许为每个上下文Context或请求设置请求头。不要只设置User-Agent。async with async_playwright() as p: browser await p.chromium.launch() # 准备一组完整的、看起来真实的请求头 headers { Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,image/apng,*/*;q0.8, Accept-Encoding: gzip, deflate, br, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Cache-Control: no-cache, Connection: keep-alive, Pragma: no-cache, Sec-Fetch-Dest: document, Sec-Fetch-Mode: navigate, Sec-Fetch-Site: none, Sec-Fetch-User: ?1, Upgrade-Insecure-Requests: 1, User-Agent: realistic_ua, # 使用之前定义的UA } context await browser.new_context(user_agentrealistic_ua, extra_http_headersheaders)提示Sec-Fetch-*系列头是现代浏览器为了安全而添加的表明请求的来源和目的。设置它们能显著增加请求的真实性。你可以用浏览器的开发者工具查看一个真实请求的Headers标签页复制这些值。5.2 持久化Cookie与本地存储许多检测系统会跟踪会话。如果你每次运行脚本都用一个全新的、无Cookie的浏览器行为会显得很可疑。应该像真人一样保持会话状态。import os import json COOKIE_FILE browser_cookies.json async def save_cookies(context): 保存当前上下文的Cookies到文件 cookies await context.cookies() with open(COOKIE_FILE, w) as f: json.dump(cookies, f) async def load_cookies(context): 从文件加载Cookies到上下文 if os.path.exists(COOKIE_FILE): with open(COOKIE_FILE, r) as f: cookies json.load(f) await context.add_cookies(cookies) return context async def main(): async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) context await browser.new_context() # 尝试加载之前的Cookie await load_cookies(context) page await context.new_page() await stealth_async(page) # ... 执行你的自动化操作 ... # 操作结束后保存当前的Cookie await save_cookies(context) await browser.close()实操心得对于需要登录的网站Cookie持久化至关重要。但要注意有些网站的Cookie有过期时间或与IP/User-Agent绑定。如果更换了IP或UA旧的Cookie可能失效甚至触发安全警报。最佳实践是固定使用一套伪装参数UA、视窗大小、时区等并将它们与Cookie一起持久化形成一个完整的“浏览器身份”。5.3 处理动态资源与XHR请求一些单页应用SPA主要靠XHRAjax加载数据。如果你的脚本只加载了HTML但没触发后续的API调用行为模式会显得异常。你需要监听网络请求并模拟。async def intercept_and_mimic_requests(page): 监听并可选地模拟页面发出的API请求 # 监听所有响应确保资源加载完整 async def handle_response(response): if response.request.resource_type in (xhr, fetch, script): # 可以在这里记录API请求用于后续分析或回放 pass page.on(response, handle_response) # 或者如果你知道特定API的调用模式可以直接用page.evaluate触发它们 # await page.evaluate(() { # // 模拟触发某个按钮点击从而发起XHR # document.querySelector(.load-more-btn).click(); # })6. 实战技巧四应对高级挑战验证码、WebSocket、TLS指纹当网站部署了更高级的防护时我们需要更专业的工具和策略。6.1 集成验证码解决服务遇到验证码时手动处理不现实。推荐集成第三方验证码识别服务如2Captcha、Anti-Captcha、CapSolver等。以下是一个使用2Captcha的示例流程import asyncio from playwright.async_api import async_playwright import requests CAPTCHA_SERVICE_KEY your_2captcha_api_key async def solve_recaptcha_v2(page, site_key, page_url): 解决Google reCAPTCHA v2 # 1. 获取页面上的site key (通常可以在iframe或div中找到) # 这里假设site_key已作为参数传入 # 2. 向2Captcha发送请求获取解决任务ID s requests.Session() params { key: CAPTCHA_SERVICE_KEY, method: userrecaptcha, googlekey: site_key, pageurl: page_url, json: 1 } resp s.post(http://2captcha.com/in.php, dataparams).json() if resp[status] ! 1: raise Exception(fFailed to create captcha task: {resp}) task_id resp[request] # 3. 轮询获取结果 for _ in range(120): # 最多轮询2分钟 await asyncio.sleep(5) result_params {key: CAPTCHA_SERVICE_KEY, action: get, id: task_id, json: 1} result_resp s.get(http://2captcha.com/res.php, paramsresult_params).json() if result_resp[status] 1: captcha_token result_resp[request] # 4. 将Token填入页面的g-recaptcha-response textarea await page.evaluate(f() {{ document.getElementById(g-recaptcha-response).innerHTML {captcha_token}; }}) # 或者如果页面是通过回调函数处理的则触发回调 await page.evaluate(f() {{ if (typeof ___grecaptcha_cfg ! undefined) {{ const callback ___grecaptcha_cfg.clients[0].callback; if (callback) callback({captcha_token}); }} }}) return True return False # 在脚本中使用 # 假设你检测到页面有reCAPTCHA # site_key await page.evaluate(() document.querySelector(.g-recaptcha).getAttribute(data-sitekey)) # if site_key: # success await solve_recaptcha_v2(page, site_key, page.url) # if success: # await page.click(#submit-button) # 然后提交表单6.2 处理WebSocket通信一些实时应用或游戏化反爬机制会使用WebSocket。Playwright可以监听WebSocket消息。async def handle_websocket(page): 监听和处理WebSocket消息 def on_websocket(ws): print(fWebSocket opened: {ws.url}) # 监听消息 ws.on(framereceived, lambda frame: print(fWS Received: {frame.text})) # 也可以发送消息 # ws.send({type:ping}) page.on(websocket, on_websocket)6.3 缓解TLS指纹识别高级这是最棘手的部分之一。TLS指纹识别通过分析SSL/TLS握手阶段的客户端Hello包中的特征如密码套件顺序、扩展列表等来识别客户端。Playwright底层使用的浏览器引擎Chromium有其特定的TLS指纹这可能被一些高级防火墙如Cloudflare的WAF识别。缓解方案使用真实浏览器配置文件通过user_data_dir参数让Playwright使用一个你已经用真实Chrome浏览器登录并浏览过网站的本地用户数据目录。这个目录里包含了浏览器完整的配置和TLS状态指纹更真实。context await browser.new_context(user_data_dir/path/to/your/chrome/profile)使用Playwright的“非无头”模式尽管效率低但有头模式headlessFalse的TLS指纹与真实浏览器几乎无异。考虑专业解决方案如果面对的是Cloudflare等企业级防护上述方法可能仍不够。这时需要考虑使用住宅代理或爬虫浏览器服务。这些服务提供真实住宅IP和经过深度修改的浏览器环境能有效绕过TLS和高级指纹检测。但这通常涉及付费服务超出了纯代码技巧的范畴。7. 实战技巧五构建健壮且可维护的自动化系统单个脚本能运行还不够我们需要一个能长期稳定运行、易于维护的系统。7.1 代理IP轮换与池化管理频繁从同一IP发起请求是自寻死路。必须使用代理IP。import aiohttp import asyncio from itertools import cycle class ProxyPool: def __init__(self, proxy_list): self.proxies cycle(proxy_list) # 循环使用 self.current None def get_next(self): self.current next(self.proxies) return self.current # 假设你有一个代理列表格式为 http://user:passhost:port 或 socks5://host:port proxy_list [ http://proxy1.example.com:8080, http://proxy2.example.com:8080, socks5://proxy3.example.com:1080, ] proxy_pool ProxyPool(proxy_list) async def create_browser_with_proxy(p): proxy proxy_pool.get_next() browser await p.chromium.launch( headlessTrue, proxy{server: proxy}, # Playwright的proxy配置 args[f--proxy-server{proxy}] ) return browser注意事项免费代理质量极差不稳定且可能被目标网站封禁。对于生产环境建议使用可靠的付费代理服务住宅代理或数据中心代理并确保代理的IP类型住宅、机房与你的“浏览器身份”如UA声称是家庭用户相匹配。7.2 错误处理与自动重试网络不稳定、元素加载失败、意外弹窗都会导致脚本中断。必须有健壮的错误处理和重试机制。import logging logging.basicConfig(levellogging.INFO) async def robust_operation(page, operation, selectorNone, max_retries3, **kwargs): 执行一个操作失败后重试 for attempt in range(max_retries): try: if operation goto: await page.goto(kwargs[url], timeout30000, wait_untilnetworkidle) elif operation click: elem await page.wait_for_selector(selector, timeout10000, statevisible) await elem.click() elif operation type: elem await page.wait_for_selector(selector, timeout10000, statevisible) await elem.fill(kwargs[text]) # ... 其他操作 logging.info(fOperation {operation} on {selector} succeeded.) return True except Exception as e: logging.warning(fAttempt {attempt 1} failed for {operation}: {e}) if attempt max_retries - 1: logging.error(fOperation {operation} failed after {max_retries} retries.) # 可以在这里保存错误页面截图便于调试 await page.screenshot(pathferror_attempt_{attempt1}.png) raise await asyncio.sleep(2 ** attempt) # 指数退避 return False # 使用示例 # await robust_operation(page, goto, urlhttps://example.com) # await robust_operation(page, click, selector#submit-btn)7.3 配置管理与环境隔离将伪装参数UA、视窗大小、代理列表等提取到配置文件如config.yaml或.env文件中便于管理和切换不同网站的配置。# config.yaml target_website: name: example_site base_url: https://www.example.com stealth: user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 viewport: { width: 1920, height: 1080 } timezone_id: Asia/Shanghai locale: zh-CN behavior: min_typing_delay: 50 max_typing_delay: 150 scroll_variation: true proxy: enabled: true list: - http://proxy1.com:8080 - http://proxy2.com:8080然后在代码中加载配置使你的脚本高度可配置化。8. 常见问题与排查技巧实录即使掌握了所有技巧在实际运行中还是会遇到各种问题。这里记录了一些典型问题的排查思路。8.1 问题速查表问题现象可能原因排查步骤与解决方案访问检测页如sannysoft仍被识别1.stealth_async调用时机不对。2. 指纹有遗漏项。3. 浏览器启动参数暴露。1. 确保在page.goto()前调用stealth_async。2. 访问检测页截图并逐一检查被标红的项。针对性地编写page.add_init_script进行修补。3. 检查启动参数避免使用--disable-blink-featuresAutomationControlled等已被广泛识别的标志。登录后立刻被踢出或要求二次验证1. Cookie或会话与浏览器指纹/IP不匹配。2. 行为模式异常如登录太快。3. 触发了风控规则。1. 确保每次运行使用相同的“浏览器身份”UA、分辨率、时区和IP如果可能。2. 在关键操作登录、提交表单前增加随机、较长的延迟。3. 尝试更换IP使用住宅代理并模拟从该IP所在地理位置出发的浏览行为设置对应时区、语言。页面元素找不到或点击无效1. 页面尚未加载完成。2. 元素在iframe内。3. 元素被动态生成选择器不对。1. 使用page.wait_for_selector并设置合理的timeout和statevisible, attached。2. 使用page.frame_locator()定位iframe内的元素。3. 使用更稳健的选择器如>脚本运行速度慢1. 不必要的延迟过多。2. 网络请求如图片、样式加载拖慢速度。3. 代理IP速度慢。1. 优化延迟只在必要步骤如跳转页面、提交表单后添加随机等待。2. 通过route功能拦截并中止不必要的资源请求如图片、字体、媒体。3. 测试代理IP的速度和稳定性建立优质代理池。遇到Cloudflare等5秒盾1. TLS指纹或浏览器指纹被识别。2. IP地址被标记。1. 尝试使用有头模式headlessFalse和真实的user_data_dir。2.这是最困难的情况。考虑使用playwright-stealth的更高阶配置或切换到专门绕过Cloudflare的库如undetected-playwright一个社区维护的、更激进的防检测Playwright分支。3. 终极方案使用前述的住宅代理爬虫浏览器服务。8.2 调试与取证技巧当脚本被阻断时不要盲目猜测。学会取证保存现场在page.goto()或关键操作后立即截图并保存HTML。await page.screenshot(pathdebug.png, full_pageTrue) html await page.content() with open(debug.html, w, encodingutf-8) as f: f.write(html)监听控制台和网络在启动上下文时开启record_har和监听控制台。context await browser.new_context( record_har_pathnetwork.har, viewport{width: 1920, height: 1080} ) page.on(console, lambda msg: print(fCONSOLE: {msg.type} - {msg.text}))分析network.har文件看是否有请求被重定向到验证码页面或者返回了特殊的拦截状态码如403、429、503。使用无头模式调试虽然无头模式易被检测但在初期调试行为模拟、监听网络时非常有用。可以结合slow_mo参数让操作慢放便于观察。browser await p.chromium.launch(headlessFalse, slow_mo100) # 每个操作延迟100毫秒8.3 最后的忠告绕过机器人检测没有一劳永逸的解决方案。这是一个持续对抗的过程。今天有效的方法明天可能因为网站更新检测算法而失效。因此你的自动化系统应该具备可观测性良好的日志记录和可适应性易于修改配置和策略。将你的伪装策略模块化定期例如每周用你的脚本去测试几个公开的机器人检测页面。保持对新技术如WebDriver BiDi、CDP协议的新特性和新的检测手段如基于机器学习的用户行为分析的关注。最终最可靠的“技巧”是尊重目标网站的robots.txt规则合理控制请求频率并将你的自动化行为尽可能模拟得与善意的人类用户别无二致。这既是技术挑战也是一场需要耐心和细心的游戏。