公司动态

Playwright爬虫实战:从动态内容到反爬策略的完整指南

📅 2026/8/26 11:08:06
Playwright爬虫实战:从动态内容到反爬策略的完整指南
1. 从“请求”到“操控”为什么现代爬虫需要Playwright如果你还在用requests加BeautifulSoup的组合吭哧吭哧地写爬虫遇到需要登录、有复杂交互或者页面元素是动态加载的网站就头疼不已那这篇文章就是为你准备的。我经历过那个阶段为了抓一个数据要手动分析登录接口、处理Cookie、模拟JavaScript执行甚至还得对付各种反爬机制效率低不说代码还脆弱得像纸糊的一样。后来像Selenium这样的浏览器自动化工具出现了它让我们能控制一个真实的浏览器所见即所得很多动态问题迎刃而解。但Selenium也有它的痛点速度相对较慢、对异步操作的支持不够优雅、跨浏览器一致性维护起来费劲。直到我遇到了Playwright。它不是一个简单的替代品而是一个理念的升级。你可以把它理解为一个“浏览器操控大师”它由微软团队开发原生支持 Chromium、Firefox 和 WebKit 三大浏览器引擎。这意味着你用一套 API 写出来的脚本可以几乎无差异地在 Chrome、Edge、Firefox 甚至 Safari 上运行。这解决了跨浏览器测试和爬取的一大难题。但对我们爬虫开发者来说Playwright 的核心吸引力远不止于此。它最厉害的地方在于其“自动化优先”的设计。它内置了对现代 Web 应用的深度支持自动等待元素出现、网络请求拦截与修改、文件上传下载、模拟移动设备、录制操作生成代码……这些功能不再是需要大量额外代码去实现的“高级特性”而是开箱即用的基础能力。特别是处理动态内容和iframePlaywright 提供了极其直观和强大的 API。当页面通过 JavaScript 动态生成内容或者你需要从一个嵌入的 iframe 里抓取数据时Playwright 可以像用户真实操作一样等待所有内容加载完成再执行下一步省去了你手动计算等待时间或者解析复杂 JavaScript 的麻烦。所以Playwright 解决的不仅仅是“爬取”的问题更是“如何像真人一样与网页进行可靠、高效交互”的问题。无论是需要登录的电商网站、数据通过 Ajax 加载的仪表盘、还是充满了 iframe 的复杂后台系统Playwright 都能提供一套稳定、高效的解决方案。接下来我将带你从零开始快速上手 Playwright并深入几个爬虫中最棘手的场景分享我踩过的坑和总结出的实战技巧。2. 环境搭建与第一个脚本告别繁琐配置上手任何新工具最怕第一步就被卡住。Playwright 在易用性上做得相当不错尤其是通过其官方提供的命令行工具可以极大简化环境准备过程。2.1 安装与浏览器部署首先你需要安装 Playwright 的 Python 库。我强烈建议使用虚拟环境来管理项目依赖避免包冲突。# 创建并进入虚拟环境以 venv 为例 python -m venv playwright-env # Windows playwright-env\Scripts\activate # macOS/Linux source playwright-env/bin/activate # 安装 Playwright Python 库 pip install playwright安装完库之后最关键的一步是安装浏览器。Playwright 的一个核心设计是它自带经过特定优化和配置的浏览器版本而不是依赖你系统里安装的 Chrome 或 Firefox。这保证了环境的一致性和 API 的可靠性。安装浏览器非常简单# 安装 Playwright 自带的 Chromium, Firefox 和 WebKit playwright install这条命令会下载所有三个浏览器引擎。如果你确定只使用 Chromium对于大多数爬虫场景Chromium 足够了它兼容 Chrome可以指定安装以节省时间和磁盘空间playwright install chromium这里有个小技巧如果你身处网络环境不太理想的地方下载浏览器可能会很慢甚至失败。Playwright 提供了一些镜像源的支持但最稳定的方式还是通过设置环境变量使用国内镜像。不过根据我的经验直接运行playwright install多数情况下是可行的如果失败可以尝试多次执行或者查阅 Playwright 中文社区的解决方案。2.2 编写并运行“Hello World”环境准备好后我们来写第一个脚本。这个脚本将打开百度首页截图并获取页面标题。虽然简单但包含了最核心的流程。import asyncio from playwright.async_api import async_playwright async def main(): # 启动 Playwright这是一个异步上下文管理器 async with async_playwright() as p: # 启动一个 Chromium 浏览器实例headlessFalse 表示显示浏览器界面 browser await p.chromium.launch(headlessFalse) # 创建一个新的浏览器上下文类似于一个独立的隐身会话 context await browser.new_context() # 在新上下文中打开一个页面 page await context.new_page() # 导航到百度 await page.goto(https://www.baidu.com) # 等待页面加载到“网络空闲”状态这是一个非常实用的等待条件 await page.wait_for_load_state(networkidle) # 截图保存 await page.screenshot(pathbaidu.png) # 获取页面标题并打印 title await page.title() print(f页面标题: {title}) # 关闭浏览器 await browser.close() # 运行异步主函数 asyncio.run(main())把这段代码保存为demo.py并运行。你会看到一个浏览器窗口自动打开访问百度然后关闭。同时当前目录下会生成一张baidu.png的截图控制台会输出“页面标题: 百度一下你就知道”。为什么这么写这里有几个关键点异步 API (async/await): Playwright 强烈推荐使用异步 API因为它能更好地处理大量并发的网络请求和页面操作效率远高于同步模式。虽然它也提供同步 API (from playwright.sync_api import sync_playwright)但在爬虫这种 I/O 密集型任务中异步是更优选择。浏览器上下文 (browser.new_context()): 这是 Playwright 中一个非常重要的概念。一个浏览器实例下可以创建多个独立的上下文。每个上下文拥有独立的Cookie、本地存储和缓存互不干扰。这在爬虫中非常有用比如你可以用一个上下文模拟用户 A 登录用另一个上下文模拟用户 B 登录或者方便地进行多账号操作和数据隔离。wait_for_load_state(networkidle): 这是 Playwright 智能等待的体现。它不会傻等一个固定时间而是等待页面网络活动基本停止通常意味着主要的动态内容已加载完成这比time.sleep()可靠和高效得多。注意初次运行如果遇到关于asyncio在 Windows 上的事件循环问题可以尝试将最后一行改为asyncio.get_event_loop().run_until_complete(main())或者确保你的 Python 版本在 3.7 以上并使用asyncio.run()。3. 核心操作精讲定位、交互与等待能打开页面只是第一步爬虫的核心是获取数据。Playwright 提供了丰富且强大的选择器引擎和页面交互 API。3.1 元素定位不止是 CSS 和 XPath定位元素是自动化的基石。Playwright 支持多种定位策略我最常用的是text和CSS选择器。# 假设 page 已经导航到某个页面 # 1. 通过文本定位非常直观 search_input page.locator(input[namewd]) # CSS 属性选择器 # 或者更精确的等待这个输入框出现 search_input page.locator(#kw) # CSS ID 选择器 await search_input.wait_for() # 等待元素附加到 DOM # 2. 通过文本内容定位对于按钮、链接特别有用 search_button page.locator(text百度一下) # “text” 选择器会匹配元素的文本内容包括子元素的文本。 # 3. 组合定位 # 找到包含特定文本的 div 下的第一个 a 标签 link page.locator(div.item:has-text(新闻) a).first # 4. XPath 定位作为备用当 CSS 选择器不够用时 complex_element page.locator(xpath//div[classlist]/ul/li[1])经验之谈优先使用CSS选择器和text选择器。text选择器在定位带有明确文本的按钮、链接时非常可靠因为它模拟了用户的视角。CSS选择器性能通常优于XPath且更易读。Playwright 的locator对象是惰性的它只是定义了如何查找元素并不会立即发起查找。真正的查找操作发生在你与之交互时如click(),fill()或者调用wait_for()时。这种设计有利于编写更健壮的代码。3.2 模拟用户交互点击、输入与更多定位到元素后就可以模拟用户操作了。# 在搜索框输入内容 await search_input.fill(Playwright 爬虫教程) # fill() 方法会先清空输入框再输入文本。 # 点击搜索按钮 await search_button.click() # click() 会等待元素可点击可见、未被禁用、在视口内后再点击。 # 模拟键盘操作比如按回车 await search_input.press(Enter) # 处理下拉选择框 await page.select_option(select#city, label北京) # 通过可见文本选择 await page.select_option(select#city, valuebeijing) # 通过 value 属性选择 # 上传文件非常简单 file_input page.locator(input[typefile]) await file_input.set_input_files(path/to/your/file.pdf) # Playwright 会模拟整个文件选择对话框的过程。 # 鼠标悬停 menu page.locator(#main-menu) await menu.hover() # 悬停后可能会显示子菜单可以继续操作避坑指南关于click()有一个常见问题。如果页面在点击后发生了导航比如跳转到新页面直接await button.click()可能会导致后续操作在旧页面上执行而失败。Playwright 提供了两种处理方式使用wait_for_navigation:async with page.expect_navigation(): await button.click() # 点击会触发导航 # 导航完成后才会执行这里的代码让click()自动处理推荐:await button.click() # Playwright 会自动等待导航完成 # 但前提是 button 的点击确实会触发导航。对于普通的 Ajax 点击则不需要。更通用的做法是在可能引起页面状态变化的操作后使用page.wait_for_load_state()或等待某个特定元素出现。3.3 智能等待让爬虫更稳定不稳定的等待是自动化脚本失败的主要原因。Playwright 的等待策略是其最大亮点之一。# 1. 自动等待几乎所有交互方法click, fill, check等都内置了自动等待。 # 它们会等待元素可操作最多等待 timeout 毫秒默认30秒。 await page.click(button.submit) # 自动等待按钮出现并可点击 # 2. 显式等待使用 page.wait_for_* 系列函数。 # 等待选择器匹配的元素出现 await page.wait_for_selector(div.result-item, statevisible) # 等待选择器匹配的元素从 DOM 中消失 await page.wait_for_selector(div.loading, statehidden) # 等待特定事件比如导航完成 await page.wait_for_url(**/search**) # 等待 URL 包含 /search await page.wait_for_load_state(domcontentloaded) # 等待 DOM 内容加载完毕 await page.wait_for_load_state(networkidle) # 等待网络空闲推荐 # 3. 等待函数最灵活的方式等待某个条件成立。 # 等待直到页面标题包含特定文本 await page.wait_for_function(document.title.includes(搜索结果)) # 等待直到某个元素的数量达到预期 await page.wait_for_function(() { const items document.querySelectorAll(.list-item); return items.length 10; })核心技巧networkidle是我在爬取单页应用 (SPA) 或 Ajax 加载页面时最常用的状态。但它有个小陷阱有些网站会有持续的心跳请求或 WebSocket 连接导致网络永远达不到“空闲”。这时可以结合wait_for_selector来等待一个标志性内容如“加载完成”的提示消失或数据列表出现更为可靠。不要过度依赖time.sleep那会让你的脚本既慢又脆弱。4. 攻克爬虫难点动态内容、iframe 与反爬策略现在我们来解决那些让传统爬虫头疼的问题。4.1 捕获动态加载的数据很多现代网站的数据不会直接写在初始 HTML 里而是通过 JavaScript 发起 Ajax 请求后动态渲染。Playwright 有两种主流应对方式。方法一等待渲染后直接抓取 DOM这是最直观的方法利用 Playwright 强大的等待能力等数据出现在页面上再抓取。# 假设点击“加载更多”按钮后会动态添加新的 .item 元素 load_more_button page.locator(button:has-text(加载更多)) while await load_more_button.is_visible(): await load_more_button.click() # 等待新项目出现。这里假设每次点击会加载10个新项目。 # 我们等待第10个新项目的占位符出现假设有.loading类加载完成后消失 # 更通用的做法是等待列表的最后一个元素发生变化或数量增加。 try: # 等待加载动画消失并且新的项目元素稳定出现 await page.wait_for_selector(div.loading-spinner, statehidden, timeout5000) await page.wait_for_timeout(1000) # 额外给一点时间让渲染完成 except: print(可能没有更多内容了或加载超时) break # 现在所有数据应该都在页面上了 all_items await page.locator(.item).all() for item in all_items: title await item.locator(.title).text_content() print(title)方法二直接监听和拦截网络请求更高效如果数据是通过明确的 API 接口通常是 XHR/Fetch返回的 JSON直接截获这个请求往往更高效省去了渲染和解析 DOM 的 overhead。# 定义一个列表来收集数据 captured_data [] # 监听并处理特定的网络响应 def handle_response(response): if /api/data/list in response.url: # 匹配目标 API try: json_data response.json() # 假设返回的 JSON 结构中有个 data 数组 captured_data.extend(json_data[data]) print(f截获到 {len(json_data[data])} 条数据) except: print(f解析响应失败: {response.url}) # 添加监听器 page.on(response, handle_response) # 然后执行你的页面操作比如点击翻页、滚动等 await page.goto(https://example.com/list) await page.click(#load-more) await page.wait_for_timeout(3000) # 给网络请求一些时间 # 操作完成后移除监听器以避免内存泄漏或干扰后续操作 page.remove_listener(response, handle_response) print(f总共收集到 {len(captured_data)} 条数据)对比与选择方法一更通用适用于任何最终会在页面上显示的数据即使它是通过复杂 JS 计算生成的。方法二更高效、更精准能直接拿到结构化数据但需要你事先知道 API 的地址和参数规律可以通过浏览器的开发者工具 Network 面板观察。对于爬虫来说我通常会先尝试方法二如果接口加密复杂或数据不完整再回退到方法一。4.2 征服 iframe进入嵌套的窗口iframe 是另一个常见的爬虫障碍。你需要先切换到 iframe 的上下文中才能操作其中的元素。# 1. 通过属性定位 iframe 元素 frame_element page.locator(iframe[srcinner.html]) # 或者通过标题、名称等 # frame_element page.locator(iframe[namecontent]) # 2. 获取 iframe 的 Frame 对象 content_frame await frame_element.content_frame() if content_frame: # 3. 现在可以在 iframe 内部操作了 # 注意所有操作都要在 content_frame 上调用而不是 page input_inside_iframe content_frame.locator(#inner-input) await input_inside_iframe.fill(Hello from inside iframe!) button_inside_iframe content_frame.locator(textSubmit) await button_inside_iframe.click() # 4. 如果需要切回主页面 # 实际上你只需要后续的操作继续在 page 对象上调用即可。 main_page_button page.locator(#main-button) await main_page_button.click() else: print(未能获取到 iframe 内容)关键点content_frame()方法是关键。它返回一个Frame对象这个对象拥有和Page对象类似的 API如locator,click,fill。一定要确保在 iframe 完全加载后再获取content_frame否则可能为None。稳妥的做法是await frame_element.wait_for_element_state(stable) # 等待 iframe 元素稳定 # 或者等待 iframe 内的某个特定元素出现 await page.wait_for_selector(iframe[srcinner.html] nth0 textLoading, statehidden) content_frame await frame_element.content_frame()4.3 应对常见反爬措施Playwright 本身并不能“绕过”所有反爬但它提供的工具能让我们更接近真实用户的行为从而降低被封锁的风险。1. 模拟真人行为模式反爬系统会检测自动化脚本的规律性操作。我们可以加入随机性和人性化延迟。import random import asyncio async def human_like_delay(min_ms500, max_ms2000): 模拟人类操作的不确定性延迟 delay random.randint(min_ms, max_ms) / 1000.0 await asyncio.sleep(delay) async def human_like_type(page, selector, text): 模拟人类打字逐个字符输入并有随机间隔 element page.locator(selector) await element.click() for char in text: await element.press(char) await human_like_delay(50, 150) # 每个字符间隔 50-150 毫秒 # 使用示例 await human_like_type(page, #search-box, Playwright Tutorial) await human_like_delay(1000, 3000) # 等待1-3秒再点击 await page.locator(#search-button).click()2. 管理 Cookie 和会话对于需要登录的网站妥善管理会话是关键。# 创建一个持久化的上下文其状态如 Cookie会保存到文件 import os from pathlib import Path context_dir Path(./playwright_contexts/user1) context_dir.mkdir(parentsTrue, exist_okTrue) # 启动浏览器时指定用户数据目录 browser await p.chromium.launch_persistent_context( user_data_dirstr(context_dir), headlessFalse ) # 注意launch_persistent_context 返回的是 BrowserContext 对象而不是 Browser # 它已经包含了默认的 page page browser.pages[0] if browser.pages else await browser.new_page() await page.goto(https://website-need-login.com) # 第一次需要手动登录... # 登录成功后Cookie 会自动保存在 user_data_dir 中。 # 下次再启动这个上下文就已经是登录状态了。 # 关闭时不需要特别操作Cookie 已持久化。3. 设置更真实的浏览器环境一些反爬会检测浏览器指纹。Playwright 可以模拟不同的设备、视口、语言、时区等。# 在创建上下文时进行配置 context await browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., localezh-CN, timezone_idAsia/Shanghai, # 可以覆盖地理位置需要浏览器支持 geolocation{latitude: 39.9042, longitude: 116.4074}, permissions[geolocation] # 授予地理位置权限 )4. 处理验证码这是一个终极难题。Playwright 无法直接破解复杂的验证码如滑块、点选图。策略包括避免触发通过维护优质 IP 池、控制访问频率、使用持久化会话来尽量减少触发验证码的概率。人工介入在headlessFalse模式下如果弹出验证码脚本暂停等待用户手动完成。第三方服务集成打码平台 API。当检测到验证码出现时例如通过截图识别特定元素将图片发送到打码平台获取结果再通过 Playwright 输入。# 伪代码示例 captcha_img page.locator(#captcha-image) await captcha_img.screenshot(pathcaptcha.png) # 调用打码平台 API 识别 captcha.png得到 code # code call_ocr_service(captcha.png) await page.fill(#captcha-input, code)重要提示任何爬虫行为都必须遵守网站的robots.txt协议尊重版权和数据隐私控制访问频率避免对目标网站造成过大负担。技术本身无罪但使用方式需合乎规范。5. 高级技巧与性能优化当你能用 Playwright 完成基本爬取后下面这些技巧能让你的脚本更健壮、更高效。5.1 并行与并发同时处理多个页面异步编程的优势在于可以轻松实现并发。我们可以同时控制多个页面或浏览器上下文来加速数据采集。import asyncio from playwright.async_api import async_playwright async def scrape_one_page(context, url): 在一个独立的上下文中抓取一个页面 page await context.new_page() try: await page.goto(url) await page.wait_for_load_state(networkidle) # ... 执行你的抓取逻辑 ... title await page.title() print(f抓取成功: {title}) return {url: url, title: title} except Exception as e: print(f抓取失败 {url}: {e}) return None finally: await page.close() async def main(): urls [https://example.com/1, https://example.com/2, https://example.com/3] async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) # 无头模式更快 # 为每个任务创建一个独立的上下文实现完全隔离 tasks [] for url in urls: context await browser.new_context() task asyncio.create_task(scrape_one_page(context, url)) tasks.append(task) # 注意这里创建了多个上下文每个上下文有自己的 cookies/cache results await asyncio.gather(*tasks) # 清理 for task in tasks: context task._context # 获取任务对应的context示例实际需更好管理 await context.close() await browser.close() print(f所有任务完成结果: {results}) asyncio.run(main())并发控制不要一次性打开成百上千个页面这会耗尽内存和网络资源。可以使用asyncio.Semaphore来控制最大并发数。semaphore asyncio.Semaphore(5) # 最大并发5个 async def bounded_scrape(context, url): async with semaphore: # 控制同时进入的协程数量 return await scrape_one_page(context, url)5.2 请求拦截与修改精准控制网络Playwright 允许你在请求发出前或响应返回后对其进行拦截和修改这对于模拟特定条件、屏蔽不必要的资源如图片、样式表以加速爬取或注入脚本非常有用。# 拦截请求阻止某些资源加载以提升速度 await page.route(**/*.{png,jpg,jpeg,svg,gif,css,woff,woff2}, lambda route: route.abort()) # 只允许文档和脚本加载 # await page.route(**/*, lambda route: route.abort() if route.request.resource_type in [image, stylesheet, font] else route.continue_()) # 修改请求头例如添加一个自定义头 async def modify_header(route): headers route.request.headers headers[x-custom-header] my-playwright-bot await route.continue_(headersheaders) await page.route(**/api/**, modify_header) # 只对 API 请求修改 # 修改响应内容高级用法谨慎使用 async def modify_response(route, request): response await route.fetch() # 先获取原始响应 body await response.text() # 对 body 进行修改例如替换某些文本 modified_body body.replace(Old Text, New Text) await route.fulfill( responseresponse, bodymodified_body, headers{**response.headers, Content-Type: text/html} ) # 注意route.fulfill 需要较新版本的 Playwright # await page.route(**/some-page.html, lambda route: modify_response(route, route.request))5.3 代码生成与录制快速生成脚本草稿如果你不确定某个操作该如何用代码实现Playwright 提供了一个强大的命令行工具playwright codegen。# 在终端运行会打开一个浏览器和一个录制器 playwright codegen https://example.com然后你手动在打开的浏览器里进行操作点击、输入等右侧的录制器会实时生成对应的 Python或其他语言代码。这是一个绝佳的学习工具和快速原型构建工具。不过生成的代码往往比较冗长需要你进行精简和优化比如合并连续的操作、添加更智能的等待、提取公共逻辑等。5.4 调试与日志让问题无处遁形脚本出错了怎么办Playwright 提供了丰富的调试手段。慢动作和可视化在非无头模式 (headlessFalse) 下运行你可以亲眼看到每一步操作。通过slow_mo参数还可以让操作慢下来方便观察。browser await p.chromium.launch(headlessFalse, slow_mo500) # 每个操作间隔500毫秒录制视频自动化运行过程可以被录制成视频这对于调试偶发问题或记录错误场景非常有用。context await browser.new_context(record_video_dirvideos/) page await context.new_page() # ... 你的操作 ... await context.close() # 关闭上下文时会自动保存视频控制台日志可以监听页面的console和pageerror事件捕获 JavaScript 的console.log和错误信息。def on_console(msg): print(f页面日志: {msg.type}: {msg.text}) page.on(console, on_console) def on_pageerror(error): print(f页面错误: {error}) page.on(pageerror, on_pageerror)网络日志监听request和response事件分析网络活动。def on_request(request): print(f {request.method} {request.url}) page.on(request, on_request) def on_response(response): print(f {response.status} {response.url}) page.on(response, on_response)6. 项目结构建议与常见问题排查当你开始编写更复杂的爬虫项目时一个好的代码结构能让你事半功倍。6.1 一个可维护的爬虫项目结构my_playwright_crawler/ ├── config.py # 配置文件放URL、选择器、超时时间等 ├── main.py # 主程序入口 ├── core/ │ ├── browser.py # 浏览器启动、上下文管理的封装 │ ├── scraper.py # 核心抓取逻辑的基类或工具函数 │ └── storage.py # 数据存储文件、数据库的封装 ├── spiders/ # 各个网站的爬虫 │ ├── __init__.py │ ├── example_spider.py │ └── another_spider.py ├── utils/ │ ├── logger.py # 日志配置 │ ├── retry.py # 重试装饰器 │ └── helpers.py # 通用辅助函数如随机延迟、解析日期 └── requirements.txt # 项目依赖封装浏览器管理在core/browser.py中可以封装一个函数来统一创建和配置浏览器上下文处理代理、用户数据目录、视图大小等。# core/browser.py async def create_browser_context(use_proxyFalse, user_data_dirNone): playwright await async_playwright().start() launch_options {headless: True} if use_proxy: launch_options[proxy] {server: http://your-proxy:port} browser await playwright.chromium.launch(**launch_options) context_options {} if user_data_dir: context_options[user_data_dir] user_data_dir context await browser.new_context(**context_options) return playwright, browser, context6.2 高频问题与解决方案问题1TimeoutError: Timeout 30000ms exceeded.这是最常见的错误意味着某个操作如等待选择器、点击超时了。检查选择器首先确认你的选择器在当前页面是否有效。使用headlessFalse模式打开开发者工具 (F12) 检查元素。检查页面状态操作前页面是否已加载完成在goto或可能导致导航的click后加上page.wait_for_load_state(networkidle)。增加超时时间对于加载慢的页面可以临时增加超时参数如await page.click(button, timeout60000)。检查元素状态元素是否被隐藏 (display: none)、被覆盖、或者不在可视区域内click要求元素可操作。可以尝试先滚动到元素所在位置await element.scroll_into_view_if_needed()。问题2Target closed或Execution context was destroyed这通常意味着你试图在一个已经关闭的页面或浏览器上下文上执行操作。检查生命周期确保你的page或browser对象在操作时仍然有效。避免在async with块外部使用它们。妥善处理异常在try...except...finally块中确保资源被正确关闭。避免循环引用在异步任务中确保没有意外的引用阻止了垃圾回收。问题3脚本被网站检测为自动化工具启用stealth模式社区有playwright-stealth这样的插件可以应用一些反检测补丁。但需注意其可能随浏览器版本更新而失效。检查navigator.webdriver在无头模式下这个属性通常为true。你可以通过注入脚本修改它但高级反爬可能会检测到修改痕迹。await page.add_init_script( Object.defineProperty(navigator, webdriver, { get: () undefined }); )终极方案使用真人浏览器配置文件 (user_data_dir)并尽量模拟真人操作节奏这是最难以被检测的方式。问题4内存泄漏长时间运行爬虫特别是并发很多页面时可能会内存增长。及时关闭页面和上下文每个任务完成后显式调用await page.close()和await context.close()。限制并发数如前所述使用Semaphore。定期重启浏览器对于需要运行数小时甚至数天的任务可以设定每处理一定数量的任务后就完全关闭并重启浏览器实例。Playwright 为现代 Web 爬虫打开了一扇新的大门。它把我们从繁琐的 HTTP 请求构造、Cookie 管理、JavaScript 逆向中解放出来让我们能更专注于数据获取的逻辑本身。从简单的页面抓取到复杂的交互式应用自动化它都是一个极其强大的工具。上手初期可能会遇到一些异步编程或选择器的问题但一旦熟悉其模式开发效率会得到质的提升。记住爬虫的世界里稳定性和可维护性往往比炫技更重要。构建健壮的异常处理、合理的重试机制、清晰的代码结构和掌握 Playwright 的 API 同样关键。