Playwright爬虫实战:从原理到应用,高效应对动态网页与反爬
1. 项目概述:为什么是Playwright?
如果你正在为动态网页、SPA应用或者那些反爬机制层出不穷的网站头疼,还在用传统的requests+BeautifulSoup或者Selenium硬扛,那今天这个内容就是为你准备的。我最近在几个数据采集项目中,彻底把主力工具从Selenium切换到了Playwright,实测下来,无论是开发效率、执行稳定性还是对复杂场景的应对能力,提升都是肉眼可见的。简单来说,Playwright是一个由微软开源的现代化浏览器自动化库,它支持Chromium、Firefox和WebKit三大内核,用一个API就能搞定,专为应对当今复杂的Web应用而生。
对于爬虫开发者而言,它的核心价值在于“真实”。它启动的是一个带有完整上下文的、真实的浏览器环境,能完美执行JavaScript、加载所有资源、处理各种前端框架(React, Vue, Angular等)生成的动态内容。这意味着,那些需要用户交互(点击、滚动、输入)后才能显示的数据,或者依赖复杂前端状态的数据,对Playwright来说都是“可见”的。更关键的是,它内置了强大的等待机制和选择器引擎,写出来的脚本异常健壮,再也不用写一堆脆弱的time.sleep来碰运气了。无论是你想自动化查询王者战绩、抓取今日头条的资讯流,还是应对像瑞数(一种动态反爬技术)这样的硬骨头,Playwright都提供了更优雅、更强大的解决方案。
2. 核心思路与工具选型解析
2.1 Playwright vs. 传统爬虫方案
在决定使用Playwright之前,我们得先搞清楚它解决了传统方案的哪些痛点。我画个简单的对比表,你一看就明白:
| 特性/方案 | Requests + BeautifulSoup/parsel | Selenium | Playwright |
|---|---|---|---|
| 核心原理 | 发送HTTP请求,解析静态HTML | 驱动真实浏览器,可执行JS | 驱动真实浏览器,多内核支持,API更现代 |
| 动态内容 | 无法处理,需逆向JS或找隐藏接口 | 可以处理,但速度较慢 | 完美处理,执行效率高,等待机制智能 |
| 上手难度 | 低,适合简单静态页 | 中,API略显陈旧,配置稍烦 | 中低,API设计直观,异步支持好 |
| 执行速度 | 极快(无浏览器开销) | 慢(浏览器启动、加载资源) | 较快(优化过的浏览器上下文,可无头运行) |
| 稳定性 | 高(针对静态内容) | 中,受浏览器驱动和网络影响大 | 高,自动等待、内置重试,网络层稳定 |
| 反爬对抗 | 弱,依赖IP池、请求头伪装 | 较强,但指纹可能被检测 | 强,模拟更真实浏览器指纹,可绕过部分检测 |
| 典型场景 | 静态新闻站、API数据抓取 | 需登录、有简单JS交互的网站 | SPA应用、复杂交互、强反爬(如瑞数) |
从表格可以看出,Playwright在需要与页面深度交互、处理复杂前端渲染的场景下,优势是碾压性的。比如,你想爬取一个用Vue或React做的后台管理系统数据,或者一个需要滚动加载、点击选项卡切换内容的资讯网站,用requests几乎无从下手,用Selenium脚本又笨重易出错,而Playwright则能轻松搞定。
2.2 为什么Playwright更适合现代爬虫?
除了上述对比,Playwright还有几个让我决定“换枪”的关键点:
- 自动等待(Auto-waiting):这是最大的福音。在Selenium里,你经常需要写
WebDriverWait来等元素出现,或者用time.sleep硬等,非常不优雅且脆弱。Playwright的大部分操作(如click,fill,text_content)内置了等待。它会自动等待元素可操作(可见、稳定、未被遮挡)后才执行,大大减少了因页面加载速度导致的脚本失败。 - 强大的选择器(Selector Engine):Playwright支持CSS、XPath,还有它独有的、非常实用的文本选择器和React/Vue组件选择器。比如,你可以直接用
page.click(‘text=“登录”’)来点击一个包含“登录”二字的按钮,而不用去管它复杂的CSS路径,这在快速编写脚本时效率极高。 - 网络拦截与模拟(Network Interception):你可以监听和修改浏览器的网络请求。这个功能在爬虫里太有用了:可以直接拦截页面发出的API请求,拿到结构化的JSON数据,比解析HTML更高效;也可以屏蔽不必要的图片、CSS资源加载,加快爬取速度;甚至能修改请求头或响应内容。
- 多上下文与隔离:可以轻松创建多个完全隔离的浏览器上下文(Context),每个上下文都有独立的cookie、localStorage,相当于开了多个互不影响的隐身窗口。这对于需要管理多个账号会话的爬虫来说,是天然的支持。
- 可靠的录制功能(Codegen):
playwright codegen命令可以启动一个浏览器并录制你的操作,直接生成对应语言的脚本。这对于快速理解页面交互流程、生成脚本骨架非常有帮助,是快速上手的神器。
注意:Playwright并非银弹。对于简单的、数据直接存在于初始HTML响应中的静态网站,使用
requests仍然是速度最快、资源消耗最低的选择。工具选型永远取决于目标场景。
3. 环境搭建与快速开始
3.1 安装Playwright
Playwright支持Python、Node.js、Java、.NET等语言。这里我们以Python为例,因为它也是爬虫领域最流行的语言。安装非常简单,一条命令搞定:
pip install playwright安装完库之后,我们还需要安装它需要操作的浏览器内核(Chromium, Firefox, WebKit)。Playwright很贴心地提供了管理命令:
playwright install这条命令会下载所有三个浏览器的最新稳定版本。如果你只想安装Chromium(最常用,兼容性最好),可以运行:
playwright install chromium实操心得:在国内网络环境下,直接安装浏览器可能会很慢甚至失败。推荐使用镜像加速。可以设置环境变量
PLAYWRIGHT_DOWNLOAD_HOST为国内镜像源,例如:# 在命令行中设置(临时) set PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright playwright install chromium # 或者在代码中设置 import os os.environ[‘PLAYWRIGHT_DOWNLOAD_HOST’] = ‘https://npmmirror.com/mirrors/playwright’使用镜像能极大提升下载速度。
3.2 你的第一个Playwright脚本:爬取网页标题
让我们从一个最简单的例子开始,感受一下Playwright的同步API(易于理解)。我们将打开百度,获取其页面标题。
from playwright.sync_api import sync_playwright def main(): # 启动Playwright,它负责管理浏览器进程 with sync_playwright() as p: # 启动一个Chromium浏览器实例,headless=False表示有界面(方便调试) browser = p.chromium.launch(headless=False) # 创建一个新的浏览器页面(标签页) page = browser.new_page() # 导航到百度 page.goto(‘https://www.baidu.com‘) # 获取页面标题并打印 print(f’页面标题是:{page.title()}‘) # 等待3秒,方便我们观察(实际脚本中应使用智能等待而非sleep) page.wait_for_timeout(3000) # 关闭浏览器 browser.close() if __name__ == ‘__main__’: main()运行这段代码,你会看到一个Chromium浏览器窗口打开,访问百度,然后在控制台打印出“页面标题是:百度一下,你就知道”,随后浏览器关闭。恭喜,你的第一个Playwright爬虫(或者说浏览器自动化脚本)已经跑通了!
3.3 使用异步API提升效率
对于需要同时操作多个页面或进行大量IO操作(如下载文件、处理多个请求)的爬虫,使用异步API能显著提升效率。Playwright对Python的asyncio支持得很好。下面是上面例子的异步版本:
import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 注意这里用的是 await browser = await p.chromium.launch(headless=True) # 无头模式,后台运行 page = await browser.new_page() await page.goto(‘https://www.baidu.com‘) title = await page.title() print(f’页面标题是:{title}‘) await browser.close() # 运行异步主函数 asyncio.run(main())在后续的复杂示例中,我们将主要使用异步API,因为它更符合高性能爬虫的需求。
4. 核心操作详解:从元素定位到数据提取
4.1 元素定位(Selector):多种武器库
定位页面元素是自动化的基础。Playwright提供了丰富而强大的选择器。
CSS选择器:最常用,与你在前端开发中使用的CSS选择器语法一致。
# 点击ID为’su’的按钮(百度搜索按钮) await page.click(‘#su’) # 点击类名包含’btn-search’的元素 await page.click(‘.btn-search’)XPath:功能强大,可以基于元素在DOM树中的路径或属性进行定位。
# 点击文本为“百度一下”的按钮 await page.click(‘//input[@value=“百度一下”]’)文本选择器(Text Selector):Playwright的特色,非常直观,通过元素包含的文本内容来定位。
# 点击页面上任何包含“登录”二字的元素 await page.click(‘text=登录’) # 精确匹配文本 await page.click(‘text=“用户登录”’)React/Vue组件选择器:针对现代前端框架,可以直接定位到组件。
# 定位到一个名为’LoginButton’的React组件(需要React开发工具支持) await page.click(‘_react=LoginButton’)
最佳实践建议:优先使用CSS选择器和文本选择器。它们通常更简洁、性能更好。XPath在结构复杂或缺乏特征的元素定位时作为备用方案。在写爬虫时,多利用浏览器的开发者工具(F12)的“检查(Inspect)”功能,直接复制元素的CSS Selector或XPath。
4.2 页面交互:点击、输入、滚动
模拟用户操作是爬取动态内容的关键。
# 1. 输入文本 - 在搜索框输入关键词 search_box = await page.query_selector(‘#kw’) # 先定位到元素 await search_box.fill(‘Playwright教程’) # fill方法会先清空再输入 # 或者更简洁的一行写法 await page.fill(‘#kw’, ‘Playwright教程’) # 2. 点击 - 点击搜索按钮 await page.click(‘#su’) # 3. 等待导航完成 - 点击后页面可能会跳转,等待新页面加载稳定 await page.wait_for_load_state(‘networkidle’) # 等待网络基本空闲 # 4. 处理下拉框(Select) await page.select_option(‘select#city’, label=‘北京’) # 通过可见文本选择 await page.select_option(‘select#city’, value=‘beijing’) # 通过value值选择 # 5. 上传文件 await page.set_input_files(‘input[type=“file”]’, ‘path/to/your/file.pdf’) # 6. 滚动页面 - 触发懒加载 await page.mouse.wheel(0, 1000) # 向下滚动1000像素 # 或者滚动到某个元素可见 target_element = await page.query_selector(‘.load-more’) await target_element.scroll_into_view_if_needed()4.3 等待策略:告别time.sleep
智能等待是编写稳定Playwright脚本的核心。永远不要使用固定的time.sleep(除非在极少数调试场景下)。
- 隐式等待(Auto-waiting):如前所述,
click,fill,text_content等操作内置等待(默认最长30秒)。 - 显式等待(Explicit Waits):使用
page.wait_for_*系列函数。# 等待某个元素出现在DOM中 await page.wait_for_selector(‘.search-result’, state=‘attached’) # 等待某个元素变为可见状态 await page.wait_for_selector(‘.modal’, state=‘visible’) # 等待元素从DOM中消失(如等待加载动画结束) await page.wait_for_selector(‘.loading-spinner’, state=‘detached’) # 等待页面导航发生 await page.wait_for_navigation() # 等待特定URL await page.wait_for_url(‘**/search**’) # 等待一个函数在页面上下文中执行结果为True await page.wait_for_function(‘window.scrollY > 500’) - 等待加载状态:
await page.goto(‘https://example.com‘, wait_until=‘domcontentloaded’) # DOM加载完成 await page.goto(‘https://example.com‘, wait_until=‘networkidle’) # 网络空闲(推荐)
避坑指南:
wait_for_selector的state参数非常有用。‘attached’只要求元素在DOM中(可能隐藏),‘visible’要求元素可见且未被遮挡。根据你的实际需求选择,能避免很多因元素状态导致的失败。
4.4 数据提取:获取文本、属性、HTML
与页面交互后,最终目的是获取数据。
# 1. 获取单个元素的文本 title = await page.text_content(‘h1’) # 2. 获取单个元素的内部HTML html_content = await page.inner_html(‘.article-content’) # 3. 获取单个元素的属性值 link_url = await page.get_attribute(‘a.download-link’, ‘href’) # 4. 获取多个元素(返回ElementHandle列表) all_articles = await page.query_selector_all(‘.article-list > li’) for article in all_articles: # 在每个元素上继续使用选择器(相对于该元素) title = await article.text_content(‘h2’) # 或者使用ElementHandle自己的方法 link = await article.get_attribute(‘a’, ‘href’) print(title, link) # 5. 使用`locator`(更现代的API,推荐) # locator与selector不同,它封装了等待逻辑,并且支持链式调用 articles_locator = page.locator(‘.article-list > li’) count = await articles_locator.count() # 获取匹配的元素数量 for i in range(count): # nth方法按索引获取locator title = await articles_locator.nth(i).locator(‘h2’).text_content() print(title)locatorvsquery_selector:locator是Playwright更推荐的方式。它懒加载,只在真正需要操作(如click)或取值(如text_content)时才去查找元素,并且自动重试和等待。而query_selector是立即执行的。在动态加载内容的页面中,使用locator更可靠。
5. 高级爬虫技巧实战
5.1 处理弹窗、新标签页与iframe
现代网页充满了各种弹窗和嵌套内容。
处理弹窗(Dialog):
# 监听弹窗事件,并在出现时接受(如alert, confirm, prompt) page.on(‘dialog’, lambda dialog: dialog.accept()) # 更精细的控制 def handle_dialog(dialog): print(f’弹窗消息:{dialog.message}‘) if dialog.type == ‘confirm’: dialog.accept() # 点击“确定” else: dialog.dismiss() # 点击“取消” page.on(‘dialog’, handle_dialog)处理新标签页/窗口:
# 在点击一个会打开新窗口的链接前,监听‘popup’事件 async with page.expect_popup() as popup_info: await page.click(‘a[target=“_blank”]’) # 点击链接 new_page = await popup_info.value # 获取新页面的Page对象 await new_page.wait_for_load_state() print(await new_page.title()) # ... 操作新页面 await new_page.close()处理iframe:这是爬虫中的常见难点,很多登录框或第三方内容都嵌在iframe里。
# 通过iframe的name、URL或选择器来定位iframe元素 iframe_element = page.frame_locator(‘iframe[name=“login-frame”]’) # 在iframe的上下文中进行操作 await iframe_element.locator(‘input#username’).fill(‘my_username’) await iframe_element.locator(‘input#password’).fill(‘my_password’) await iframe_element.locator(‘button:has-text(“登录”)’).click()5.2 拦截与修改网络请求
这是Playwright爬虫进阶的“王牌技能”。通过监听网络请求,我们可以直接拿到API返回的JSON数据,效率远高于解析HTML。
async def handle_request(route, request): # 1. 直接放行请求 # await route.continue_() # 2. 修改请求头(例如添加特定的Referer或User-Agent) headers = request.headers headers[‘my-custom-header’] = ‘my-value’ await route.continue_(headers=headers) # 3. 完全覆写请求(例如将请求重定向到本地Mock数据) # await route.fulfill( # status=200, # content_type=‘application/json’, # body=json.dumps({“data”: “mocked”}) # ) async def handle_response(response): # 监听响应,特别关注API接口 if ‘/api/data’ in response.url: try: json_data = await response.json() print(f’抓到API数据:{json_data}‘) # 这里可以保存json_data到文件或数据库 except: pass # 启用路由拦截和响应监听 await page.route(‘**/*’, handle_request) # 拦截所有请求 page.on(‘response’, handle_response) await page.goto(‘https://your-target-site.com‘)实战应用:打开浏览器开发者工具的“Network”面板,找到数据加载的XHR/Fetch请求,然后通过page.route来监听这个特定URL模式(如**/api/search**)的响应,直接提取JSON。这比解析渲染后的HTML要高效和稳定得多。
5.3 管理Cookie与登录状态
保持登录状态是爬取需要认证页面的前提。
# 1. 手动添加Cookie(适用于已知Cookie) await page.context.add_cookies([{ ‘name’: ‘sessionid’, ‘value’: ‘your_session_token_here’, ‘domain’: ‘.example.com‘, ‘path’: ‘/’, }]) await page.goto(‘https://example.com/dashboard‘) # 此时应已处于登录状态 # 2. 自动化登录并保存状态(更通用) async def login_and_save(): async with async_playwright() as p: browser = await p.chromium.launch(headless=False) # 有头模式方便观察 context = await browser.new_context() page = await context.new_page() await page.goto(‘https://example.com/login‘) await page.fill(‘#username’, ‘your_username’) await page.fill(‘#password’, ‘your_password’) await page.click(‘button[type=“submit”]’) # 等待登录成功,例如跳转到首页或出现用户菜单 await page.wait_for_url(‘**/home**’) # **关键步骤:保存当前上下文的存储状态(包含cookies, localStorage)** storage_state = await context.storage_state(path=‘auth_state.json’) await browser.close() return storage_state # 3. 使用保存的状态恢复登录会话 async def use_saved_state(): async with async_playwright() as p: # 加载之前保存的状态文件 browser = await p.chromium.launch(headless=True) context = await browser.new_context(storage_state=‘auth_state.json’) # 恢复状态 page = await context.new_page() # 直接访问需要登录的页面 await page.goto(‘https://example.com/profile‘) # 此时页面应该显示已登录的用户信息 print(await page.text_content(‘.user-name’)) await browser.close()通过storage_state,你可以实现“一次登录,多次使用”,非常适合需要长时间运行的爬虫任务。
5.4 应对反爬虫策略
Playwright本身模拟的是真实浏览器,已经能绕过很多基于请求头(如User-Agent)或简单JS检测的反爬。但对于更高级的反爬(如瑞数、极验等),需要更多策略。
- 伪装更真实的浏览器指纹:
context = await browser.new_context( viewport={‘width’: 1920, ‘height’: 1080}, user_agent=‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...’, locale=‘zh-CN’, timezone_id=‘Asia/Shanghai’, # 可以覆盖其他设备属性,如屏幕色彩深度、硬件并发数等 # extra_http_headers={‘Accept-Language’: ‘zh-CN,zh;q=0.9’} ) - 随机化行为模式:在操作中加入人类化的随机延迟和移动轨迹。
import random 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 + random.uniform(-5, 5), box[‘y’] + box[‘height’] / 2 + random.uniform(-5, 5) ) await page.wait_for_timeout(random.randint(100, 500)) # 随机等待100-500ms await element.click() - 使用代理IP:这是应对IP封锁的基本手段。
browser = await p.chromium.launch( proxy={ ‘server’: ‘http://your-proxy-server:port‘, ‘username’: ‘user’, # 如果需要认证 ‘password’: ‘pass’ } ) - 应对WebDriver检测:一些网站会检测
navigator.webdriver属性。Playwright默认会将其设置为true,但可以隐藏。# 在新上下文或页面中注入JS,覆盖webdriver属性 await page.add_init_script(“”” Object.defineProperty(navigator, ‘webdriver’, { get: () => undefined }); “””)注意:对于瑞数等强动态混淆的加密方案,仅靠Playwright可能不够。这类方案通常通过执行一段不断变化的JavaScript来生成加密参数。破解它们需要深入逆向JS逻辑,这超出了自动化工具的范畴,属于逆向工程领域。Playwright在这里的作用,更多是提供一个稳定执行环境来运行你逆向出来的JS代码。
6. 完整项目实战:爬取动态新闻列表
假设我们要爬取一个类似“今日头条”的网站,其新闻列表是滚动加载的。我们将综合运用上述所有技巧。
import asyncio import json from playwright.async_api import async_playwright async def fetch_news(): async with async_playwright() as p: # 1. 启动浏览器,可配置代理 browser = await p.chromium.launch(headless=True) # 生产环境用无头 context = await browser.new_context( viewport={‘width’: 1920, ‘height’: 1080}, user_agent=‘Mozilla/5.0 ...’, ) page = await context.new_page() # 2. 监听并拦截API请求,直接获取结构化数据 news_list = [] def handle_response(response): if ‘/api/news/feed’ in response.url: # 假设这是新闻列表API try: data = response.json() # 假设API返回的新闻数据在data[‘items’]里 for item in data.get(‘items’, []): news_list.append({ ‘title’: item.get(‘title’), ‘url’: item.get(‘url’), ‘publish_time’: item.get(‘publish_time’), }) except: pass page.on(‘response’, handle_response) # 3. 访问目标网站 await page.goto(‘https://example-news-site.com‘, wait_until=‘networkidle’) # 4. 模拟滚动加载(假设需要加载5页) for _ in range(5): # 滚动到页面底部 await page.evaluate(‘window.scrollTo(0, document.body.scrollHeight)’) # 等待可能的新内容加载(例如出现“加载中”提示然后消失) try: await page.wait_for_selector(‘.loading-indicator’, state=‘visible’, timeout=2000) await page.wait_for_selector(‘.loading-indicator’, state=‘detached’, timeout=5000) except: # 如果没有加载指示器,简单等待一下网络 await page.wait_for_timeout(1000) # 5. 如果拦截API失败,作为备选方案,从已渲染的页面中提取数据 if not news_list: print(“未拦截到API,从HTML提取...”) articles = await page.locator(‘.news-item’).all() for article in articles: title_elem = article.locator(‘h2’) if await title_elem.count(): title = await title_elem.first().text_content() link_elem = article.locator(‘a’).first url = await link_elem.get_attribute(‘href’) if await link_elem.count() else None news_list.append({‘title’: title, ‘url’: url}) # 6. 输出结果 print(f’共抓取到 {len(news_list)} 条新闻’) with open(‘news.json’, ‘w’, encoding=‘utf-8’) as f: json.dump(news_list, f, ensure_ascii=False, indent=2) # 7. 关闭资源 await context.close() await browser.close() if __name__ == ‘__main__’: asyncio.run(fetch_news())这个例子展示了混合策略:优先通过网络拦截获取高效的JSON数据,失败后则降级到DOM解析作为保障。同时模拟了滚动加载行为。
7. 常见问题与调试技巧
7.1 元素找不到或操作超时
这是最常见的问题。
- 检查选择器:首先确认你的选择器在页面当前状态下是唯一的、正确的。使用
await page.screenshot(path=‘debug.png’)截图,或await page.content()打印HTML来辅助调试。 - 检查元素状态:元素可能被隐藏(
display: none)、被遮挡、或者存在于iframe/shadow DOM中。使用state参数(如‘visible’)或尝试先定位到父级框架。 - 增加超时时间:
wait_for_selector等函数可以设置timeout参数(单位毫秒)。await page.wait_for_selector(‘.dynamic-content’, timeout=10000) # 等待10秒 - 等待更合适的时机:如果数据是AJAX加载的,确保在数据加载完成后再操作。使用
wait_for_load_state(‘networkidle’)或等待特定元素出现。
7.2 脚本在无头模式下运行正常,但有头模式失败
这通常是因为有头模式下,浏览器窗口可能不是焦点,或者视图大小不同导致元素位置变化。
- 确保视图大小一致:在
new_context时固定viewport。 - 禁用动画和过渡:有时CSS动画会影响交互。
await page.add_style_tag(content=‘’‘ *, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; } ‘’‘)
7.3 如何调试Playwright脚本?
- 慢动作模式(Slow Mo):在启动浏览器时加入
slow_mo参数,让所有操作慢速执行,方便观察。browser = await p.chromium.launch(headless=False, slow_mo=100) # 每个操作延迟100ms - 启动开发者工具:在无头模式下,也可以打开DevTools。
browser = await p.chromium.launch(headless=False, devtools=True) - 录制工具(Codegen):这是最强的学习工具。在命令行运行:
它会打开一个浏览器和一个录制器,你的所有操作都会被实时转换成代码,是编写脚本原型的绝佳方式。playwright codegen https://example.com - 详细的日志:设置环境变量
DEBUG=pw:api可以打印详细的API调用日志。
7.4 性能优化与资源管理
- 复用浏览器上下文:避免为每个任务都启动/关闭浏览器,开销巨大。可以启动一个浏览器实例,为每个独立会话创建新的上下文(Context)。
- 合理使用无头模式:生产环境务必使用
headless=True(或headless=“new”),节省大量GUI资源。 - 拦截不必要的资源:如果不需要图片、样式、字体等,可以拦截并中止它们,大幅提升加载速度。
async def block_assets(route, request): if request.resource_type in [‘image’, ‘stylesheet’, ‘font’, ‘media’]: await route.abort() else: await route.continue_() await page.route(‘**/*’, block_assets) - 并发控制:虽然异步IO可以处理很多页面,但同时打开过多页面(如上百个)会消耗大量内存。使用信号量(
asyncio.Semaphore)来控制并发数。
从简单的页面信息抓取,到复杂的动态交互、API拦截、状态管理,Playwright为现代Web爬虫和自动化测试提供了一个强大而统一的工具箱。它的设计哲学是“为现代Web而生”,这正好契合了当前Web应用日益复杂化的趋势。抛弃那些笨重和脆弱的旧工具,花点时间掌握Playwright,你会发现处理那些棘手的爬虫任务突然变得轻松了许多。记住,核心是理解它的等待机制、选择器以及网络拦截能力,剩下的就是结合具体网站结构进行灵活应用了。
