在 Playwright 之前,我一般会使用 Selenium 或者 Puppeteer 来进行浏览器自动化操作。然而,Selenium 经常会有一些奇怪的 bug, Puppeteer 则是没有官方 Python 版,非官方版本也只有 async 版本,并且也是有一些奇怪的 bug. 另外,众所周知,Python 的 Async API 并不是那么好使。
Playwright 是微软出品的浏览器自动化工具,代码质量应该是有足够保证的。而且它还官方支持同步版的 Python API, 同时支持三大浏览器,所以赶紧切换过来了。
特别注意 Playwright 的拼写,别把中间的 "w" 丢了。
安装
pip install playwright==1.8.0a1 # 很奇怪,必须指定版本,不指定会安装到一个古老的版本 python -m playwright install # 安装浏览器,此处国内网络可能会有问题(你懂的),请自行解决
基本使用
Playwright 支持 Firefox / Chrome / WebKit(Safari). 其中 webkit 最轻量了,所以没有什么特殊需求最好使用 webkit, 不要使用 chromium.
from playwright.sync_api import sync_playwright as playwright with playwright() as pw: webkit = pw.webkit.launch(headless=False) context = webkit.new_context() # 需要创建一个 context page = context.new_page() # 创建一个新的页面 page.goto("https://www.apple.com") print(page.content()) webkit.close()
Playwright 官方推荐使用 with 语句来访问,不过如果你不喜欢的话,也可以用 pw.start() 和 pw.stop().
新概念:Context
和 Puppeteer 不同的是,Playwright 新增了 context 的概念,每个 context 就像是一个独立的匿名模式会话,非常轻量,但是又完全隔离。比如说,可以在两个 context 中登录两个不同的账号,也可以在两个 context 中使用不同的代理。
通过 context 还可以设置 viewport, user_agent 等。
context = browser.new_context( user_agent='My user agent' ) context = browser.new_context( viewport={ 'width': 1280, 'height': 1024 } ) context = browser.new_context( http_credentials={"username": "bill", "password": "pa55w0rd"} ) # new_context 其他几个比较有用的选项: ignore_https_errors=False proxy={"server": "http://example.com:3128", "bypass": ".example.com", "username": "", "password": ""} extra_http_headers={"X-Header": ""}
context 中有一个很有用的函数context.add_init_script
, 可以让我们设定在调用 context.new_page
的时候在页面中执行的脚本。
# hook 新建页面中的 Math.random 函数,总是返回 42 context.add_init_script(script="Math.random = () => 42;") # 或者写在一个文件里 context.add_init_script(path="preload.js")
还可以使用 context.expose_binding
和 context.expose_function
来把 Python 函数暴露到页面中,不过个人感觉还是使用 add_init_script 暴露 JS 函数方便一些。
和 Puppeteer 一样,Playwright 的核心概念依然是 page, 核心 API 几乎都是 page 对象的方法。可以通过 context 来创建 page.
页面基本操作
按照官网文档,调用 page.goto(url) 后页面加载过程:
- 设定 url
- 通过网络加载解析页面
- 触发 page.on("domcontentloaded") 事件
- 执行页面的 js 脚本,加载静态资源
- 触发 page.on("laod") 事件
- 页面执行动态加载的脚本
- 当 500ms 都没有新的网络请求的时候,触发 networkidle 事件
page.goto(url)
会跳转到一个新的链接。默认情况下 Playwright 会等待到 load 状态。如果我们不关心加载的 CSS 图片等信息,可以改为等待到 domcontentloaded 状态,如果页面是 ajax 加载,那么我们需要等待到 networkidle 状态。如果 networkidle 也不合适的话,可以采用 page.wait_for_selector 等待某个元素出现。不过对于 click 等操作会自动等待。
page.goto(url, referer="", timeout=30, wait_until="domcontentloaded|load|networkidle")
Playwright 会自动等待元素处于可操作的稳定状态。当然也可以用 page.wait_for_*
函数来手工等待:
page.wait_for_event("event", event_predict, timeout) page.wait_for_function(js_function) page.wait_for_load_state(state="domcontentloaded|load|networkidle", timeout) page.wait_for_selector(selector, timeout) page.wait_for_timeout(timeout) # 不推荐使用
对页面的操作方法主要有:
# selector 指的是 CSS 等表达式 page.click(selector) page.fill(selector, value) # 在 input 中填充值 # 例子 page.click("#search")
获取页面中的数据的主要方法有:
page.url # url page.title() # title page.content() # 获取页面全文 page.inner_text(selector) # element.inner_text() page.inner_html(selector) page.text_content(selector) page.get_attribute(selector, attr) # eval_on_selector 用于获取 DOM 中的值 page.eval_on_selector(selector, js_expression) # 比如: search_value = page.eval_on_selector("#search", "el => el.value") # evaluate 用于获取页面中 JS 中的数据,比如说可以读取 window 中的值 result = page.evaluate("([x, y]) => Promise.resolve(x * y)", [7, 8]) print(result) # prints "56"