【2023年】Python PlaywrightでWebスクレイピング(6) Downloadボタンでファイル保存(sync/async)

PythonPlaywrightでWebScrapying6 Playwright
doestar<br>(ドエスタ)
doestar
(ドエスタ)

ペンぞうさん、Webスクレイピング(3)では、スクレイピング結果をCSV保存しました。
でもね、サイト内ののDownloadボタンクリック時して欲しいファイルを一括保存したい

ときがあります。ダウンロード先のフォルダーも指定したいんです。できます?

ぺんぞう
ぺんぞう

ほう、ファイルダウンロードじゃな。Playwrightにも、Downloadメソッドが用意されとるぞ。今回、わしが特別にダウンロードテスト用のサンプルサイトを作ったのでそこにアクセスして試してもらおうかの~、では早速始めるぞ!

問題: アイキャッチ画像をファイル保存

下記 Download Testページ内にあるアイキャッチ画像横のダウンロード(赤枠)をクリックし、
指定フォルダーにアイキャッチ画像ファイルを保存してください。
(Download Test)
https://www.doestarian.com/python/playwright/download-test/ 

出力結果例です。
私は、”dl”という名のフォルダーを作ってそこに保存しました。


1.解説

ここでは、私が問題を解くまでの過程をご紹介します。
今すぐ解答を見たい方は、2.解答サンプルへ急ぎましょ!

1-1. Codegenでサンプルコード生成(ダウンロードボタンをクリック)

ダウンロードをクリックしてファイル保存するコードはどう書けばよいのでしょうか? 

以前、自動テスト(1)でご紹介した 自動生成機能 (Codegen)を使って、実際にダウンロードボタンをクリックしてサンプルコードを作ってみましょう。

playwright codegen https://www.doestarian.com/python/playwright/download-test/ -o test.py

こちらが test.pyで得られたDownloadボタンクリック近辺のコード抜粋です。page.expect_download()というメソッドを使っているようです。

    # Go to https://www.doestarian.com/python/playwright/download-test/
    page.goto("https://www.doestarian.com/python/playwright/download-test/")

    # Click text=AutoTest1 ダウンロード >> a
    with page.expect_download() as download_info:
        page.locator("text=AutoTest1 ダウンロード >> a").click()
    download = download_info.value

1-2. ダウンロード:page.expect_download()

では、さっそくメソッドを調べてみましょう。
Playwrightの公式ページ、https://playwright.dev/python/docs/downloads を見ると、、


download.save_as(”ファイル名”)で保存先を指定できるようです。


また、get_by_text(“ダウンロードボタン名”)でクリックするみたいですが、今回のボタンはすべて”ダウンロード”なので、アイキャッチ画像保存ファイル名が、全て”ダウンロード.png” になってしまいます、ファイル名からアイキャッチ画像を判別できないのでちょっと不便ですね。

Codegenで生成したコードは、
page.locator(“text=AutoTest1 ダウンロード >> a“).click() 
でしたので、page.locatorに設定するtext名を利用してAutoTest1.pngのように保存したいです。

次節で、AutoTest1 ダウンロード >> aの部分のCSSについて調査していきましょう。

1-3. DownloadボタンのCSS Selector調査

Codegenで生成したコードは、
page.locator(“text=AutoTest1 ダウンロード >> a”).click() 
でしたね。

では、ダウンロードボタンのCSSをブラウザのデベロッパーツール(Ctrl + Shift + i)を開いて確認してみましょう。

ダウンロードボタンのCSSSelectorを1番目、2番目、、最後を確認すると

## AutoTest1のダウンロードボタン
#post-773 > div > div:nth-child(2) > div.label-box-content.block-box-content.box-content > div > div:nth-child(2) > div > a
## AutoTest2のダウンロードボタン
#post-773 > div > div:nth-child(3) > div.label-box-content.block-box-content.box-content > div > div:nth-child(2) > div > a

(省略)
## WebScraping5のダウンロードボタン
#post-773 > div > div:nth-child(9) > div.label-box-content.block-box-content.box-content > div > div:nth-child(2) > div > a

AutoTest1のCSSSelectorを1番目、2番目から調査すると、、

## AutoTest1 テキスト
#post-773 > div > div:nth-child(2) > div.label-box-label.block-box-label.box-label > span
## AutoTest2 テキスト
#post-773 > div > div:nth-child(3) > div.label-box-label.block-box-label.box-label > span

