browser
一行命令完成安装依赖和浏览器。
playwright install --with-deps chromium firefox
万能的 record 模式
参考 [[playwright 五分钟上手指南]],遇到任何定位问题直接哪里不会点哪里。
官方文档不推荐的写法
参考 Auto-waiting,绝大部分方法都会自动等待页面加载完成才执行相应操作,超过系统默认超时时间(30s)才会报错。
1
2
3
|
# https://playwright.dev/python/docs/api/class-page#page-wait-for-timeout
await page.wait_for_timeout(1000) # DISCOURAGED
await page.wait_for_load_state("networkidle") # 等待网络加载完成
|
XPath locator和 CSS locator 也是不推荐的,更直观的写法是使用 Locator 的各种定位 API。

要等待某个元素出现选 is_enabled() | focus() 优于 is_visible()。
is_visible() 在 headless = False 下没有问题,但调试的下切换到其他窗口,is_visible() 就会失去聚焦对象。
playwright._impl._api_types.Error: Playwright connection closed
多半是过度封装创建函数导致的。page 对象一旦跳出 async with async_playwright() as playwright: 上下文就无法获取 browser | context 原来的属性。
代理
1
2
3
4
5
6
7
8
|
async with async_playwright() as playwright:
# 对整个 browser 设置代理
browser = await playwright.firefox.launch(headless=browser_headless, proxy=browser_proxy)
context = await browser.new_context()
# 对不同 context 设置代理
browser = await playwright.firefox.launch(headless=False)
context = await browser.new_context(proxy=browser_proxy)
|
v2ex
点击签到按钮提示:“你的浏览器有一些奇奇怪怪的设置,请用一个干净安装的浏览器重试一下吧”。
原因:首页 referer 与其他页不匹配。
解决办法:得先回首页再跳转到签到页面。
1
2
|
await goto(home_url)
await goto(sign_url)
|
b站直播间
使用 chromium 提示浏览器版本过低,firefox 则没问题。

同时提示:Cannot read properites of null (reading ‘sendDanmaku’)

鼠标悬停
hover 或 focus。
模拟正常输入
1
|
type('123', delay=1000) # 每个字的输入间隔为一秒
|
多个匹配元素报错
strict mode violation: get_by_placeholder("发个弹幕呗~") resolved to 2 elements
直接 await page.pause() 开启调试模式找出是第几顺位的元素,根据情况使用:first、last、nth()。
1
2
3
4
|
# exact=True 精准匹配
print(await page.get_by_text("发个弹幕呗").count())
await page.get_by_placeholder("发个弹幕呗~").nth(1).fill("2")
await page.get_by_text("发送", exact=True).nth(1).click():
|
docker 容器的定位超时问题
原理不明,本地测试通过而 huggingface 运行第一种写法的代码会超时。
1
2
3
4
|
# ×
if await page.get_by_text("你无任何进行任务", exact=True).is_enabled()
#√
if page.get_by_text("你无任何进行任务", exact=True)
|
多线程
文档确说明 Playwright’s API is not thread-safe,issue/623 也有人给出多线程的实现,但还是推荐使用 async 完成。
多进程
同上,与其手动处理进程资源(开销),不如直接 async。
多 context & 多 page
文档推荐的写法:Multiple Contexts in a Single Test,通过示例可以看出 playwright 自上而下的资源创建过程:
- 创建全局 browser 浏览器对象,
- 创建一个或多个 context 对象;
- 每个 context 又可以创建一个或多个 page对象。
可以理解为在一个任务里面只有一个谷歌浏览器(browser),可以拥有多个谷歌账号(context), 每个账号(local storage)可以存储所有标签页(page) 的 cookie 信息。
也就是说:
只有一个 page 对象在遇到耗时任务时一定会持续等待当前任务完成,async/await 无法发挥作用,完成时间和串行完成任务没区别。
一个 context 下启动多个 page 也是近乎串行完成任务。而且启动多个 page 会占用资源,其他 page 一直在等上一个 page 完成任务才能开始工作。
真正意义上的异步同时完成任务就得分别创建多个 browser 对象。
可以简单测试单个 page 和 多个 page 的区别。先启动一个 fastapi 服务器,设置两个耗时任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import time
from fastapi import FastAPI
from uvicorn import run
app = FastAPI()
# 一!
@app.get("/sleep_one_second")
async def sleep_one_second():
time.sleep(1)
return {"res": 1}
# 五!
@app.get("/sleep_five_second")
async def sleep_five_second():
time.sleep(5)
return {"res": 5}
if __name__ == "__main__":
run("server:app", host="0.0.0.0", port=8000, reload=True
|
测试 single/multi page 的执行时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
import asyncio
import time
from functools import wraps
from playwright.async_api import async_playwright
def timeit(func):
@wraps(func)
async def wrapper(*args, **kwargs):
s_time = time.perf_counter()
await func(*args, **kwargs)
print(f"{func.__name__} cost {time.perf_counter() - s_time}s")
return loop.create_task(wrapper())
async def single_page(playwright):
chromium = playwright.chromium
browser = await chromium.launch(headless=False)
context = await browser.new_context()
page = await context.new_page()
await page.goto("http://localhost:8000/sleep_one_second")
await page.goto("http://localhost:8000/sleep_five_second")
await browser.close()
async def multi_page(playwright):
chromium = playwright.chromium
browser = await chromium.launch(headless=False)
context = await browser.new_context()
page1 = await context.new_page()
page5 = await context.new_page()
await page1.goto("http://localhost:8000/sleep_one_second")
await page5.goto("http://localhost:8000/sleep_five_second")
await browser.close()
@timeit
async def main():
async with async_playwright() as playwright:
await single_page(playwright)
# await multi_page(playwright)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
try:
loop.run_forever()
except (KeyboardInterrupt, SystemExit):
exit()
|
分别注释执行你会发现两个任务都是 8秒4(创建浏览器+执行任务+关闭浏览器) 左右完成,和上面分析一样。
一个浏览器环境下并不会影响执行时间,创建多个 page 只是便于区分任务。
多开
如果还是不死心,一定要双开多开,以下写法也是可行的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import asyncio
import time
from playwright.async_api import async_playwright
async def init_page(playwright):
browser = await playwright.chromium.launch(headless=False)
context = await browser.new_context()
page = await context.new_page()
return page
async def sleep_one_second(playwright):
page = await init_page(playwright)
await page.goto("http://localhost:8000/sleep_one_second")
await page.close()
async def sleep_five_second(playwright):
page = await init_page(playwright)
await page.goto("http://localhost:8000/sleep_five_second")
await page.close()
async def main():
async with async_playwright() as playwright:
task1 = asyncio.create_task(sleep_one_second(playwright))
task2 = asyncio.create_task(sleep_five_second(playwright))
tasks = [task1, task2]
await asyncio.gather(*tasks)
if __name__ == "__main__":
s_time = time.perf_counter()
asyncio.run(main())
print(f"cost {time.perf_counter() - s_time}s")
|
因为创建了两个浏览器对象,耗时多了一秒。
