JavaScript动态渲染页爬取——Playwright的使用(一)

简介: JavaScript动态渲染页爬取——Playwright的使用(一)

Playwright的使用
Playwright是微软在2020年年初开源的新一代自动化测试工具,其功能和Selenium、Pyppeteer等类似,都可以驱动浏览器进行各种自动化操作。Playwright对市面上的主流浏览器都提供了支持,API功能简洁又强大,虽然诞生比较晚,但是现在发展的非常火热。

1. Playwright的特点

  • Playwright支持当前所有的主流浏览器,包括Chrome和Edge(基于Chromium)、Firefox、Safari(基于WebKit),提供完善的自动化控制API。
  • Playwright支持移动端页面测试,使用设备模拟技术,可以让我们在移动Web浏览器中测试响应式Web应用程序。
  • Playwright支持所有浏览器的无头模式和非无头模式的测试。
  • Playwright的安装和配置过程非常简单,安装过程中会自动安装对应的浏览器和驱动,不需要额外配置WebDriver等
  • Playwright提供和自动等待相关的API,在页面加载时会自动等待对应的节点加载,大大减**小了API编写的复杂度。
  1. 安装**
    命令如下:
pip3 install playwright

完成后需要进行一些初始化操作:

playwright install

这时Playwright会安装Chromium、Firefox和WebKit浏览器并配置一些驱动,我们不必关心具体的配置过程,Playwright会自动为我们配置好。

3. 基本使用
Playwright支持两种编写模式,一种是和Pyppeteer一样的异步模式,一种是和Selenium一样的同步模式,可以根据实际需要选择使用不同的模式。

先来看一个同步模式的例子:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    for browser_type in [p.chromium, p.firefox, p.webkit]:
        browser = browser_type.launch(headless=False)
        page = browser.new_page()
        page.goto('<https://www.baidu.com>')
        page.screenshot(path=f'screenshot-{browser_type.name}.png')
        print(page.title())
        browser.close()

注意:如果不把headless参数设置为False,就会以默认的无头模式启动浏览器,我们将看不到任何窗口。

这里我们首先导入并直接调用了sync_playwright方法,该方法的返回值是一个PlaywrightContextManager对象,可以理解为一个浏览器上下文管理器,我们将其赋值为p变量。然后依次调用p的Chromium、firefox和webkit属性创建了Chromium、Firefox以及Webkit浏览器实例。接着用一个for循环依次执行了这3个浏览器的launch方法,同时设置headless参数为False。运行一下这段代码,可以看到有3个浏览器依次启动,分别是Chromium、Firefox和Webkit浏览器,启动后都是加载百度首页,页面加载后,生成页面截图,然后把页面标题打印到控制台,就退出了。

image.png

控制台的运行结果如下:

百度一下,你就知道
百度一下,你就知道
百度一下,你就知道

可以发现,我们非常方便地启动了三种浏览器,完成了自动化操作,并通过几个API获取了页面的截图和数据,整个过程速度非常快,这就是Playwright最为基本的用法。

当然,除了同步模式,Playwright还提供了支持异步模式的API, 如果我们的项目里面使用了asyncio关键字,就应该使用异步模式,写法如下:

from playwright.async_api import async_playwright
import asyncio

async def main():
    async with async_playwright() as p:
        for browser_type in [p.chromium, p.firefox, p.webkit]:
            browser = await browser_type.launch()
            page = await browser.new_page()
            await page.goto('<https://www.baidu.com>')
            await page.screenshot(path=f'screenshot-{browser_type.name}.png')
            print(await page.title())
            await browser.close()

asyncio.run(main())

写法和同步模式基本一样,只不过是这里导入的是async_playwright方法,不再是sync_playwright方法,以及写法上添加了async/await 关键字,最后的运行效果和同步模式是一样的。
**

  1. 代码生成**
    Playwright还有一个强大的功能,可以录制我们在浏览器中操作并自动生成代码,有了这个功能,我们甚至一行代码都不用写。这个功能可以通过playwright命令行调用codegen实现,先来看看codegen命令都有什么参数,输入如下命令:
playwright codegen --help

结果类似如下:

Usage: playwright codegen [options] [url]

open page and generate code for user actions

Options:
  -o, --output <file name>             saves the generated script to a file
  --target <language>                  language to generate, one of javascript,
                                       playwright-test, python, python-async,
                                       python-pytest, csharp, csharp-mstest,
                                       csharp-nunit, java, java-junit (default:
                                       "python")
  --save-trace <filename>              record a trace for the session and save
                                       it to a file
  --test-id-attribute <attributeName>  use the specified attribute to generate
                                       data test ID selectors
  -b, --browser <browserType>          browser to use, one of cr, chromium, ff,
                                       firefox, wk, webkit (default:
                                       "chromium")
  --block-service-workers              block service workers
  --channel <channel>                  Chromium distribution channel, "chrome",
                                       "chrome-beta", "msedge-dev", etc
  --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"
  --ignore-https-errors                ignore https errors
  --load-storage <filename>            load context storage state from the
                                       file, previously saved with
                                       --save-storage
  --lang <language>                    specify language / locale, for example
                                       "en-GB"
  --proxy-server <proxy>               specify proxy server, for example
                                       "<http://myproxy:3128>" or
                                       "socks5://myproxy:8080"
  --proxy-bypass <bypass>              comma-separated domains to bypass proxy,
                                       for example
                                       ".com,chromium.org,.domain.com"
  --save-har <filename>                save HAR file with all network activity
                                       at the end
  --save-har-glob <glob pattern>       filter entries in the HAR by matching
                                       url against this glob pattern
  --save-storage <filename>            save context storage state at the end,
                                       for later use with --load-storage
  --timezone <time zone>               time zone to emulate, for example
                                       "Europe/Rome"
  --timeout <timeout>                  timeout for Playwright actions in
                                       milliseconds, no timeout by default
  --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

