強大易用!新一代爬蟲利器 Playwright 的介紹

作者:崔慶才

Playwright 是微軟在 2020 年初開源的新一代自動化測試工具,它的功能類似於 Selenium、Pyppeteer 等,都可以驅動瀏覽器進行各種自動化操作。它的功能也非常強大,對市面上的主流瀏覽器都提供了支持,API 功能簡潔又強大。雖然誕生比較晚,但是現在發展得非常火熱。

因爲 Playwright 是一個類似 Selenium 一樣可以支持網頁頁面渲染的工具,再加上其強大又簡潔的 API,Playwright 同時也可以作爲網絡爬蟲的一個爬取利器。

  1. Playwright 的特點

本節我們就來了解下 Playwright 的使用方法。

  1. 安裝

Playwright 目前提供了 Python 和 Node.js 的 API,下面我們針對 Python 版的 Playwright 進行介紹。

要使用 Playwright,需要 Python 3.7 版本及以上,請確保 Python 的版本符合要求。

要安裝 Playwright,可以直接使用 pip3,命令如下:

pip3 install playwright

安裝完成之後需要進行一些初始化操作:

playwright install

這時候 Playwrigth 會安裝 Chromium, Firefox and WebKit 瀏覽器並配置一些驅動,我們不必關心中間配置的過程,Playwright 會爲我們配置好。

具體的安裝說明可以參考:https://setup.scrape.center/playwright。

安裝完成之後,我們便可以使用 Playwright 啓動 Chromium 或 Firefox 或 WebKit 瀏覽器來進行自動化操作了。

  1. 基本使用

Playwright 支持兩種編寫模式,一種是類似 Pyppetter 一樣的異步模式,另一種是像 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()

首先我們導入了 sync_playwright 方法,然後直接調用了這個方法,該方法返回的是一個 PlaywrightContextManager 對象,可以理解是一個瀏覽器上下文管理器,我們將其賦值爲變量 p。

接着我們調用了 PlaywrightContextManager 對象的 chromium、firefox、webkit 屬性依次創建了一個 Chromium、Firefox 以及 Webkit 瀏覽器實例,接着用一個 for 循環依次執行了它們的 launch 方法,同時設置了 headless 參數爲 False。

注意:如果不設置爲 False,默認是無頭模式啓動瀏覽器,我們看不到任何窗口。

launch 方法返回的是一個 Browser 對象,我們將其賦值爲 browser 變量。然後調用 browser 的 new_page 方法,相當於新建了一個選項卡,返回的是一個 Page 對象,將其賦值爲 page,這整個過程其實和 Pyppeteer 非常類似。接着我們就可以調用 page 的一系列 API 來進行各種自動化操作了,比如調用 goto,就是加載某個頁面,這裏我們訪問的是百度的首頁。接着我們調用了 page 的 screenshot 方法,參數傳一個文件名稱,這樣截圖就會自動保存爲該圖片名稱,這裏名稱中我們加入了 browser_type 的 name 屬性,代表瀏覽器的類型,結果分別就是 chromium, firefox, webkit。另外我們還調用了 title 方法,該方法會返回頁面的標題,即 HTML 中  title 節點中的文字,也就是選項卡上的文字,我們將該結果打印輸出到控制檯。最後操作完畢,調用 browser 的 close 方法關閉整個瀏覽器,運行結束。

運行一下,這時候我們可以看到有三個瀏覽器依次啓動並加載了百度這個頁面,分別是 Chromium、Firefox 和 Webkit 三個瀏覽器,頁面加載完成之後,生成截圖、控制檯打印結果就退出了。

這時候當前目錄便會生成三個截圖文件,都是百度的首頁,文件名中都帶有了瀏覽器的名稱,如圖所示:

控制檯運行結果如下:

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

通過運行結果我們可以發現,我們非常方便地啓動了三種瀏覽器並完成了自動化操作,並通過幾個 API 就完成了截圖和數據的獲取,整個運行速度是非常快的,者就是 Playwright 最最基本的用法。

當然除了同步模式,Playwright 還提供異步的 API,如果我們項目裏面使用了 asyncio,那就應該使用異步模式,寫法如下:

import asyncio
from playwright.async_api 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()
            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 關鍵字的使用,最後的運行效果是一樣的。

另外我們注意到,這例子中使用了 with as 語句,with 用於上下文對象的管理,它可以返回一個上下文管理器,也就對應一個 PlaywrightContextManager 對象,無論運行期間是否拋出異常,它能夠幫助我們自動分配並且釋放 Playwright 的資源。

  1. 代碼生成

