【① Selenium編】当日の天気を知らせてくれるSlack Botを作ってみた(Python + Selenium + Slack + AWS ECS + Github Actions)
毎日、当日の天気を能動的に調べるのは面倒なので、天気を知らせてくれるSlack Botを作ってみました。
処理の流れは以下のようになっています。
天気予報のWebページを取得 --> 当日の天気予報のスクリーンショットを撮る --> Slackへアップロード
天気予報のAPIから情報を取得・整形してSlackへ通知するようなBotでもよかったのですが、いい感じのAPIがなかったのと、リッチな通知にしたかったのでこのようになりました。
実装からAWS ECSへのデプロイ・スケジューリングまでを技術ごとに分けて、どのように作成したかを説明していこうと思います。今回は【Selenium編】です。
構成
- ① Selenium編 <-- いまここ
- ② Slack編
- ③ Github Actions編
- ④ AWS ECS編
Seleniumとは?
Seleniumとは様々な言語でWebブラウザの操作を簡単に自動化することができるツール・ライブラリです。スクリーンショットを撮ったり、フォームを送信したり、様々なことを自動化することができます。ただ、それなりに複雑な処理を自動化しようと思うとJavaScriptの知識が必要になります。
現在公式でサポートされている言語はC#Java・Python・JavaScript・Rubyらしいです。今回の開発ではPythonを使用します。
実装
環境
- Python 3.7
- Pipenv
- selenium 3.141.0
- chrome
- chrome driver
- Docker
- docker-compose
ライブラリのインストール
仮想環境をカレントディレクトリで管理するためにPIPENV_VENV_IN_PROJECT
の環境変数を設定しておくのをお勧めします。
export PIPENV_VENV_IN_PROJECT=true
pipenv install selenium
ソースコード
Chromeのオプション設定
Chromeをそのまま起動するのはハードウェアリソースを喰いますし、Pythonから操作する訳なのでヘッドレスモードで起動させます。MacOSやLinux上で起動させるのであれば問題ないのですが、Docker上で起動させる際はChromeの機能の一部を無効にしないと正常起動しませんので注意が必要です。設定するオプションはMacOSであればopen -n -a "Google Chrome"
、Linuxであればgoogle-chrome
コマンドのオプションで使用出来るものと同じです。
ここでは6個のオプションを設定します。
from selenium.webdriver.chrome.options import Options
def set_option():
options = Options()
options.add_argument('--headless')
options.add_argument('--incognito')
options.add_argument('--hide-scrollbars')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
return options
Chromeの起動
SeleniumのdriverのChromeクラスに先ほどのオプションを渡してChromeを起動させます。
Chromeの起動にはchrome driverが必要になるので、あらかじめダウンロードし、driverのバイナリファイルをPATH
に含まれているディレクトリ下に移動させておく必要があります。PATH
以外にある場合は、Chromeクラスにインスタンスを作成する際にexecutable_path
を指定しましょう。
from selenium.webdriver import Chrome
def main(url, target):
options = set_option()
browser = Chrome(options=options)
スクリーンショット撮影
千代田区の3時間天気 - 日本気象協会 tenki.jpのid=forecast-point-3h-today
またはxpath=//*[@id="forecast-point-3h-today"]
の要素のスクリーンショットを撮ります。Webページにアクセスすればわかると思いますが、なかなかリッチなUIをしています。
スクリーンショットを撮りたい要素をidとxpathによって指定する2通りの方法を紹介します。お好みでどうぞ。
idによる要素の指定
JavaScriptを使用して要素までスクロールしたあとでスクリーンショットを撮っています。windown.scrollTo
の引数の中で-50
しているのは、ヘッダーが邪魔にならないようにするためです。
browser.find_element_by_id(target).screenshot_as_png
で画像のバイナリデーを取得できるので、save_image
でバイナリデータをファイルに書き込んでいます。
import os
import sys
def main(url, target):
...
browser.get(url)
take_by_id(browser, target)
def save_image(data):
with open('tmp.png', 'wb') as f:
f.write(data)
def take_by_id(browser, target):
script = f'''
const ele = document.getElementById({repr(target)});
window.scrollTo(0, ele.getBoundingClientRect().top - 50);
'''
browser.execute_script(script)
img = browser.find_element_by_id(target).screenshot_as_png
save_image(img)
xpathによる要素の指定
基本的な流れはidと同じですが、要素までスクロールするためのJacaScriptを工夫する必要があります。idのようにgetElementByIdがないので、document.evaluate
を使います。詳しくはDocument.evaluateを参照してください。
import os
import sys
def main(url, target):
...
browser.get(url)
take_by_xpath(browser, target)
def take_by_xpath(browser, target):
script = f'''
const ele = document.evaluate(
{repr(target)},
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
window.scrollTo(0, ele.getBoundingClientRect().top - 50);
'''
browser.execute_script(script)
img = browser.find_element_by_xpath(target).screenshot_as_png
save_image(img)
完成形
import os
import sys
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
def main(url, target):
options = set_option()
browser = Chrome(options=options)
browser.get(url)
take_by_id(browser, target)
def set_option():
"""Configure Chrome options"""
options = Options()
options.add_argument('--headless')
options.add_argument('--incognito')
options.add_argument('--hide-scrollbars')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
return options
def save_image(data):
with open('tmp.png', 'wb') as f:
f.write(data)
def take_by_id(browser, target):
script = f'''
const ele = document.getElementById({repr(target)});
window.scrollTo(0, ele.getBoundingClientRect().top - 50);
'''
browser.execute_script(script)
img = browser.find_element_by_id(target).screenshot_as_png
save_image(img)
if __name__ == '__main__':
try:
url = os.environ['URL']
target = os.environ['TARGET']
except KeyError:
sys.exit('URLとTARGET環境変数を設定してください')
main(url, target)
Dockerfile
スクショした画像が文字化けしないようにするためにIPAfontをダウンロードします。Noto Serif CJK JPを使用して日本語対応している記事がありましたが、今回はうまくいかなかったのでIPAfontを使用しています。
FROM python:3.7-alpine
WORKDIR /tmp
RUN apk add --no-cache \
fontconfig \
chromium \
chromium-chromedriver && \
wget --no-cache -nv https://oscdl.ipa.go.jp/IPAfont/ipag00303.zip && \
mkdir -p /usr/share/fonts/ipa && \
unzip ipag00303.zip -x *.txt -d /usr/share/fonts/ipa && \
fc-cache -f && \
apk del --no-cache --purge fontconfig && \
rm -rf /tmp/* && \
addgroup executor && \
adduser -D -G executor executor && \
mkdir -p /home/executor/app && \
pip3 install --no-cache-dir pipenv
WORKDIR /home/executor
COPY --chown=executor:executor Pipfile* /home/executor/
RUN pipenv install --system --deploy --clear
COPY --chown=executor:executor src /home/executor/app
USER executor
CMD ["python3", "app/screenshot.py"]
docker-compose
version: '3'
services:
app:
build: .
image: app
container_name: app_dev
env_file: .env
.env
URL=https://tenki.jp/forecast/3/16/4410/13101/3hours.html
TARGET=forecast-point-3h-today
終わりに
次は【slack編】を書きます。今回撮ったスクショをSlackへアップロードする方法を説明します。
Author And Source
この問題について(【① Selenium編】当日の天気を知らせてくれるSlack Botを作ってみた(Python + Selenium + Slack + AWS ECS + Github Actions)), 我々は、より多くの情報をここで見つけました https://qiita.com/homines22/items/9fe2fa3de08dd978d31b著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .