Cloud Functions で headless chromium を使っている際にメモリエラーが発生したときの調査メモです。
結論
Chrome driver を一旦止めてから再開することで回避できます。
def get_driver():
...
return return webdriver.Chrome(service=service, options=options)
driver.quit()
driver = get_driver()
現象
driver.get(url)
を実行する回数が多い時にメモリエラーが発生する。
具体的には次のようなログが出力される。
'Memory limit of 2048 MiB exceeded with 2060 MiB used. Consider increasing the memory limit, see https://cloud.google.com/functions/docs/configuring/memory'
やったこと
どこでメモリを消費しているのか調査しました。
memory-profiler を使った調査
詳細は割愛しますが、実行している関数のどの部分でメモリを消費しているか行単位で確認できます。
確認した結果、python プログラムはたかだか 80MB しか使っていなかったため、Chrome でメモリを消費している可能性が高いと判断しました。
driver の停止と再開
何回か driver.get()
を実行したら deiver.quit()
を実行します。
for i, url in enumerate(urls, 1):
driver.get(url)
if i % 10 == 0:
driver.quit()
driver = get_driver()
これでエラーが出なくなりました。
実用的な問題
読み込むページ数が多いサイトを扱うごとに上記のような driver.quit() を挟むような実装をするのは効率が悪いため避けたいです。
自作の get_deriver 関数は様々なスクレイピング対象のサイト用のスクリプトから呼び出しているため、WebDriver のラッパーを作って get_driver を改修しました。
def get_driver():
return WebDriverWrapper()
class WebDriverWrapper:
def __init__(self):
self.driver = self._create_driver()
self.load_count = 0
self.max_loads_before_quit = 10 # 任意の回数に設定
def _create_driver(self) -> webdriver.Chrome:
"""新しい webdriver.Chrome インスタンスを作成する"""
options = webdriver.ChromeOptions()
# 略
return webdriver.Chrome(service=service, options=options)
def _restart_driver(self):
"""ドライバーを再起動する"""
self.driver.quit()
self.driver = self._create_driver()
self.load_count = 0
def get(self, url):
"""get メソッドのラッパー"""
if self.load_count >= self.max_loads_before_quit:
self._restart_driver()
self.driver.get(url)
self.load_count += 1
def __getattr__(self, name):
"""get メソッド以外はオリジナルのドライバメソッドを返す"""
return getattr(self.driver, name)
def quit(self):
"""明示的にドライバーを終了する"""
if self.driver:
self.driver.quit()
self.driver = None
これで get メソッドを10回使うたびにドライバーを再起動することができます。