Playwright 還有一個強大的功能,那就是可以錄製我們在瀏覽器中的操作並將代碼自動生成出來,有了這個功能,我們甚至都不用寫任何一行代碼,這個功能可以通過 playwright 命令行調用 codegen 來實現,我們先來看看 codegen 命令都有什麼參數,輸入如下命令:

playwright codegen --help

結果類似如下:

Usage: npx 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 use, one of javascript, python, python-async, csharp (default: "python")
  -b, --browser <browserType>  browser to use, one of cr, chromium, ff, firefox, wk, webkit (default: "chromium")
  --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"
  --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"
  --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 (default: "10000")
  --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,即會生成同步模式的操作代碼,如果傳入 python-async 就會生成異步模式的代碼;-b 代表的是使用的瀏覽器,默認是 Chromium,其他還有很多設置,比如 --device 可以模擬使用手機瀏覽器,比如 iPhone 11,--lang 代表設置瀏覽器的語言,--timeout 可以設置頁面加載超時時間。

好,瞭解了這些用法,那我們就來嘗試啓動一個 Firefox 瀏覽器,然後將操作結果輸出到 script.py 文件,命令如下:

playwright codegen -o script.py -b firefox

這時候就彈出了一個 Firefox 瀏覽器,同時右側會輸出一個腳本窗口,實時顯示當前操作對應的代碼。

我們可以在瀏覽器中做任何操作,比如打開百度,然後點擊輸入框並輸入 nba,然後再點擊搜索按鈕,瀏覽器窗口如下:

可以看見瀏覽器中還會高亮顯示我們正在操作的頁面節點,同時還顯示了對應的選擇器字符串 input[],右側的窗口如圖所示:

在操作過程中,該窗口中的代碼就實時變化,可以看到這裏生成了我們一系列操作的對應代碼,比如在搜索框中輸入 nba,就對應如下代碼:

page.fill("input[name=\"wd\"]""nba")

操作完畢之後,關閉瀏覽器,Playwright 會生成一個 script.py 文件,內容如下:

from playwright.sync_api import sync_playwright

def run(playwright):
    browser = playwright.firefox.launch(headless=False)
    context = browser.new_context()

    # Open new page
    page = context.new_page()

    # Go to https://www.baidu.com/
    page.goto("https://www.baidu.com/")

    # Click input[]
    page.click("input[name=\"wd\"]")

    # Fill input[]
    page.fill("input[name=\"wd\"]""nba")

    # Click text=百度一下
    with page.expect_navigation():
        page.click("text=百度一下")

    context.close()
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

可以看到這裏生成的代碼和我們之前寫的示例代碼幾乎差不多,而且也是完全可以運行的,運行之後就可以看到它又可以復現我們剛纔所做的操作了。

所以,有了這個功能,我們甚至都不用編寫任何代碼,只通過簡單的可視化點擊就能把代碼生成出來,可謂是非常方便了!

另外這裏有一個值得注意的點,仔細觀察下生成的代碼,和前面的例子不同的是,這裏 new_page 方法並不是直接通過 browser 調用的,而是通過 context 變量調用的,這個 context 又是由 browser 通過調用 new_context 方法生成的。有讀者可能就會問了,這個 context 究竟是做什麼的呢?

其實這個 context 變量對應的是一個 BrowserContext 對象,BrowserContext 是一個類似隱身模式的獨立上下文環境,其運行資源是單獨隔離的,在做一些自動化測試過程中,每個測試用例我們都可以單獨創建一個 BrowserContext 對象,這樣可以保證每個測試用例之間互不干擾,具體的 API 可以參考 https://playwright.dev/python/docs/api/class-browsercontext。

  1. 移動端瀏覽器支持

Playwright 另外一個特色功能就是可以支持移動端瀏覽器的模擬,比如模擬打開 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()

這裏我們先用 PlaywrightContextManager 對象的 devices 屬性指定了一臺移動設備,這裏傳入的是手機的型號,比如 iPhone 12 Pro Max,當然也可以傳其他名稱,比如 iPhone 8,Pixel 2 等。

前面我們已經瞭解了 BrowserContext 對象,BrowserContext 對象也可以用來模擬移動端瀏覽器,初始化一些移動設備信息、語言、權限、位置等信息,這裏我們就用它來創建了一個移動端 BrowserContext 對象,通過 geolocation 參數傳入了經緯度信息,通過 permissions 參數傳入了賦予的權限信息,最後將得到的 BrowserContext 對象賦值爲 context 變量。

