侧边栏壁纸
博主头像
怪客のBlog 博主等级

行动起来,活在当下

  • 累计撰写 35 篇文章
  • 累计创建 1 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

微软开源自动化神器 Playwright 安装和使用

怪客
2022-06-03 / 0 评论 / 3 点赞 / 1307 阅读 / 0 字

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

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

image-1660883528526

打开网页并截图保存

打开网易云音乐首页然后截图保存本地

    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(); 

运行代码截图就已经保存成功了
image-1660883978057

浏览器和浏览器上下文

浏览器

目前支持三种内核的浏览器(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)加载网页的过程:

  1. 设定 url
  2. 通过网络加载解析页面
  3. 触发 page.on(“domcontentloaded”) 事件
  4. 执行页面的 js 脚本,加载静态资源(css、图片等)
  5. 触发 page.on(“laod”) 事件
  6. 页面执行动态加载的脚本(ajax)
  7. 当 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邮箱的登录)然后找到我们要操作的相关元素

用户名输入框
image-1660895875847
密码输入框
image-1660895909472
登录按钮
image-1660895942406

定位元素并执行操作

看个人习惯可以使用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去查找操作元素
image-1660896022381
修改代码

//输入用户名
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();

爬取网页数据

这里以爬取博客园首页推荐文章列表数据为例

找到列表项的数据源

image-1660896147863
可以看到明细项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");

}

输出结果

image-1660896238487

3

评论区