Seleniumでスクレイピングすると、サーバからHTML・CSS等をダウンロードする時間よりも、HTMLを解析する時間の方が長くなる傾向がありました。HTML解析をなんの工夫しないで実装すると、絶望的に長くなります。
でも、どれだけ長くなるかは明確になってなかったので、また同じように計測して数値化しました。何も考えずに行った実装が、どれだけ爆発的に時間を費やすかわかることでしょう、
ただ遅いのを確認しただけだと、ブルーな気分になって終わってしまうので、私の回避方法の1つをお伝えします。もちろん、ここで以前に書いた方法以外のやり方です。回避方法を取り入れる事で、解析時間が1/10になることが期待できます。
SleniumがHTML解析する時間は、WEBから取得完了するまでの時間の2倍以上掛かることもある
控えめに2倍と書きましたが、下の計測結果は14倍以上になりました。
2~14倍以上となった下手な方法を再現し、ウェブページのHTMLのダウンロード時間と、それを解析する時間を比較します。解析といっても、divタグの総数とdivの下のpタグの総数のカウントです。
get()が完了する時間と解析が完了する時間をそれぞれ30回計測し、単純平均を比較しました。
計測対象としたウェブページ
1ページのタグの数が多いものと、少ないもので計測しました。
解析対象としたサイト | divタグの総数 | pタグの総数 |
---|---|---|
PC Watch | 627 | 3040 |
ソニー銀行 | 142 | 38 |
※PC Watchは広告が入るため、タグの総数が一定ではありません。
計測したPythonのソースコード
下が私が別のプラットフォームでやらかしていた間抜けなロジックの再現です。divタグを全部見つけて、そこに必要なデータがないか探し続けるものです。
実際のものは、下のように多重ループを回して、必要なものを探し続けていました。
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
def selenium_speed():
url = "https://pc.watch.impress.co.jp/"
driver = webdriver.Chrome()
wait_a = WebDriverWait(driver, 10)
driver.get(url)
time.sleep(3)
div_count = 0 # divタグの総数
p_count = 0 # pタグの総数
dt_a = [] # get()完了までの時間
dt_b = [] # HTML解析完了までの時間
for count in range(30):
# get()完了までの時間
crt_counter = time.perf_counter()
driver.get(url)
wait_a.until(EC.visibility_of_all_elements_located)
dt_a.append(time.perf_counter() - crt_counter)
# print(driver.page_source)
# HTML解析完了までの時間
crt_counter = time.perf_counter()
div_elements = driver.find_elements_by_css_selector("div")
div_count = len(div_elements)
p_count = 0
for div_elem in div_elements:
for p_elem in div_elem.find_elements_by_css_selector("p"):
p_count += 1
dt_b.append(time.perf_counter() - crt_counter)
計測結果 ダウロードにかかった時間と解析にかかった時間の比較
PC Watch
項目 | 平均時間(sec) | 標準偏差(sec) |
---|---|---|
get()完了時間 | 0.8443 | 0.6236 |
解析時間 | 12.0381 | 0.5214 |
<.div>
タグの数が多いため、解析時間が12秒オーバーとなりました。もはやフリーズを疑うレベルの長さです。
ソニー銀行
項目 | 平均時間(sec) | 標準偏差(sec) |
---|---|---|
get()完了時間 | 1.0928 | 0.1915 |
解析時間 | 2.6473 | 0.4193 |
pタグが38と少ないため、解析時間が短くて済みました。
seleniumによる解析を速くする究極手段
既に書いたように、seleniumにアクセスする度に時間が取られます。そのため、アクセスを回避すれば解析時間が短く済みます。アクセス頻度を下げる方法が他にあれば良いのですが実査にはないことが多いです。
しかし、以前に解析したページが再度解析する場合に有効な方法があります。更新されてないか確認し、されていなかったら以前のデータを返すものです。
下のサンプルは、アマゾンの商品価格をスクレイピングしています。
金額が入っているtable.a-lineitemのtextが以前に拾ったのと同じテキストだったら、以前に取得したデータを返す関数を定義して、seleniumのアクセス数を減らしています。textの変化によって、データ解析するかを判断しています。
僅か3行2列のtableデータですが、これをするだけで、解析時間が1/10に減りました。
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
def selenium_speed_2():
url = 'https://www.amazon.co.jp/Microsoft365-Personal-%E3%82%AA%E3%83%B3%E3%83%A9%E3%82%A4%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%89%E7%89%88-%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E5%8F%B0%E6%95%B0%E7%84%A1%E5%88%B6%E9%99%90-%E5%90%8C%E6%99%82%E4%BD%BF%E7%94%A8%E5%8F%AF%E8%83%BD%E5%8F%B0%E6%95%B05%E5%8F%B0/dp/B00O2TXF8O/ref=zg_bs_software_1?_encoding=UTF8&psc=1&refRID=1RKTZD23NDNVRFTYHA1M'
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)
driver.get(url)
time.sleep(3)
dt_a = []
dt_b = []
for count in range(31):
driver.get(url)
wait.until(EC.visibility_of_all_elements_located)
table_element = driver.find_element_by_css_selector("table.a-lineitem")
crt_counter = time.perf_counter()
res_dict_a = get_dict(table_element, True)
if count >= 1:
dt_a.append(time.perf_counter() - crt_counter)
crt_counter = time.perf_counter()
res_dict_b = get_dict(table_element, False)
if count >= 1:
dt_b.append(time.perf_counter() - crt_counter)
static_table_text: str = ""
static_table_dict: str = ""
def get_dict(table_element, do_calc) -> str:
global static_table_text
global static_table_dict
res_dict = {}
if not do_calc and (static_table_text == table_element.text):
return static_table_dict
else:
for tr_elem in table_element.find_elements_by_css_selector("tr"):
tds = tr_elem.find_elements_by_css_selector("td")
res_dict[tds[0].text] = tds[1].text
static_table_text = table_element.text
static_table_dict = res_dict
return static_table_dict
アマゾンの金額取得に掛かった時間
項目 | 平均時間(sec) | 標準偏差(sec) |
---|---|---|
WEBページを解析 | 0.1443 | 0.0051 |
過去のデータを取得 | 0.0132 | 0.0023 |
上の方法だと、一度走らせて、その後にまた同じページを解析するときにのみ有効です。
現実的な方法は、解析した結果を収めたインスタンスをpickleを使ってfile化しておきます。そして、次に同じページのデータを解析するときに、メモリ上においたものと比較して、返します。
コメント