Examples:

  $ codegen
  $ codegen --target=python
  $ codegen -b webkit <https://example.com>

可以看到结果中有个选项,-o代表输出的代码文件的名称;-target代表使用的语言,默认是python,代表会生成同步模式的操作代码。

了解这些用法后,我们来尝试启动一个Firefox浏览器,然后将操作结果输出到script.py文件,命令如下:

playwright codegen -o script.py -b firefox

image.png

可以看到,浏览器中会高亮显示我们正在操作的页面节点,同时显示对应的选择器字符串 page.locator(“#kw”).fill(“nba”),右侧代码窗口如下图:

image.png

在操作浏览器的过程中,该窗口中的代码会跟着实时变化,现在这里已经生成了刚刚一系列操作对应的代码,例如:

page.locator("#kw").fill("nba")

这行代码就对应在搜索框中输入nba的操作,所有操作完毕之后,关闭浏览器,Playwright会生成一个script.py文件,内容如下:

from playwright.sync_api import Playwright, sync_playwright, expect

def run(playwright: Playwright) -> None:
    browser = playwright.firefox.launch(headless=False)
    context = browser.new_context()
    page = context.new_page()
    page.goto("<https://www.baidu.com/>")
    page.locator("#kw").click()
    page.locator("#kw").press("CapsLock")
    page.locator("#kw").fill("nba")

    # ---------------------
    context.close()
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

可以看到这里生成的代码和我们之前写的示例代码几乎差不多,而且也可以运行,运行之后会看到它在浮现我们刚才所做的操作。所以,有了代码生成功能,只通过简单的可视化点击操作就能生成代码,可谓非常方便。

5. 支持移动端浏览器
Playwright的另一个特色就是支持模拟移动端浏览器,例如模拟打开iPhone12 Pro Max上的Safari浏览器。

示例代码如下:

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')
    page = context.new_page()
    page.goto('<https://www.whatismybrowser.com/>')
    page.wait_for_load_state(state='networkidle')
    page.screenshot(path='browser-iphone.png')
    browser.close()

这里我们先用PlaywrightContextManager对象的devices属性指定了一台移动设备,传入的参数是移动设备的型号,例如iPhone 12 Pro Max,当然也可以传入其他内容,例如iPhone 8、Pixel 2等。

运行一下代码,可以发现弹出了一个移动版浏览器,然后加载出了对应的页面,如下图所示:

image.png

输出的截图也是浏览器中显示的结果,可以看到,这里显示的浏览器信息是iPhone上的Safari浏览器。也就是我们成功模拟了一个移动端浏览器。

6. 选择器
Playwright扩展了方便好用的规则,直接根据文本内容筛选、根据节点层级结构筛选等。

文本选择
文本选择支持直接使用text=这样的语法进行筛选,示例如下:

page.click("text=Log in")

CSS选择器
根据id或class筛选:

page.click("button")
page.click("#nav-bar .contact-us-item")

根据特定的节点属性筛选:

page.click("[data-test=login-button]")
page.click("[aria-label='Sign in']")

CSS选择器+文本值
可以使用CSS选择器结合文本值的方式进行筛选,比较常用的方法是has-text和text,前者代表节点中包含指定的字符串,后者代表节点中的文本值和指定的字符串完全匹配,示例如下:

page.click("article:has-text('Playwright')")
page.click("#nav-bar :text('Contact us')")

第一行代码就是选择文本值中包含Playwright字符串的article节点,第二行代码是选择id为nav-bar的节点中文本值为Contact us的节点。

CSS选择器 + 节点关系
CSS选择器还可以结合节点关系来筛选节点,例如使用has指定另外一个选择器,示例如下:

page.click(".item-description:has(.item-promo-banner")

XPath
当然,XPath也是支持的,不过xpath这个关键字需要我们自行指定,示例如下:

page.click("xpath=//button")

这里在开头指定“xpath=字符串”,代表这个字符串是一个XPath表达式。

7. 常用的操作方法
所有的方法都可以从Page对象的API文档查找,文档地址是https://playwright.dev/python/docs/api/class-page。

事件监听
Page对象提供一个on方法,用来监听页面中发送的各个事件,例如close、console、load、request、response等。

这里监听reponse事件,在每次网络请求得到响应的时候会出发这个事件,我们可以设置回调方法来获取响应中的全部信息,示例如下:

from playwright.sync_api import sync_playwright

def on_response(response):
    print(f'Statue {response.status}: {response.url}')

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.on('response', on_response)
    page.goto('<https://spa6.scrape.center/>')
    page.wait_for_load_state('networkidle')
    browser.close()

创建Page对象后,开始监听response事件,同时将回调方法设置为on_response,on_response接收一个参数,然后输出响应中的状态码和链接。

运行上述代码后,可以看到控制台输出如下结果:

Statue 200: <https://spa6.scrape.center/>
Statue 200: <https://spa6.scrape.center/css/chunk-19c920f8.2a6496e0.css>
Statue 200: <https://spa6.scrape.center/css/app.ea9d802a.css>
Statue 200: <https://spa6.scrape.center/js/chunk-vendors.77daf991.js>
Statue 200: <https://spa6.scrape.center/js/app.5ef0d454.js>
Statue 200: <https://spa6.scrape.center/js/chunk-2f73b8f3.8f2fc3cd.js>
....
....
Statue 200: <https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@464w_644h_1e_1c>
Statue 200: <https://p0.meituan.net/movie/289f98ceaa8a0ae737d3dc01cd05ab052213631.jpg@464w_644h_1e_1c>
Statue 200: <https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c>
Statue 200: <https://p0.meituan.net/movie/223c3e186db3ab4ea3bb14508c709400427933.jpg@464w_644h_1e_1c>
Statue 200: <https://p1.meituan.net/movie/b607fba7513e7f15eab170aac1e1400d878112.jpg@464w_644h_1e_1c>
Statue 200: <https://p0.meituan.net/movie/8959888ee0c399b0fe53a714bc8a5a17460048.jpg@464w_644h_1e_1c>
Statue 200: <https://p0.meituan.net/movie/27b76fe6cf3903f3d74963f70786001e1438406.jpg@464w_644h_1e_1c>

可以发现,这个输出结果其实正好对应浏览器Network面板中的所有请求和响应,和下图的内容一一对应。

image.png

这个网站,真实数据都是Ajax加载的,同时Ajax请求中还带有加密参数,不好轻易获取。但有了on_response方法,如果我们想截获Ajax请求,岂不是就非常容易了?改写一下这里的判定条件,输出对应的JSON的结果,代码如下:

from playwright.sync_api import sync_playwright

def on_response(response):
    if '/api/movie/' in response.url and response.status == 200:
        print(response.json())

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.on('response', on_response)
    page.goto('<https://spa6.scrape.center/>')
    page.wait_for_load_state('networkidle')
    browser.close()

控制台的输出结果如下:

{
   'count': 102, 'results': [{
   'id': 1, 'name': '霸王别姬', 'alias': 'Farewell My Concubine', 'cover': '<https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@_644h_1e_1c>', 
'categories': ['剧情', '爱情'], 'published_at': '1993-07-26', 'minute': 171, 'score': 9.5, 'regions': ['中国内地', '中国香港']}, {
   'id': 2, 'name': '这个杀手不on', 'cover': '<https://p1.meituan.net/movie/6bea9af4524dfbd0b6>

....
113e174332.jpg@464w1e_1c', 'categories': ['剧情', '喜剧', '爱情'], 'published_at': '1999-02-13', 'minute': 85, 'score': 9.5, 'regions': ['中国香港']}, {'id': 9, 'name': '楚门的世界', 'alias':ow', 'cover': '<https://p0.meituan.net/movie/8959888ee0c399b0fe53a714bc8a5a17460048.jpg@464w_644h_1e_1c>', 'categories': ['剧情', '科幻'], 'published_at': None, 'minute': 103core': 9.0, 'regions': ['美国']}, {'id': 10, 'name': '狮子王', 'alias': 'The Lion King', 'cover': '<https://p0.meituan.net/movie/27b76fe6cf3903f3d74963f70786001e1438406.jpg@644h_1e_1c>', 
 'categories': ['动画', '歌舞', '冒险'], 'published_at': '1995-07-15', 'minute': 89, 'score': 9.0, 'regions': ['美国']}]}

通过on_response方法拦截了Ajax请求,直接拿到了响应结果,即使这个Ajax请求中有加密参数,也不用担心,因为我们截获的是最后的响应结果。

接下文 JavaScript动态渲染页爬取——Playwright的使用(二)

相关文章
|
1月前
|
JavaScript 前端开发 API
JavaScript动态渲染页爬取——Playwright的使用(二)
JavaScript动态渲染页爬取——Playwright的使用(二)
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
95 2
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的医院综合管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的医院综合管理系统附带文章源码部署视频讲解等
46 5
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
122 4
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
81 4
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物交易平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物交易平台附带文章源码部署视频讲解等
73 4
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的大学生入伍人员管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的大学生入伍人员管理系统附带文章源码部署视频讲解等
92 4
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp宿舍管理系统的附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp宿舍管理系统的附带文章源码部署视频讲解等
84 3
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的家政平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的家政平台附带文章源码部署视频讲解等
62 3
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物医院系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物医院系统附带文章源码部署视频讲解等
56 2