【備忘録】Python×Seleniumでデータ収集を自動化

# はじめに
Pythonの「requests」と「bs4（beautifulsoup）」を使って、ウェブサイトからデータをスクレイピングしようとしましたが、そのサイトは動的にデータを表示するタイプだったため、思うようにデータを取得することができませんでした。

そこで、「Selenium」を試したところ、うまくスクレイピングができましたので、備忘録として「Python × Selenium」を使ったスクレイピングの方法をまとめておきたいと思います。

# 想定する読者
* Python × Seleniumでデータ収集を自動化したい方

# Seleniumとは？
Seleniumは、ウェブブラウザを自動で操作するためのオープンソースのテスト自動化ツールです。ウェブアプリケーションの機能テストやスクレイピング、ブラウザ操作の自動化に使用されます。

Seleniumは、開発者やテスターが実際にブラウザを使って行う操作をプログラムでシミュレートすることができます。


**主な特徴**
1. ブラウザ自動操作
    * Seleniumを使用すると、ブラウザで行う操作（ページの読み込み、リンクのクリック、テキストの入力、フォームの送信など）を自動化できます
    * テストや定型作業の効率を大幅に向上させることができます
2. 複数のブラウザやプラットフォームに対応
    * Seleniumは、Google Chrome、Firefox、Microsoft Edge、Safariなど主要なブラウザで動作します
    * Windows、macOS、Linuxといった様々なプラットフォームでも使用可能です
3. 多言語対応
    * Seleniumは、Python、Java、C#、Ruby、JavaScriptなどのさまざまなプログラミング言語で操作できるため、開発環境に合わせて選択できます
4. 動的なコンテンツにも対応
    * JavaScriptで動的に生成されるコンテンツや、複雑なWebページの処理を自動化するのに適しています
    * 通常のリクエストベースのスクレイピングでは取得できない動的なデータも扱うことができます

**Seleniumの構成要素**
1. Selenium WebDriver
    * WebDriverは、実際にブラウザを自動操作するためのエンジンです。これを通じて、プログラムからブラウザに指示を送り、ユーザーがブラウザ上で行う操作を模倣できます
    * WebDriverは、各ブラウザ用に異なるドライバー（例:ChromeDriver、EdgeDriverなど）を使用して動作します

# 環境構築
Windows10 or Windows11 64bit
pip 24.2
Python 3.8.10
Selenium 4.17.2

<details open><summary>Seleniumのインストール方法</summary>

```python
pip install selenium
```
</details>

<details open><summary>WebDriverについて</summary>

:::note info
**Selenium 4**以上では、WebDriverの手動ダウンロードは基本的に不要です
:::
Seleniumが自動的に適切なWebDriverをダウンロード・管理してくれます。
ただし、手動で特定のバージョンのWebDriverを管理したい場合や、プロジェクトの要件に応じては、```webdriver_manager```ライブラリなどを使用します。

</details>


# 基本的な使い方
Seleniumの基本的は書き方を示します。
このプログラムは、PythonのSeleniumライブラリを使用して自動的にブラウザを操作し、Googleのウェブページにアクセスするものです。
Selenium 4以上と未満で挙動は同じです。

### Selenium 4以上を使用する場合
```python:selenium_helloworld.py
from selenium import webdriver  # Seleniumのwebdriverモジュールをインポート

# Chromeドライバーを使って新しいブラウザウィンドウを開く
driver = webdriver.Chrome()

# 指定したURL（ここではGoogleのホームページ）を開く
driver.get('https://www.google.com/')

# 現在のタブまたはウィンドウを閉じる
driver.close()

# ブラウザ全体（すべてのウィンドウやタブ）を終了する
driver.quit()
```

### Selenium 4未満を使用する場合
```diff_python:selenium_helloworld.py
from selenium import webdriver  # Seleniumのwebdriverモジュールをインポート
+ from webdriver_manager.chrome import ChromeDriverManager  # webdriver_managerでChromeDriverを管理


# Chromeドライバーを使って新しいブラウザウィンドウを開く
- driver = webdriver.Chrome()
+ # Chromeドライバーを自動でインストール
+ driver = webdriver.Chrome(ChromeDriverManager().install())

# 指定したURL（ここではGoogleのホームページ）を開く
driver.get('https://www.google.com/')

# 現在のタブまたはウィンドウを閉じる
driver.close()

# ブラウザ全体（すべてのウィンドウやタブ）を終了する
driver.quit()
```

1. ```from selenium import webdriver```
    - Seleniumの```webdriver```モジュールをインポートしています
    - このモジュールを使用することで、ブラウザをプログラムで制御することができます

2. ```driver = webdriver.Chrome()```
    - SeleniumのChromeドライバーを使用して、新しいChromeブラウザウィンドウを開きます
    - この時点で、プログラムによってChromeが自動的に操作可能な状態になります

3. ```driver.get('https://www.google.com/')```
    - getメソッドを使って、指定されたURLにアクセスします
    - ここでは、Googleのホームページにアクセスしています
    - ブラウザが指定されたURLを開き、そのページを表示します

4. ```driver.close()```
    - 現在開いているブラウザのタブまたはウィンドウを閉じます
    - もし複数のタブやウィンドウが開いていた場合でも、現在操作中のものだけを閉じます

5. ```driver.quit()```
    - ブラウザ全体を終了します
    - このコマンドは、すべてのタブやウィンドウを閉じるとともに、Seleniumのドライバーも終了します
    - Chromeブラウザは完全に閉じられ、プログラムによる操作が終了します