接着我們就可以用 BrowserContext 對象來新建一個頁面,還是調用 new_page 方法創建一個新的選項卡,然後跳轉到高德地圖,並調用了 wait_for_load_state 方法等待頁面某個狀態完成,這裏我們傳入的 state 是 networkidle,也就是網絡空閒狀態。因爲在頁面初始化和加載過程中,肯定是伴隨有網絡請求的,所以加載過程中肯定不算 networkidle 狀態,所以這裏我們傳入 networkidle 就可以標識當前頁面和數據加載完成的狀態。加載完成之後,我們再調用 screenshot 方法獲取當前頁面截圖,最後關閉瀏覽器。

運行下代碼,可以發現這裏就彈出了一個移動版瀏覽器,然後加載了高德地圖,並定位到了故宮的位置,如圖所示:

輸出的截圖也是瀏覽器中顯示的結果。

所以這樣我們就成功實現了移動端瀏覽器的模擬和一些設置,其操作 API 和 PC 版瀏覽器是完全一樣的。

  1. 選擇器

前面我們注意到 click 和 fill 等方法都傳入了一個字符串,這些字符串有的符合 CSS 選擇器的語法,有的又是 text= 開頭的,感覺似乎沒太有規律的樣子,它到底支持怎樣的匹配規則呢?下面我們來了解下。

傳入的這個字符串,我們可以稱之爲 Element Selector,它不僅僅支持 CSS 選擇器、XPath,Playwright 還擴展了一些方便好用的規則,比如直接根據文本內容篩選,根據節點層級結構篩選等等。

文本選擇

文本選擇支持直接使用 text= 這樣的語法進行篩選,示例如下:

page.click("text=Log in")

這就代表選擇文本是 Log in 的節點,並點擊。

CSS 選擇器

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 選擇器 + 節點關係

還可以結合節點關係來篩選節點,比如使用 has 來指定另外一個選擇器,示例如下:

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

比如這裏選擇的就是選擇 class 爲 item-description 的節點,且該節點還要包含 class 爲 item-promo-banner 的子節點。

另外還有一些相對位置關係,比如 right-of 可以指定位於某個節點右側的節點,示例如下:

page.click("input:right-of(:text('Username'))")

這裏選擇的就是一個 input 節點,並且該 input 節點要位於文本值爲 Username 的節點的右側。

XPath

當然 XPath 也是支持的,不過 xpath 這個關鍵字需要我們自行制定,示例如下:

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

這裏需要在開頭指定 xpath= 字符串,代表後面是一個 XPath 表達式。

關於更多選擇器的用法和最佳實踐,可以參考官方文檔:https://playwright.dev/python/docs/selectors。

  1. 常用操作方法

上面我們瞭解了瀏覽器的一些初始化設置和基本的操作實例,下面我們再對一些常用的操作 API 進行說明。

常見的一些 API 如點擊 click,輸入 fill 等操作,這些方法都是屬於 Page 對象的,所以所有的方法都從 Page 對象的 API 文檔查找就好了,文檔地址:https://playwright.dev/python/docs/api/class-page。

下面介紹幾個常見的 API 用法。

事件監聽

Page 對象提供了一個 on 方法,它可以用來監聽頁面中發生的各個事件,比如 close、console、load、request、response 等等。

比如這裏我們可以監聽 response 事件,response 事件可以在每次網絡請求得到響應的時候觸發,我們可以設置對應的回調方法獲取到對應 Response 的全部信息,示例如下:

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 對象接收一個參數,然後把 Response 的狀態碼和鏈接都輸出出來了。

運行之後,可以看到控制檯輸出結果如下:

Statue 200: https://spa6.scrape.center/
Statue 200: https://spa6.scrape.center/css/app.ea9d802a.css
Statue 200: https://spa6.scrape.center/js/app.5ef0d454.js
Statue 200: https://spa6.scrape.center/js/chunk-vendors.77daf991.js
Statue 200: https://spa6.scrape.center/css/chunk-19c920f8.2a6496e0.css
...
Statue 200: https://spa6.scrape.center/css/chunk-19c920f8.2a6496e0.css
Statue 200: https://spa6.scrape.center/js/chunk-19c920f8.c3a1129d.js
Statue 200: https://spa6.scrape.center/img/logo.a508a8f0.png
Statue 200: https://spa6.scrape.center/fonts/element-icons.535877f5.woff
Statue 301: https://spa6.scrape.center/api/movie?limit=10&offset=0&token=NGMwMzFhNGEzMTFiMzJkOGE0ZTQ1YjUzMTc2OWNiYTI1Yzk0ZDM3MSwxNjIyOTE4NTE5
Statue 200: https://spa6.scrape.center/api/movie/?limit=10&offset=0&token=NGMwMzFhNGEzMTFiMzJkOGE0ZTQ1YjUzMTc2OWNiYTI1Yzk0ZDM3MSwxNjIyOTE4NTE5
Statue 200: https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e041108.jpg@464w_644h_1e_1c
Statue 200: https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c
....
Statue 200: https://p1.meituan.net/movie/b607fba7513e7f15eab170aac1e1400d878112.jpg@464w_644h_1e_1c

