1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Selenium】Lambda環境下において稀にWebDriverがハングする事象について

Last updated at Posted at 2025-06-20

はじめに

皆様、Seleniumでスクレイピング生活してますでしょうか?
Webサービスを持っている会社さんであれば、自社のサイトの正常性を定期的に確認するべく、
EventBridge + Lambdaで定期的に監視することもあるでしょう。
※CloudwatchSyntheticsを使え という話は一旦置いときましょう。笑

私は、業務上LambdaでSelenium定期実行することが多いのですが、
掲題の通り稀にWebDriverがハングする事象を発見しました。
今回はそれに対する考察と対策について記載したいと思います。

※本記事の文章には生成AIを一切使用しておらず、すべて自分の言葉で表現しています

構成図

Dockerコンテナ型Lambdaでデプロイします。
今回は事象の再現が難しいので、インフラを構成するためのコードは載せません。
lambda_selenium.drawio.png

事象

言語:Python3.12
OS:AmazonLinux2
Lambdaスペック:メモリ2GB
コード:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

# オプション設定
options = Options()
options.add_argument("--headless")
options.add_argument("--disable-gpu")
options.add_argument("--no-sandbox")
options.add_argument("--single-process")  # これがないとLambdaで動かない

service = Service(executable_path="/opt/chrome/chromedriver")

# WebDriverの起動
driver = webdriver.Chrome(service=service, options=options)

# タイムアウト設定(秒)
driver.set_page_load_timeout(10)
driver.set_script_timeout(10)

try:
    url = "https://hotel-example-site.takeyaqa.dev/ja/"
    driver.get(url)
except Exception as e:
    pass
finally:
    driver.quit()

この条件下の時に、たまーに(100回に1回くらい)driver.get(url)のところでハングします。
Lambdaの最大実行時間は15分ですが、15分いっぱいまでハングします。

set_page_load_timeoutset_script_timeoutでタイムアウトを指定していますが、まったく意味を成しません。

体感ですが、傾向としては重たいページで頻度が多いイメージです。

考察

ローカルやEC2上で動かしているときに遭遇したことはないので、
おそらく--single-processが原因だと思います。
シングルプロセスで動かしているので、Webリソースの取得で通信不可になった際に割り込みでタイムアウト処理(=切断処理)が入らないと予想しています。
※当てずっぽうです。WebDriver詳しい方居れば教えてください。

対策

結論としては、timeout_decoratorを使いましょう。
pip install timeout-decoratorでインストールしたうえで、以下のように実装します。

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

import timeout_decorator
import os
import signal
import subprocess


# オプション設定
options = Options()
options.add_argument("--headless")
options.add_argument("--disable-gpu")
options.add_argument("--no-sandbox")
options.add_argument("--single-process")  # これがないとLambdaで動かない

service = Service(executable_path="/opt/chrome/chromedriver")

# WebDriverの起動
driver = webdriver.Chrome(service=service, options=options)

# タイムアウト設定(秒)
driver.set_page_load_timeout(10)
driver.set_script_timeout(10)

@timeout_decorator.timeout(10)
def access():
    url = "https://hotel-example-site.takeyaqa.dev/ja/"
    driver.get(url)
    driver.quit()

try:
    access()
except Exception as e:
    # Chromeプロセスが残存している可能性もあるので、killする
    result = subprocess.run(['pgrep', '-f', 'chrome'], stdout=subprocess.PIPE, text=True)
    pids = result.stdout.strip().split('\n')
    for pid in pids:
        if pid.isdigit():
            os.kill(int(pid), signal.SIGKILL)
  • @timeout_decorator.timeout(10)を付与したメソッドにラップすることで、ハングしてもtimeout_decoratorが割り込みしてException発生させてくれます。
  • driver.get(url)でハングした場合は、driver.quit()まで行かないので、Chromeのプロセスがゾンビで生き残ってしまいます。
    そのため、except Exception as e:の後にkillする仕組みを導入しています。
  • 余談ですが、Lambdaは内部的に同じインスタンスが使い回されることがあります。(サーバレスとは言っても中身はEC2インスタンス(と思われます)。)
    前回実行時に/tmp配下に保存したファイルや、プロセスが残存している可能性があるので、後処理を必ず実装することをお勧めします。

(余談)
ECS Fargateを使用するという手もあります。
ただしLambdaよりは高額になるのと、タスクとして定義するので起動レイテンシが若干ネックです。

以上

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?