post-773 > div > div:nth-child(X) の部分は共通でしたので
「div.label-box-label.block-box-label.box-label > span」の取得値  ダウンロード >> aを利用すれば、下記のようなファイル名でダウンロードできそうです。

  • AutoTest1.png
  • AutoTest2.png
  • (省略)
  • Webscraping5.png

こんな感じできました! 次章で解答サンプルを紹介します。

2. 解答サンプル(コードと trace調査)

今回も同期・非同期でコードを書いてみましたのでご参考までに、、、
やっぱり最適化した非同期コードは高速です。

今回のサンプルコードには、Webスクレイピング(4)で紹介した処理時間解析用のtraceコマンド(context.tracing.start / context.tracing.stop)を含んだmain_trace.pyというファイルで紹介しています。その後、trace解析結果を記載しています。

実際の計測では、traceコマンドを除いたmain.pyというファイルで計測してます。

2.1 解答サンプル(sync)

コード

シンプルなsync型のコードです。

from playwright.sync_api import Playwright, sync_playwright, expect
import time


#---------------------------------------------------- 
# playwright run
#---------------------------------------------------- 
def playwright_run(playwright: Playwright, url):
    
    browser = playwright.chromium.launch(headless=True)
    context = browser.new_context()
    
    # Start tracing before creating / navigating a page.
    context.tracing.start(screenshots=True, snapshots=True, sources=True)

    #### 1.Open url page
    page = context.new_page()
    page.goto(url)
    
    #### 2. Get title of download button.
    # ダウンロードボタンのタイトル名をリストで取得
    titles = page.locator('div.label-box-label.block-box-label.box-label > span').all_inner_texts()
    # print(titles)
    
    #### 3. Download data(Click download button)
    for title in titles:
        with page.expect_download() as download_info:
            target=f'text={title} ダウンロード >> a'
            page.locator(target).click()
        download = download_info.value
        download.save_as(f'dl/{title}.png')
        
    # Stop tracing and export it into a zip archive.
    context.tracing.stop(path = "trace_sync.zip") 
        
    #### 4. Close page
    page.close()
    context.close()
    browser.close()
    
    return "OK"

#---------------------------------------------------- 
# main
#---------------------------------------------------- 
if __name__ == "__main__":
    
    print(f'--> Download Start.')
    start = time.time() 
    
    # Download Test Page
    url = "https://www.doestarian.com/python/playwright/download-test/"
    

    # Playwright 実行
    with sync_playwright() as playwright:
        result = playwright_run(playwright,url)

    print(f'result:{result}')
    
    end = time.time()
    elapsed_time = end - start
    print(f'経過時間:{elapsed_time:.2f} 秒')
trace解析

traceコマンドを実行して処理時間を解析しましょう。

playwright show-trace .\trace_sync.zip

Download.save.asで約200ms~400msと時間がかかっていることがわかります。
ほかにもPage.expect_downloadでDownloadボタンをクリックまでも100ms超かかっていますね。

計測結果

main.pyの計測結果です。
経過時間:8.95 秒
経過時間:9.20 秒
経過時間:9.46 秒
経過時間:9.23 秒
経過時間:8.99 秒

2.2 解答サンプル(async)

次に、asyncコードです。 単純にsync部分をasyncに置き換えました。

コード
import asyncio
from playwright.async_api import async_playwright
import time


#---------------------------------------------------- 
# playwright run
#----------------------------------------------------
async def main(url): 
    async with async_playwright() as playwright:
        
        browser = await playwright.chromium.launch(headless=True)
        context = await browser.new_context()
        
        # Start tracing before creating / navigating a page.
        await context.tracing.start(screenshots=True, snapshots=True, sources=True)

        #### 1.Open url page
        page = await context.new_page()
        await page.goto(url)
        
        #### 2. Get title of download button.
        # ダウンロードボタンのタイトル名をリストで取得
        titles = await page.locator('div.label-box-label.block-box-label.box-label > span').all_inner_texts()
        # print(titles)
        
        #### 3. Download data(Click download button) [async]
        for title in titles:
            async with page.expect_download() as download_info:
                target=f'text={title} ダウンロード >> a'
                await page.locator(target).click()
            download = await download_info.value
            await download.save_as(f'dl/{title}.png')
            
            
        # Stop tracing and export it into a zip archive.
        await context.tracing.stop(path = "trace_async.zip") 
        
        #### 4. Close page
        await page.close()
        await context.close()
        await browser.close()
       
        return "OK"

