前回で、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 | 説明 | 投稿記事 |
1 | Playwrightの概要とメリット、インストール方法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取得部分など実装できない部分がありました。
スキルアップしてよりスマートなコードが書けるようにしたいです。
コメント