注意:這裏省略了部分重複的內容。

可以看到,這裏的輸出結果其實正好對應瀏覽器 Network 面板中所有的請求和響應內容,和下圖是一一對應的:

這個網站我們之前分析過,其真實的數據都是 Ajax 加載的,同時 Ajax 請求中還帶有加密參數,不好輕易獲取。

但有了這個方法,這裏如果我們想要截獲 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': 100, 'results'[{'id': 1, 'name''霸王別姬''alias''Farewell My Concubine''cover''https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c''categories'['劇情''愛情']'published_at''1993-07-26''minute': 171, 'score': 9.5, 'regions'['中國大陸''中國香港']}, 
...
'published_at': None, 'minute': 103, 'score': 9.0, 'regions'['美國']}{'id': 10, 'name''獅子王''alias''The Lion King''cover''https://p0.meituan.net/movie/27b76fe6cf3903f3d74963f70786001e1438406.jpg@464w_644h_1e_1c''categories'['動畫''歌舞''冒險']'published_at''1995-07-15''minute': 89, 'score': 9.0, 'regions'['美國']}]}

簡直是得來全不費工夫,我們直接通過這個方法攔截了 Ajax 請求,直接把響應結果拿到了,即使這個 Ajax 請求有加密參數,我們也不用關心,因爲我們直接截獲了 Ajax 最後響應的結果,這對數據爬取來說實在是太方便了。

另外還有很多其他的事件監聽,這裏不再一一介紹了,可以查閱官方文檔,參考類似的寫法實現。

獲取頁面源碼

要獲取頁面的 HTML 代碼其實很簡單,我們直接通過 content 方法獲取即可,用法如下:

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://spa6.scrape.center/')
    page.wait_for_load_state('networkidle')
    html = page.content()
    print(html)
    browser.close()

運行結果就是頁面的 HTML 代碼。獲取了 HTML 代碼之後,我們通過一些解析工具就可以提取想要的信息了。

頁面點擊

剛纔我們通過示例也瞭解了頁面點擊的方法,那就是 click,這裏詳細說一下其使用方法。

頁面點擊的 API 定義如下:

page.click(selector, **kwargs)

這裏可以看到必傳的參數是 selector,其他的參數都是可選的。第一個 selector 就代表選擇器,可以用來匹配想要點擊的節點,如果傳入的選擇器匹配了多個節點,那麼只會用第一個節點。

這個方法的內部執行邏輯如下:

click 方法的一些比較重要的參數如下:

具體的 API 設置參數可以參考官方文檔:https://playwright.dev/python/docs/api/class-page/#pageclickselector-kwargs。

文本輸入

文本輸入對應的方法是 fill,API 定義如下:

page.fill(selector, value, **kwargs)

這個方法有兩個必傳參數,第一個參數也是 selector,第二個參數是 value,代表輸入的內容,另外還可以通過 timeout 參數指定對應節點的最長等待時間。

獲取節點屬性

除了對節點進行操作,我們還可以獲取節點的屬性,方法就是 get_attribute,API 定義如下:

page.get_attribute(selector, name, **kwargs)

這個方法有兩個必傳參數,第一個參數也是 selector,第二個參數是 name,代表要獲取的屬性名稱,另外還可以通過 timeout 參數指定對應節點的最長等待時間。

示例如下:

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://spa6.scrape.center/')
    page.wait_for_load_state('networkidle')
    href = page.get_attribute('a.name''href')
    print(href)
    browser.close()

這裏我們調用了 get_attribute 方法,傳入的 selector 是 a.name,選定了 class 爲 name 的 a 節點,然後第二個參數傳入了 href,獲取超鏈接的內容,輸出結果如下:

/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx

可以看到對應 href 屬性就獲取出來了,但這裏只有一條結果,因爲這裏有個條件,那就是如果傳入的選擇器匹配了多個節點,那麼只會用第一個節點。

那怎麼獲取所有的節點呢?

獲取多個節點