:::note info
詳しくは公式ドキュメントを参照ください。
https://selenium-python.readthedocs.io/

:::

# 応用例
Google Choromeでキーワードを指定し、Webページのタイトルを取得するプログラムを示します。
タイトルは指定した個数分取得しますが、ページ遷移を考慮していないため、現状は1ページ目のタイトルしか取得できません。（ここは今後修正します）

```Python:get_titles_from_google_chrome.py
from selenium import webdriver
from selenium.webdriver.common.by import By
import time

class GoogleScraperSelenium:
    """
    Google検索を自動化し、検索結果のタイトルを取得するためのクラス。

    Attributes:
        keyword (str): 検索キーワード。
        num_results (int): 取得する検索結果の件数。
        url (str): 検索を実行するURL（Googleの検索ページ）。
        driver (webdriver): SeleniumのWebDriverオブジェクト。
    """

    def __init__(self, keyword, num_results, url):
        """
        コンストラクタ。検索キーワード、取得件数、URLを初期化する。

        Args:
            keyword (str): 検索キーワード。
            num_results (int): 取得する検索結果の件数。
            url (str): 検索ページのURL（GoogleのURLを使用）。
        """
        self.keyword = keyword  # 検索するキーワード
        self.num_results = num_results  # 取得する検索結果の件数
        self.url = url  # 検索するページのURL（Google）
        self.driver = None  # WebDriverオブジェクトの初期化（後でセットアップ）

    def setup_driver(self):
        """
        Chromeドライバーを初期化してセットアップするメソッド。
        Webブラウザを起動し、指定されたURLにアクセスする準備を行う。
        """
        self.driver = webdriver.Chrome()

    def search(self):
        """
        Google検索を実行し、指定された件数の検索結果のタイトルを取得する。

        Returns:
            results (list): 検索結果のタイトルを辞書形式でリストに格納したもの。
        """
        self.setup_driver()
        self.driver.get(self.url)
        
        # 検索ボックスを取得し、キーワードを入力して検索を実行
        search_box = self.driver.find_element(By.NAME, 'q')  # name属性が'q'の要素（Googleの検索ボックス）を取得
        search_box.send_keys(self.keyword)  # 検索ボックスにキーワードを入力
        search_box.submit()  # フォームを送信して検索を実行

        time.sleep(2)  # 検索結果ページが表示されるまで2秒待機

        results = []  # 検索結果を保存するリスト
        
        # 検索結果の要素を取得し、指定された件数分タイトルを抽出
        search_results = self.driver.find_elements(By.CSS_SELECTOR, 'div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc') 
        for result in search_results[:self.num_results]:
            try:
                title = result.find_element(By.TAG_NAME, 'h3').text
            except:
                title = 'タイトルが見つかりませんでした'
            
            results.append({"title": title})

        self.driver.quit()
        
        return results

    def display_results(self, results):
        """
        取得した検索結果のタイトルを表示する。

        Args:
            results (list): 検索結果のタイトルを格納したリスト。
        """
        # 取得した検索結果を順番に表示
        for i, data in enumerate(results):
            print(f"結果 {i + 1}:") 
            print(f"タイトル: {data['title']}") 
            print("-" * 50) 

def main():
    """
    メイン処理。ユーザーから検索キーワードと取得件数を入力させて検索を実行し、
    結果を表示する。
    """
    # 検索キーワードと取得件数をユーザーから入力させる
    keyword = input("検索キーワードを入力してください: ") 
    num_results = int(input("取得する件数を入力してください: ")) 
    url = "https://www.google.com/"  # 検索するURL（Googleのトップページ）

    # GoogleScraperSelenium クラスをインスタンス化し、検索を実行
    scraper = GoogleScraperSelenium(keyword, num_results, url) 
    results = scraper.search()
    
    scraper.display_results(results) 

# メイン処理の実行
main()
```

```Python:実行
python .\main.py
検索キーワードを入力してください: 北千住　良さ
取得する件数を入力してください: 10
```
```Ptyhon:結果
結果 1:
タイトル: 北千住の住みやすさ。家賃相場や治安、メリット・デメリットを紹介
--------------------------------------------------
結果 2:
タイトル: 北千住の住みやすさは？都心へのアクセスやおすすめスポット ...
--------------------------------------------------
結果 3:
タイトル: 穴場な街「北千住」は、アクセスの良さと昔ながらの風情が人気 ...
--------------------------------------------------
結果 4:
タイトル: 北千住駅周辺の住みやすさは？治安や家賃相場についてご ...
--------------------------------------------------
結果 5:
タイトル: 【北千住の住みやすさ】成長著しいエネルギッシュな街はどんな ...
--------------------------------------------------
結果 6:
タイトル: エリア分析｜足立区最大のターミナル駅がある「北千住」の魅力 ...
--------------------------------------------------
結果 7:
タイトル: 【最新】北千住の住みやすさ徹底解説！下町商店街と大型 ...
--------------------------------------------------
結果 8:
タイトル: 北千住駅周辺の住みやすさは？治安は悪い？家賃相場・口コミ ...
--------------------------------------------------
結果 9:
タイトル: 北千住駅周辺の住みやすさと交通利便性はどう？住環境も ...
--------------------------------------------------
結果 10:
タイトル: 【北千住駅の住みやすさ】クチコミ・街レビュー（東京都足立区）
--------------------------------------------------
```
正常にタイトルの取得ができました。

# おわりに
この記事では、Ptyhon × Seleniumでデータ収集を自動化する方法を示しました。

Seleniumを使って自動化を行いたい方の助けになれば幸いです。

# 変更履歴
2024/09/17 新規作成