Python×Seleniumでスクレイピングの基礎を理解する

2023/7/17

(最終更新: 2023/7/17

アイキャッチ画像

はじめに

Python でよく使われるフレームワークであるSeleniumを使ってみます。私は実施したことがないのですが、Web 画面の単体テストや受入テストなどでも利用されているようです。

今後仕事で使う機会があるかもしれないのと、今加入しているオンライン英会話の講師検索がどうにもやりにくいので、さくっと自動化できないかなーと思ったのもあり、とりあえず試してみることにしました。

結果、環境のセットアップと基本的な操作のコツをつかんだので、記録に残しておきます。

講師のさくっと検索までは至っていないので、次回以降にします。

ただし、ローカルにインストールした Python と WebDriver でしか試せていないので、定期的にクローリングしたい場合などは別途サーバ上で動かす工夫が必要になりそうです。

環境

  • 利用 PC:Windows11
  • OS:Windows 11 pro
  • Python バージョン:Python 3.9.12
  • Anaconda バージョン:conda 22.9.0
  • IDE:PyCharm
  • Selenium:4.10.0
  • WebDriver:ChromeDriver 114.0.5735.90
  • Chrome:114.0.5735.199

初期設定

Python の環境準備

今回は Anaconda を利用してインストールした Python を使います。IDE としてPyCharmを利用しています。

環境構築の仕方はこちらの記事で説明しています。

REST-APIを作ってみる(Python編)

REST-APIを作ってみる(Python編)

REST-APIを作ってみるシリーズ第三弾。PythonでWeb-APIを作成する。

https://bunsugi.com/rest-api-python

Project 作成

PyCharm で「File」→「New Project」から新しくプロジェクトを作成します。

New Project

私は以下の場所に作成しました。:

C:\Users\user\workspace\PycharmProjects\bizmates_search

とりあえずshift + f10 を押すと、main.pyを実行してくれます。「Hi, Pycharm」と文字が表示されたので OK です。

Execute of sample project

Selenium モジュールのインストール

Pycharm の機能でSelenium モジュールをインストールします。

PyCharm で「File」→「Settings」→「Project」→「Python Interpreter」→「+」→「selenium」

selenium

WebDriver のインストール

ブラウザを自動で動かすために WebDriver(ChromeDriver)のインストールも必要になるようです。

以下のサイトからインストールします。

https://sites.google.com/chromium.org/driver/

Getting started with ChromeDriver on Desktop (Windows, Mac, Linux)

Download

念のため自分の Chrome のバージョンを「ヘルプ」→「Google Chrome について」から確認し、適合しているバージョンをダウンロードして、適当な場所に解凍します。

※解凍した場所を環境変数の PATH に追加しておくように書いてあるが、今のところ追加していなくてもできている…?念のため追加しておいたほうが良いかも。

【参考】GitHub 連携

いつものようにGitHubと連携しておきます。

念のためメモ。

  • GitHub 上で New repositories 作成
  • ターミナル(PowerShell)のプロジェクトフォルダで以下実施
> echo "# bizmates_search" >> README.md
> git init
> git add .
> git commit -m "first commit"
> git branch -M main
> git remote add <GitHubのURL>
> git push -u origin main

GitHub 推奨の Python 用.gitignoreファイルも作っておきました。

参照:gitignore/Python.gitignore at main · github/gitignore

更に、動作の確認のためにスクリーンショットを取っていますが、これも GitHub にアップロードすると意図せず情報が漏れてしまいそうなので、.gitignoreに以下を記述します。

./.gitignore
*.png

Selenium を使ったコードの基本形

以下が一連の流れの基本形となります。

  • webdriverのインポート
  • driverの作成
  • driverインスタンスの持つ関数を使って、ページの内容を取得したりスクリーンショットを取ったりといった操作
main.py
from selenium import webdriver #seleniumモジュールからwebdriverオブジェクトをインポート

def main():
    driver = webdriver.Chrome('ChromeDriverを解凍した場所…C:\\Users\\user\\chromedriver_win32\\chromedriver') #driver作成

    driver.get('https://google.com') #URLを開く
    driver.save_screenshot('test.png') #スクリーンショット
    driver.quit() #プロセスを閉じる

if __name__ == '__main__':
    main()

Selenium を使い自動で Web サイトにログインしてみる

ここから、もともとやりたかった Web サイトに自動ログイン → 情報を取得する機能に挑戦します。

セキュリティ事前準備:環境変数を使えるようにする

ログイン情報を GitHub にアップロードしてしまうと大問題なので、以下の方法をとります。(この辺は Node.js と同じ考え方ですね。)

  • 秘密情報は.envファイルに記載し変数として扱えるようにする
  • python コード内では、変数名を記載する
  • .gitignoreファイルに.envを記述し、Git で共有されないようにする。
  • .envファイルの中身を変数として扱えるようにするためのモジュールpython-dotenvを利用する。

.envファイルを作成

試しに以下の形で作成します。

./.env
LOGIN_URL=https://{行きたいサイトURL}/login/
EMAIL=xxx@yyyyy.com
PASS=abcdefg

プログラム側から呼び出し

.envで設定した変数は以下のように呼び出します。

./main.py
import os
load_dotenv()

def main():
    print(os.getenv('LOGIN_URL')) #LOGIN_URLを呼び出し
    print(os.getenv('EMAIL')) #EMAILを呼び出し
    print(os.getenv('PASSWORD')) #PASSWORDを呼び出し

if __name__ == '__main__':
    main()

.gitignoreファイルの確認

先ほどのページからコピペしているとすでに以下の記述が.gitignoreに含まれるはずなので、確認だけおこないました。

./.gitignore
.env

モジュールインストール

python-dotenvをインストールする。

install python-dotenv

Selenium でログインしてみる

ここまでわかると、Web サイトのログインまでは簡単にできます。

main.py
import os
from selenium import webdriver
from dotenv import load_dotenv

load_dotenv()

def main():
    driver = webdriver.Chrome('C:\\Users\\user\\chromedriver_win32\\chromedriver') # webdriverをインストールした場所
    login_url = os.getenv('LOGIN_URL')
    email = os.getenv('EMAIL')
    password = os.getenv('PASSWORD')

    driver.get(login_url)

    # 入力
    driver.find_element_by_id('email').send_keys(email)
    driver.find_element_by_id('password').send_keys(password)

    # ログイン
    driver.find_element_by_id('login_submit').click()
    driver.save_screenshot('test2_after_submit.png')

if __name__ == '__main__':
    main()

利用した関数

  • driver.find_element_by_id('email'):は、id がemailの要素を探して操作してくれます。ブラウザの検証ツール(「ページ内の要素を選択して検査」等)を使って、操作したい要素の特徴を地道に探します。id が振られていればそれが確実ですが、無い場合は class や要素名などを利用して指定します。
  • .send_keys(値)で値を入力できます。
  • .click()でクリックできます。

search element to operate

講師のスケジュールを取得してみる

講師の方のスケジュールを取得してみます。 活用するところまではできておらず、基本操作を習得したところが今回のゴールです。

こんな形で、各トレーナーさんのスケジュールを参照することができます。

将来的に、トレーナーさんの埋まり具合を簡単に見て、人気度などもぱっとわかるといいなと思いながら、今回は向こう一週間の〇の数(予約可能な数)を取得します。

Schedule of a trainer

完成系

最終的なソースコードは以下となります。 セレクタ名や ID、クラス名は今回のサイト限定のため念のため一部伏せています。

main.py
import os
from selenium import webdriver
from dotenv import load_dotenv
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from time import sleep

load_dotenv()


def main():
    driver = webdriver.Chrome('C:\\Users\\user\\chromedriver_win32\\chromedriver')
    login_url = os.getenv('LOGIN_URL')
    email = os.getenv('EMAIL')
    password = os.getenv('PASSWORD')
    teacher_url = os.getenv('TEACHER_URL')

    driver.get(login_url)
    driver.save_screenshot('test.png')

    # 入力
    driver.find_element_by_id('email').send_keys(email)
    driver.find_element_by_id('password').send_keys(password)

    # ログイン
    driver.find_element_by_id('login_submit').click()
    driver.get(teacher_url)
    driver.save_screenshot('test3_teacher.png')

    trainer_details = driver.find_elements_by_css_selector('a.trainer_xxxx')

    # 複数いるトレーナーのうち7番目の人の情報をクリック
    trainer_details[6].click()

    # 最大の読み込み時間を設定 今回は最大30秒待機できるようにする
    wait = WebDriverWait(driver=driver, timeout=30)
    # トレーナー詳細の上のタブが出るまで待機
    wait.until(EC.visibility_of_element_located((By.ID, 'xxxx')))
    driver.save_screenshot('trainer_details.png')
    # 「レッスン予約」をクリック
    driver.find_element_by_css_selector("#xxx > ul > li:nth-child(2)").click()
    # スケジュール表が表示されるまで待機
    wait.until(EC.visibility_of_element_located((By.ID, 'xxxxxxxx')))
    driver.save_screenshot('trainer_details2.png')

    # 朝の時間帯ボタンをクリック
    time_zones = driver.find_elements_by_css_selector('#xxxxxx td')
    number_of_available = []

    for time_zone in time_zones:
        time_zone.click()
        # スケジュール表が表示されるまで待機
        sleep(0.5)
        wait.until(EC.presence_of_element_located((By.ID, 'trainerScheduleSearchResult')))
        # id=trainerScheduleSearchResultの中の〇(i要素)の要素をすべて取得し配列に入れる
        a = driver.find_elements_by_css_selector('#trainerScheduleSearchResult i')

        number_of_available.append(len(a))

    print(number_of_available)
    driver.quit()


if __name__ == '__main__':
    main()

出力は以下のようになります。

[0, 0, 9]
# とあるトレーナーは、向こう一週間で朝の時間帯は0枠空きあり、昼の時間帯は0枠空きあり、夜の時間帯は9枠空きありという意味

最終的なソースコードはそのまま使えるものではないので、参考程度にとどめるとして、以下新しく覚えたものを解説します。

driver.find_elements_by_css_selector()

要素を特定する方法は複数あり、IDではなくCSSセレクタでも実施できます。

driver.find_elements_by_css_selector()は CSS のセレクタ表現を使って要素を特定できます。

今回使っているのは

  • driver.find_elements_by_css_selector('a.trainer_detail')
    <a>タグで class がtrainer_detailのもの。<a class="trainer_detail"></a>のように書かれるもの。
  • find_element_by_css_selector("#tabs > ul > li:nth-child(2)")
    一意に特定できるidが付いた要素から追っていった。id="tabs"の配下の<ul>配下の 2 個目の<li>要素を指定する。

実際の HTML は不要な点を省くとこのような形。

<div id="tabs">
  <div>xx</div>
  <ul>
    <li>
      <a href="xxx"><i class="fa fa-caret-down" aria-hidden="true"></i><span>プロフィール</span></a>
    </li>
    <li>      <a href="xxx"><i class="fa fa-caret-down" aria-hidden="true"></i><span>レッスン予約</span></a>    </li>  </ul>
</div>

また、find_elements(複数)関数はすべての要素を配列形式で取得、find_element(単数)では条件に合う 1 つめの要素を取得します。

参考資料

CSS のセレクタとは?覚えておきたい 25 種類と書き方

EC.visibility_of_element_located

画面の読み込みに時間がかかることがあるので、要素がちゃんと表示されるまで次の処理を待つ関数です。

以下のインポートが必要です。

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

waitインスタンスを作成して、あとは待ちの処理を入れたいところでwait.until()関数の中に条件を指定します。

# 最大の読み込み時間を設定 今回は最大30秒待機できるようにする
wait = WebDriverWait(driver=driver, timeout=30)
# トレーナー詳細の上のタブが出るまで待機
wait.until(EC.visibility_of_element_located((By.ID, 'tabs')))

ちなみに、以下の部分については、当初は2行目だけで良いと思ったのですが、実際はタブを切り替える操作の際にid='trainerScheduleSearchResult'の要素が使いまわされているために切り替え前の要素をもって「表示完了」と判断してしまっていました。

苦肉の策でsleepを追加しています。

# スケジュール表が表示されるまで待機
sleep(0.5)
wait.until(EC.presence_of_element_located((By.ID, 'trainerScheduleSearchResult')))

for ~ in ... :

for time_zone in time_zones:の部分では、for文を使っています。Webサイトの都合上、朝昼晩と3つのタブが用意されており、それぞれ同じように情報を取得する必要があったためです。

for time_zone in time_zones:
    time_zone.click()
    # スケジュール表が表示されるまで待機
    sleep(0.5)
    wait.until(EC.presence_of_element_located((By.ID, 'trainerScheduleSearchResult')))
    # id=trainerScheduleSearchResultの中の〇(i要素)の要素をすべて取得し配列に入れる
    a = driver.find_elements_by_css_selector('#trainerScheduleSearchResult i')

    number_of_available.append(len(a)) #配列の後ろに値を追加

おわりに

Seleniumを使ったスクレイピングアプリの基礎を作成することができました。

もう少しこなれてきたら役に立つものを作りたい。



個別連絡はこちらへ→Twitterお問い合わせ

プロフィール

プロフィールイメージ

はち子

事業会社のシステム部門で働きはじめて5年目の会社員。システム企画/要件定義/システムアーキテクチャ等。

Twitter→@bun_sugi

過去の記事について

はてなブログに掲載の記事(主にプログラミングメモ)についてはこちらに掲載しております。(本ブログに移行中)

タグ一覧

関連記事

Copyright© 2023, エンジニアを目指す日常ブログ

お問い合わせ|プライバシーポリシー