文章目录
前言
本教程旨在引导读者从零开始,逐步掌握使用Python和Playwright进行Web自动化测试的技能。无论你是初涉自动化测试的开发者,还是希望提升现有测试框架效能的资深测试工程师,本教程都将为你提供丰富的实战案例、详细的步骤解析以及最佳实践分享。
一.playwright是什么
Playwright是微软在2020 年初
开源自动化测试工具,功能和 selenium 类似,都可以驱动浏览器进行各种自动化操作。
- 支持主流浏览器,如Chrome、Firefox、Safari 等,同时支持以无头模式、有头模式运行,并提供了同步、异步的 API,可以结合
主流测试框架
使用,并且支持浏览器端的自动化脚本录制等功能。
特点:
- 跨浏览器:Playwright 支持所有现代渲染引擎,包括Chromium、WebKit 和 Firefox;
- 跨平台:在 Windows、Linux 和 MacOS 上进行本地或 CI、无头或有头测试;
- 跨语言:在 TypeScript、JavaScript、Python、.NET、Java 中使用Playwright API;
- 测试移动网络:适用于 Android 和 Mobile Safari 的 Google Chrome 原生移动仿真。相同的渲染引擎适用于您的桌面和云端。
- 官网:https://playwright.dev/
官方文档
其他鼎鼎大名的selenium、Pyppeteer、DrissionPage等
。
推荐原因:
- 运行 playwright codegen命令 可自动自动生成代码,降低编写爬虫代码的门槛和难度,不用自己逐个去分析页面代码结构
- playwright微软是从2020年开始创建的项目,更新稳定且频率不低,可长期使用一个库或框架
- 代码结构清晰,功能齐全,门槛低
- 支持多个语言版本:python、Node.js、Java、.net,原生支持同步异步两种方式**
- 自动等待 pw在做某个操作之前需要有一个前提条件成立才进行,系统会
自动等待检查通过,直到超时
。
比如我要系统自动点击某个元素,那么playwright会自动:
1. 等待指定选择器的元素出现在 DOM 中(不用自己去写轮询等待了)
2. 等待它显示出来,即不为空,不为display:none,不为visibility:hidden (这个太人性化了,不用去判断元素是否隐藏)
3. 等待它停止移动,例如,直到 css 转换完成
4.将元素滚动到视图中
(这个太人性化了,不用自己去滚动了)
5. 等待它在动作点接收指针事件,例如,等待直到元素变得不被其他元素遮挡
如果元素检测到上述任何场景,则重试
二.python引入playwright
1.安装
-
安装 playwright-python 依赖库 (需要注意的是
,playwright库需要依赖 Python3.7+以上
)- 可在https://pypi.org/project/playwright/查看它的依赖版本信息。
pip install playwright
- 官网推荐
pip install pytest-playwright
来安装,但没必要,会安装playwright及其他一堆测试所用的库,如果只是使用playwright,那么就没必要这样去安装。
-
自动下载使用的浏览器
playwright install
-
执行命令以后,会自动下载chromium、firefox以及webkit三种浏览器,存放文件夹路径为(windows环境):
c:\Users\YOURUSERNAME\AppData\Local\ms-playwright\
- 以上三种浏览器分别对应三种不同内核的浏览器,在爬虫过程中可以自定义选择任意一种浏览器
-
2.playwright命令行参数
想查看Playwright支持的功能, 可在命令行输入:
playwright help
Usage: index [options] [command]
Options:
-V, --version output the version number
-b, --browser <browserType> browser to use, one of cr, chromium, ff, firefox, wk,
webkit (default: "chromium")
--color-scheme <scheme> emulate preferred color scheme, "light" or "dark"
--device <deviceName> emulate device, for example "iPhone 11"
--geolocation <coordinates> specify geolocation coordinates, for example
"37.819722,-122.478611"
--lang <language> specify language / locale, for example "en-GB"
#设置代理
--proxy-server <proxy> specify proxy server, for example "http://myproxy:3128" or
"socks5://myproxy:8080"
--timezone <time zone> time zone to emulate, for example "Europe/Rome"
--timeout <timeout> timeout for Playwright actions in milliseconds (default:
"10000")
#设置user-agent
--user-agent <ua string> specify user agent string
#设置浏览器打开窗口的大小
--viewport-size <size> specify browser viewport size in pixels, for example "1280,
720"
-h, --help display help for command
Commands:
open [url] open page in browser specified via -b, --browser
cr [url] open page in Chromium
ff [url] open page in Firefox
wk [url] open page in WebKit
codegen [options] [url] open page and generate code for user actions
screenshot [options] <url> <filename> capture a page screenshot
pdf [options] <url> <filename> save page as pdf
install Ensure browsers necessary for this version of Playwright
are installed
help [command] display help for command
3.playwright codegen自动生成代码
在命令行输入下面代码会自动弹出一个浏览器和一个代码编辑器 在该浏览器上每一步操作都会自动生成到代码编辑器上,可复制使用
playwright codegen https://www.baidu.com/ -o script.py
查看录制脚本的命令说明
playwright codegen --help
Usage: index codegen [options] [url]
open page and generate code for user actions
Options:
#生成自动化脚本路径
-o, --output <file name> saves the generated script to a file
# –target 脚本语言,包含 JS 和 Python,分别对应值为:python 和 javascript
--target <language> language to use, one of javascript, python, python-async, csharp (default: "python")
#帮助文档
-h, --help display help for command
Examples:
$ codegen
$ codegen --target=python
#指定浏览器驱动
$ -b webkit codegen https://example.com
-
如要在baidu.com搜索,用chromium驱动,将结果保存为mikezhou.py的python文件。
playwright codegen --target python -o 'mikezhou.py' -b chromium https://www.baidu.com
命令行输入后会自动打开浏览器,然后可以看见在浏览器上的一举一动都会被自动翻译成代码,如下所示:
最后,自动化脚本会自动生成,保存到文件中mikezhou.py
, 且上述所有的人工操作,都会被自动转化成代码:
from playwright import sync_playwright
def run(playwright):
browser = playwright.chromium.launch(headless=False)
context = browser.newContext()
# Open new page
page = context.newPage()
# Go to https://www.baidu.com/
page.goto("https://www.baidu.com/")
# Click input[name="wd"]
page.click("input[name=\"wd\"]")
# Fill input[name="wd"]
page.fill("input[name=\"wd\"]", "禾目大")
# Press CapsLock
page.press("input[name=\"wd\"]", "CapsLock")
# Fill input[name="wd"]
page.fill("input[name=\"wd\"]", "自动化测试实战宝典 ")
# Press Enter
page.press("input[name=\"wd\"]", "Enter")
# assert page.url() == "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%AE%9E%E6%88%98%E5%AE%9D%E5%85%B8%20&fenlei=256&rsv_pq=af40e9aa00012d5a&rsv_t=c659gpz2%2Fjri1SAoIXdT9gP%2BmrqufXzRtMSSAL0n0fv7GSoLF5vaiNVPA3U&rqlang=cn&rsv_enter=1&rsv_dl=tb&rsv_sug3=38&rsv_sug1=22&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=8034&rsv_sug4=9153"
# Close page
page.close()
# ---------------------
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)
4.Chrome和Chromium有什么关系?
可看做同一个项目下的两个分支,chromium是测试开源版
,所有的功能都会先在其身上测试,确定稳定运行后再移植到chrome上,而chrome是Google正式商业版
浏览器,两者由Google官方和chromium社区
进行维护
三.基本概念
1. 无头浏览器(Headless Browser)
HeadlessBrowser 俗称的无头浏览器, 实际上就是没有图形界面的浏览器, 因为省去了视觉渲染的工作, 性能和开销有较大优化, 粗略估计, 原本只能启动十个浏览器的内存, 使用 Headless 模式可以至少启动三倍的数量
无头浏览器应用场景
- 无浏览器 UI,运行速度较快,常用于自动化运行
- 有浏览器 UI,常用于调试代码
Playwright 支持以无头模式(Headless Browser
)执行自动化测试,这样就不会实际打开可见的浏览器窗口
。无头模式对于持续集成(CI)、后台执行测试或在没有图形界面的服务器环境中运行测试非常有用
在使用Playwright的无头浏览器模式(headless=True)时遇到找不到元素的问题
-
可能是网站反爬虫机制或User-agent参数问题导致的。
- 常见的反爬虫手段是通过检测当前user-agent是否为
真实浏览器
来区分当前请求是否来自真实用户。爬虫使用的常见user-agent类型为:- user-agent为空。没有设置user-agent。
- user-agent中包含特殊字符。如:python,java,bot,spider,headless等。
- 常见的反爬虫手段是通过检测当前user-agent是否为
而使用Playwright的Chrome无头浏览器访问网站时,user-agent中会自动添加Headless字段。当网站检测到user-agent包含Headless时判定为非真实请求时,可能会返回空页面,所以导致无头浏览器找不到元素。
- 用浏览器到https://www.useragentstring.com/index.php 查看当前浏览器使用的
User Agent String
,在Playwright配置中设置自定义的User-agent#通过args设置 browser = playwright.chromium.launch(headless=True, args=['--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36']) #通过上下文设置 context = browser.new_context(no_viewport=True,user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
2.同步和异步模式操作playwright
2.1.同步(Sync)模式
在同步模式下,代码按照从上到下的顺序执行。每个操作都会阻塞直到完成
,然后再执行下一行代码。这意味着在等待某个操作(如页面加载)完成之前,程序不会继续往下执行。
- 关键字:
sync_playwright
- 对于初学者或习惯于同步编程模型的开发者来说,同步模式可能更加直观和易于理解,因为它遵循了传统的线性编程逻辑。
# 导入Playwright类和sync_palywright 同步类
from playwright.sync_api import sync_playwright
# -------------------------写法1
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=False) # 启动 chromium 浏览器
context = browser.new_context() # 打开一个上下文
page = context.new_page() # 打开一个标签页
page.goto("https://www.baidu.com") # 打开百度地址
print(page.title()) # 打印当前页面title
context.close()
browser.close() # 关闭浏览器对象
# -------------------------写法2
# 如果不习惯with语句,也可以用start() 和stop() 的方式:
def main(playwright):
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("https://www.baidu.com/")
print(page.title())
context.close()
browser.close()
with sync_playwright() as playwright:
main(playwright)
同步方式代码模板
from playwright.sync_api import Playwright, sync_playwright, expect
def main(playwright: Playwright) -> None:
"""
这是一个名为main的函数定义,它接受一个名为playwright的参数,该参数被类型注解为Playwright
(这表明playwright应该是一个Playwright实例,但注意Python本身不强制类型注解,这是为了代码可读性和工具支持)。
函数没有返回值(-> None表示返回类型为None)。
"""
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
###################这里将是操作页面的代码块##################
#**完整的playwright流程**
#1. 创建browser(浏览器实例)
#2. 创建context(共cookie、session)
#3. 创建page(具体选项卡页面),然后用页面去模拟操作
#4. 完成所有的操作后关闭之前创建的三种对象。
pass
#############################################################
page.close()
context.close()
browser.close()
with sync_playwright() as playwright:
main(playwright)
2.2.异步(Async)模式
异步模式利用Python的asyncio库
,允许非阻塞的并发操作
。可以在等待某个耗时操作(如网络请求)的同时,程序可以继续执行其他任务
。你需要使用await关键字来等待异步操作的结果
。
-
关键字为:
async_playwright
,异步操作可结合asyncio
同时进行三个浏览器操作。 -
效率: 异步模式能显著提高脚本的执行效率,特别是在进行大量网络请求或需要同时管理多个页面/浏览器实例的场景下。因为它能够更好地利用系统资源,减少闲置时间。
- 比我想爬取300章小说,如果用单线程去爬,那么时间是线性的,爬取每一章节所使用的时间积累起来就是最终所用的总时间;如果用多线程,比如说同时用10个线程去爬,那么理论上总时间就是单线程所用时间的
1/10
。
相反,如果我只是想获取某一个页面的内容,那么直接单线程就完事
- 比我想爬取300章小说,如果用单线程去爬,那么时间是线性的,爬取每一章节所使用的时间积累起来就是最终所用的总时间;如果用多线程,比如说同时用10个线程去爬,那么理论上总时间就是单线程所用时间的
-
复杂度: 相比同步模式,异步编程模型引入了额外的概念,如
协程(coroutine)、事件循环(event loop)
等,这可能会增加代码的复杂度,尤其是对于不熟悉异步编程的开发者而言。
import asyncio
from playwright.async_api import Playwright, async_playwright
# 写法1
with async_playwright() as playwright:
browser = await playwright.chromium.launch(headless=False) # 启动 chromium 浏览器
context = await browser.new_context() # 打开一个上下文
page = await context.new_page() # 打开一个标签页
await page.goto("https://www.baidu.com") # 打开百度地址
print(await page.title()) # 打印当前页面title
await context.close()
await browser.close()
# 写法2 如果不习惯with语句,也可以用start() 和stop() 的方式:
async def main():
async with async_playwright() as playwright:
browser = await playwright.chromium.launch(headless=False)
context = await browser.new_context()
page = await context.new_page()
await page.goto("https://www.baidu.com/")
print(await page.title())
await context.close()
await browser.close()
asyncio.run(main())
例如:
import asyncio
from playwright import async_playwright
# 异步执行
async def main():
async with async_playwright() as p:
for browser_type in [p.chromium, p.firefox, p.webkit]:
# 指定为有头模式,方便查看
browser = await browser_type.launch(headless=False)
page = await browser.new_page()
await page.goto('http://baidu.com')
# 执行一次搜索操作
await page.fill("input[name=\"wd\"]", "自动化测试实战宝典")
await page.press("input[name=\"wd\"]", "Enter")
# 等待页面加载完全
await page.wait_for_selector("text=搜索工具")
# 截图
await page.screenshot(path=f'test-{browser_type.name}.png')
await browser.close()
asyncio.get_event_loop().run_until_complete(main())
异步方式代码模板
import asyncio
from playwright.async_api import Playwright, async_playwright, expect
async def run(playwright: Playwright) -> None:
browser = await playwright.chromium.launch(headless=False)
context = await browser.new_context()
page = await context.new_page()
#########################操作页面的代码块#############
#**完整的playwright流程**
#1. 创建browser(浏览器实例)
#2. 创建context(共cookie、session)
#3. 创建page(具体选项卡页面),然后用页面去模拟操作
#4. 完成所有的操作后关闭之前创建的三种对象。
pass
####################################################
await page.close()
await context.close()
await browser.close()
async def main() -> None:
async with async_playwright() as playwright:
await run(playwright)
asyncio.run(main())
3.Browser(浏览器驱动)
- 对应一个浏览器实例(Chromium、Firefox或WebKit) 支持多种浏览器:Chromium(chrome、edge)、Firefox、WebKit(Safari),一般每一种浏览器只需要创建一个 browser 实例。示例:
#launch()方法是Playwright中用于启动浏览器的函数。它接受一个可选参数,该参数可以是一个字典,用于配置浏览器的选项。
#1.创建浏览器:(这里创建的是谷歌浏览器)
browser = playwright.chromium.launch(headless=False) # headless=False 是有头模式,也就是代码运行时候,需要浏览器页面
#(创建的是火狐浏览器)
browser = playwright.firefox.launch()
#关闭浏览器
browser.close()
browser对象常用配置项
1.是否无头模式(即是否隐藏浏览器界面): headless = False # 显示界面,为True时隐藏界面
2.打开时最大化窗口:args = ['--start-maximized'] # 还要配合context中设置 no_viewport = True
3.网络代理:
proxy = {
"server": "http://127.0.0.1:8080", # 代理服务器的地址
"bypass": "*.http://bing.com", # 不使用代理的域名
"username": "Mike", # 代理服务器的用户名
"password": "123456" # 代理服务器的密码
}
4.指定下载保存路径:downloads_path = r"d:\"
5.viewport: 字典,用于指定浏览器窗口的大小和位置。例如:{'width': 800, 'height': 600}。
6.slow_mo: 浮点数,默认为0。如果设置为大于0的值,则会增加浏览器操作的延迟时间(单位为毫秒)。
7.ignore_https_errors: 默认为False。如果设置为True,则在访问HTTPS网站时不会检查证书错误。
8.args: 列表,用于传递给浏览器进程的命令行参数。例如:['--disable-gpu']
4.Context(浏览器上下文)
一个浏览器实例下可以有多个context,将浏览器分割成不同的上下文,以实现会话
的隔离,如需要不同用户登录同一个网页,不需要创建多个浏览器实例,只需要创建多个context即可
- 可以理解为
轻量级的浏览器实例
.如需要不同用户登录同一个网页,不需要创建多个浏览器实例,只需要创建多个context即可。
浏览器上下文,相当于一个全新的浏览器配置文件,提供了完全的测试隔离,并且零开销。创建一个新的浏览器上下文只需要几毫秒,每个上下文都有自己的Cookie、浏览器存储和浏览历史记录。
浏览器上下文允许同时打开多个页面并与之交互,每个页面都有自己单独的状态,一个 BrowserContext 可以包含多个 Page
#new_context()方法是Playwright库中用于创建一个新的浏览器上下文的函数。它接受一个可选参数,该参数可以是一个字典,用于配置浏览器上下文的选项
#1.创建浏览器上下文
context = browser.new_context()
#2.关闭
context.close()
Context相关常用配置项
1.no_viewport=True:最大化窗口(与browser的args联合使用)
2.java_script_enabled=False: 禁用javascript:
3.viewport: 字典,用于指定浏览器窗口的大小和位置。例如:{'width': 800, 'height': 600}。
4.忽略https错误: ignore_https_errors=True
5.user_agent: 字符串,默认为当前浏览器的用户代理字符串。如果设置为其他值,则会使用指定的用户代理字符串。
6.accept_downloads: 布尔值,默认为False。如果设置为True,则会在下载文件时自动接受下载对话框。
7.record_har: 字典,用于录制HTTP请求和响应数据。例如:{'path': '/tmp/har.har'}。
8.其他配置
device_scale_factor: 浮点数,指定设备缩放比例,例如 1.5。如果不指定,则使用默认的设备缩放比例。
is_mobile: 布尔值,指定是否模拟移动设备,默认为 False。
has_touch: 布尔值,指定是否支持触摸事件,默认为 False。
bypass_csp: 布尔值,指定是否绕过内容安全策略,默认为 False。
locale: 字符串,指定浏览器的语言和地区,例如 “en-US” 或 “zh-CN”。如果不指定,则使用默认的语言和地区。
timezone_id: 字符串,指定浏览器的时区,例如 “Asia/Shanghai” 或 “America/New_York”。如果不指定,则使用默认的时区。
geolocation: 字典,指定浏览器的地理位置,包括 latitude(纬度),longitude(经度)和 accuracy(精度),例如 {“latitude”: 31.2304, “longitude”: 121.4737, “accuracy”: 10}。如果不指定,则使用默认的地理位置。
permissions: 列表,指定浏览器的权限,例如 [“geolocation”, “notifications”, “camera”]。如果不指定,则使用默认的权限。
extra_http_headers: 字典,指定浏览器的额外 HTTP 头部,例如 {“x-foo”: “bar”}。如果不指定,则使用默认的 HTTP 头部。
offline: 布尔值,指定是否模拟离线状态,默认为 False。
http_credentials: 字典,指定浏览器的 HTTP 认证,包括 username(用户名)和 password(密码),例如 {“username”: “admin”, “password”: “123456”}。如果不指定,则使用默认的 HTTP 认证。
color_scheme: 字符串,指定浏览器的配色方案,可以是 “dark” 或 “light”。如果不指定,则使用默认的配色方案。
record_video: 字典,指定是否录制浏览器的视频,包括 dir(视频保存的目录)和 size(视频的宽度和高度),例如 {“dir”: “videos/”, “size”: {“width”: 800, “height”: 600}}。如果不指定,则不录制视频。
proxy: 字典,指定代理设置,包括 server(代理服务器地址),bypass(要绕过代理的域名列表),username(代理用户名),password(代理密码)
context常用方法
context.pages :获取context所有page对象
context.new_page():生成一个新的page对象
context.close():关闭context
context.add_cookies():将cookie添加到此浏览器上下文中。此上下文中的所有页面都将安装这些cookie。
只能传入列表 List[{name: str, value: str, url: Union[str, None], domain: Union[str, None], path: Union[str, None], expires: Union[float, None]
context.clear_cookies():清除context的cookie
context.grant_permissions():授予浏览器上下文的指定权限,具体见api
context.clear_permissions():清除授权
5.Page页面(浏览器标签页)
一个context下可以有多个page,一个page就代表一个浏览器的标签页或弹出窗口
,用于进行页面操作。这个也是我们主要操作的对象。后续打开网页 、定位元素、页面操作都是基于page
#1.创建一个新的浏览器页面
page = context.new_page()
#2.打开一个网页
page.page.goto(url , **kwargs)
#默认是在当前tab打开 page.goto(url) 如果你想要在同一个上下文中打开多个页面,重新创建page即可
# url就是网址,需要包含访问协议,比如https://www.bing.com
# **kwargs包括:
# timeout = 10000 # 可选项,单位ms,超时时间,默认30秒,设为0则永不超时
# wait_until = 'load' # 可选项,等待页面状态符合指定值,默认为load,具体解释参加下方内容
#3.关闭
page.close()
#4.获取当前页面的URL
page.url
#5.在页面上执行JavaScript代码
page.evaluate('() => document.title')
#6.截取页面的屏幕截图
page.screenshot(path='screenshot.png')
async def new_page(
self,
viewport: ViewportSize = None,#为每个页面设置一致的窗口。默认为1280x720窗口
screen: ViewportSize = None, # 通过“window.screen”模拟网页内可用的一致窗口屏幕大小 ,只能在viewport设置之后使用
noViewport: bool = None,# 不强制固定窗口,允许在标题模式下调整窗口大小
ignoreHTTPSErrors: bool = None,
javaScriptEnabled: bool = None, #禁用 javaScript
bypassCSP: bool = None,
userAgent: str = None, # 设置代理用于上下文
locale: str = None, #指定用户区域,设置将影响Accept-Language'请求标头值以及数字和日期格式规则
timezoneId: str = None, #设置时区
geolocation: Geolocation = None,
permissions: List[str] = None,
extraHTTPHeaders: Dict[str, str] = None,
offline: bool = None,
httpCredentials: HttpCredentials = None,
deviceScaleFactor: float = None,
isMobile: bool = None, #设备相关,不用管
hasTouch: bool = None,
colorScheme: ColorScheme = None, #Union["dark", "light", "no-preference", "null", None]
#设置颜色主题
forcedColors: ForcedColors = None, #Union["active", "none", "null", None]
reducedMotion: ReducedMotion = None,
acceptDownloads: bool = None,
defaultBrowserType: str = None,
proxy: ProxySettings = None, #设置代理
recordHarPath: Union[Path, str] = None,
recordHarOmitContent: bool = None,
recordVideoDir: Union[Path, str] = None,
recordVideoSize: ViewportSize = None,
storageState: Union[StorageState, str, Path] = None,
baseURL: str = None,
strictSelectors: bool = None,
serviceWorkers: ServiceWorkersPolicy = None,
recordHarUrlFilter: Union[Pattern[str], str] = None,
recordHarMode: HarMode = None,
recordHarContent: HarContentPolicy = None,
) -> Page:
四.页面元素定位
1.locator选择器
locator()方法支持所有的
CSS选择器`,包括:
- 基本选择器:如 div, span, .my-class, #my-id 等。
- 属性选择器:如 [href], [class=“my-class”], [data-my-attr=“value”] 等。
- 伪类选择器:如 :hover, :focus, :first-child, :last-of-type 等。
- 结合选择器:如 div.my-class, div, span, div > p, div + p 等。
-
操作元素有两种方式
- 先定位元素再操作元素
# 先定位再操作 page.locator('#kw').fill("上海悠悠") page.locator('#su').click()
- 直接在操作元素的时候定位元素
如:调用fill 和 click 方法,传入Selector选择器**
page.fill('#kw', "欧阳博客") page.click('#su')
-
locator()方法可以根据元素的CSS选择器来查找。您可以使用各种CSS选择器,包括但不限于:
标签名:例如 page.locator('button') 类名:例如 page.locator('.my-class') ID:例如 page.locator('#my-id') 属性:例如 page.locator('[data-testid="my-test-id"]') 文本内容:例如 page.locator(':text("My Text")')
- 使用 locator 定位元素,不管元素存不存在,都会返回一个
locator 对象
,可以用到count() 方法统计元素的个数,如果元素个数是 0, 那么元素就不存在 - locator 是定位
当前页面上的元素,不会自动等待
,如果用click等方法结合使用,会自动去等待元素处于可点击状态。
- 使用 locator 定位元素,不管元素存不存在,都会返回一个
1.文本选择器
文本选择器是一个非常实用的定位方式,根据页面上看到的text文本就可以定位了 playwright 封装了text文本定位的方式,也可以支持2种文本定位方式
- page.click(
'text=登录'
)没有加引号(单引号或者双引号),模糊匹配,对大小写不敏感
- page.click(
'text="登录"'
)有引号,精确匹配,对大小写敏感
text()查找第一个文本等于...的元素
has_text():筛选包含指定文本的元素,匹配元素内或子元素中的文本内容。
has_not_text():筛选不包含指定文本的元素。
比如:<article><div >Playwright</div></article>.
page.locator(':has_text("Playwright")').click()
# 也可以这样写,指定标签
page.locator('article:has_text("Playwright")').click()
# 也可以这样
page.locator(":text('Playwright')").click()
# 还可以这样
page.locator('article:has'text=Playwright').click()
page.locator("div ").locator("text='Playwright'")
2.css选择器和Xpath 定位
page.locator('css=button').click() # 根据标签
page.locator('css=#nav-bar .contact-us-item').click() # 通过id +class
page.locator('css=[data-test=login-button]').click() # 属性定位
page.locator("css=[aria-label='Sign in']").click()
page.locator(xpath="//div[@id='myId']").click()
#不需要前面的前缀css= 和 xpath=, 它会自动判断你写的是css还是xpath语法,前提是你语法没有错误。
3.组合定位:text、css、xpath三者可以两组合定位
-
css+text组合定位
page.locator("article:has-text('Playwright')").click() page.locator("#nav-bar :text('Contact us')").click()
-
css+css组合定位
page.locator(".item-description:has(.item-promo-banner)").click()
-
Xpath + css 组合定位
page.fill('//div[@class="SignFlow-account"] >>css=[name="username"]',"0863")
-
xpath+xpath组合定位
page.fill('//div[@class="SignFlowInput"] >> //input[@name="password"]',"ma160065")
-
利用HTML元素的属性来定位,比如ID、name或其他自定义属性。
page.locator("[data-testid='my-element']")
-
组合定位: 在复杂场景下,你可能需要结合多个条件来定位元素,Playwright 支持链式调用来实现这一需求。
page.locator("div.container").locator("input[type='text']")
2.playwright推荐的内置定位——get_by
get_by_role: 根据元素在页面中扮演的角色(如按钮、链接、输入框等)进行定位 例如:page.get_by_role("button", name="Submit")
get_by_text: 通过使用文本内容来定位元素,适合于元素没有唯一标识符的情况。例如:page.get_by_text("Submit")
get_by_label: 根据label属性值查找元素,类似于HTML中的label标签和对应的for属性。
get_by_id: 通过元素的 id 属性来查找元素,例如:page.get_by_id("my-id")
get_by_name: 通过元素的 name 属性来查找元素,例如:page.get_by_name("my-name")
get_by_title: 通过元素的 title 属性来查找元素,例如:page.get_by_title("my-title")
get_by_placeholder: 通过元素的 placeholder 属性来查找元素,例如:page.get_by_placeholder("my-placeholder")
get_by_selector: 通过 CSS 选择器来查找元素,例如:page.get_by_selector("#submit-button")
get_by_xpath: 通过 XPath 表达式来查找元素,例如:page.get_by_xpath("//div[@class='my-class']")
3.html5的role属性与get_by_role
html5总能role的作用是 是增强html语义性,当现有的html标签不能充分表达语义性的时候,就可以借助role来说明。
-
通常这种情况出现在一些
自定义的组件上,这样可增强组件的可访问性、可用性和可交互性。
-
role的作用是描述一个非标准的tag的实际作用。比如用div做button,那么设置div 的 role=“button”,辅助工具就可以认出这实际上是个button
-
元素的role属性参考https://www.w3.org/TR/2014/REC-wai-aria-implementation-20140320/#mapping_role_tablehttp://
常用role属性
1. button :表示一个按钮,用于触发某个操作。
2. link :表示一个链接,通常用于导航目的。
3. heading:表示一个标题,标识文档结构。
4. textbox :表示一个文本框,用户可以在其中输入文本。
5. checkbox:表示一个复选框,用户可以选择或取消选择。
6. radiobutton :表示一个单选按钮,用户可以从一组选项中选择一个。
7. menu :表示一个菜单,用户可以从中选择一个选项。
8. list :表示一个列表,可以是有序或无序的。
9. progressbar :表示一个进度条,用于表示任务的进度。
10. dialog :表示一个对话框,用户可以与之交互。
get_by_role
#元素:<li class="el-menu-item" role="menuitem" tabindex="-1">队列管理</li>
page.get_by_role("menuitem", name="队列管理").click()
#元素:<button class="el-button el-button--primary el-button--default mr-24px mb-12px" aria-disabled="false" type="button"><span>新增队列</span></button>
page.get_by_role("button", name="新增队列").click()
五.浏览器操作
比如:输入文本、单选、多选、选择下拉框、点击按钮/文本、按下某个按键、上传文件、元素对应焦点、鼠标的拖拽、执行JS脚本等等。
1.Text input文本输入
对应的是input、textarea、contenteditable等元素
locator = page.get_by_label("Password")
locator.fill("mypassword") # 输入一段文字
locator.fill("type")#一个字符一个字符地输入字段
2.Checkboxes and radio buttons 单选和多选
locator = page.get_by_label('I agree to the terms above')
locator.check()
3.Select options下拉选择
locator = page.get_by_label('Choose a color')
locator.select_option('blue')
4.Click鼠标单击和双击
# 点击
page.get_by_role("button").click()
# 双击
page.get_by_text("Item").dblclick()
# 右击
page.get_by_text("Item").click(button="right")
# Shift + 点击
page.get_by_text("Item").click(modifiers=["Shift"])
# 鼠标悬停在元素上
page.get_by_text("Item").hover()
# 点击左上角
page.get_by_text("Item").click(position={ "x": 0, "y": 0})
5.Press按下指定的键
# 按Enter键
page.get_by_text("Submit").press("Enter") # locator上按回车键
# 按ctrl + 右方向键
page.get_by_role("textbox").press("Control+ArrowRight") # ctrl + 右方向键
# 按键盘上的$符合
page.get_by_role("textbox").press("$") # 按下$
上述特殊按键有:
Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape,
ArrowDown, End, Enter, Home, Insert, PageDown, PageUp, ArrowRight,
ArrowUp, F1 - F12, Digit0 - Digit9, KeyA - KeyZ
可以组合按下指定的键
# <input id=name>
page.locator('#name').press('Shift+A') # id为name的元素中按下shift + A
# <input id=name>
page.locator('#name').press('Shift+ArrowLeft')
6.Focus聚焦
page.get_by_label('password').focus()
7.Darg and Drop拖拉
效果是先将鼠标移动要操作的locator上,然后按住左键,移动鼠标到目标locator所在位置,松开鼠标
page.locator("#item-to-be-dragged").drag_to(page.locator("#item-to-drop-at"))
# 将一个locator拖到另一个locator上
8.鼠标移动到指定的locator上
这在处理一些隐藏菜单很有效,鼠标放到菜单上后,菜单显示,然后就可以操作
page.locator("#item-to-be-dragged").hover()
page.mouse.down()
page.locator("#item-to-drop-at").hover()
page.mouse.up()
9.运行JS脚本
这也是一个很有效的手段,比如某个日期输入框是只读的,无法直接录入想要的日期,只能通过日期选择框去选择,而通过日期选择框去选择效率会很低下,这个时候我们只需要通过运行JS脚本将该输入框的只读属性去掉,然后使用input方法录入日期即可
# 将id为txtStartDtate的元素去掉readonly属性
page.evaluate('document.getElementById("txtStartDate").removeAttribute("readonly");')
10.文件上传
# Select one file
page.get_by_label("Upload file").set_input_files('myfile.pdf')
# page.get_by_label("Upload file")为一个locator,选择一个文件myfile.pdf
# Select multiple files,选择多个文件
page.get_by_label("Upload files").set_input_files(['file1.txt', 'file2.txt'])
# Remove all the selected files,清楚选择的文件名
page.get_by_label("Upload file").set_input_files([])
# Upload buffer from memory
page.get_by_label("Upload file").set_input_files(
files=[
{"name": "test.txt", "mimeType": "text/plain", "buffer": b"this is a test"}
],
)
11.页面事件监听
Page 对象提供了一个 on 方法
,它可以用来监听页面中发生的各个事件,比如 close、console、load、request、response
等等
from playwright.sync_api import sync_playwright
def run(playwright):
chromium = playwright.chromium
browser = chromium.launch()
page = browser.new_page()
# 监听请求和响应事件
page.on("request", lambda request: print(">>", request.method, request.url))
page.on("response", lambda response: print("<<", response.status, response.url))
page.goto("https://example.com")
browser.close()
with sync_playwright() as playwright:
run(playwright)
12.获取元素文本
inner_text():获取元素的文本内容
六.断言
expect(locator).to_be_checked() 复选框可被选中
expect(locator).to_be_disabled() 元素处于禁用状态
expect(locator).to_be_editable() 元素为可编辑状态
expect(locator).to_be_empty() 容器为空
expect(locator).to_be_enabled() 元素状态为enabled
expect(locator).to_be_focused() 元素位于焦点
expect(locator).to_be_hidden() 元素不可见
expect(locator).to_be_visible() 元素可见
expect(locator).to_contain_text() 元素包含文本
expect(locator).to_have_attribute() Element has a DOM attribute
expect(locator).to_have_class() Element has a class property
expect(locator).to_have_count() List has exact number of children
expect(locator).to_have_css() Element has CSS property
expect(locator).to_have_id() Element has an ID
expect(locator).to_have_js_property() Element has a JavaScript property
expect(locator).to_have_text() Element matches text
expect(locator).to_have_value() Input has a value
expect(locator).to_have_values() Select has options selected
expect(page).to_have_title() Page has a title
expect(page).to_have_url() Page has a URL
expect(response).to_be_ok() Response has an OK status
可自定义一个不符合条件的错误信息:
# 不符合条件超时错误后,提示should be logged in
expect(page.get_by_text("Name"), "should be logged in").to_be_visible()
举例说明,访问bing页面,会根据所在的ip地理位置显示不同版本的页面
page.goto('https://www.bing.com')
try:
# 可尝试更改'必应'为'bing',看是否会报错
expect(page, '非中文页面').to_have_title('必应') # 显示的页面标题是否为必应,否则报错:非中文页面
print('中文页面')
except Exception as e:
print('进入非中文页面')
print(e)
可设置全局expect超时时间,默认为5秒
expect.set_options(timeout=10_000) # 超时为10秒
七.浏览器常见配置
1.截图
1.1. 截图整个页面
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False) # headless=False表示以有头模式运行,可以看到浏览器界面
page = browser.new_page()
page.goto("https://example.com")
page.screenshot(path="screenshot.png") # 截图并保存到当前目录下的screenshot.png
browser.close()
- 如果你想要截取整个可滚动页面的屏幕截图(长截图),可以添加full_page=True参数:
page.screenshot(path="full_page_screenshot.png", full_page=True)
1.2. 截取整个页面并裁剪
首先,你可以截取整个页面,然后使用图像处理库(如Pillow(Python)、Sharp(Node.js)等)来裁剪出你需要的部分。
Python 示例(使用Pillow):
from PIL import Image
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com")
# 截取整个页面
full_page_screenshot = page.screenshot(path="full_page.png", full_page=True)
# 截取整个可滚动页面的屏幕截图(长截图),可以添加full_page=True参数:
# 使用Pillow裁剪图片(这里需要你指定裁剪的坐标和大小)
img = Image.open("full_page.png")
cropped_img = img.crop((left, top, right, bottom)) # left, top, right, bottom 是裁剪区域的坐标
cropped_img.save("cropped_image.png")
browser.close()
- 注意:你需要根据页面内容和布局来手动计算裁剪区域的坐标(left, top, right, bottom)。
2. 设置窗口(Viewport)大小
另一种方法是调整浏览器窗口(Viewport)的大小,以匹配你想要截取的图片大小,然后截取整个页面。但是,这种方法可能不适用于需要滚动条来查看整个内容的页面。
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context(viewport_size={"width": 800, "height": 600}) # 设置窗口大小
page = context.new_page()
page.goto("https://example.com")
# 由于窗口大小已设置,截取的图片大小将与窗口大小相匹配
page.screenshot(path="viewport_screenshot.png")
browser.close()
4.截取特定元素并调整大小
Playwright还支持截取页面中的特定元素。你可以使用locator来定位元素,并调用其screenshot方法:
- 如果你只需要截取页面上的特定元素,并且想要控制输出图片的大小,你可以先截取该元素,然后使用图像处理库来调整图片大小。
# ...(之前的代码,定位到特定元素)
# 截取特定元素
element_screenshot = page.locator("selector").screenshot(path="element_screenshot.png")
# 使用Pillow调整图片大小(如果需要)
# ...(与上面裁剪图片的代码类似,但使用resize方法)
5. 捕获截图为字节流
如果你不想将截图保存到文件,而是想将其捕获为字节流以便进一步处理(如发送到服务器或进行像素差异比较),可以这样做:
import base64
screenshot_bytes = page.screenshot()
screenshot_base64 = base64.b64encode(screenshot_bytes).decode()
print(screenshot_base64) # 输出截图的Base64编码
2.自定义Header
browser_context.set_extra_http_headers(headers)
from playwright.async_api import async_playwright
async def run(playwright):
browser = await playwright.chromium.launch()
context = await browser.new_context()
# 设置额外的HTTP头部
extra_headers = {
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Custom-Header": "HeaderValue"
}
await context.set_extra_http_headers(extra_headers)
# 现在,由这个上下文中的任何页面发起的请求都将包含这些额外的HTTP头部
page = await context.new_page()
await page.goto('https://example.com')
# ... 其他操作 ...
await browser.close()
async_playwright().start(run)
3.自定义UserAgent
创建浏览器上下文(BrowserContext)时,通过new_context方法的user_agent参数来设置user-agent。这种方式该上下文创建的所有页面都会共用user_agent
from playwright.sync_api import Playwright, sync_playwright
with sync_playwright() as playwright:
browser = playwright.chromium.launch()
# 在浏览器上下文级别设置user-agent
context = browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36')
page = context.new_page()
page.goto('https://example.com')
# 后续操作...
browser.close()
4.设置浏览器代理
在使用Playwright进行自动化测试或浏览器操作时,设置代理是一个常见的需求。Playwright支持在多个层面上设置代理,包括全局代理、上下文代理以及页面级别的代理。以下是如何在Playwright中设置代理的几种方式:
1. 启动时设置全局代理
在启动浏览器时,可以通过launch方法的proxy参数来设置全局代理。这种方式会影响由该浏览器实例发起的所有请求。
示例代码:
from playwright.sync_api import Playwright, sync_playwright
with sync_playwright() as playwright:
# 不需要身份验证的代理
browser = playwright.chromium.launch(proxy={'server': 'http://ip:port'})
# 需要身份验证的代理
# browser = playwright.chromium.launch(proxy={'server': 'http://ip:port', 'username': '用户名', 'password': '密码'})
# 后续操作...
# browser.close()
2. 上下文设置代理
通过为每个页面创建一个独立的context对象,并在该对象上设置代理,可以实现为特定页面或一组页面指定不同的代理设置。
from playwright.sync_api import Playwright, sync_playwright
with sync_playwright() as playwright:
browser = playwright.chromium.launch()
# 设置代理
context = browser.new_context(proxy={'server': 'http://ip:port'})
# 基于该上下文创建页面
page = context.new_page()
# 后续操作...
# page.close()
# context.close()
# browser.close()
5.自动等待机制
1.自动等待和##可操作性校验(Auto-waiting)
Playwright具有内置的自动等待机制,该机制在执行某些操作(如点击、输入等)之前,会自动检查目标元素是否满足以下条件:
- 元素加载DOM完成:确保元素已经被加载到DOM中。
- 元素可见:确保元素在页面中可见,没有被其他元素遮挡。
- 元素是稳定的:确保元素的状态已经稳定,不会因为页面的其他操作而发生变化。(非动画中或动画已完成)
- 元素没有被其他元素遮挡:确保点击或交互的位置没有其他元素(如覆盖层)阻挡。
- 元素是可操作的:确保元素是可交互的,没有被禁用。(例如,按钮的enabled属性为true)
如果以上校验在设定的超时时间内得没有通过,Playwright将继续执行下一步操作;否则,将抛出TimeoutError异常
。这种自动等待机制大大简化了测试脚本的编写,并提高了测试的稳定性。
2.显式等待API(Explicit Waiting)
除了自动等待外,Playwright还提供了多种显式等待方法,API允许开发者在测试脚本中设置等待条件,从而避免手动等待的繁琐和不确定性。
wait_for_selector(selector, options):等待指定的选择器匹配到的元素出现在页面上。
wait_for_timeout(timeout):等待指定的时间。
wait_for_url(url, options):等待URL包含指定的字符串。
wait_for_navigation(options):等待页面完成导航。
wait_for_event:等待给定的事件被触发,如click、submit等。
wait_for_function:等待指定的JavaScript函数返回true。
详细介绍
-
wait_for_selector(selector, options):等待指定的选择器匹配到的元素出现在页面上。(等待元素加载完毕)
-
selector:CSS选择器或XPath表达式,用于指定要等待的元素。
-
options(可选):一个对象,包含等待的选项,如timeout(超时时间,单位毫秒)。
element = await page.wait_for_selector('#my-element', { timeout: 5000 }) #这个示例将等待最多5秒钟,直到页面上出现ID为my-element的元素。
-
在进行页面跳转后,可以加入wait_for_selector等待元素加载,以确保页面内容已经完全加载完成
-
state 参数可以设置等待状态,用四个状态:“attached”, “detached”, “hidden”, “visible”。
-
wait_for() 方法和>wait_for() 方法 和 wait_for_selector()使用区别:
- page.locator(‘定位元素’).wait_for() 返回的是None,后面不能继续操作元素
- page.wait_for_selector(“定位方法”)
返回的是locator 对象,后面可以继续操作元素
-
-
wait_for_timeout(timeout):等待指定的时间。这通常用于调试目的,但在生产环境中应谨慎使用。
- timeout:等待的时间,单位毫秒。
await page.wait_for_timeout(5000)
- timeout:等待的时间,单位毫秒。
-
wait_for_url(url, options) 等待当前页面的URL包含指定的字符串或完全匹配指定的URL。
- url:要等待的URL字符串或包含部分URL的字符串。
- options(可选):一个对象,包含等待的选项,如timeout。
await page.wait_for_url('https://example.com', { timeout: 10000 }) #等待最多10秒钟,直到当前页面的URL包含https://example.com。
-
wait_for_navigation(options) 等待页面完成导航。
-
options(可选):一个对象,包含等待的选项,如waitUntil(指定等待的导航状态,如’networkidle’表示等待网络请求空闲)和timeout。
await page.click('a.some-link') await page.wait_for_navigation({ waitUntil: 'networkidle' }) #首先点击一个链接,然后等待页面导航到新的URL,并等待直到网络请求空闲。
-
3.全局设置等待超时时间
- 这些设置将影响所有接受timeout参数的方法。设置的优先级是:页面级别的优先于浏览器上下文级别。
browser_context.set_default_timeout(timeout):设置浏览器上下文级别的默认超时时间。
page.set_default_timeout(timeout):设置页面级别的默认超时时间。
browser_context.set_default_navigation_timeout(timeout):设置浏览器上下文级别的默认导航超时时间。
page.set_default_navigation_timeout(timeout):设置页面级别的默认导航超时时间。
6.处理新的窗口、弹窗,iframe
selenium处理iframe比较麻烦,但是playwright就比较简单,有不同方法
- 直接定位一个frame,在frame基础上操作
# ********同步*********
#根据iframe名字
frame = page.frame('frame-login')
#根据iframe的url
frame = page.frame(url=r'.*domain.*')
# Interact with the frame
frame.fill('#username-input', 'John')
# *********异步***********
#根据iframe名字
frame = page.frame('frame-login')
#根据iframe的url
frame = page.frame(url=r'.*domain.*')
# Interact with the frame
await frame.fill('#username-input', 'John')
- 直接定位到frame再定位到上面的元素,在元素基础上操作
username = page.frame_locator('.frame-class').locator('#username-input')
username.fill('jonas')
处理弹窗,一般注册、或者点击一些按钮容易出现弹窗,我们可以利用page.expect_popup()
来获取新窗口的iframe
示例:
with page.expect_popup() as popup_info:
# iframe中的id如果是动态的,所以我们只匹配关键字
page.frame_locator("iframe[id^=x-URS-iframe]").locator("text=注册新帐号").click()
register_page = popup_info.value
# 点击邮箱地址输入框
register_page.locator("input[id=\"username\"]").click()
# 输入邮箱
register_page.locator("input[id=\"username\"]").fill("TesterRoad")
# 点击密码输入框
register_page.locator("input[id=\"password\"]").click()
# 输入密码
register_page.locator("input[id=\"password\"]").fill("TesterRoad@126")
手动设置等待是为了确保接下来的操作可以成立,旧版本里使用了很多类似wait_for_selector的用法,但新版本推荐使用的是expect方法,就是期望某个条件成立,默认超时时间为5秒
7.支持Pytest框架
另外,还可以配合pytest插件一起使用,给出一段官网示例:
# 导入pytest和playwright的sync_api(也可以使用async_api)
import pytest
from playwright.sync_api import Page
# 使用pytest的fixture来初始化浏览器页面
@pytest.fixture(scope="function")
def page(browser_type):
"""启动浏览器并创建一个新页面"""
browser = browser_type.launch(headless=False) # 如果想看到浏览器运行,可以设置为False
page = browser.new_page()
yield page # 测试函数将在这里接收page对象
browser.close() # 测试完成后关闭浏览器
# 编写测试函数
def test_example(page):
"""示例测试:访问Playwright官网并验证标题"""
# 导航到Playwright官网
page.goto("https://playwright.dev/")
# 验证页面标题是否包含"Playwright"
title = page.title()
assert "Playwright" in title
# 进一步的操作,比如点击链接、填写表单等
# ...
# 注意:上述代码中的browser_type是由pytest-playwright插件自动提供的fixture
# 你可以通过它来启动浏览器(如Chromium、Firefox、WebKit等)
# headless=False 表示以有头模式运行浏览器,这样你可以看到浏览器界面
# 如果你想要以无头模式运行(即不显示浏览器界面),可以将headless设置为True或省略该参数(因为默认就是无头模式)
8.移动端浏览器支持
移动端支持Safari 浏览器、谷歌
、不支持火狐,可以传入的设备有iPhone和Pixel 2 (Pixel 2 是谷歌的一款安卓手机)
-
示例:模拟打开 iPhone 12 Pro Max 上的 Safari 浏览器,然后手动设置定位,并打开百度地图并截图
-
故宫的经纬度是 39.913904, 116.39014,我们可以通过
geolocation
参数传递给 Webkit 浏览器并初始化
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
iphone_12_pro_max = p.devices['iPhone 12 Pro Max']
browser = p.webkit.launch(headless=False)
context = browser.new_context(
**iphone_12_pro_max,
locale='zh-CN',
geolocation={'longitude': 116.39014, 'latitude': 39.913904},
permissions=['geolocation']
)
page = context.new_page()
page.goto('https://amap.com')
page.wait_for_load_state(state='networkidle')
page.screenshot(path='location-iphone.png')
browser.close()
注意事项
- 设置user-agent时,请确保它符合你的测试需求或目标网站的兼容性要求。
- 如果你需要频繁地更改user-agent,考虑将设置user-agent的逻辑封装成一个函数或方法,以便在需要时重复使用。
- 请注意,某些网站可能会根据user-agent的不同而返回不同的内容或执行不同的逻辑。因此,在自动化测试或爬虫开发过程中,合理地设置user-agent是非常重要的。
额外信息
– 除了user-agent之外,Playwright还允许你在创建浏览器上下文时设置其他许多浏览器级别的参数,如窗口大小(viewport)、地理位置(geolocation)、语言(locale)、时区(timezone)
等。这些参数可以帮助你更精确地模拟不同用户的浏览器环境,从而提高自动化测试或爬虫开发的效率和准确性。
八.playwright如何绕过反爬虫检测
检测地址:https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html
-
网站为了保护信息不被爬取会添加一些反爬虫策略,比如直接打开谷歌浏览器,在控制台输入
window.navigator.webdriver
,可以看到该属性为false
-
用selenium或者playWright打开浏览器,该属性为true
-
网站很容易在前端根据这些属性判断是否使用了
playwright
,从而阻止用户采用自动化工具获取信息
那么如何屏蔽掉这些属性,让网站无法识别是否使用了playwright呢?。
-
首先通过浏览器打开网站https://bot.sannysoft.com/,该网站列出了常用的一些反爬虫检测属性
-
使用playwright打开这个网站, 发现
webdriver属性
被检测出异常
from playwright.sync_api import sync_playwright
import time
with sync_playwright() a playwright:
browser _playwright.chromium.launch(headless=False)
page = browser.new_page()
page.goto('https://bot.sannysoft.com/')
time.sleep(100)
browser.close()
1 防止webdriver属性被检测
from playwright.sync_api import sync_playwright
import time
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=False)
page = browser.new_page()
js="""
Object.defineProperties(navigator, {webdriver:{get:()=>false}});
"""
page.add_init_script(js)
page.goto('https://bot.sannysoft.com/')
time.sleep(1000)
browser.close()
-
运行后可以看到webdriver还是没有通过,打开控制台,输入
navigator.webdriver
-
发现这个值的确为false,那么没有通过的原因是什么?找到网站检测的源码,有这么一几行代码
// Webdriver Test const webdriverElement = document.getElementById('webdriver-result'); if (navigator.webdriver || _.has(navigator, "webdriver")) { webdriverElement.classList.add('failed'); webdriverElement.classList.remove('passed'); webdriverElement.innerHTML = 'present (failed)'; } else { webdriverElement.classList.add('passed'); webdriverElement.classList.remove('failed'); webdriverElement.innerHTML = 'missing (passed)'; }
- 属性
_.has(navigator, “webdriver”)
为true
没有通过,这个表示navigator中有webdriver
这个属性了,必须去掉
才可以过这个检测。- 这个去掉该属性直接用GitHub上下载的一个
stealth.min.js
,地址为https://github.com/kingname/stealth.min.js/blob/main/stealth.min.js
- 这个去掉该属性直接用GitHub上下载的一个
from playwright.sync_api import sync_playwright import time with sync_playwright() as p: ''' 防止被浏览器检测的处理方法 ''' browser = p.chromium.launch(headless=False) page = browser.new_page() #加载stealth.min.js的javascript with open('stealth.min.js','r') as f: js=f.read() page.add_init_script(js) page.goto('https://bot.sannysoft.com/') time.sleep(1000) browser.close()
- 运行后,这次检测已经通过了。
- 属性
2 headless=True无头浏览器如何绕过反爬虫
上面可以过检测用的是带界面浏览,当为无头浏览器是怎么样的呢,这里采用运行后进行截图
的方式进行调试
from playwright.sync_api import sync_playwright
import time
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
with open('stealth.min.js','r') as f:
js=f.read()
page.add_init_script(js)
page.goto('https://bot.sannysoft.com/')
#进行截图
page.screenshot(path='bot_sannysoft.png',full_page=True)
time.sleep(1000)
browser.close()
- 当用无头浏览器时
user agent
没有通过,因此需重新设置user agent
from playwright.sync_api import sync_playwright
import time
with sync_playwright() as p:
browser = p.chromium.launch(headless=True,
args=['--disable-blink-features=AutomationControlled',
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'])
page = browser.new_page()
with open('stealth.min.js','r') as f:
js=f.read()
page.add_init_script(js)
# 设置网页大小也可以防止无头浏览器被检测
page.set_viewport_size({'width': 1024, 'height': 768})
page.goto('https://bot.sannysoft.com/')
#进行截图
page.screenshot(path='bot_sannysoft.png',full_page=True)
time.sleep(1000)
browser.close()
- 运行后,发现无头浏览器所有检测也均能通过。
3.stealth.min.js作用
stealth.min.js 这个JavaScript主要用于保护用户隐私和匿名性,同时也在自动化测试中隐藏自动化特征。
主要功能和应用场景:
-
隐藏行为:可以通过模拟用户行为的方式来执行各种操作,比如点击、滚动、输入等,从而隐藏真实用户的操作轨迹使得网站无法准确追踪用户的行为。
-
保护用户隐私和匿名性:通过修改User Agent、禁用浏览器指纹识别、隐藏浏览器插件信息、阻止第三方Cookie等方式,stealth.min.js可以帮助用户在使用网络时保持匿名,防止被网站或第三方服务追踪个人信息。
-
防止爬虫检测:在自动化测试中,如使用Selenium进行网页测试时,stealth.min.js可以帮助隐藏自动化工具的特征,防止被网站识别为非法访问,从而提高测试的效率和准确性。
4.防止爬虫检测的方式
- 使用无头浏览器:无头浏览器不会显示图形界面,从而降低了被检测的风险。
- 设置User-Agent:通过模拟常见的浏览器User-Agent,可以减少被检测的可能性。
- 添加随机延迟:在爬虫中添加随机延迟,模拟真实用户的操作习惯,降低被检测的风险。
- 使用代理IP:通过代理IP来隐藏真实的IP地址,降低被识别和封锁的风险。
九.通过CDP(Chrome DevTools Protocol)连接现有浏览器
1.CDP(Chrome DevTools Protocol)和WDP (WebDriver Protocol)协议
WDP 和 CDP 是用于自动化浏览器
的2个主要协议
,大多数的浏览器自动化工具都是这两个协议实现和浏览器交互,通过代码来控制浏览器,完成浏览器的自动化行为(包括网页加载,点击、输入、按键、截图,导出pdf等)。
2.WebDriver Protocol
- WebDriver 是一个用于控制浏览器的
远程控制接口
,由Selenium HQ
开发,后来由 W3C 标准化。它提供了一个平台和语言中立的接口,支持几乎所有主流浏览器,如 Chrome、 Firefox、 Safari、 Edge、 Opera
等。 - 它和浏览器的通信是通过
JSON Wire 协议
完成的,提供了RESTful
的web服务,该服务端就被被称为webdriver,例如chromeDriver、geckoDriver等。- webdriver的客户端可为
任何语言
,由webdriver客户端和webdriver服务端交互,webdriver服务端和浏览器交互,从而操作浏览器 - 常见的客户端就是
selenium,nightwatch,webdriverio
加上我们的自己写的自动化测试代码之后,交互流程如下:
- 常见的调用流程如下
- webdriver客户端(Python代码) 通过调用自动化库API
- 自动化库通过对应的webdriver服务端(如selenium)基于WDP协议通过
JSON Wire 协议
RESTful方式向浏览器发起操作请求(打开/关闭/点击/输入/按键/截图等等) - 浏览器接收到自动化工具请求后,完成相关操作然后返回响应给webdriver服务端
- webdriver服务器端接收到浏览器响应,返回响应给webdriver客户端
- webdriver的客户端可为
3.Chrome DevTools Protocol
-
ChromeDevTools Protocol
(CDP)是一个基于Chromium
的浏览器的调试协议,如Chrome、 Edge、 Opera
等。通过它可以直接和浏览器交互。控制浏览器的行为。
-
浏览器是由客户端使用 CDP 协议直接控制的
,客户端和浏览器之间没有类似于WebDriver Protocol
的服务端(webdriver),客户端通过WebSocket
直接和浏览器连接 ,由客户端通过WebSocket发送命令给浏览器,浏览器执行并返回响应
。
-
常见的客户端就是:
Puppeteer和Playwright
。它们不依赖于webdriver,而是通过Chrome DevTools Protocol (CDP)
直接与浏览器通话。从而更加灵活稳定的控制浏览器。
4.WebDriver Protocol和Chrome DevTools Protocol对比
5.通过CDP控制本地原生谷歌浏览器
from playwright.sync_api import sync_playwright
import subprocess
"""
1、打开浏览器,指定调试端口
在 CMD 终端中通过命令行启动 Chrome 浏览器
cd C:\Program Files\Google\Chrome\Application && chrome.exe --remote-debugging-port=9222 --user-data-dir=“C:/playwright/user_data”
复制地址C:\Program Files\Google\Chrome\Application 添加到环境变量Path下
chrome.exe --remote-debugging-port=12345 --user-data-dir="D:\playwright_chrome"
参数配置
在启动浏览器的时候,我们还可以带上一些其它参数
--incognito 隐私模式打开
-–start-maximized:窗口最大化
--new-window:直接打开网址
--remote-debugging-port 是指定浏览器调试端口号,只要没被占用就行
--user-data-dir 指定运行浏览器的运行数据(用户配置文件目录),新建一个干净目录,不影响系统原来的数据(不存在会新建),如果不显式指定该参数,运行会污染浏览器默认的配置文件
"""
# 1.打开浏览器,指定调试端口 这个路径可以是Google浏览器的exe路径,也可以是快捷方式的路径
# 使用connect_over_cdp()方法接管前面已经打开的浏览器,获取到context 上下文,通过上下文再获取到page对象
chrome_path = r'"C:\Users\87772\AppData\Local\Google\Chrome\Application\chrome.exe"'
debugging_port = "--remote-debugging-port=9999"
command = f"{chrome_path} {debugging_port}"
subprocess.Popen(command, shell=True)
# 2.拦截请求
def intercept_xhr(route, request):
route.continue_()
response = route.fetch()
json = response.json()
print(json)
with sync_playwright() as p:
# 2.连接到已经通过CDP启动的浏览器实例。
browser = p.chromium.connect_over_cdp("http://localhost:9999")
content = browser.contexts[0]
page = content.new_page()
# 设置拦截规则
page.route("**/api/sns/web/v1/homefeed", lambda route, request: intercept_xhr(route, request))
page.goto('https://www.xiaohongshu.com/')
page.wait_for_selector('.feeds-container')
#点击关闭
page.locator(".close-button").click()
# 获取页面内容高度
page_height = page.evaluate('() => document.body.scrollHeight')
# 模拟鼠标滚动操作,向下滚动到底部
while page.evaluate('() => window.scrollY + window.innerHeight') < page_height:
page.mouse.wheel(0, 100) # 这里的参数可以根据需要进行调整
page.wait_for_timeout(5000)
page.close()
content.close()
browser.close()
十.拓展知识
屏幕坐标系,世界坐标系
屏幕坐标系,主要有两种,
-
第一种:以左上角为原点。代表的操作系统有
Windows,Android,Symbian,iOS 的Core Graphics
-
第二种:以左下角为原点。比如iOS的
CGContextDrawImage
- IOS 零点在左上角
- mac 零点在左下角
十一.Playwright 和 Selenium 的区别
- Playwright的优点是简单方便、功能强大、稳定性高,缺点是相对新,用户群体少,学习资料少。只能看官网文档
- Selenium的优点是灵活性高、用户群体大、学习资料多,缺点是需要自己封装或者导入其他模块来实现一些功能,启动速度慢,稳定性差。
如果你是新手,毫不犹豫应该直接学playwright