バスケやデータの雑記帳

バスケやデータ分析について徒然なるままに

Python+Seleniumを使ったスクレイピングその2(2023年10月時点)

この前はSeleniumを使ってスクレイピングを行い、ページソースを取得する部分までをメモとして残しました。今回は、さらにそのページソースから必要な部分を取得するやり方について、メモとして残しておきます。なお、今回もWindowsで行う前提です。

今回も例としてPythonの公式サイトにアクセスしてみます。公式サイトのトップページから、Latest Newsの各ページにアクセスし、本文を取得してデータフレームに格納することに挑戦してみます。

Webサイトへのアクセス

前回のおさらいですが、目的とするwebサイトにアクセスします。

# ライブラリをインポート
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# アクセスしたいURLを指定
url = "http://www.python.org"

# ブラウザを操作するためのwebdriverを設定
options = Options()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
wait = WebDriverWait(driver=driver, timeout=30) # 最大待ち時間の設定

# ブラウザを起動し、URLにアクセス
driver.get(url)
wait.until(EC.presence_of_all_elements_located) # HTMLの要素が全てレンダリングされるまで待つ

ここで以下のように処理すると、あとはBeautifulSoupでHTMLを処理するだけになります。ただし、今回は勉強も兼ねてSeleniumだけでHTMLを処理し、必要な要素を取得してみようと思います。

from bs4 import BeautifulSoup

def get_soup(driver):
    html = driver.page_source
    soup = BeautifulSoup(html, "html.parser")
    return soup

soup = get_soup(driver)

HTMLを確認

実際にブラウザでWebサイトにアクセスし、処理したいHTMLの構造を確認しておきます。Chromeであれば、Ctrl + Shift +iでDevToolsを開き、WebサイトのHTMLを確認することが出来ます。右側のパネル上で、実際に取得したい要素の種類や、つけられているクラス名などを知ることが出来ます。

要素を取得

それでは、実際に要素を取得していきます。まずはLatest NewsのタイトルとURLの一覧を取得しておきます。

要素を取得する場合、次の2つのメソッドと、要素の指定方法、要素の中身(文章等)の取得方法を一つだけ覚えておけばなんとかなります。

要素の取得方法

取得する際のメソッドは次の通りです。

  • find_element()|要素を一つだけ取得します。同じ条件の要素が複数ある場合は最初の要素のみ取得します。
  • find_elements()|条件に当てはまる要素を全て、リスト形式で取得します。

また、指定方法は次の通りです。

  • By.CSS_SELECTOR|要素の種類(div等)とclass名、あるいはid名で指定することが出来ます。ID名の場合は基本的に一要素にしかつけないので、find_element()としか組み合わせません。

class名で指定する場合、.を前につけて指定します。id名の場合は#です。なお、ある要素のさらに子要素を取得する場合は、直接の子要素であれば>、孫以降の要素も含むのであれば" "(※スペース)をつければOKです。

例えば、次のようなHTMLのページがあり、driverでアクセスしていたものとします。

<div id="id_name">
  <p class="a_class">a_hoge</p>
  <p class="a_class">a_fuga</p>
  <p class="b_class">b_fuga</p>
</div>

次のように指定することで、要素の取得が出来ます。

# id名で指定する場合
id_element = driver.find_element(By.CSS_SELECTOR, "div#id_name")

# class名で指定する場合
a_class_element_list = driver.find_elements(By.CSS_SELECTOR, "p.a_class")

# id名で指定した後、その下の子要素をまとめて指定する場合
p_element_list = driver.find_elements(By.CSS_SELECTOR, "div#id_name p")

要素の中身を取得する際には、get_attribute()を使います。aタグに対して行う場合、textContentを指定すれば文章を、hrefを指定すればURLを取得できます。

# 要素を取得
a_element = driver.find_element(By.CSS_SELECTOR, "a#sample_id")

# 要素内の中身を取得
txt = a_alement.get_attribute("textContent")
href = a_element.get_attribute("href")

タイトルとURLを取得

それでは、実際にLatest NewsのタイトルとURLを取得していきます。

# Latest NewsのタイトルとURLを取得する
latest_news_elements_list = driver.find_elements(By.CSS_SELECTOR, "div.blog-widget li")

latest_news_url_dict = {}
for elem in latest_news_elements_list:
    # aタグを取得
    a_tag = elem.find_element(By.CSS_SELECTOR, "a")
    
    # aタグから、タイトルとURLを取得
    news_title = a_tag.get_attribute("textContent")
    news_url = a_tag.get_attribute("href")
    
    # dictに保存
    latest_news_url_dict[news_title] = news_url
print(latest_news_url_dict)
{'Announcing our new Community Communications Manager!': 'https://pyfound.blogspot.com/2023/10/announcing-community-communications-mgr.html', 'September & October Board Votes': 'https://pyfound.blogspot.com/2023/10/september-october-board-votes.html', 'Security Developer-in-Residence 2023 Q3 Report': 'https://pyfound.blogspot.com/2023/10/security-developer-in-residence-2023-q3-report.html', 'Python 3.13.0 alpha 1 is now available': 'https://pythoninsider.blogspot.com/2023/10/python-3130-alpha-1-is-now-available.html', 'Python 3.11.6 is now available': 'https://pythoninsider.blogspot.com/2023/10/python-3116-is-now-available.html'}

Latest Newsの本文を取得

それでは、実際に各URLにアクセスし、本文を取得していきます。要領は先程と同じです。各URLにアクセスする際、ちゃんと本文が表示されてから本文の取得を開始するように、WebDriverWait()を使っています。

import polars as pl

# 各Latest NewsのURLにアクセスし、本文(pタグ)を取得する
news_description_list = []
for title, url in latest_news_url_dict.items():
    # URLにアクセス
    driver.get(url)
    WebDriverWait(driver, 30).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, "div.post"))
    )
    
    # 本文を取得(画像等が間に本文中に存在する影響で、複数のpタグに分かれていることに注意)
    description_elements = driver.find_elements(By.CSS_SELECTOR, "div.post p")

    description = ""
    for elem in description_elements:
        description += elem.get_attribute("textContent")
    
    # リストに保存
    news_description_list.append(description)

# データフレームに格納
latest_news_dict = {
    "title": latest_news_url_dict.keys(),
    "url": latest_news_url_dict.values(),
    "description": news_description_list
}
df = pl.from_dict(latest_news_dict)
df.head()
shape: (5, 3)
titleurldescription
strstrstr
"Announcing our…"https://pyfoun"We announced o…
"September & Oc…"https://pyfoun"We’re writing …
"Security Devel…"https://pyfoun"It’s been thre…
"Python 3.13.0 …"https://python" It’s not a ve…
"Python 3.11.6 …"https://python"  Python 3.11.…

しっかりと本文を取得し、データフレームに格納することが出来ました!

参考にさせていただいた記事