#---------------------------------------------------- 
# main
#---------------------------------------------------- 
if __name__ == "__main__":
    
    print(f'--> Download Start[async].')
    start = time.time() 
    
    # Download Test Page
    url = "https://www.doestarian.com/python/playwright/download-test/"
    

    # Playwright 実行
    result = asyncio.run(main(url))

    print(f'result:{result}')
    
    end = time.time()
    elapsed_time = end - start
    print(f'経過時間:{elapsed_time:.2f} 秒')
trace解析

今回も、traceコマンドで実行しましょう。

playwright show-trace .\trace_async.zip

syncで実行した時より、Download.save_as から次の page.expect_download の隙間が少なくなったけどまだまだですね。ダウンロード部分を並行で動かしたいです。

計測結果

main.pyの計測結果です。syncよりは高速ですが、体感はあまり変わらないですね。

経過時間:8.65 秒
経過時間:8.30 秒
経過時間:8.34 秒
経過時間:8.57 秒
経過時間:8.65 秒

2.3 解答サンプル(async_task)

asyncの下記コードの部分を切り出してマルチタスクで動作させるように変更しました。

        #### 3. Download data(Click download button) [async]
        for title in titles:
            async with page.expect_download() as download_info:
                target=f'text={title} ダウンロード >> a'
                await page.locator(target).click()
            download = await download_info.value
            await download.save_as(f'dl/{title}.png')
コード
import asyncio
from playwright.async_api import async_playwright
import time

#---------------------------------------------------- 
# playwright run
#----------------------------------------------------
#### 3. Download data(Click download button) [async]
async def download_task(title,page):
    async with page.expect_download() as download_info:
        target=f'text={title} ダウンロード >> a'
        await page.locator(target).click()
    download = await download_info.value
    await download.save_as(f'dl/{title}.png')
                
async def main(url): 
    async with async_playwright() as playwright:
        
        browser = await playwright.chromium.launch(headless=True)
        context = await browser.new_context()
        
        # Start tracing before creating / navigating a page.
        await context.tracing.start(screenshots=True, snapshots=True, sources=True)

        #### 1.Open url page
        page = await context.new_page()
        await page.goto(url)
        
        #### 2. Get title of download button.
        # ダウンロードボタンのタイトル名をリストで取得
        titles = await page.locator('div.label-box-label.block-box-label.box-label > span').all_inner_texts()
        # print(titles)
        
        #### 並行処理 :3. Download data(Click download button) [async] 
        tasks = []
        for title in titles:
            tasks.append(asyncio.create_task(download_task(title,page)))            
        await asyncio.gather(*tasks)
           
        # Stop tracing and export it into a zip archive.
        await context.tracing.stop(path = "trace_async_task.zip") 
        
        #### 4. Close page
        await page.close()
        await context.close()
        await browser.close()
        
        return "OK"

#---------------------------------------------------- 
# main
#---------------------------------------------------- 
if __name__ == "__main__":
    
    print(f'--> Download Start[async_task].')
    start = time.time() 
    
    # Download Test Page
    url = "https://www.doestarian.com/python/playwright/download-test/"
    
    # Playwright 実行
    result = asyncio.run(main(url))

    print(f'result:{result}')
    
    end = time.time()
    elapsed_time = end - start
    print(f'経過時間:{elapsed_time:.2f} 秒')
    
trace解析
playwright show-trace .\trace_async_task.zip

Download.save_asの時間は伸びましたが不要な待ち時間は無くなりました。

計測結果

main.pyの計測結果です。ちょっとですが、体感でも速くなった気がします。

経過時間:7.01 秒
経過時間:6.18 秒
経過時間:6.60 秒
経過時間:6.51 秒
経過時間:6.41 秒

2.4 比較結果

結果をまとめです。非同期+並行処理により変更前より29%も高速化できました。

実験PCのスペック
 CPU :Intel(R) Core(TM) i5-4200M CPU @ 2.50GHz
 Memory:8.00 GB
 OS : Windows 10 Pro (64ビット)

さいごに

今回は、ダウンロード”page.expect_download()”について学びました。
Playwrightは、まだまだ奥が深いですね。これからも学び続けようと思います。

doestar<br>(ドエスタ)
doestar
(ドエスタ)

今回の教訓でPlaywright におけるdoestar 3か条ができたよ

  • Codegen でサンプルコードを生成しよう
  • Traceで時間解析しよう
  • async/await +create_taskで処理時間を短縮しよう

<< Playwright道場

コメント

タイトルとURLをコピーしました