1. 项目概述为什么是Playwright如果你正在寻找一个能应对现代复杂Web应用的爬虫或自动化工具并且已经厌倦了Selenium的配置繁琐和Puppeteer的JavaScript绑定那么Playwright的出现很可能就是为你准备的。我最初接触Playwright是在一个需要爬取大量动态渲染内容比如电商价格、社交媒体动态的项目中传统的requestsBeautifulSoup组合在JavaScript渲染的内容面前彻底失效而Selenium的稳定性和速度又让人头疼。Playwright以其“多浏览器、快如闪电、API优雅”的特点迅速成为了我的主力工具。简单来说Playwright是一个由微软开源的浏览器自动化库。它最核心的优势在于原生支持Chromium、Firefox和WebKit三大浏览器引擎并且为它们提供了统一的API。这意味着你用同一套代码就能控制Chrome、Firefox和Safari内核浏览器进行自动化操作。对于爬虫而言这带来了几个立竿见影的好处一是规避检测能力更强你可以轮换使用不同浏览器内核降低被目标网站识别为自动脚本的风险二是可靠性极高Playwright自动等待元素、网络请求完成的机制让脚本健壮性大大提升三是性能出色其底层通信协议比Selenium的WebDriver高效得多启动和操作速度更快。这个教程的目标就是带你从零开始掌握使用Python版本的Playwright进行网络爬虫开发。无论你是想自动化填报网页表单、抓取需要登录才能查看的数据还是应对反爬虫策略日益严密的单页面应用SPAPlaywright都能提供一套优雅的解决方案。我们将从环境搭建开始一步步深入到实战技巧和高级应用最终让你能独立编写稳定、高效的爬虫脚本。2. 环境准备与核心概念解析2.1 安装与初始化一步到位Playwright的安装过程非常简洁这得益于其优秀的工具链设计。首先你需要确保已经安装了Python建议3.7及以上版本。然后通过pip进行安装pip install playwright安装完Python库后Playwright还需要下载它要控制的浏览器内核。这是通过其内置的CLI工具完成的playwright install这条命令会默认下载Chromium、Firefox和WebKit三个浏览器的最新稳定版本。如果你只想安装其中某一个比如只需要Chromium可以运行playwright install chromium。下载的浏览器会存放在用户目录下的缓存中与系统安装的浏览器互不干扰。注意playwright install这一步是必须的很多新手会忘记导致运行时报错找不到浏览器。另外由于需要从网络下载几百兆的浏览器文件请确保网络通畅。安装完成后让我们通过一个最简单的脚本来验证环境并理解几个核心对象from playwright.sync_api import sync_playwright def main(): with sync_playwright() as p: # 1. 启动浏览器 browser p.chromium.launch(headlessFalse) # headlessFalse 表示显示浏览器窗口 # 2. 创建浏览器上下文 (Context) context browser.new_context() # 3. 打开新页面 (Page) page context.new_page() # 4. 导航到目标网址 page.goto(https://www.example.com) # 5. 获取页面标题并打印 print(page.title()) # 6. 关闭资源 page.close() context.close() browser.close() if __name__ __main__: main()这段代码揭示了Playwright最核心的三个对象Browser代表一个浏览器实例。通过launch()方法启动你可以在这里配置是否无头模式、代理、窗口大小等。BrowserContext浏览器上下文。这可以理解为一个独立的隐身会话。每个Context拥有独立的cookie、缓存和本地存储相互隔离。这在爬虫中极其有用例如你可以为每个任务创建独立的Context防止数据污染或者模拟多个用户同时操作。Page代表一个标签页。我们绝大部分的自动化操作点击、输入、抓取数据都是在Page对象上进行的。2.2 同步与异步API的选择你可能注意到了上面的导入是from playwright.sync_api import ...。Playwright提供了两套API同步和异步。对于爬虫这种I/O密集型任务我强烈推荐使用异步API。它能让你在等待页面加载、元素出现时不会阻塞整个程序可以同时管理多个页面显著提升爬取效率。下面是上面例子的异步版本import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) # 通常爬虫用无头模式 context await browser.new_context() page await context.new_page() await page.goto(https://www.example.com) print(await page.title()) await page.close() await context.close() await browser.close() asyncio.run(main())从同步切换到异步主要是将with换成async with方法调用前加上await以及使用asyncio.run()来运行主函数。本教程后续的示例将主要使用异步API因为这才是Playwright在爬虫领域的威力所在。3. 核心操作定位、交互与数据提取3.1 元素定位多种选择器策略与页面交互的第一步是找到元素。Playwright支持丰富的选择器引擎远超简单的CSS选择器或XPath。CSS选择器 XPath最基础也是最常用的。# CSS 选择器 element await page.query_selector(div.content h1) # XPath element await page.query_selector(xpath//button[idsubmit])文本选择器通过元素文本内容来定位对于按钮、链接非常方便。# 点击文本为“登录”的按钮 await page.click(text登录) # 点击文本内容包含“下一步”的元素 await page.click(text下一步)Playwright专属选择器非常强大能根据元素属性进行精准定位。[rolebutton]定位具有特定ARIA角色的元素。:has-text(xxx)定位内部包含特定文本的元素。:near(:text(xxx), 200)定位距离某个文本元素200像素范围内的元素。实操心得不要过度依赖XPath尤其是绝对路径的XPath因为页面结构微调就会导致脚本失效。优先使用有语义的CSS选择器如ID、Class或文本选择器。对于动态生成的复杂元素可以组合使用:has()和:text()等伪类定位更精准。3.2 页面交互模拟真实用户定位到元素后就可以进行交互了。Playwright的API设计非常直观。# 输入文本 await page.fill(input#username, my_username) # 点击元素 await page.click(button#submit) # 勾选复选框 await page.check(input[typecheckbox]) # 选择下拉框选项 await page.select_option(select#city, valuebeijing) # 上传文件 await page.set_input_files(input[typefile], path/to/file.pdf) # 鼠标悬停 await page.hover(nav .menu-item) # 键盘操作 await page.press(body, ControlA) # 全选自动等待机制这是Playwright的一大亮点。像click(),fill()这类方法内部都包含了充分的等待。它会等待元素变得可交互可见、未被禁用、稳定等后再执行操作并等待相关的网络请求完成。这省去了我们手动编写time.sleep()或显式等待的麻烦让脚本异常稳定。3.3 数据提取获取文本、属性和动态内容提取数据是爬虫的最终目的。Playwright提供了多种方式。# 1. 获取单个元素的文本或属性 title await page.text_content(h1) link_url await page.get_attribute(a.download, href) # 2. 获取多个元素返回ElementHandle列表 all_items await page.query_selector_all(.product-list li) for item in all_items: name await item.text_content() price_elem await item.query_selector(.price) price await price_elem.text_content() if price_elem else N/A print(name, price) # 3. 直接通过Page对象执行JavaScript终极武器 # 这对于提取复杂数据结构或操作DOM非常有用 dimensions await page.evaluate(() { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, deviceScaleFactor: window.devicePixelRatio, }; }) print(dimensions) # 更常见的提取页面中所有商品信息到一个列表 products_data await page.evaluate(() { const items Array.from(document.querySelectorAll(.product-item)); return items.map(item ({ name: item.querySelector(.name).innerText, price: item.querySelector(.price).innerText, link: item.querySelector(a).href })); })page.evaluate()是在浏览器上下文执行JavaScript代码并将结果返回到Python端。你可以直接操作页面的全局对象如document,window这对于处理那些数据藏在JavaScript变量里、或者需要复杂计算才能得到的情况是无可替代的。4. 高级爬虫实战技巧4.1 处理弹窗、新窗口与iframe现代网页充满了各种弹窗和嵌入式内容。对话框alert, confirm, promptPlaywright可以监听并接受或取消它们。# 监听对话框事件并自动接受 page.on(dialog, lambda dialog: dialog.accept()) await page.click(button#trigger-alert) # 点击触发alert的按钮弹出新窗口Popup通过监听popup事件来捕获。async with page.expect_popup() as popup_info: await page.click(a[target_blank]) # 点击打开新标签页的链接 popup_page await popup_info.value # 现在可以在 popup_page 上操作了 await popup_page.close()iframe内嵌框架需要先定位到iframe元素然后获取其对应的Frame对象。# 通过选择器定位iframe元素 frame_element await page.query_selector(iframe#preview) # 获取Frame对象 frame await frame_element.content_frame() # 在Frame内操作 text_in_iframe await frame.text_content(body)4.2 网络请求拦截与模拟控制网络请求是高级爬虫和测试的必备技能。你可以拦截请求进行修改或者拦截响应以获取数据。# 1. 路由Route拦截并修改请求或提供模拟响应 await page.route(**/*.{png,jpg,jpeg}, lambda route: route.abort()) # 拦截图片请求并中止加快爬取速度 await page.route(https://api.example.com/data, lambda route: route.fulfill( status200, content_typeapplication/json, bodyjson.dumps({mock: data}) # 返回模拟数据 )) # 2. 监听请求/响应事件直接抓取API数据这是爬虫大杀器 async def log_response(response): if /api/products in response.url: try: json_data await response.json() print(f抓到API数据: {json_data}) # 这里可以将json_data保存下来 except: pass page.on(response, log_response) await page.goto(https://some-ecommerce-site.com) # 页面加载过程中发出的所有API请求的响应都会被log_response函数处理通过监听response事件你可以直接拿到Ajax请求返回的原始JSON数据这比从渲染后的HTML中解析要高效和准确得多也是应对动态网站反爬的利器。4.3 身份验证与状态保持爬取需要登录的网站关键在于保存和复用登录状态。import json import asyncio from playwright.async_api import async_playwright async def login_and_save_state(): async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) context await browser.new_context() page await context.new_page() await page.goto(https://target-site.com/login) await page.fill(#username, your_username) await page.fill(#password, your_password) await page.click(#login-btn) # 等待登录成功后的跳转或某个登录后特有的元素出现 await page.wait_for_selector(.user-avatar, timeout10000) # 关键步骤将当前上下文包含cookies, local storage的状态保存到文件 await context.storage_state(pathauth_state.json) await browser.close() async def use_saved_state(): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) # 启动浏览器时直接加载之前保存的状态文件 context await browser.new_context(storage_stateauth_state.json) page await context.new_page() # 此时访问网站已经是登录状态 await page.goto(https://target-site.com/dashboard) print(await page.title()) # 应该显示用户仪表盘 # ... 进行后续爬取操作 await browser.close() # 第一次运行登录并保存状态 # asyncio.run(login_and_save_state()) # 后续运行直接使用保存的状态无需再次登录 asyncio.run(use_saved_state())storage_state功能非常强大它保存了cookies、localStorage和sessionStorage。这意味着你只需要登录一次就可以在后续的脚本中无限次复用这个登录会话极大提高了效率。4.4 反反爬虫策略集成Playwright本身就能绕过很多基础的检测因为它模拟的是真实的浏览器。但要应对更高级的防护还需要一些策略。随机化User-Agent和视窗每次创建Context或Page时进行随机化。import random user_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..., Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ..., # ... 更多UA ] viewports [ {width: 1920, height: 1080}, {width: 1366, height: 768}, {width: 1536, height: 864}, ] context await browser.new_context( user_agentrandom.choice(user_agents), viewportrandom.choice(viewports), # 还可以随机化地理位置、时区等 localezh-CN, timezone_idAsia/Shanghai, )使用代理IP在启动浏览器或创建上下文时配置代理。browser await p.chromium.launch( headlessTrue, proxy{ server: http://your-proxy-server:port, username: user, # 如果需要认证 password: pass } )控制操作频率模拟人类行为在关键操作之间加入随机延迟并模拟人类的鼠标移动轨迹。import time from random import uniform async def human_like_click(page, selector): element await page.wait_for_selector(selector) box await element.bounding_box() # 模拟鼠标移动不是直线而是带有一点随机偏移 await page.mouse.move( box[x] box[width] / 2 uniform(-5, 5), box[y] box[height] / 2 uniform(-5, 5), stepsrandom.randint(10, 20) # 移动步数使其看起来不是瞬移 ) await page.wait_for_timeout(uniform(100, 500)) # 随机停顿一下再点击 await page.click(selector)利用BrowserContext隔离为每个任务或每批请求使用独立的BrowserContext。即使某个站点的cookie或指纹被标记也不会影响到其他Context中的任务。5. 工程化与性能优化5.1 结构化你的爬虫项目当爬虫逻辑变复杂时良好的代码结构至关重要。my_playwright_crawler/ ├── config.py # 配置文件代理、UA列表、目标URL等 ├── auth_state.json # 保存的登录状态.gitignore忽略 ├── main.py # 主程序入口 ├── core/ │ ├── browser_manager.py # 浏览器/上下文管理单例或池化 │ └── utils.py # 工具函数随机延迟、数据处理等 ├── spiders/ │ ├── base_spider.py # 爬虫基类封装通用方法 │ ├── spider_a.py # 针对A网站的爬虫 │ └── spider_b.py # 针对B网站的爬虫 └── items/ └── data_pipeline.py # 数据清洗、验证、存储管道浏览器管理示例简易池化# core/browser_manager.py import asyncio from playwright.async_api import async_playwright class BrowserPool: def __init__(self, pool_size3): self.pool_size pool_size self.browsers [] self._lock asyncio.Lock() async def init(self): self.playwright await async_playwright().start() for _ in range(self.pool_size): browser await self.playwright.chromium.launch(headlessTrue) self.browsers.append(browser) async def acquire_browser(self): async with self._lock: while not self.browsers: await asyncio.sleep(0.1) return self.browsers.pop() async def release_browser(self, browser): async with self._lock: self.browsers.append(browser) async def close(self): for browser in self.browsers: await browser.close() await self.playwright.stop() # 在爬虫中使用 pool BrowserPool(3) await pool.init() browser await pool.acquire_browser() try: context await browser.new_context() page await context.new_page() # ... 执行任务 finally: await context.close() await pool.release_browser(browser)5.2 并发爬取与性能瓶颈Playwright的异步API天生适合并发。但并发不是简单的开很多个Page因为每个Page仍然共享同一个浏览器进程的资源CPU、内存。更高效的并发是启动多个浏览器实例或使用多个独立的BrowserContext。import asyncio from playwright.async_api import async_playwright async def crawl_task(url, context): 单个爬取任务 page await context.new_page() try: await page.goto(url, timeout60000) # ... 数据提取逻辑 data await page.text_content(h1) return data finally: await page.close() async def main(): urls [https://site.com/1, https://site.com/2, ...] # 很多URL async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) # 创建多个相互隔离的上下文模拟多个独立用户会话 contexts [await browser.new_context() for _ in range(5)] tasks [] # 简单的轮询分配URL到各个上下文 for i, url in enumerate(urls): context contexts[i % len(contexts)] task asyncio.create_task(crawl_task(url, context)) tasks.append(task) results await asyncio.gather(*tasks, return_exceptionsTrue) # 处理结果... for ctx in contexts: await ctx.close() await browser.close() asyncio.run(main())性能瓶颈排查CPU/内存使用无头模式(headlessTrue)通常资源占用更低。监控系统资源如果不足减少并发浏览器实例数。网络拦截不必要的资源如图片、样式表、字体可以大幅提升页面加载速度。使用page.route进行拦截。目标网站限制过快的请求会导致IP被封。必须在并发数、请求间隔之间取得平衡并准备好代理IP池。5.3 错误处理与健壮性网络爬虫运行在复杂多变的网络环境中健壮的错误处理是保证长期稳定运行的关键。async def robust_crawler(page, url, max_retries3): for attempt in range(max_retries): try: # 设置每次尝试的超时 response await page.goto(url, timeout30000, wait_untilnetworkidle) if response and response.ok: # 检查页面内容是否正常加载例如是否有错误提示 if await page.query_selector(.error-msg): raise Exception(页面显示错误信息) # ... 正常处理逻辑 return True else: raise Exception(f页面加载失败状态码: {response.status if response else 未知}) except Exception as e: print(f第{attempt1}次尝试失败错误: {e}) if attempt max_retries - 1: print(fURL {url} 重试{max_retries}次后仍失败放弃。) # 可以将失败URL记录到文件后续重试 return False await asyncio.sleep(2 ** attempt) # 指数退避等待 # 可以尝试刷新页面或回到首页重新操作 await page.reload() return False # 在主循环中使用 success await robust_crawler(page, some_url) if not success: # 记录日志或进行其他补偿操作 pass关键点重试机制对网络超时、元素未找到等临时性错误进行重试。指数退避重试等待时间逐渐增加避免对服务器造成持续压力。状态检查不仅检查HTTP状态码还要检查页面内容是否如预期例如没有“验证码”或“访问受限”提示。异常隔离一个任务的失败不应导致整个程序崩溃。使用asyncio.gather(..., return_exceptionsTrue)或为每个任务包裹try...except。6. 实战案例爬取动态电商商品列表与详情让我们综合运用以上知识完成一个实战案例爬取一个模拟电商网站的商品列表并进入每个商品详情页抓取更具体的信息。假设网站是单页面应用SPA商品列表通过滚动加载。import asyncio import json from playwright.async_api import async_playwright async def scrape_ecommerce_site(): async with async_playwright() as p: # 1. 启动浏览器可配置代理 browser await p.chromium.launch(headlessTrue) # 生产环境用True context await browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ... ) page await context.new_page() # 2. 监听并捕获商品列表的API数据更高效的方式 product_list_data [] def capture_api_response(response): if /api/products/list in response.url: asyncio.create_task(parse_list_response(response)) page.on(response, capture_api_response) async def parse_list_response(response): try: data await response.json() product_list_data.extend(data.get(items, [])) print(f捕获到 {len(data.get(items, []))} 个商品概要) except: pass # 3. 导航到商品列表页 await page.goto(https://demo-ecom-site.com/products, wait_untilnetworkidle) # 4. 模拟滚动触发加载更多如果API不是一次性返回 scroll_attempts 0 previous_height 0 while scroll_attempts 5: # 最多滚动5次防止无限循环 current_height await page.evaluate(document.body.scrollHeight) if current_height previous_height: break # 高度未变说明已滚动到底或没有更多内容 await page.evaluate(window.scrollTo(0, document.body.scrollHeight)) await page.wait_for_timeout(2000) # 等待新内容加载 previous_height current_height scroll_attempts 1 # 等待一会儿确保所有API响应都被捕获 await page.wait_for_timeout(3000) page.remove_listener(response, capture_api_response) print(f总计捕获到 {len(product_list_data)} 个商品概要) # 5. 遍历商品概要进入详情页抓取完整信息 detailed_products [] for idx, product_summary in enumerate(product_list_data[:10]): # 示例只爬前10个 product_url product_summary.get(url) if not product_url: continue print(f正在处理商品 {idx1}: {product_summary.get(name)}) # 在新标签页中打开详情页保持主列表页不变 async with await context.expect_page() as new_page_info: await page.click(fa[href{product_url}]) # 假设列表页有链接 detail_page await new_page_info.value try: await detail_page.wait_for_load_state(networkidle) # 方案A直接从详情页的API抓取数据如果有 # 方案B从渲染的页面中提取 detail_data { id: product_summary.get(id), name: await detail_page.text_content(h1.product-title), price: await detail_page.text_content(.price), description: await detail_page.text_content(.description), specs: await detail_page.evaluate(() { const specItems Array.from(document.querySelectorAll(.specs li)); return specItems.map(li li.innerText); }), image_urls: await detail_page.evaluate(() { const imgs Array.from(document.querySelectorAll(.product-gallery img)); return imgs.map(img img.src); }), summary: product_summary # 保留概要信息 } detailed_products.append(detail_data) # 保存到文件增量保存 with open(fproduct_{detail_data[id]}.json, w, encodingutf-8) as f: json.dump(detail_data, f, ensure_asciiFalse, indent2) except Exception as e: print(f处理商品 {product_url} 时出错: {e}) finally: await detail_page.close() await page.wait_for_timeout(1000) # 礼貌性间隔 # 6. 清理与汇总 await context.close() await browser.close() print(f爬取完成共获取 {len(detailed_products)} 个商品的详细信息。) # 可以将 detailed_products 合并保存或存入数据库 return detailed_products if __name__ __main__: asyncio.run(scrape_ecommerce_site())这个案例展示了几个关键实战技巧优先拦截API通过监听response事件直接获取结构化的商品列表数据效率最高。处理滚动加载通过模拟滚动和判断页面高度变化来触发并等待动态加载的内容。多页面协同在主列表页点击链接在新打开的详情页中操作操作完成后关闭详情页保持主页面干净。结构化存储将每个商品详情立即保存为独立的JSON文件实现“断点续爬”和错误隔离。也可以批量存入数据库。全面的错误处理在每个商品详情抓取步骤包裹try...except确保一个商品失败不影响整体任务。7. 调试、问题排查与常用工具即使有了完善的代码爬虫在运行中依然会遇到各种问题。掌握调试方法至关重要。7.1 Playwright的内置调试工具慢动作与录制在开发阶段关闭无头模式并启用慢动作可以清晰看到每一步操作。browser await p.chromium.launch(headlessFalse, slow_mo500) # 每个操作延迟500毫秒使用playwright codegen命令可以启动一个浏览器和代码生成器你手动操作浏览器它会自动生成对应的Playwright代码是学习API和编写脚本原型的利器。浏览器开发者工具在非无头模式下你可以直接按F12打开开发者工具查看网络请求、Console输出和DOM结构这对定位问题帮助极大。Playwright Trace Viewer这是Playwright的“时光机”。录制操作过程之后可以像看视频一样回放并检查每一步的DOM快照、网络请求和Console日志。# 开始录制 await context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) # ... 执行你的爬虫操作 ... # 停止并保存Trace文件 await context.tracing.stop(pathtrace.zip)使用命令playwright show-trace trace.zip即可打开图形化查看器。7.2 常见问题与解决方案速查表问题现象可能原因解决方案TimeoutError: Timeout 30000ms exceeded网络慢、页面未加载完、元素一直不出现1. 增加超时时间page.goto(url, timeout60000)。2. 检查选择器是否正确元素是否在iframe内。3. 使用wait_for_selector等待特定元素而非整个页面networkidle。Error: Target closed页面或浏览器在你操作前被关闭了检查代码逻辑确保在操作Page对象时其所属的Context和Browser未被提前关闭。使用try...finally确保资源正确释放顺序。页面卡住脚本不继续执行有未处理的弹窗、导航卡住、死循环1. 添加全局弹窗处理page.on(dialog, ...)。2. 检查是否有未完成的导航使用page.wait_for_load_state()。3. 为循环添加退出条件。抓取到的数据是空的或旧数据页面是动态渲染数据在JS加载后才有1. 等待特定元素出现await page.wait_for_selector(.data-loaded)。2.最佳实践直接监听和拦截XHR/Fetch API响应。被网站识别为机器人浏览器指纹、行为模式被检测1. 使用多个BrowserContext隔离并随机化UA、视窗、时区。2. 添加随机延迟和人类化鼠标移动。3. 使用住宅代理IP。playwright._impl._api_types.Error: Browser closed.浏览器进程意外崩溃1. 检查系统内存是否不足。2. 尝试更新Playwright和浏览器版本playwright install --force。3. 简化脚本减少同时打开的页面数。7.3 性能监控与日志对于长期运行的爬虫加入监控和日志是必要的。import logging import time logging.basicConfig(levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(crawler.log), logging.StreamHandler() ]) logger logging.getLogger(__name__) async def monitored_crawl(page, url): start_time time.time() logger.info(f开始爬取: {url}) try: await page.goto(url) # ... 业务逻辑 logger.info(f爬取成功: {url}, 耗时: {time.time()-start_time:.2f}s) return True except Exception as e: logger.error(f爬取失败: {url}, 错误: {e}, exc_infoTrue) return False通过记录每个任务的开始结束时间、成功失败状态你可以在后期分析爬虫的效率瓶颈和稳定性问题。结合系统监控工具如psutil库还可以记录内存和CPU使用情况。我个人在多个生产爬虫项目中深度使用Playwright它的稳定性和开发效率确实远超之前的方案。最大的体会是不要试图用Playwright去解决所有问题。对于简单的静态页面requestsBeautifulSoup仍然是更轻量、更快的选择。Playwright的威力在于处理复杂的、动态的、需要交互的Web场景。将它与传统的HTTP请求库结合根据目标网站的特点灵活选用工具才是构建高效爬虫系统的最佳实践。例如用requests处理API接口和登录如果可能用Playwright处理登录后的主站动态内容往往能取得事半功倍的效果。