【2023年】Python PlaywrightでWebスクレイピング(2) – Table情報抽出 / get_attribute() –

PythonPlaywrightでWebScrapying2 Playwright
ぺんぞう
ぺんぞう

前回で、Webスクレイピングの必須スキル「page.locator()とサイトのPage情報確認方法」を学んだようじゃのう。では、さっそく実践じゃ、つまづきながらでいいのでファイトじゃ!

問題① タイトルとテーブル情報の抽出

下記のURLを指定して以下の2つの情報を抜き出してください。
https://www.doestarian.com/python/playwright/ 

① タイトル名Playwright 道場 | DOEStarian Blog
② テーブル情報 (入門編)*No. / 説明 / 投稿記事のURL
* テーブル情報(実践編)は取得しないこと

出力結果はつぎのとおりです。

Title: 
Playwright 道場  |  DOEStarian Blog

Table:
No , 説明, 投稿記事
1 , Playwrightの概要とメリット、インストール方法codegenによる自動テストコードの生成方法の紹介, https://www.doestarian.com/python-playwright%e3%81%a7%e8%87%aa%e5%8b%95%e3%83%86%e3%82%b9%e3%83%88%e3%81%97%e3%81%a6%e3%81%bf%e3%82%8b/
2 , page.goto(url)によるエラー発生時の対処方法, https://www.doestarian.com/2023-playwright-test2-error/
3 , Safari、Firefox上でテストする方法, https://www.doestarian.com/2023-playwright-test3-url-browser/

解説

TableのCSS selector 調査

Titleは前回説明したとおり、下記のように”title”指定で情報をとることができました。

# Title抽出
title = page.locator("title").inner_text()

では、テーブル情報はどのようにとればいいのでしょうか?

Title抽出のときと同様にTableのCSS selector(以下、selector)を調査してみましょう。
まずは、対象のテーブルのヘッダー情報(No、説明、投稿記事)を調査。

下記のように最後のtd:nth-child(1)の部分が変更されていることが確認できました。

No#post-93 > div > figure:nth-child(6) > table > tbody > tr:nth-child(1) > td:nth-child(1)
説明#post-93 > div > figure:nth-child(6) > table > tbody > tr:nth-child(1) > td:nth-child(2)
投稿記事#post-93 > div > figure:nth-child(6) > table > tbody > tr:nth-child(1) > td:nth-child(3)

次に、2行目(1つ目の投稿記事)のテーブル情報のselectorを調査してみましょう

今度は、tr:nth-child(1)の部分がtr:nth-child(2)に変更されていることが確認できました。
また、投稿記事のリンクに関しては<a>タグが付与されていることも確認できました。

1#post-93 > div > figure:nth-child(6) > table > tbody > tr:nth-child(2) > td:nth-child(1)
Playwrightの概要…#post-93 > div > figure:nth-child(6) > table > tbody > tr:nth-child(2) > td:nth-child(2)
【2023年】Python...#post-93 > div > figure:nth-child(6) > table > tbody > tr:nth-child(2) > td:nth-child(3) > a

もう一つ確認してみましょう。
【実践編】Webスクレイピングのテーブルのヘッダー情報(No、説明、投稿記事)はどうでしょう?

figure:nth-child(6)の部分が、figure.wp-block-table.is-style-regular に変更されてました。

No#post-93 > div > figure.wp-block-table.is-style-regular > table > tbody > tr:nth-child(1) > td:nth-child(1)
説明#post-93 > div > figure.wp-block-table.is-style-regular > table > tbody > tr:nth-child(1) > td:nth-child(2)
投稿記事#post-93 > div > figure.wp-block-table.is-style-regular > table > tbody > tr:nth-child(1) > td:nth-child(3)

selectorの指定は、調査対象のselector以降の情報を指定すればよいです。
したがって、下記の対象テーブル情報を取得したい場合は、

  figure:nth-child(6) > table > tbody > tr:nth-child(1) > td:nth-child(1)
のようにfigure:nth-child(6)以降を指定すればよいです。

テーブル情報の抽出 (リンク:get_attribute)

次に、実際のテーブル情報を取得する場合について考えましょう。
例えば、 No.1の説明部分「Playwrightの概要と…」のテキストを抽出したい場合は

No説明投稿記事
1Playwrightの概要とメリット、インストール方法codegenによる自動テストコードの生成方法の紹介【2023年】Python Playwrightで自動テストしてみよう

このように書けばOKですね。

# 説明
overview = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(2) > td:nth-child(2)').inner_text()

では、【2023年】Python Playwrightで自動テストしてみよう の部分はどうでしょうか?

figure:nth-child(6) > table > tbody > tr:nth-child(2) > td:nth-child(3) > a 

テーブルのHTMLコードを見てみましょう。

 <tbody>
  <tr>
  <td>No</td>
  <td>説明</td>
  <td>投稿記事</td>
 </tr>
 <tr>
  <td>1</td>
  <td>Playwrightの概要とメリット、インストール方法codegenによる自動テストコードの生成方法の紹介</td>
  <td><a href="https://www.doestarian.com/python-playwright%e3%81%a7%e8%87%aa%e5%8b%95%e3%83%86%e3%82%b9%e3%83%88%e3%81%97%e3%81%a6%e3%81%bf%e3%82%8b/" target="_blank">
【2023年】Python Playwrightで自動テストしてみよう</a></td>
 </tr>