獲取所有節點可以使用 query_selector_all 方法,它可以返回節點列表,通過遍歷獲取到單個節點之後,我們可以接着調用單個節點的方法來進行一些操作和屬性獲取,示例如下:

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://spa6.scrape.center/')
    page.wait_for_load_state('networkidle')
    elements = page.query_selector_all('a.name')
    for element in elements:
        print(element.get_attribute('href'))
        print(element.text_content())
    browser.close()

這裏我們通過 query_selector_all 方法獲取了所有匹配到的節點,每個節點對應的是一個 ElementHandle 對象,然後 ElementHandle 對象也有 get_attribute 方法來獲取節點屬性,另外還可以通過 text_content 方法獲取節點文本。

運行結果如下:

/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
霸王別姬 - Farewell My Concubine
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy
這個殺手不太冷 - Léon
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIz
肖申克的救贖 - The Shawshank Redemption
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI0
泰坦尼克號 - Titanic
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI1
羅馬假日 - Roman Holiday
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2
唐伯虎點秋香 - Flirting Scholar
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3
亂世佳人 - Gone with the Wind
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI4
喜劇之王 - The King of Comedy
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI5
楚門的世界 - The Truman Show
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIxMA==
獅子王 - The Lion King

獲取單個節點

獲取單個節點也有特定的方法,就是 query_selector,如果傳入的選擇器匹配到多個節點,那它只會返回第一個節點,示例如下:

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://spa6.scrape.center/')
    page.wait_for_load_state('networkidle')
    element = page.query_selector('a.name')
    print(element.get_attribute('href'))
    print(element.text_content())
    browser.close()

運行結果如下:

/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
霸王別姬 - Farewell My Concubine

可以看到這裏只輸出了第一個匹配節點的信息。

網絡劫持

最後再介紹一個實用的方法 route,利用 route 方法,我們可以實現一些網絡劫持和修改操作,比如修改 request 的屬性,修改 response 響應結果等。

看一個實例:

from playwright.sync_api import sync_playwright
import re

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()

    def cancel_request(route, request):
        route.abort()

    page.route(re.compile(r"(\.png)|(\.jpg)"), cancel_request)
    page.goto("https://spa6.scrape.center/")
    page.wait_for_load_state('networkidle')
    page.screenshot(path='no_picture.png')
    browser.close()

這裏我們調用了 route 方法,第一個參數通過正則表達式傳入了匹配的 URL 路徑,這裏代表的是任何包含 .png.jpg  的鏈接,遇到這樣的請求,會回調 cancel_request 方法處理,cancel_request 方法可以接收兩個參數,一個是 route,代表一個 CallableRoute 對象,另外一個是 request,代表 Request 對象。這裏我們直接調用了 route 的 abort 方法,取消了這次請求,所以最終導致的結果就是圖片的加載全部取消了。

觀察下運行結果,如圖所示:

可以看到圖片全都加載失敗了。

這個設置有什麼用呢?其實是有用的,因爲圖片資源都是二進制文件,而我們在做爬取過程中可能並不想關心其具體的二進制文件的內容,可能只關心圖片的 URL 是什麼,所以在瀏覽器中是否把圖片加載出來就不重要了。所以如此設置之後,我們可以提高整個頁面的加載速度,提高爬取效率。

另外,利用這個功能,我們還可以將一些響應內容進行修改,比如直接修改 Response 的結果爲自定義的文本文件內容。

首先這裏定義一個 HTML 文本文件,命名爲 custom_response.html,內容如下:

<!DOCTYPE html>
<html>
  <head>
    <title>Hack Response</title>
  </head>
  <body>
    <h1>Hack Response</h1>
  </body>
</html>

代碼編寫如下:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()

    def modify_response(route, request):
        route.fulfill(path="./custom_response.html")

    page.route('/', modify_response)
    page.goto("https://spa6.scrape.center/")
    browser.close()

這裏我們使用 route 的 fulfill 方法指定了一個本地文件,就是剛纔我們定義的 HTML 文件,運行結果如下:

可以看到,Response 的運行結果就被我們修改了,URL 還是不變的,但是結果已經成了我們修改的 HTML 代碼。

所以通過 route 方法,我們可以靈活地控制請求和響應的內容,從而在某些場景下達成某些目的。

  1. 總結

本節介紹了 Playwright 的基本用法,其 API 強大又易於使用,同時具備很多 Selenium、Pyppeteer 不具備的更好用的 API,是新一代 JavaScript 渲染頁面的爬取利器。

本節代碼:https://github.com/Python3WebSpider/PlaywrightTest。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/LBjf2natwOPhvhpKYIrlvw