はじめに
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を利用しています。
環境構築の仕方はこちらの記事で説明しています。
Project 作成
PyCharm で「File」→「New Project」から新しくプロジェクトを作成します。
私は以下の場所に作成しました。:
C:\Users\user\workspace\PycharmProjects\bizmates_search
とりあえずshift + f10
を押すと、main.py
を実行してくれます。「Hi, Pycharm」と文字が表示されたので OK です。
Selenium モジュールのインストール
Pycharm の機能でSelenium モジュールをインストールします。
PyCharm で「File」→「Settings」→「Project」→「Python Interpreter」→「+」→「selenium」
WebDriver のインストール
ブラウザを自動で動かすために WebDriver(ChromeDriver)のインストールも必要になるようです。
以下のサイトからインストールします。
https://sites.google.com/chromium.org/driver/
→Getting started with ChromeDriver on Desktop (Windows, Mac, Linux)
念のため自分の 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
に以下を記述します。
*.png
Selenium を使ったコードの基本形
以下が一連の流れの基本形となります。
webdriver
のインポートdriver
の作成driver
インスタンスの持つ関数を使って、ページの内容を取得したりスクリーンショットを取ったりといった操作
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
ファイルを作成
試しに以下の形で作成します。
LOGIN_URL=https://{行きたいサイトURL}/login/
EMAIL=xxx@yyyyy.com
PASS=abcdefg
プログラム側から呼び出し
.env
で設定した変数は以下のように呼び出します。
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
に含まれるはずなので、確認だけおこないました。
.env
モジュールインストール
python-dotenv
をインストールする。
Selenium でログインしてみる
ここまでわかると、Web サイトのログインまでは簡単にできます。
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()
でクリックできます。
講師のスケジュールを取得してみる
講師の方のスケジュールを取得してみます。 活用するところまではできておらず、基本操作を習得したところが今回のゴールです。
こんな形で、各トレーナーさんのスケジュールを参照することができます。
将来的に、トレーナーさんの埋まり具合を簡単に見て、人気度などもぱっとわかるといいなと思いながら、今回は向こう一週間の〇の数(予約可能な数)を取得します。
完成系
最終的なソースコードは以下となります。 セレクタ名や ID、クラス名は今回のサイト限定のため念のため一部伏せています。
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 つめの要素を取得します。
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を使ったスクレイピングアプリの基礎を作成することができました。
もう少しこなれてきたら役に立つものを作りたい。