
1. 项目概述为什么是Playwright如果你最近在搞自动化测试或者数据采集大概率已经听过Playwright这个名字了。它不像Selenium那样是个“老前辈”也不像Puppeteer那样只跟Chrome玩。Playwright是微软在2020年推出的一个现代化浏览器自动化库它的野心很大用一个统一的API搞定Chromium、Firefox和WebKitSafari的引擎三大浏览器内核。这意味着你写一套脚本就能在几乎所有主流浏览器环境里稳定运行。我最初接触它是因为一个老项目的爬虫需求。那个网站反爬机制升级传统的requestsBeautifulSoup组合频频受挫动态加载的内容根本抓不到。用Selenium吧速度慢、不稳定还经常因为驱动版本问题翻车。后来试了Playwright最直接的感受就是“快”和“稳”。它内置了浏览器不用你单独下载和管理WebDriver它的自动等待机制非常智能不用写一堆time.sleep去猜页面加载时间它对动态页面的支持更是天生优势因为本质上它就是一个“程序控制的真实浏览器”。所以这个“实战案例”系列我不想只讲语法。我想带你看看怎么把Playwright这些特性变成解决实际问题的“利器”。无论是自动化测试里模拟复杂用户操作流还是爬虫项目中对抗各种反爬策略Playwright都能提供一套高效、精准的解决方案。这篇文章就是为你拆解这些“秘密”。2. 核心优势与底层原理拆解2.1 架构优势为什么比Selenium更“现代”要理解Playwright的好得先看看它和Selenium的根本区别。Selenium的核心是WebDriver协议这是一个标准的、但相对古老的远程控制协议。你的脚本通过WebDriver向浏览器驱动发送指令驱动再控制浏览器。这个链条长通信开销大而且不同浏览器的驱动质量参差不齐是很多不稳定问题的根源。Playwright走了另一条路。它直接通过DevTools ProtocolChrome或类似的私有协议Firefox、WebKit与浏览器内核通信。你可以把它想象成浏览器的“原生遥控器”而不是一个“外部指挥官”。这种架构带来了几个立竿见影的好处执行速度快通信更直接指令传输和执行的延迟更低。稳定性高避免了WebDriver这个中间层可能带来的兼容性问题。功能强大且一致Playwright团队为三大浏览器实现了统一的API你在Chromium上能用的功能比如拦截网络请求、模拟移动设备在Firefox和WebKit上几乎一样能用体验一致。2.2 自动等待告别“time.sleep”的玄学调试这是Playwright让我拍案叫绝的设计。在Selenium里你经常需要这样写element driver.find_element(By.ID, “my-button”) element.click() time.sleep(3) # 祈祷3秒够页面加载完 next_element driver.find_element(By.NAME, “next-step”)time.sleep(3)是种糟糕的实践。设短了元素没加载出来就报错设长了白白浪费执行时间。于是大家开始用“显式等待”WebDriverWait但代码写起来啰嗦。Playwright把“智能等待”做到了骨子里。它的绝大多数操作如click,fill,hover都内置了等待。这个等待不是傻等而是会持续检查目标元素是否达到了“可操作状态”。比如page.click(“button#submit”)Playwright会依次检查元素是否存在DOM中。元素是否可见非隐藏CSS的display: none或visibility: hidden都不行。元素是否稳定位置和大小不再变化。元素是否可交互未被其他元素遮挡且disabled属性为false。只有所有这些条件都满足它才会执行点击。否则它会一直等到超时默认30秒。这几乎根除了因页面加载速度或动画导致的“元素不可交互”错误让脚本的健壮性上了好几个台阶。2.3 浏览器上下文与多页面实现资源隔离与并行这是Playwright另一个强大的抽象概念——BrowserContext浏览器上下文。你可以把它理解为一个独立的“隐身会话”。每个BrowserContext都有独立的cookie、localStorage、sessionStorage和缓存相互之间完全隔离。这个特性太有用了爬虫场景你可以为每个任务创建一个新的context确保账号、登录状态互不干扰。采集完一个网站直接关闭context所有痕迹清除得干干净净。测试场景可以并行运行多个独立的测试用例而不会因为共享cookie导致状态污染。性能在同一个浏览器实例下创建多个context比启动多个独立浏览器进程要轻量得多。在context内部你还可以创建多个Page页面标签页。这些页面共享同一个context的上下文但可以导航到不同的URL实现真正的多页面并行操作。import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) # 创建两个完全隔离的上下文 context1 await browser.new_context() context2 await browser.new_context() page1 await context1.new_page() await page1.goto(“https://example.com/login”) # 在page1上登录... page2 await context2.new_page() await page2.goto(“https://example.com”) # page2是未登录状态与page1完全无关 await browser.close()3. 环境搭建与核心API精讲3.1 一站式环境安装与配置Playwright的安装是我见过最省心的。以Python为例一条命令搞定所有pip install playwright playwright installplaywright install这个命令会自动下载Chromium、Firefox和WebKit三大浏览器的可执行文件放到一个统一的位置。你不需要去各自官网找驱动更不用担心版本匹配问题。如果你想只安装某个浏览器可以用playwright install chromium。注意在某些网络环境下下载浏览器可能会很慢甚至失败。这是因为二进制文件托管在可能访问不畅的地址。解决方法是设置环境变量使用国内镜像源。例如在终端中执行# Linux/macOS export PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwright playwright install # Windows (PowerShell) $env:PLAYWRIGHT_DOWNLOAD_HOST“https://npmmirror.com/mirrors/playwright” playwright install这能极大提升下载速度。Playwright支持同步和异步两种编程模式。对于大多数爬虫和自动化任务我强烈推荐使用异步APIplaywright.async_api。浏览器操作本质上是I/O密集型任务大量时间花在等待网络响应和页面加载上。异步模式可以让你的脚本在等待一个页面操作时去执行其他操作或发起新的请求充分利用系统资源速度比同步模式快得多。3.2 元素定位从基础到高级策略精准定位元素是所有自动化的基石。Playwright提供了丰富且强大的定位器LocatorAPI。基础定位和Selenium类似支持CSS选择器、XPath、文本内容等。# CSS 选择器 await page.click(“button.submit”) await page.fill(“input#username”, “my_name”) # XPath await page.click(“//button[contains(text(), ‘Submit’)]”) # 按文本内容定位非常实用 await page.click(“text‘登录’”) await page.click(“text/Log\s*in/i”) # 支持正则表达式高级定位器Locator这是Playwright的推荐做法。page.locator()返回一个Locator对象它代表一个查询而不是立即找到的元素。这个对象可以链式调用进行更精细的操作和等待。# 创建一个定位器 submit_btn page.locator(“button”, has_text“提交”) # 等待该元素可见 await submit_btn.wait_for(state“visible”) # 点击 await submit_btn.click() # 链式定位寻找包含特定子元素的父元素 row page.locator(“tr”, haspage.locator(“text‘目标数据’”))最佳实践优先使用CSS选择器和文本定位它们通常比XPath更易读、性能更好。使用># 监听所有请求 page.on(“request”, lambda request: print(f“ {request.method} {request.url}”)) # 监听所有响应 page.on(“response”, lambda response: print(f“ {response.status} {response.url}”)) await page.goto(“https://example.com”)拦截并修改请求比如你可以阻止图片加载以加快速度或者修改请求头。await page.route(“**/*.{png,jpg,jpeg,webp,gif}”, lambda route: route.abort()) # 阻断图片 await page.route(“**/api/data”, lambda route: route.continue_(headers{**route.request.headers, “x-custom-header”: “my-value”})) # 修改API请求头直接模拟API响应这对于处理那些数据通过XHR/Fetch动态加载的页面极其有用。你不需要等页面渲染完再抓取可以直接在请求层面拿到数据。async def handle_route(route): # 获取原始请求 request route.request if “/api/list” in request.url: # 伪造一个响应 json_data [{“id”: 1, “name”: “Fake Data”}] await route.fulfill(status200, content_type“application/json”, bodyjson.dumps(json_data)) else: # 其他请求正常继续 await route.continue_() await page.route(“**/api/**”, handle_route) await page.goto(“https://spa-site.com”) # 页面将接收到我们伪造的API数据下载文件Playwright让文件下载变得异常简单。# 等待下载事件开始 async with page.expect_download() as download_info: await page.click(“a#download-link”) # 触发下载 download await download_info.value # 保存到指定路径 await download.save_as(“/path/to/save/file.pdf”)4. 实战案例一对抗反爬的动态内容爬取4.1 场景分析与策略制定假设我们要爬取一个新闻网站它的头条新闻列表是滚动到页面底部时通过JavaScript动态加载的。直接requests.get拿到的HTML里没有这些内容。传统爬虫可能会去分析其XHR请求但接口可能被加密或有复杂的参数。我们的Playwright策略很直接启动浏览器打开目标页面。模拟人类滚动操作触发动态加载。等待新内容出现并提取数据。处理可能的登录、验证码等障碍本例暂不涉及复杂登录。4.2 代码实现与逐行解析import asyncio from playwright.async_api import async_playwright import json async def scrape_dynamic_news(): async with async_playwright() as p: # 1. 启动浏览器建议用chromium兼容性最好 browser await p.chromium.launch(headlessTrue) # 生产环境用headless # 创建一个上下文方便管理cookies和缓存 context await browser.new_context( viewport{‘width’: 1920, ‘height’: 1080}, user_agent‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 …’ # 设置一个真实的UA ) page await context.new_page() # 2. 导航到目标页面 await page.goto(‘https://target-news-site.com’) # 3. 模拟滚动加载。这里假设最多滚动5次或者直到没有新内容 news_items [] seen_titles set() scroll_attempts 0 max_attempts 5 while scroll_attempts max_attempts: # 滚动前记录当前页面新闻条目的数量或最后一个条目的标识 # 这里我们通过获取所有新闻标题元素来判断 current_items await page.locator(‘.news-title’).count() # 模拟滚动到底部 await page.evaluate(“window.scrollTo(0, document.body.scrollHeight)”) # 关键等待新内容加载。这里等待新的.news-title元素出现 # 使用wait_for_function在页面上下文中执行判断 try: await page.wait_for_function(f’document.querySelectorAll(“.news-title”).length {current_items}’, timeout5000) except Exception as e: print(f“第{scroll_attempts1}次滚动后可能没有新内容加载或加载超时: {e}”) break # 没有新内容退出循环 # 等待一小会儿让新加载的内容完全渲染稳定 await page.wait_for_timeout(1000) # 4. 提取当前所有新闻数据 items await page.locator(‘.news-item’).all() # 假设每条新闻在一个.news-item元素内 for item in items: title_elem item.locator(‘.news-title’) if await title_elem.count() 0: title await title_elem.inner_text() if title not in seen_titles: seen_titles.add(title) # 提取其他信息如链接、时间、摘要 link await item.locator(‘a’).get_attribute(‘href’) time await item.locator(‘.time’).inner_text() news_items.append({ ‘title’: title, ‘link’: link, ‘time’: time }) scroll_attempts 1 print(f“已完成第{scroll_attempts}次滚动累计获取{len(news_items)}条新闻。”) # 5. 数据存储 with open(‘news.json’, ‘w’, encoding‘utf-8’) as f: json.dump(news_items, f, ensure_asciiFalse, indent2) print(f“爬取结束共获取{len(news_items)}条唯一新闻。数据已保存到news.json”) # 6. 清理资源 await context.close() await browser.close() # 运行爬虫 asyncio.run(scrape_dynamic_news())4.3 关键技巧与避坑指南wait_for_function与wait_for_selector的选择wait_for_selector是等待一个特定的元素出现。适合等待已知的、确定会出现的元素如加载完成的指示器。wait_for_function是在页面上下文中执行一段JavaScript并等待其返回真值。它更灵活适合判断像“列表长度增加”这种动态条件。在上面的代码中我们用它来判断新闻条目数量是否增长。滚动策略优化单纯的scrollTo可能不够。有些网站使用“虚拟滚动”技术需要滚动到特定的容器内。这时需要调整JavaScript代码# 如果新闻列表在一个id为news-list的div里 await page.evaluate(“document.getElementById(‘news-list’).scrollTop document.getElementById(‘news-list’).scrollHeight”)处理“加载更多”按钮有些网站不是无限滚动而是有一个“加载更多”按钮。策略更简单while await page.locator(‘button:has-text(“加载更多”)’).is_visible(): await page.click(‘button:has-text(“加载更多”)’) await page.wait_for_timeout(2000) # 等待加载 # 提取数据...反反爬要点User-Agent务必设置一个常见的、真实的桌面浏览器UA。Viewport设置一个合理的视窗大小移动端和桌面端网站布局可能不同。减少指纹context.new_context()会给你一个干净的、带默认指纹的上下文。对于一般网站够用。如果遇到高级指纹检测可以考虑使用playwright.chromium.launch_persistent_context来使用一个真实的用户数据目录但这会带入你的浏览历史、插件等信息。速度控制在循环中增加await page.wait_for_timeout(random.uniform(1000, 3000))来模拟人类阅读和操作的不确定性避免请求过于规律被识别为机器人。5. 实战案例二复杂Web应用的端到端自动化测试5.1 测试场景设计假设我们要测试一个在线文档编辑应用类似简化的Google Docs的核心流程用户登录。创建一个新文档。输入文本并格式化如加粗。插入一张图片。分享文档给另一个用户。退出登录。我们将使用Playwright Test Runner一个基于Playwright的测试框架来编写和管理这个测试。它比直接用Playwright API写测试更结构化支持夹具Fixtures、钩子Hooks、断言等。5.2 使用Playwright Test Runner构建健壮测试首先安装测试框架pip install pytest-playwright。然后安装浏览器playwright install。创建一个测试文件test_doc_editor.pyimport re from playwright.sync_api import Page, expect def test_complete_document_workflow(page: Page): “”“端到端测试文档编辑器的核心流程”“” # 1. 登录 page.goto(“https://doc-app.example.com/login”) page.fill(“input[name‘email’]”, “test_userexample.com”) page.fill(“input[name‘password’]”, “secure_password_123”) page.click(“button[type‘submit’]”) # 等待登录成功跳转到仪表盘 expect(page).to_have_url(re.compile(r”.*/dashboard”)) expect(page.locator(“text‘欢迎回来test_user’“)).to_be_visible() # 2. 创建新文档 page.click(“button:has-text(‘新建文档’)”) # 等待文档编辑页面加载 expect(page).to_have_url(re.compile(r”.*/doc/”)) expect(page.locator(“.doc-title”)).to_be_editable() # 3. 输入并格式化文本 editor page.locator(“.ProseMirror”).first # 假设编辑器使用ProseMirror editor.click() page.keyboard.type(“这是测试文档的标题\n”) # 选中刚输入的文字进行加粗 page.keyboard.press(“ShiftArrowUp”) # 模拟选中一行具体快捷键可能因应用而异 page.click(“button[title‘加粗’]”) # 点击工具栏的加粗按钮 # 验证文本是否被加粗 expect(editor.locator(“strong”)).to_have_text(“这是测试文档的标题”) # 4. 插入图片 # 假设通过上传按钮插入 with page.expect_file_chooser() as fc_info: page.click(“button[title‘插入图片’]”) file_chooser fc_info.value await file_chooser.set_files(“./test_image.png”) # 异步API示例同步API用file_chooser.set_files # 等待图片上传并显示 expect(page.locator(“.editor img”)).to_be_visible() # 5. 分享文档 page.click(“button:has-text(‘分享’)”) share_modal page.locator(“[role‘dialog’]”) expect(share_modal).to_be_visible() share_modal.fill(“input[placeholder‘输入邮箱’]”, “collaboratorexample.com”) share_modal.select_option(“select[name‘permission’]”, “can_edit”) share_modal.click(“button:has-text(‘发送邀请’)”) # 验证分享成功提示 expect(page.locator(“text‘邀请已发送’“)).to_be_visible(timeout10000) # 6. 退出登录 page.click(“[data-testid‘user-avatar’]”) page.click(“text‘退出登录’“) expect(page).to_have_url(“https://doc-app.example.com/login”)5.3 测试最佳实践与高级特性使用Fixtures管理状态Playwright Test提供了强大的Fixture如page,context,browser。你还可以自定义Fixture来复用登录状态。# conftest.py import pytest from playwright.sync_api import Page pytest.fixture(scope“session”) def logged_in_page(page: Page): “”“返回一个已登录状态的page fixture”“” page.goto(“/login”) page.fill(“#email”, “testexample.com”) page.fill(“#password”, “password”) page.click(“button[type‘submit’]”) expect(page).to_have_url(“/dashboard”) return page # 在测试中直接使用 def test_with_logged_in_user(logged_in_page): logged_in_page.goto(“/profile”) # ... 已经是登录状态并行测试与隔离Playwright Test默认在不同工作进程中并行运行测试每个测试都获得自己独立的browser context保证了绝对的隔离性不会相互干扰。强大的断言使用expect断言它自动包含智能等待比直接写assert更健壮。# 不推荐 assert page.locator(“text‘成功’“).is_visible() # 可能元素还没出现就断言导致失败 # 推荐 expect(page.locator(“text‘成功’“)).to_be_visible() # 会自动等待元素可见追踪与调试Playwright可以录制测试执行的追踪文件Trace包含每一步操作的截图、网络请求、控制台日志。测试失败时这是绝佳的调试工具。# 运行测试并生成追踪仅在失败时 pytest --tracingon-failure运行后会生成一个trace.zip文件用Playwright的命令行工具打开playwright show-trace trace.zip可以可视化地回放测试的每一步。跨浏览器测试在配置文件中轻松指定多浏览器测试。# playwright.config.py import pytest pytest.mark.parametrize(“browser_name”, [“chromium”, “firefox”, “webkit”]) def test_across_browsers(browser_name, page): # 这个测试会在三个浏览器上各运行一次 page.goto(“https://example.com”) # ...6. 性能优化与大规模部署实战6.1 提升脚本执行效率当你的爬虫或自动化任务需要处理成千上万个页面时性能至关重要。1. 异步并发控制 Playwright的异步API天生适合并发但无限制地打开大量页面会压垮内存。我们需要一个生产者-消费者模型来控制并发度。import asyncio import aiohttp from playwright.async_api import async_playwright async def worker(browser, semaphore, url_queue): “”“工作协程从队列取URL用浏览器处理”“” async with semaphore: # 控制并发数 context await browser.new_context() page await context.new_page() while True: url await url_queue.get() if url is None: # 终止信号 url_queue.task_done() break try: await page.goto(url, timeout60000) # ... 你的处理逻辑 ... print(f“Processed: {url}”) except Exception as e: print(f“Error processing {url}: {e}”) finally: url_queue.task_done() await context.close() async def main(): urls [“https://site.com/page1”, “https://site.com/page2”, …] # 大量URL url_queue asyncio.Queue() for url in urls: await url_queue.put(url) # 添加终止信号 for _ in range(CONCURRENT_TASKS): await url_queue.put(None) async with async_playwright() as p: # 启动一个浏览器实例多个context共享 browser await p.chromium.launch(headlessTrue) semaphore asyncio.Semaphore(10) # 控制最大并发数为10 tasks [] for i in range(CONCURRENT_TASKS): task asyncio.create_task(worker(browser, semaphore, url_queue)) tasks.append(task) await url_queue.join() # 等待所有任务完成 # 取消worker任务 for task in tasks: task.cancel() await asyncio.gather(*tasks, return_exceptionsTrue) await browser.close() CONCURRENT_TASKS 15 # 根据机器性能调整 asyncio.run(main())2. 资源复用与清理复用Browser实例如上所示所有任务共享一个browser实例但创建独立的context。这比每个任务都启动一个浏览器快得多。及时清理每个context用完后要close()每个page也要及时关闭防止内存泄漏。使用async with语句块可以自动管理资源。3. 禁用不必要的资源加载# 创建context时设置可以大幅提升页面加载速度 context await browser.new_context( bypass_cspTrue, # 绕过内容安全策略有时需要 viewport{‘width’: 1920, ‘height’: 1080}, # 拦截不必要的资源 java_script_enabledTrue, # 爬动态页面必须为True has_touchFalse, is_mobileFalse, # 通过route全局拦截 ) # 或者在page层面拦截 await page.route(“**/*.{png,jpg,jpeg,svg,gif,css,woff,woff2}”, lambda route: route.abort())6.2 部署与调度让自动化任务7x24运行1. 无头模式与服务器部署 在服务器Linux上运行务必使用headlessTrue模式。对于没有图形界面的服务器你可能需要安装一些依赖库。# Ubuntu/Debian 系统 sudo apt-get install -y libgbm-dev libnss3 libatk-bridge2.0-0 libdrm-dev libxkbcommon-dev libgtk-3-0 libasound2对于Docker部署Playwright官方提供了镜像mcr.microsoft.com/playwright/python里面已经包含了所有依赖和浏览器。2. 任务调度简单定时使用系统的cronLinux或计划任务Windows。复杂工作流使用CeleryRedis/RabbitMQ构建分布式任务队列。将待处理的URL或任务参数放入队列由多个Worker运行Playwright脚本的进程并发消费。容器化与编排使用Docker封装你的Playwright脚本和环境然后通过Kubernetes或Docker Compose进行编排和伸缩轻松管理大规模爬虫集群。3. 状态持久化与监控存储将爬取结果及时存入数据库如PostgreSQL, MySQL或文件系统避免内存堆积。日志使用Python的logging模块将不同级别的日志INFO, ERROR输出到文件和控制台方便排查问题。监控记录关键指标如任务成功率、平均耗时、内存使用量。可以集成Prometheus和Grafana进行可视化监控。7. 常见问题排查与调试技巧即使Playwright很稳定在实际复杂环境中也会遇到各种问题。这里记录一些我踩过的坑和解决方法。7.1 元素定位失败最常见的问题问题现象可能原因排查与解决TimeoutError: Timeout 30000ms exceeded1. 选择器写错了元素不存在。2. 元素在iframe内。3. 页面加载太慢或元素在动态加载后出现。1.检查选择器在浏览器开发者工具里用$$(“你的选择器”)测试。2.处理iframe先用page.frame_locator(“iframe选择器”)定位到iframe再在里面找元素。3.增加超时或优化等待page.click(“selector”, timeout60000)。或者先page.wait_for_selector(“selector”)。Element is not visible1. 元素被其他元素遮挡。2. 元素CSS属性为visibility: hidden或opacity: 0。3. 元素在视窗外需要滚动。1.强制点击page.click(“selector”, forceTrue)。慎用可能违反交互逻辑。2.滚动到元素await element.scroll_into_view_if_needed()。3.检查遮挡用page.locator(“selector”).bounding_box()看元素坐标用page.screenshot()截图查看。Element is disabled元素确实处于禁用状态。检查前置操作是否完成如勾选协议、填写必填项。有时需要先触发其他事件如focus,input来激活元素。调试技巧在脚本中临时加入page.pause()脚本会在此处进入调试模式并打开Playwright Inspector界面你可以单步执行、查看页面快照、实时测试选择器非常直观。7.2 页面加载与网络问题页面卡在加载中可能是某个资源如第三方JS、广告加载超时。可以设置全局导航超时或拦截有问题的请求。# 设置页面级超时 page.set_default_timeout(60000) page.set_default_navigation_timeout(60000) # 或者在goto时单独设置 await page.goto(url, wait_until“networkidle”, timeout60000) # wait_until可选load, domcontentloaded, networkidle, commitnetworkidle会等待网络活动停止对于SPA单页应用很有效但有时也可能等不到。可以改用domcontentloaded然后自己用wait_for_selector等关键元素。请求被屏蔽或返回403网站可能检测到了Playwright的自动化特征。检查User-Agent确保设置了一个常见的UA。使用stealth模式社区有playwright-stealth这样的插件可以尝试隐藏更多自动化指纹但并非万能。尝试不同的wait_until策略有时networkidle会暴露特征换成domcontentloaded可能绕过。终极方案考虑使用playwright.chromium.launch_persistent_context(user_data_dir“/path/to/profile”)来加载一个真实的、你手动配置好的浏览器用户目录。这会让浏览器看起来更像真人使用。7.3 异步编程中的常见陷阱忘记await这是新手最常犯的错误。Playwright的异步API几乎每个操作都需要await。# 错误 page.goto(“https://example.com”) # 这行会立即返回一个协程对象但不会执行 page.click(“button”) # 因为页面还没加载这里肯定失败 # 正确 await page.goto(“https://example.com”) await page.click(“button”)并发任务相互干扰即使每个任务有自己的context和page如果它们访问同一个网站也可能因为IP、Cookie池等问题被关联。确保做好隔离必要时使用代理IP。context await browser.new_context( proxy{“server”: “http://your-proxy:port”} )异常处理不完善网络环境不稳定必须做好异常处理避免一个页面失败导致整个程序崩溃。try: await page.goto(url, timeout45000) except playwright._impl._errors.TimeoutError: print(f“Timeout loading {url}, skipping...”) await page.close() return None # 或进行重试 except Exception as e: print(f“Unexpected error on {url}: {e}”) await page.close() return None7.4 性能问题排查如果脚本运行越来越慢检查内存是否page和context没有正确关闭使用await page.close()和await context.close()。检查CPU无头模式也会消耗CPU。确保没有陷入死循环或过密集的同步操作。网络瓶颈检查是否是目标网站响应慢或者代理IP速度不行。可以尝试调整并发数Semaphore的值。启用DevTools日志仅限Chromium启动浏览器时添加参数可以看到更底层的日志。browser await p.chromium.launch(headlessTrue, args[‘--enable-logging’, ‘--v1’])Playwright的调试能力很强结合page.screenshot(path“debug.png”)、console.log和Playwright Inspector大部分问题都能定位。关键是要有耐心把大问题分解成小步骤逐一验证。