はじめに
どうも。最近Selenium 4.1.3(Python 3.9.7)をよく使っていたので、そのときに気づいたことなどをまとめました。
注意事項
スクレイピングしている方ならご存知だと思いますが、念のため置いておきます。
岡崎市立中央図書館事件
ドライバーはwebdriver_managerを使用する
バージョンが変わるごとにドライバーを入れ替えるのは大変ですよね。
そこで役立つのが、このwebdriver_managerです。
pip install webdriver_manager
Chromeのドライバーを入れたい場合、このように記述します。
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(ChromeDriverManager().install(), options=options)
非常に便利です。
class化したほうが使い勝手がよい
classにしたほうが圧倒的に使いやすい。オブジェクト指向に基づいて考えると当たり前ですが。
以下が私がよく使用しているclassです。
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.common.exceptions import *
import time
import os
from datetime import datetime
from logging import getLogger, Formatter, FileHandler, StreamHandler, INFO, DEBUG
class Driver_Format():
"""
Driver Format
"""
def __init__(self, headless:bool=False, class_name:str=__name__) -> None:
if not os.path.exists('log'):
os.makedirs('log')
self.logger = getLogger(class_name)
self.logger.setLevel(INFO)
self.file_name = './log/log_' + datetime.now().strftime('%Y%m%d_%H%M%S')
self.fh = FileHandler(self.file_name+'.log')
self.fh.setLevel(INFO)
fh_formatter = Formatter('%(asctime)s - %(levelname)s - %(name)s - %(funcName)s - %(message)s')
self.fh.setFormatter(fh_formatter)
self.ch = StreamHandler()
self.ch.setLevel(INFO)
ch_formatter = Formatter('%(asctime)s - %(levelname)s - %(name)s - %(funcName)s - %(message)s')
self.ch.setFormatter(ch_formatter)
self.logger.addHandler(self.fh)
self.logger.addHandler(self.ch)
self.logger.info('Initialization Logger')
self.Initialization_Driver(headless)
def Initialization_Driver(self, headless:bool=False):
options = Options()
options.add_argument("--window-size=1920,1080")
if headless:
options.add_argument('--headless')
self.driver = webdriver.Chrome(ChromeDriverManager().install(), options=options, service_log_path=self.file_name+'_driver.log')
self.driver.implicitly_wait(10)
def close_driver(self, removeHandler_tf = True):
if removeHandler_tf:
self.logger.removeHandler(self.fh)
self.logger.removeHandler(self.ch)
self.driver.quit()
def wait_and_get_element(self, tag, path:str):
"""
Wait and return element from any tags
"""
for _ in range(10):
try:
WebDriverWait(self.driver, 10).until(
EC.visibility_of_element_located((tag, path))
)
return self.driver.find_element(tag, path)
except:
self.driver.refresh()
else:
st = 'Time OUT : ' + path
raise Exception(st)
def wait_and_click(self, tag, path:str):
for _ in range(10):
try:
WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((tag, path))
)
element = self.wait_and_get_element(tag, path)
ActionChains(self.driver).move_to_element(element).perform()
element.click()
break
except ElementClickInterceptedException:
time.sleep(1)
except StaleElementReferenceException:
time.sleep(1)
except TimeoutException:
time.sleep(1)
else:
st = 'Time OUT : ' + path
raise Exception(st)
スクレイピングをしていると、やはりログが必要であることが大半(個人的感想)なのでこのようにログを混ぜて定義すると、非常に使いやすいです。
要素の選択にはWebDriverWaitを使うべき
スクレイピングが速すぎると要素を取得できず、よくエラーが発生します。time.sleep()を使用してやっていたとしても、ページのロードに時間が掛かると結局はエラーが発生します。そこで、このWebDriverWaitを使用することでわざわざtime.sleep()を使用しなくても待ってくれます。
以下のように使用します。
# 要素が見つかるまで待つ
WebDriverWait(self.driver, 待機時間).until(EC.visibility_of_element_located((tag, path)))
# クリックできるようになるまで待つ
WebDriverWait(self.driver, 待機時間).until(EC.element_to_be_clickable((tag, path)))
# フレームが使用可能になるまで待機し、移動する
WebDriverWait(self.driver, 待機時間).until(EC.frame_to_be_available_and_switch_to_it((tag, path)))
他にもありますが、私が最近使用したのはこの3つです。これで大体のことは可能かと思われます。
フレーム移動に関する注意事項ですが、私がスクレイピングしていたページの問題かもしれませんが、EC.frame_to_be_available_and_switch_to_it
を使用した後、すぐにframe内の要素を取得しようとする(WebDriverWaitを使用)とselenium.common.exceptions.NoSuchWindowException
というエラーが発生しました。結局原因は分からず、time.sleep(1)
を使用することでなんとか解消しました。
frame内にいるときにrefreshを行うともとに戻る
ただの豆知識程度ですが、iframe内などにいるときにdriver.refresh()
を行うと一番外側のframeに戻されます。
Google Chromeのユーザーデータ(履歴、ブックマーク、パスワード等)を使用する方法
optionsでユーザーデータのフォルダやProfileを指定することで、ログイン情報などを引き継いで使用することが出来ます。
次のように設定します。
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('--user-data-dir=E:\python\Google_Chrome\locals') # Profileが格納されているフォルダの指定
options.add_argument('--profile-directory=Profile1') # Profileの指定
注意事項として、--user-data-dir=
はフルパスで指定する必要があります。(相対パスではエラーが発生する)
Windowsでしか確認が取れていませんが、--user-data-dir=
や--profile-directory=
で指定したフォルダが存在しない場合、自動でフォルダが作成されます。
自分が使用しているユーザーデータを使用することも可能ですが、同時に使用するとエラーが発生するためそこは注意しましょう。
最後に
最後まで読んでいただきありがとうございます。
何かご指摘や質問などありましたら、コメントをよろしくお願いいたします。
それでは失礼いたします。