今回はstreamlit cloud(旧:streamlit sharing)でseleniumを使う方法を解説します
- 2023年12月30日コード修正しています(ブログに記載しているコードは修正後のコードです)
修正内容は以下のとおり・ChromeTypeのインストールするためのコード修正
実際に私がstreamlit cloudでアプリをデプロイした際に
いろんなエラーに遭遇して泣きそうになったので
エラーの解決方法をここにまとめておきます
この記事を読むとわかること・・
- streamlit cloudでseleniumを使う方法
- streamlit cloudでパッケージをインストールするときに気をつけること
- seleniumを使う際のポイント
この記事で解説しないこと・・・
- selenium・streamlitの仕組みやできることについて
- selenium・streamlitの基本的なコードの書き方
- streamlit cloudでアプリのdeploy(公開)の仕方
この記事では上の点については詳しく解説しませんので
知っていることを前提に解説をしていきます
この記事で紹介しているサンプルコードはgithub上に公開していますので
参考にされてください
エラー解決後のコード
遭遇した数々のエラーを解説する前に
最初に全てのエラーを解決した後のコードを
公開しておきます
ちなみにこのプログラムはstreamlit cloud上でseleniumを使って
適当なwebページ(当サイト)のタイトル画像を取得するプログラムです
実用的なアプリではなく、seleniumを使うための
最小限のコードとなっています
フォルダ構成
root
├── packages.txt
└── main_app
└── requirements.txt
└── app.py
- packages.txt:apt-get によりインストールするパッケージを記載
- requirements.txt:pipによりインストールするパッケージを記載
- app.py:streamlit cloud上にdeploy(公開)するファイル
packages.txt
chromium
packages.txtに記載したパッケージは
アプリを公開すると同時にインストールされます
今回はseleniumを使うためにwebブラウザ・webドライバーが必要なので
packages.txtでchromiumブラウザをインストールします
詳細についてはstreamlit公式ドキュメントに記載してあります
公式にはファイル名によってpipやconda、pipenv、poetryおよびapt-getなど
どのコマンドでパッケージをインストールするか変わるよーって書いてあります
簡単にまとめると・・・
requirements.txt
selenium
webdriver-manager
app.pyで使用するpythonパッケージを記載します
標準パッケージ一覧はこちらに記載してあります
webdriver-managerはseleniumで必要な
webドライバーをインストールしてくれるパッケージです
webドライバーとwebブラウザは
同じバージョンにしておく必要がありますが
webdriver-managerを使用することで
自動でwebブラウザと同じバージョンの
webドライバーをインストールしてくれます
requirements.txtとpackages.txtの超ざっくりとした使い分けは
- requirements.txt:pythonで使用されるパッケージをインストールする場合
- packages.txt:python以外でも使用されるソフトウェアをインストールする場合
app.py
# coding:utf-8
# 必要なパッケージのインポート
import streamlit as st
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome import service as fs
from selenium.webdriver import ChromeOptions
from webdriver_manager.core.os_manager import ChromeType
from selenium.webdriver.common.by import By
# タイトルを設定
st.title("seleniumテストアプリ")
# ボタンを作成(このボタンをアプリ上で押すと"if press_button:"より下の部分が実行される)
press_button = st.button("スクレイピング開始")
if press_button:
# スクレイピングするwebサイトのURL
URL = "https://ohenziblog.com"
# ドライバのオプション
options = ChromeOptions()
# option設定を追加(設定する理由はメモリの削減)
options.add_argument("--headless")
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
# webdriver_managerによりドライバーをインストール
# chromiumを使用したいのでchrome_type引数でchromiumを指定しておく
CHROMEDRIVER = ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install()
service = fs.Service(CHROMEDRIVER)
driver = webdriver.Chrome(
options=options,
service=service
)
# URLで指定したwebページを開く
driver.get(URL)
# webページ上のタイトル画像を取得
img = driver.find_element(By.TAG_NAME, 'img')
src = img.get_attribute('src')
# 取得した画像をカレントディレクトリに保存
with open(f"tmp_img.png", "wb") as f:
f.write(img.screenshot_as_png)
# 保存した画像をstreamlitアプリ上に表示
st.image("tmp_img.png")
# webページを閉じる
driver.close()
# スクレピン完了したことをstreamlitアプリ上に表示する
st.write("スクレイピング完了!!!")
今回のstreamlit cloud上に公開するプログラムです
内容としては・・・
『スクレイピング開始』ボタンを押すと
seleniumが起動して当サイトトップページを開き
タイトル画像を取得・表示するだけのシンプルなwebアプリです
streamlit cloudで実行するとこんな感じになります
ファイル構成とファイルの内容については以上になります
プログラムの中身の解説は下の方で説明します
実際に遭遇したエラーについて
次に実際に遭遇したエラーと
その解決方法を説明します
参考にしたページもあわせて載せておきます
生じたエラーは全部でこんな感じ・・・
- webブラウザがstreamlit cloud上にない
→(解決策)packages.txtをrootディレクトリ直下に配置することで解決
- packages.txtではchrome・firefoxはインストールできない
→(解決策)chromeはあきらめてchromiumに変更して解決
- メモリが上限だからアプリ動かない
→(解決策)メモリファイルを変更する設定をドライバーのオプションに追加して解決
- webブラウザとwebドライバーのバージョンが違う
→(解決策)ChromeDriverManagerのchrome_type引数にChromeType.CHROMIUMを追加して解決
1つずつ解決方法を説明していきます
エラーについて解説する前に
最初にデプロイした初期コードを載せておきます
最終コードと違う箇所に印をつけています
フォルダ構成
root
└── main_app
└── packages.txt # main_appの下に配置していた
└── requirements.txt
└── app.py
packages.txt
chrome # 初期はchromeブラウザを使用したかった
requirements.txt
selenium
webdriver-manager
app.py
# coding:utf-8
import streamlit as st
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome import service as fs
from selenium.webdriver import ChromeOptions
# タイトルを設定
st.title("seleniumテストアプリ")
# スクレイピングするwebサイトのURL
URL = "https://ohenziblog.com"
# ドライバのオプション
options = ChromeOptions()
# option設定を追加
options.add_argument("--headless") # ブラウザを画面に表示せずに起動できる(streamlit cloudでは、この設定は必須)
# webdriver_managerによりドライバーをインストール
CHROMEDRIVER = ChromeDriverManager().install()
service = fs.Service(CHROMEDRIVER)
driver = webdriver.Chrome(
options=options,
service=service
)
# URLで指定したwebページを開く
driver.get(URL)
# webページを閉じる
driver.close()
# スクレピン完了したことをstreamlitアプリ上に表示する
st.write("selenium終了")
最初は、画像を取得・表示!なんてことはせず
ただ、seleniumを起動・終了するだけのコードにしてました
理由としてはseleniumがstreamlit cloud上で使えることが
確認できればいい!と思っていたので
こんなクソみたいなコードになっています笑
【エラーその1】webブラウザがインストールされていない
上記の初期コードでデプロイしたときに
次のようなエラーが発生しました
Traceback (most recent call last):
File "/home/appuser/venv/lib/python3.9/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 563, in _run_script
exec(code, module.__dict__)
File "/app/root/main_app/blog_app.py", line 27, in <module>
driver = webdriver.Chrome(
File "/home/appuser/venv/lib/python3.9/site-packages/selenium/webdriver/chrome/webdriver.py", line 69, in __init__
super().__init__(DesiredCapabilities.CHROME['browserName'], "goog",
File "/home/appuser/venv/lib/python3.9/site-packages/selenium/webdriver/chromium/webdriver.py", line 89, in __init__
self.service.start()
File "/home/appuser/venv/lib/python3.9/site-packages/selenium/webdriver/common/service.py", line 98, in start
self.assert_process_still_running()
File "/home/appuser/venv/lib/python3.9/site-packages/selenium/webdriver/common/service.py", line 110, in assert_process_still_running
raise WebDriverException(
selenium.common.exceptions.WebDriverException: Message: Service /home/appuser/.wdm/drivers/chromedriver/linux64/107.0.5304/chromedriver unexpectedly exited. Status code was: 127
上記のエラーについていろいろ調べてみると
chromedriver周りでエラーが出たときに上がるエラーコードらしくて
考えられる直接的な原因がなかなかわかりませんでした
ここでseleniumはwebドライバーとwebブラウザの両方がないと
起動することができないので、そのどちらかがうまくインストールできてない
可能性があるかなと考えてstreamlit cloud上のログを確認してみると
webドライバーは以下のログがあったのでインストールできてそうな感じでした
2022-10-28 14:53:07.427 ====== WebDriver manager ======
2022-10-28 14:53:07.448 Get LATEST chromedriver version for google-chrome None
2022-10-28 14:53:07.502 There is no [linux64] chromedriver for browser None in cache
2022-10-28 14:53:07.502 About to download new driver from https://chromedriver.storage.googleapis.com/107.0.5304.62/chromedriver_linux64.zip
[WDM] - Downloading: 100%|██████████| 7.26M/7.26M [00:00<00:00, 194MB/s]-28 14:53:07.572878]
2022-10-28 14:53:07.728 Driver has been saved in cache [/home/appuser/.wdm/drivers/chromedriver/linux64/107.0.5304]
ちなみにstreamlit coludのログはwebページ上の
画像下の赤枠の部分をクリックすると表示されます
webドライバーはインストールできているので
webブラウザがうまくインストールできていないのかなーと考え
streamlitの公式ドキュメントを確認してみると
以下の記載がありました
packages.txt
がリポジトリのルートディレクトリに存在する場合、自動的に検出、解析し、リストされたパッケージをインストールします。公式ドキュメントはこちら
要はpackages.txtはrootディレクトリの直下に配置しろよーーってことです
私はrootディレクトリの直下ではなく
main_appディレクトリ配下にpackages.txtを
配置していたのでchromeブラウザを
インストールできていませんでした
【エラーその2】chrome・firefoxはパッケージリポジトリにない
packages.txtをrootディレクトリ直下に配置して
streamlit cloudログを見てみると・・・
Apt dependencies were installed from /app/streamlit_temple/packages.txt using apt-get.
どうやらpackages.txtをみつけたから
書かれているパッケージをapt-getで
インストールするよーって書かれています
やったー!と思った次の瞬間・・・
こんなエラーがでました泣
E: Unable to locate package chrome
chromeってパッケージみつからないよーってことらしいです
どういうことやねん!と思いながら、調べてみると
githubでstreamlit cloudでseleniumが使えるサンプルコードを
公開している海外の方がおられまして(githubサイトはこちら)
そのページを見てみるとpackages.txtに書かれたパッケージは
Debian Buster apt package repositoriesという
リポジトリからインストールされるらしく
そのデフォルトリポジトリに
chromeは存在していないらしいです
デフォルトリポジトリにあるのは
chromiumだよーってことも書いてありました
てことで、chromeはあきらめてchromiumブラウザを
使うことにしました
ちなみにfirefoxもchrome同様インストールできませんでした
デフォルトリポジトリに存在するためインストールできます
packages.txtを以下のとおり修正
chromium
【エラーその3】メモリ不足によりseleniumが強制終了
packages.txtを修正して更新してみると
次はこんなエラーが発生しました
selenium.common.exceptions.WebDriverException: Message: unknown error: session deleted because of page crash
どうやらページがクラッシュしたらしいです
なんや!クラッシュってなんや!勘弁してくれ!
と思いながら、折れそうな心にムチ打って調べてみると
メモリが不足していることによって
ページがクラッシュしていることがわかりました
webドライバーにはオプションで様々な設定を
追加することができますが
『–disable-dev-shm-usage』オプションがあったので追加してみました
chromiumの場合『–disable-dev-shm-usage』オプションを追加すると
メモリファイルが『/dev/shm』→『/tmp』ディレクトリに変更され
メモリ不足によるクラッシュを防ぐことができるそうです
『/dev/shm』は容量が64MBしかないためすぐクラッシュするらしいです
結論を言うと『–disable-dev-shm-usage』オプションだけで
メモリ不足のエラーは解消できましたが
そのほかにも必要そうな『–disable-gpu』『–no-sandbox』オプションを追加しました
以下のとおりオプションを追加しました
# ドライバのオプション
options = ChromeOptions()
# option設定を追加
options.add_argument("--headless")
# option設定を追加(設定する理由はメモリの削減)
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
オプションの簡単は説明は以下のとおり
- –headless:ブラウザを非表示で起動する(streamlit cloudではこのオプションは必須)
- –disable-gpu:GPUハードウェアアクセラレーションを無効にする→『–headless』とセットで必要
- –no-sandbox:sandbox(仮想環境によるセキュリティ向上)モードを解除→権限不足によるエラー防止
- –disable-dev-shm-usage:メモリファイルを『/dev/shm』(64MB)→『/tmp』ディレクトリに変更しメモリ不足回避
【エラーその4】ブラウザとドライバーのバージョンが違う
上のとおり修正をして今度こそデプロイしてみたところ・・・
またエラーがでました泣
2022-10-25 13:14:39.062 ====== WebDriver manager ======
2022-10-25 13:14:39.098 Get LATEST chromedriver version for google-chrome None
2022-10-25 13:14:39.166 Driver [/home/appuser/.wdm/drivers/chromedriver/linux64/107.0.5304/chromedriver] found in cache
2022-10-25 13:14:39.799 Uncaught app exception
--(略)--
selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 107
Current browser version is 106.0.5249.119 with binary path /usr/bin/chromium
どうやらドライバーとブラウザのバージョンが違うらしい
今回の場合・・・
- webドライバー version:107.0.5304
- webブラウザ version:106.0.5249
バージョンが一致すればエラーは解消しそうだけど
そもそもドライバーはwebdriver_managerでインストールしているので
自動でブラウザど同じバージョンのドライバーを
インストールしてくれるはずなのにー、おかしいなーという
壁にぶち当たりました
いろいろ考えたところ・・・
現在のドライバーを起動するコードはこの3行
# webdriver_managerによりドライバーをインストール
CHROMEDRIVER = ChromeDriverManager().install()
service = fs.Service(CHROMEDRIVER)
driver = webdriver.Chrome(options=options, service=service)
現在packages.txtによりインストールしているのは
chromiumブラウザです
chromeブラウザを使用するときも
同じコードを使用しているため
chromiumを使用する場合は、引数とかに何かしらを
記載する必要があるかなーと思って公式ドキュメント読んでみると
ChromeDriverManager()の引数にchrome_typeを
指定する必要がありました
てことで以下のようにドライバー部分を修正
# CromeTypeクラスを新たにインポート
from webdriver_manager.core.os_manager import ChromeType
# webdriver_managerによりドライバーをインストール
CHROMEDRIVER = ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install()
service = fs.Service(CHROMEDRIVER)
driver = webdriver.Chrome(options=options, service=service)
これで四度目の正直のデプロイをしたところ
ついに・・・
seleniumが正常に起動しました
めでたしめでたし
これでstreamlit cloud上でseleniumが起動することが確認できたので
スクレイピングでwebページのタイトル画像を取得・表示と
スクレイピング開始ボタンを追加して最終版のコードが完成
# coding:utf-8
# 必要なパッケージのインポート
import streamlit as st
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome import service as fs
from selenium.webdriver import ChromeOptions
from webdriver_manager.core.os_manager import ChromeType
from selenium.webdriver.common.by import By
# タイトルを設定
st.title("seleniumテストアプリ")
# ボタンを作成(このボタンをアプリ上で押すと"if press_button:"より下の部分が実行される)
press_button = st.button("スクレイピング開始")
if press_button:
# スクレイピングするwebサイトのURL
URL = "https://ohenziblog.com"
# ドライバのオプション
options = ChromeOptions()
# option設定を追加(設定する理由はメモリの削減)
options.add_argument("--headless")
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--remote-debugging-port=9222')
# webdriver_managerによりドライバーをインストール
CHROMEDRIVER = ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install() # chromiumを使用したいので引数でchromiumを指定しておく
service = fs.Service(CHROMEDRIVER)
driver = webdriver.Chrome(
options=options,
service=service
)
# URLで指定したwebページを開く
driver.get(URL)
# webページ上のタイトル画像を取得
img = driver.find_element(By.TAG_NAME, 'img')
src = img.get_attribute('src')
# 取得した画像をカレントディレクトリに保存
with open(f"tmp_img.png", "wb") as f:
f.write(img.screenshot_as_png)
# 保存した画像をstreamlitアプリ上に表示
st.image("tmp_img.png")
# webページを閉じる
driver.close()
# スクレピン完了したことをstreamlitアプリ上に表示する
st.write("スクレイピング完了!!!")
デプロイして動作確認をしたことろ
ちゃんと取得した画像が表示されました
今回のデバック作業で公式ドキュメントを読むことの大切さがわかりました笑
ちなみに今後はstreamli cloudを使って
競馬予想アプリを公開する予定ですので
そちらもまた完成次第公開しますので
よろしければぜひ参考にしてみてください
まとめ
streamlit cloudでseleniumを使用する時のポイント
- packages.txtはrootディレクトリ直下に配置する
- packages.txtではchrome・firefoxブラウザはインストールできない
- ドライバにオプションを設定してメモリ不足を避ける
- chromiumブラウザを使用する場合は
ChromeDriverManagerの引数にChromeType.CHROMIUMを追加する