Playwright介绍
Playwright是微软在2020年初开源发布的一款Web自动化工具,通过对应的API就可以在Chromium、Firefox、WebKit等主流浏览器进行自动化操作,并同时支持以无头模式、有头模式运行。支持跨平台,兼容Windows、MacOS、Linux操作系统。
Playwright特点
- 支持当前所有主流浏览器,包括 Chrome 和 Edge(基于 Chromium)、Firefox、Safari(基于 WebKit) ,提供完善的自动化控制的 API。
- 支持移动端页面测试,使用设备模拟技术可以使我们在移动 Web 浏览器中测试响应式 Web 应用程序。
- 支持所有浏览器的 Headless 模式和非 Headless 模式的测试。
- 安装和配置非常简单,安装过程中会自动安装对应的浏览器和驱动,不需要额外配置 WebDriver 等。
- 提供了自动等待相关的 API,当页面加载的时候会自动等待对应的节点加载,大大简化了 API 编写复杂度。
官方DotNet文档参考
DotNet下的安装和基本使用
项目安装Microsoft.Playwright包
Install-Package Microsoft.Playwright -ProjectName YourProject
启动时自动安装Playwright相关依赖文件
var exitCode = Microsoft.Playwright.Program.Main(new[] { "install" });
if (exitCode != 0)
{
throw new Exception($"Playwright exited with code {exitCode}");
}
上面代码调用了封装的命令行工具进行安装,等价于以下命令:
pwsh bin\Debug\netX\playwright.ps1 install
执行安装命令时检测本地有没有相关依赖文件(浏览器二进制文件、ffmepg等),没有则下载到本地,Windows下默认会放到如下位置
C:\Users\用户名\AppData\Local\ms-playwright
打开网页并截图保存
打开网易云音乐首页然后截图保存本地
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync();
await using var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync("https://music.163.com/");
await page.ScreenshotAsync(new PageScreenshotOptions { Path = "screenshot.png" });
Console.ReadKey();
运行代码截图就已经保存成功了
浏览器和浏览器上下文
浏览器
目前支持三种内核的浏览器(Chromium、Firefox、Webkit)可以自行选择使用
浏览器选择
using var playwright = await Playwright.CreateAsync();
//Chromium
await using var chromium = await playwright.Chromium.LaunchAsync();
//Firefox
await using var firefox = await playwright.Firefox.LaunchAsync();
//Webkit
await using var webkit = await playwright.Webkit.LaunchAsync();
是否以无头模式启动
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = false
});
Headless = false表示以有头模式启动,展示浏览器UI界面,否则以无头模式启动
浏览器常用启动选项(BrowserTypeLaunchOptions)
选项名 | 类型 | 说明 |
---|---|---|
Args | IEnumerable?< string > | 传递给浏览器实例的附加参数。可以在此处找到 Chromium 标志列表 |
ChromiumSandbox | bool ? | 启用 Chromium 沙盒。默认为false |
Devtools | bool ? | 是否为每个选项卡自动打开开发者工具面板 |
DownloadsPath | string ? | 如果指定,则将接受的下载下载到此目录中。否则,会在浏览器关闭时创建并删除临时目录。在任何一种情况下,当创建它们的浏览器上下文关闭时,下载都会被删除。 |
ExecutablePath | string ? | 指定要运行的浏览器可执行文件路径 |
Headless | bool ? | 是否以无头模式运行浏览器 |
IgnoreAllDefaultArgs | bool ? | 为true时 Playwright 不传递自己的配置参数,而使用自定义传递的 Args 参数值 |
Proxy | Proxy ? | 网络代理设置 |
Timeout | double ? | 等待浏览器实例启动的最长时间(毫秒)。默认为30000(30 秒)。通过0禁用超时。 |
SlowMo | double? | 已毫秒为单位减慢浏览器每步操作的时间。 |
浏览器上下文
BrowserContext是浏览器实例中的一个隔离的类似隐身会话。
await using var browser = playwright.Chromium.LaunchAsync();
var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
浏览器上下文可以用于模拟指定设备、权限、区域语言、配色等各种场景
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Webkit.LaunchAsync();
var options = new BrowserNewContextOptions(playwright.Devices["iPhone 12 Pro"])
{
Geolocation = new() { Longitude = 12.492507f, Latitude = 41.889938f },
Permissions = new[] { "geolocation" },
Locale = "de-DE"
};
await using var context = await browser.NewContextAsync(options);
var page = await browser.NewPageAsync();
多浏览器上下文
多浏览器上下文是单个浏览器实例上的多个彼此隔离的环境,彼此互不影响(不会与其他浏览器上下文共享 cookie/缓存等。),可以用来模拟多用户功能(多用户登录操作、多人聊天等)
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync();
await using var userContext = await browser.NewContextAsync();
await using var adminContext = await browser.NewContextAsync();
获取浏览器的的所有上下文及页面
var all=browser.Contexts.Select(t => new
{
Context = t,
Pages = t.Pages
});
浏览器上下文常用选项(BrowserNewContextOptions)
选项名 | 类型 | 说明 |
---|---|---|
AcceptDownloads | bool ? | 是否自动下载所有附件。默认为true接受所有下载的位置 |
BaseURL | string ? | 基路径Url。当使用Page.GotoAsync(url, options)、Page.RouteAsync(url, handler, options)、Page.WaitForURLAsync(url, options)、Page.RunAndWaitForRequestAsync(action, urlOrPredicate, options)或Page.RunAndWaitForResponseAsync(action, urlOrPredicate, options)URL() 时会自动在前面加上配置的BaseURL。 |
BypassCSP | bool | 切换绕过页面的内容安全策略 |
ExtraHTTPHeaders | IDictionary ?< string , string > | 包含要随每个请求一起发送的附加 HTTP 标头的对象 |
Geolocation | Geolocation ? | 地理位置坐标 。Latitude (经度)Longitude (纬度) |
HasTouch | bool ? | 是否支持触摸。默认为false |
HttpCredentials | HttpCredentials? | HTTP 身份验证的凭据。 |
IgnoreHTTPSErrors | bool ? | 发送网络请求时是否忽略 HTTPS 错误。默认为false |
IsMobile | bool ? | 是否移动端(是否考虑meta viewport标签并启用触摸事件。默认为false. Firefox 不支持) |
JavaScriptEnabled | bool ? | 是否在上下文中启用 JavaScript。默认为true. |
Locale | string ? | 指定用户区域设置,例如en-GB,de-DE等。区域设置将影响navigator.language值、Accept-Language请求头值以及数字和日期格式规则. |
Offline | bool ? | 是否模拟网络离线。默认为false. |
Permissions | IEnumerable ?< string > | 授予此上下文中所有页面的权限列表。 参考 |
Proxy | Proxy ? | 用于此上下文的网络代理设置。 |
RecordVideoDir | string? | 将所有页面的视频录制到指定目录中。如果未指定,则不会录制视频。确保调用BrowserContext.CloseAsync()来保存视频。 |
RecordVideoSize | RecordVideoSize ? | 录制视频的尺寸。如果未指定,大小将等于viewport缩小以适应 800x800。如果viewport未明确配置,则视频大小默认为 800x450。如有必要,每页的实际图片将按比例缩小以适合指定尺寸。 |
ScreenSize | ScreenSize ? | 模拟网页内可用的一致窗口屏幕大小window.screen。仅在viewport设置 时使用。 |
UserAgent | string ? | 在此上下文中使用的特定用户代理。 |
ViewportSize | ViewportSize ? | 每个页面的窗口大小。默认为 1280x720 。用于ViewportSize.NoViewport禁用默认视口 |
基础操作
打开指定网页
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = false
});
await using var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync("https://www.baidu.com",new PageGotoOptions { WaitUntil= WaitUntilState.DOMContentLoaded });
page.GotoAsync(url) 会跳转到一个新的链接。默认情况下 Playwright 会等待到 Load 状态。如果不关心加载的 CSS 图片等信息,可以改为等待到 DOMContentLoaded 状态,如果页面是 ajax 加载,那么我们需要等待到 NetworkIdle 状态。以下是page.GotoAsync(url)加载网页的过程:
- 设定 url
- 通过网络加载解析页面
- 触发 page.on(“domcontentloaded”) 事件
- 执行页面的 js 脚本,加载静态资源(css、图片等)
- 触发 page.on(“laod”) 事件
- 页面执行动态加载的脚本(ajax)
- 当 500ms 都没有新的网络请求的时候,触发 networkidle 事件
获取网页内容
//获取当前页面所有内容
var content = await page.ContentAsync();
//获取指定元素的所有文本内容
var text = await page.TextContentAsync("//div[@id='wrapper']");
//获取元素内部内容
await page.InnerHTMLAsync("//div[@id='wrapper']");
//获取元素内部文本
await page.InnerTextAsync("//div[@id='wrapper']");
查找定位元素
目前常用的有Text、Css、Xpath等选择器
文本选择器
文本选择器是playwright内部定义的一种新的语法,可以通过指定的文本内容来定位元素
await page.Locator("text=Log in").ClickAsync();
1. text=Log in 默认不区分大小写,并且搜索子字符串文本。例如,text=Log匹配< button >Log in< /button >
2. 可以通过对匹配文本加单引号或双引号进行转义来进行精准匹配。例如,text="Log"不匹配< button >Log in< /button >但是,text=“Log"匹配 < button >Log< span >in< /span >< /button >,因为< button>包含一个文本节点"Log”。这种精确模式意味着区分大小写的匹配,因此text="Download"不会匹配< button>download< /button>
定位器Locator
返回单个匹配元素
var btn=await page.Locator("text=Log in");
QuerySelectorAsync和QuerySelectorAllAsync
//返回单个匹配节点元素
var searchBtn=await page.QuerySelectorAsync("text=搜索");
//返回多个匹配节点元素
var articles = await page.QuerySelectorAllAsync("//div[@class='list-articles']/article");
获取指定元素的指定属性值
await page.GetAttributeAsync("//input[@id='su']", "class");
网页写入内容
await page.SetContentAsync("<h1>666<h1>");
输入内容
await page.FillAsync("//input[@id='kw']", "输入的内容");
鼠标点击元素
await page.ClickAsync("//input[@id='su']");
鼠标点击指定位置
await page.Mouse.ClickAsync(50,100);
鼠标双击
await page.DblClickAsync("//input[@id='su']");
鼠标按下
默认按下左键
await page.Mouse.DownAsync(new MouseDownOptions { Button = MouseButton.Left });
鼠标释放
await page.Mouse.UpAsync();
鼠标移动到指定位置
await page.Mouse.MoveAsync(50,100);
鼠标滚轮滚动到指定位置
await page.Mouse.WheelAsync(50,100);
获取元素焦点
await page.FocusAsync("//input[@id='kw']");
鼠标悬停在指定元素上
await page.HoverAsync("//input[@id='kw']");
设置复选框/单选框 选中或取消
await page.SetCheckedAsync("//input[@id='su']", true);
截图
await page.ScreenshotAsync(new PageScreenshotOptions { Path="test.jpg" });
填写文本并触发键盘事件
//输入的每一个字符都会触发keydown、keypress/input和keyup事件
await page.TypeAsync("#kw","测试");
输入键盘操作
//按下Enter键
await page.PressAsync("#su","Enter");
调度事件
await page.DispatchEventAsync("#su","click");
等待元素渲染加载完成
await page.WaitForSelectorAsync("//div[@class='yidun_bgimg']");
等待自定义时间(毫秒)
await page.WaitForTimeoutAsync(1000);
模拟用户登录
找到要操作的元素
打开登录页(这里我模拟的是163邮箱的登录)然后找到我们要操作的相关元素
用户名输入框
密码输入框
登录按钮
定位元素并执行操作
看个人习惯可以使用Xpath也可以使用Css选择器定位元素,这里我采用的是Xpath进行元素节点定位
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = false
});
await using var context = await browser.NewContextAsync();
var page=await context.NewPageAsync();
//打开网页
await page.GotoAsync("https://mail.163.com/");
//输入用户名
await page.FillAsync("//input[contains(@placeholder,'邮箱帐号或手机号码')]", "admin");
//输入密码
await page.FillAsync("//input[@type='password'][2]", "123456");
//点击登录按钮
await page.ClickAsync("//a[@id='dologin']");
运行代码后发现没有进行任何操作,原因是这些登录相关元素都是在一个Frame中嵌套进页面的,所以直接找是找不到元素的,要使用对应的Frame去查找操作元素
修改代码
//输入用户名
await page.FrameLocator("//div[@id='loginDiv']/iframe").Locator("//input[contains(@placeholder,'邮箱帐号或手机号码')]").FillAsync("admin");
//输入密码
await page.FrameLocator("//div[@id='loginDiv']/iframe").Locator("//input[@type='password'][2]").FillAsync("123456");
//点击登录按钮
await page.FrameLocator("//div[@id='loginDiv']/iframe").Locator("//a[@id='dologin']").ClickAsync();
爬取网页数据
这里以爬取博客园首页推荐文章列表数据为例
找到列表项的数据源
可以看到明细项dom节点的位置
//div[@id='post_list']/article
代码实现
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = false
});
await using var context = await browser.NewContextAsync();
var page=await context.NewPageAsync();
//打开网页
await page.GotoAsync("https://www.cnblogs.com/");
var items = await page.QuerySelectorAllAsync("//div[@id='post_list']/article");
foreach (var item in items)
{
var title = item.QuerySelectorAsync("//a[@class='post-item-title']").Result?.InnerTextAsync().Result;
var description = item.QuerySelectorAsync("//p[@class='post-item-summary']").Result?.InnerTextAsync().Result;
Console.WriteLine($"标题:{title}\n描述:{description}\n\n");
}
评论区