seleniumによるHTMLの解析速度を10倍速くした方法

seleniumによるHTML解析を10倍速くした方法 Pythonの使い方

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にアクセスする度に時間が取られます。そのため、アクセスを回避すれば解析時間が短く済みます。アクセス頻度を下げる方法が他にあれば良いのですが実査にはないことが多いです。

【Python】Seleniumが遅い原因と対処法【知らないとヤバい】
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化しておきます。そして、次に同じページのデータを解析するときに、メモリ上においたものと比較して、返します。

コメント

タイトルとURLをコピーしました