...
 </tbody>

リンク先のURLが、

<a href=”http…”> 【2023年】Python Playwrightで自動テストしてみよう</a>

のように<a>タグ内に記述されています。
このタグ内の情報を属性といいます。今回の場合は、aタグのhref属性といった感じですね。

今回のように属性情報を取得したい場合は、get_attribute()メソッドを使います。

# 投稿記事
url = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(2) > td:nth-child(3) > a').get_attribute('href')

解答例 ①

今回は、入門編の投稿記事が増えないことを前提につくってみました。

from playwright.sync_api import Playwright, sync_playwright, expect


def run(playwright: Playwright) -> None:
    browser = playwright.chromium.launch(headless=True)
    context = browser.new_context()

    # Open new page
    page = context.new_page()

    # Go to https://www.doestarian.com/python/playwright/
    page.goto("https://www.doestarian.com/python/playwright/")
    
    # Title取得
    title = page.locator("title").inner_text()
    print(f'Title:\n {title}\n')

    # テーブル情報(入門編)を取得
    print('Table:')
    # テーブルヘッダ
    no       = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(1) > td:nth-child(1)').inner_text()
    overview = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(1) > td:nth-child(2)').inner_text()
    url      = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(1) > td:nth-child(3)').inner_text()
    print(f'{no} , {overview}, {url}') 
    
    # No.1
    no       = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(2) > td:nth-child(1)').inner_text()
    overview = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(2) > td:nth-child(2)').inner_text()
    url      = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(2) > td:nth-child(3) > a').get_attribute('href')
    print(f'{no} , {overview}, {url}')
    
    # No.2
    no       = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(3) > td:nth-child(1)').inner_text()
    overview = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(3) > td:nth-child(2)').inner_text()
    url      = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(3) > td:nth-child(3) > a').get_attribute('href')
    print(f'{no} , {overview}, {url}')
    
    # No.3
    no       = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(4) > td:nth-child(1)').inner_text()
    overview = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(4) > td:nth-child(2)').inner_text()
    url      = page.locator('figure:nth-child(6) > table > tbody > tr:nth-child(4) > td:nth-child(3) > a').get_attribute('href')
    print(f'{no} , {overview}, {url}')
        
    # Close page
    page.close()

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


with sync_playwright() as playwright:
    run(playwright)

解答例 ②(投稿記事が増えても対応)

入門編の記事が増えたら対応できなくなりますよね。
投稿記事が増えても対応できるようにしましょう。

実は、つぎのように書くとテーブル情報内のテキスト情報を一気に取得することができるんです。

# 入門編のテキスト取得
table_str = page.locator('figure:nth-child(6) > table').inner_text() 
# or
table_str = page.locator('table',has_text="自動テスト").inner_text()

1つ目:入門編のテーブルselectorを直接指定
2つ目:”自動テスト”と書かれているテーブルselectorの情報を抽出

■投稿数の調査(+テキスト出力)

# 投稿数とテーブルテキスト取得
# table_str = page.locator('figure:nth-child(6) > table').inner_text() 
table_str = page.locator('table',has_text="自動テスト").inner_text()
count_posts = len(table_str.splitlines())-1
print(f'投稿数:{count_lines}\n {table_str}\n')

投稿数(count_posts)は、ヘッダ行を除いて3つあることが確認できました。
こちらを使って改良です。

from playwright.sync_api import Playwright, sync_playwright, expect


def run(playwright: Playwright) -> None:
    browser = playwright.chromium.launch(headless=True)
    context = browser.new_context()

    # Open new page
    page = context.new_page()

    # Go to https://www.doestarian.com/python/playwright/
    page.goto("https://www.doestarian.com/python/playwright/")
    
    # Title取得
    title = page.locator("title").inner_text()
    print(f'Title:\n {title}\n')
    
    # 行数を計算
    table_str = page.locator('figure:nth-child(6) > table').inner_text() 
    count_lines = len(table_str.splitlines())-1
    
    # テーブル情報を取得
    print('Table:')
    for i in range(count_lines):
        index = i+2
        no       = page.locator(f'figure:nth-child(6) > table > tbody > tr:nth-child({index}) > td:nth-child(1)').inner_text()
        overview = page.locator(f'figure:nth-child(6) > table > tbody > tr:nth-child({index}) > td:nth-child(2)').inner_text()
        url      = page.locator(f'figure:nth-child(6) > table > tbody > tr:nth-child({index}) > td:nth-child(3) > a').get_attribute('href')
        print(f'{no} , {overview}, {url}')
        
    # Close page
    page.close()

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


with sync_playwright() as playwright:
    run(playwright)

できました

さいごに

いかがだったでしょうか?
単発ページならこれでとれるようになったと思います。
次回は、検索結果を使った情報取得、複数ページの情報取得、CSVリストに記載されたURLの情報取得などに紹介したいと思います。

おまちくださいませ。

(筆者の悩み:2023.4.1時点)
HTMLのデザインは頻繁に変わる可能性があります。そのたびにselectorの修正するのは面倒ですね。
Playwrightは、それらに対応できるようにpage.locator(‘table’,has_text=”自動テスト”)
など、スマートに記載できるようになってます。
ただ、URL取得部分など実装できない部分がありました。
スキルアップしてよりスマートなコードが書けるようにしたいです。

<< Playwright道場

< Prev                                 

コメント

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