2024/12/1 地域の防災リーダー「防災士」(後編) を投稿しました

日々の安心した生活は防災から!
「もしも」に備えるための防災グッズやアプリ、お役立ち情報などを発信中!

Pythonで火山カメラ画像をキャプチャする(6)

Python

こんにちは、管理人のアカツキです。Pythonによる火山カメラ画像のキャプチャプログラムの解説も実際にブラウザを起動する段階に入ってきました。今回はブロック4についてその続きです。前回までの記事はこちらになります。

スポンサーリンク

(再掲)火山カメラ・キャプチャ コード

例によりまして、全体のコードを再掲します。

"""
JMA火山監視カメラキャプチャ Ver.1.01
・JMAの火山監視カメラ画像をキャプチャする
・連続キャプチャを実施する(重複はスキップ)
・火山名をリストから選択する
・最初のリストはよくアクセスすると思われる火山
・火山リストをアップデート(20220727更新分)
"""
# ブロック1
from selenium import webdriver
from selenium.webdriver.chrome import service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import os
import requests
import time

# ブロック2
dir_path = "(適当なディレクトリ)/volcano_camera/"

fav_volcano = {1:'十勝岳 白金模範牧場',
               2:'阿蘇山 草千里',
               3:'桜島 牛根',
               4:'諏訪之瀬島 寄木',
               5:'その他'}

area_volcano = {1:'北海道',
                2:'東北地方',
                3:'関東・中部地方',
                4:'伊豆・小笠原諸島',
                5:'九州地方',
                6:'戻る'}

hokkaido_volcano = {1: 'アトサヌプリ 北東山麓', 2: 'アトサヌプリ 硫黄山駐車場北',
                    3: 'アトサヌプリ 屈斜路湖南', 4: '雌阿寒岳 上徹別',
                    5: '雌阿寒岳 阿寒富士北', 6: '大雪山 忠別湖東',
                    7: '大雪山 旭岳姿見2', 8: '十勝岳 白金模範牧場',
                    9: '十勝岳 避難小屋南東', 10: '樽前山 別々川',
                    11: '倶多楽 414m山', 12: '倶多楽 地獄谷',
                    13: '有珠山 東湖畔', 14: '有珠山 月浦',
                    15: '北海道駒ヶ岳 鹿部公園南東', 16: '北海道駒ヶ岳 赤井川',
                    17: '北海道駒ヶ岳 剣ヶ峯', 18: '恵山 高岱',
                    19: '恵山 火口原', 20: '戻る'}

tohoku_volcano = {1: '岩木山 百沢東', 2: '八甲田山 大川原', 3: '八甲田山 地獄沼',
                  4: '十和田 銀山', 5: '秋田焼山 栂森', 6: '岩手山 柏台',
                  7: '岩手山 黒倉山', 8: '鳥海山 上郷2', 9: '栗駒山 大柳',
                  10: '栗駒山 展望岩頭', 11: '蔵王山 遠刈田温泉', 12: '蔵王山 上山金谷',
                  13: '蔵王山 刈田岳', 14: '蔵王山 御釜北', 15: '吾妻山 上野寺',
                  16: '安達太良山 若宮', 17: '安達太良山 鉄山', 18: '磐梯山 剣ケ峯',
                  19: '磐梯山 櫛ヶ峰', 20: '戻る'}

kanto_tyubu_volcano = {1: '那須岳 湯本ツムジケ平', 2: '那須岳 日の出平北', 3: '日光白根山 歌ヶ浜',
                       4: '日光白根山 上小川', 5: '草津白根山 奥山田', 6: '草津白根山 逢ノ峰山頂',
                       7: '草津白根山 草津', 8: '浅間山 鬼押', 9: '浅間山 追分',
                       10: '新潟焼山 宇棚', 11: '弥陀ヶ原 芦峅', 12: '焼岳 中尾峠',
                       13: '乗鞍岳 乗鞍高原', 14: '御嶽山 三岳黒沢', 15: '御嶽山 奥の院',
                       16: '白山 白峰', 17: '富士山 萩原', 18: '箱根山 宮城野',
                       19: '箱根山 大涌谷', 20: '箱根山 箱根峠', 21: '伊豆東部火山群 大原',
                       22: '伊豆東部火山群 大崎', 23: '戻る'}

izu_ogasawara_volcano = {1: '伊豆大島 北西外輪', 2: '伊豆大島 中央火孔北', 3: '新島 式根',
                         4: '神津島 前浜南東', 5: '三宅島 坪田', 6: '三宅島 神着',
                         7: '三宅島 山頂火口北西', 8: '八丈島 楊梅ヶ原', 9: '青ヶ島 手取山', 
                         10: '戻る'}

kyusyu_volcano = {1: '鶴見岳・伽藍岳 塚原無田', 2: '九重山 上野', 3: '九重山 星生山北尾根',
                  4: '九重山 飯田大原', 5: '阿蘇山 草千里', 6: '阿蘇山 車帰',
                  7: '阿蘇山 南阿蘇村', 8: '雲仙岳 野岳', 9: '雲仙岳 垂木台地南',
                  10: '霧島山 猪子石(新燃岳)', 11: '霧島山 猪子石(御鉢)', 12: '霧島山 御鉢火口南縁',
                  13: '霧島山 高原西麓', 14: '霧島山 八久保', 15: '霧島山 韓国岳',
                  16: '霧島山 えびの高原', 17: '霧島山 硫黄山南', 18: '桜島 牛根',
                  19: '桜島 東郡元', 20: '桜島 垂水荒崎', 21: '桜島 中央港新町',
                  22: '薩摩硫黄島 岩ノ上', 23: '口永良部島 本村西', 24: '口永良部島 屋久島吉田',
                  25: '諏訪之瀬島 寄木', 26: '諏訪之瀬島 キャンプ場', 27:'戻る'}

area_volcano2 = {'北海道':hokkaido_volcano,
                '東北地方':tohoku_volcano,
                '関東・中部地方':kanto_tyubu_volcano,
                '伊豆・小笠原諸島':izu_ogasawara_volcano,
                '九州地方':kyusyu_volcano}

# ブロック3
# メニューを行き来するためのフラグを設定
Flag = True
while Flag:
    for vkey1, vvalue1 in fav_volcano.items():
        print(f"{vkey1} : {vvalue1}")
    
    select_vkey1 = input("火山を選択してください [No.?] >>> ")
    select_vkey1 = int(select_vkey1)
    select_vvalue1 = fav_volcano[select_vkey1]
    
    # 1~6を選んだ場合の処理→値を取得してループ離脱
    if not select_vvalue1 == "その他":
        select_volcano = select_vvalue1
        break
    
    elif select_vvalue1 == "その他":
        
        # もう一つwhileを入れる
        while Flag:
            for vkey2, vvalue2 in area_volcano.items():
                print(f"{vkey2} : {vvalue2}")

            select_vkey2 = input("地域を選択してください [No.?] >>> ")
            select_vkey2 = int(select_vkey2)
            select_vvalue2 = area_volcano[select_vkey2]
            
            if select_vvalue2 == "戻る":
                # 再度前の画面に戻る
                break

            elif select_vvalue2 != "戻る":
                for vkey3,vvalue3 in area_volcano2[select_vvalue2].items():
                    print(f"{vkey3} : {vvalue3}")
             
                select_vkey3 = input("火山を選択してください [No.?] >>> ")
                select_vkey3 = int(select_vkey3)
                select_vvalue3 = area_volcano2[select_vvalue2][select_vkey3]
                
                if not select_vvalue3 == "戻る":
                    # 火山名を取得してループを抜ける
                    select_volcano = select_vvalue3
                    Flag = False

                elif select_vvalue3 == "戻る":
                    # 地域選択画面に戻る
                    pass

print(select_volcano,"を選びました")
print(select_volcano,"にアクセスします")

# ブロック4
chrome_driver = "(適当なディレクトリ)/chromedriver.exe"
chrome_service = service.Service(executable_path=chrome_driver)
driver = webdriver.Chrome(service=chrome_service)
wait = WebDriverWait(driver=driver, timeout=10)

driver.implicitly_wait(5)
driver.set_window_position(50,50)
driver.set_window_size(1400,1300)

# JMAの監視カメラ画像のページに直接アクセスする
driver.get("https://www.data.jma.go.jp/vois/data/tokyo/volcam/volcam.php")
# wait.until(EC.presence_of_all_elements_located)

# 選択された火山カメラを開く
volcam = driver.find_element(By.LINK_TEXT,select_volcano)
volcam.click()

# wait.until(EC.presence_of_all_elements_located)

# スライダを左端に移動
driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/oldest.png']").click()

# 早送りボタンを取得
forward = driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/next-frame.png']")

# ブロック5
# 画像を保存する(繰り返し)
# 撮影枚数を取得
img_num = driver.find_element(By.XPATH,"//*[@id='main']/form/table[2]/tbody/tr[2]/td/table/tbody/tr[3]/td[5]/input[3]")
img_num = img_num.get_attribute("value")
time.sleep(1)

for i in range(int(img_num)):
    img_tag = driver.find_element(By.NAME,"myImg")
    img_url = img_tag.get_attribute("src")
    
    img = requests.get(img_url)
    file_name = os.path.basename(img_url)
    
    # 保存ディレクトリの作成
    # 取得した火山名を使う
    save_dir = os.path.join(dir_path,select_volcano)
    
    # if not文で判断→フォルダがない→False→if notはTrue→フォルダを作る
    if not os.path.isdir(save_dir):
        os.makedirs(save_dir)
    
    # 撮影の重複をチェック
    check_file = os.path.join(save_dir,file_name)
    
    # if notで判定する
    # 条件が真ならif notは偽を返す
    # 条件が偽ならif notは真を返す
    # ファイルがない=撮影していない=if notがTrue=Trueの処理=キャプチャする
    if not os.path.isfile(check_file):
        with open(check_file,"wb") as f:
            f.write(img.content)
    
    # 次の時間に移動
    forward.click()
    time.sleep(1)

driver.quit()
print("終わりました")

プログラムの機能は次のようになっています。

JMA火山監視カメラキャプチャ Ver.1.01(volcano_capture.py)

  • 気象庁(JMA)が提供している火山監視カメラ画像について、
    任意の火山画像をキャプチャ(保存)する
  • キャプチャする火山名はリストから選択する(番号を入力して指定)
  • 画像を保存するフォルダ・ファイル名は自動で生成する
  • 火山リストを更新(2022.7.27 カメラ追加のプレスリリースあり)

全体コードにはその役割ごとにブロック番号を付けています。

ブロック一覧とその役割

  1. プログラムで使用するモジュールの呼び出し
  2. ファイル保存や火山名の選択に使う変数やデータ
  3. 火山選択メニュー
  4. 選択された火山カメラをブラウザで読み込む
  5. キャプチャした画像の保存処理

今回もブロック4についての解説を引き続き行っていきます。後半部分です。

ブロック4・火山カメラの読み込み(後半)

# ブロック4
chrome_driver = "(適当なディレクトリ)/chromedriver.exe"
chrome_service = service.Service(executable_path=chrome_driver)
driver = webdriver.Chrome(service=chrome_service)
wait = WebDriverWait(driver=driver, timeout=10)

driver.implicitly_wait(5)
driver.set_window_position(50,50)
driver.set_window_size(1400,1300)

# JMAの監視カメラ画像のページに直接アクセスする
driver.get("https://www.data.jma.go.jp/vois/data/tokyo/volcam/volcam.php")
# wait.until(EC.presence_of_all_elements_located)

# 選択された火山カメラを開く
volcam = driver.find_element(By.LINK_TEXT,select_volcano)
volcam.click()

# wait.until(EC.presence_of_all_elements_located)

# スライダを左端に移動
driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/oldest.png']").click()

# 早送りボタンを取得
forward = driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/next-frame.png']")

まずはサイトを観察してみよう

前回、無事にブラウザが起動して気象庁の監視カメラ画像のページにアクセスすることができました。この動作確認によって今までのコードの旅が終了しました。この記事もちょっと(どころか大分、それも思い出したかのように唐突に再開して)時間がかかってしまいましたが計6回に渡る連載と相成りました。ここまで辛抱強くお付き合いをして頂いた方には厚く御礼を申し上げます!アカツキ先生の次回作にご期待ください!!






…と思いたくなるほど達成感で満たされたブラウザの実動作ですが、ここからが本番です。あらためて今目的としていることを確認します。立ち上げた監視カメラ画像のページから調べたい火山のカメラにアクセスすることでしたね。

そこでまずはこのサイトページの構成を観察してみましょう。

監視カメラ画像・トップページ
監視カメラ画像・トップページ

最初に目に入るのは「地図から監視カメラを選択」という項目です。日本地図が表示され、そこに活火山の絵が表示されていますね。その下には「一覧から監視カメラを選択」とあり、アクセスできる火山一覧が地方ごとにリストアップされています。さらにその下には「監視カメラの運用に関するお知らせ」とあり、最新情報が掲載されています。

この構成を見るに、火山カメラには次の二つのアクセス方法が用意されていることがわかります。

  1. 地図上から選択
  2. 火山リストから選択

どちらのルートから出発しても到着先は同じはずです。まずはそれぞれの挙動を確認してみます。

地図上から選択

まずはビジュアル的にわかりやすい「地図から監視カメラを選択」から見てみましょう。左下の+ボタンで拡大できますのでポチポチと押してみます。すると火山の名前が出てきました(1)。ある程度の拡大倍率で火山名が表示されるようです。次に火山にカーソルを近付けると形が変わります。どうやらクリックできるようですね。

試しに富士山をクリックすると地図が富士山中心に拡大され、地図内に「富士山 萩原」というカメラアイコンが現れました(2)。これが富士山に対しての監視カメラということですね。このカメラアイコンをクリックすると富士山の監視カメラ画像が現れました(3)

地図から監視カメラを選択(1)
地図から監視カメラを選択(1)
地図から監視カメラを選択(2)
地図から監視カメラを選択(2)
地図から監視カメラを選択(3)
地図から監視カメラを選択(3)

地図による選択操作は左上にあるプルダウンメニューからも行うことができます。「地方選択」から地方を選ぶと地図が自動的にその地方に移動して火山を表示してくれます(4)。また、火山名が表示される良い塩梅の倍率に拡大されます。そして「火山選択」にはその地方の火山がセットされるので後は見たい火山名を選択してあげます。

ここでは桜島をクリックしてみました。すると四つのカメラアイコンが出てきました(5)。火山によっては複数の監視カメラを持つ場合があり、特に九州地方の火山はその傾向が強くなっているように見えます。後はカメラアイコンをクリックすれば監視カメラ画像を確認することができます(6)

地図から監視カメラを選択(4)
地図から監視カメラを選択(4)
地図から監視カメラを選択(5)
地図から監視カメラを選択(5)
地図から監視カメラを選択(6)
地図から監視カメラを選択(6)
中央に黒っぽいものが見えますが、どうもカメラに付いた汚れのようです。夜になると目立たなくなります。

このように地図から火山を選択する方法は、火山の地理的位置やカメラの監視方向を把握しながら行うことができるため、見た目にわかりやすい強みがあると言えます。

火山リストから選択

続いて「一覧から監視カメラを選択」する場合を見てみます。こちらはあらかじめ火山カメラ一覧がリストアップされていますから、どの火山を見たいか、また複数のカメラがある場合はどのカメラを見たいかがはっきりしている場合に使いやすくなると言えます。何せクリック一発でアクセスできますから。

以上のことからそれぞれのポイントをまとめてみます。

地図から監視カメラを選択

  • 地図上でその火山が日本のどの場所に位置しているかを確認してから選択できる
  • 複数のカメラがある場合は、その火山をどの方角から見ているか確認できる
  • 地方から選択も可能で、連動して地図が動くため場所を確認しながら選択できる

一覧から監視カメラを選択

  • 各火山に設置されているカメラのリストが地方別に整理されている
  • あらかじめ見たい火山と画角(カメラが複数ある場合)が決まっていればすぐにアクセスできる

さて、ここで一度原点に立ち返ります。このプログラムでは何がやりたかったのか?ということです。ある火山のカメラを見ることでしたよね。ということはアクセスしたい火山はすでに決まっているということです。

地図上から火山を選ぶプロセスを見直してみますと、何度もクリックを続けてようやく一つの火山カメラにアクセスすることができました。一方で一覧から選ぶ場合は、該当するリンクをワンクリックするだけでカメラにアクセスできました。選ぶ火山が決まっている場合、どちらが簡単に動作を完結できるでしょうか。

今までサイトの挙動を観察し、考察していったのはプログラムを組むに当たってどちらが確実に、効率的に目的を達成できるか?という問いに答えを出すためでした。結果的に本プログラムは、後者の「一覧から監視カメラを選択」を利用して火山カメラへのアクセスを実現する運びとなっています。

カメラへの入り口は?ロケータで要素を探す

プログラムの方向性が定まりました。そこで次のコードが活躍します。

# 選択された火山カメラを開く
volcam = driver.find_element(By.LINK_TEXT,select_volcano)
volcam.click()

この二行のコードを実行すれば火山カメラのページに到達できます。二行目はclickという文言が見えますし、これが何をしているのかはすぐに予想が付きそうですよね。ということで問題は一行目です。

# Web要素を検索する
find_element(ロケータ,ロケータに渡す値)

まずはfind_element()メソッドです。こちらも名前的にそのままの機能を持っており、括弧内の引数に指定されたWeb要素(WebElement)を検索します。

Web要素とは、SeleniumにおいてDOM要素を意味しています。DOMとはドキュメント・オブジェクト・モデル(Document Object Model)と呼ばれ、HTMLやXML文書の構造などを決めたAPIとされています。

何やら難しい言葉がいっぱいですね。私もこの辺りの概念を今イチ理解できていません。が、HTML文書の構造という点では少しイメージが沸くかも知れません。

私たちが普段Webブラウザを通して見ているWebページは、HTMLファイルと呼ばれる、タグ(AはBより大きい(A>B)、小さい(A<B)といった表現に使われる等号記号(山括弧の<>と似ているというかそっくりですが、ちょっと違います)を使います)の集まりで作られています。タグには開始タグ(<>)終了タグ(</>)があり、タグの中に文字列を入れると機能を持たせることができます。

たとえば文字を太くしたい時には文字列をbタグで囲んでやります。

<!--文字を太くする(ボールド)には文字列をbタグで囲む--!>
<b>てすと</b>

そしてタグで囲まれた一つの塊を要素と呼びます。HTMLファイルは要素の集まりからできており、実体はただのテキストファイルです。それをブラウザ側でタグを解釈してWebページに変換しているという寸法です。

ですのでSeleniumで言うところのWeb要素とは、こういったHTML要素やページによってはテキストを入力したりクリックするためのボタンといったフォームなども用意されていると思いますが、そういったものも含めWebページを構成するすべてを指すことになるでしょうか。何とも曖昧な説明で申し訳ありません。

これを踏まえてもう一度find_element()に戻ります。このメソッドは引数に指定された要素を検索するものでした。ではその引数にはどういうものがあるのか?ということですが、この引数にはロケータ(locator)というものを使います。ロケータとは要素を識別するための方法で、いずれもByクラスのクラス変数として定義されています。

ロケータ一覧
ロケータ一覧
Selenium公式サイト「要素を探す」にByクラス変数名との関連を追加

このようにSeleniumでは八種類が用意されています。しかもHTMLタグだけではなくWebページのレイアウトや背景、フォントの色などを決めるCSS、XMLドキュメントの特定の部分を指定する方法であるXPathなども用意されています。これらをその状況状況に照らし合わせて使い分けていくことになります。ですのでロケータの解説はその都度局面に応じてやっていきたいと思います。

今回の事例では、ロケータはBy.LINK_TEXTでした。先の一覧表によればLINK_TEXTという部分がクラス変数名であり、その値がlink textになります。クラス変数の呼び出しは

クラス名.クラス変数名

で行うことができます。一度何かのインスタンスを生成することなく値に直接アクセスできることが押さえておく点になるかと思います。

ところでlink textは何を探しているのかというと、a要素のテキストが一致する要素を探す、というものでした。このa要素というのは、要はWebリンクのことです。基本的にa要素で囲まれたテキストにはアンダーラインが引かれ、クリックすることでa要素に設定されたリンク先に移動することができます。

さて火山カメラのトップページを見てみますと、「一覧から監視カメラを選択」ではそのリンクが張られた火山リストが並んでいます。そして火山リストは全て火山名であり、これはプログラムの冒頭でselect_volcanoに納めた値そのものです。つまり、link textをロケータとしてselect_volcanoに一致する要素を探せばそれが目的の火山カメラへのリンクということになります。

# 選択された火山カメラを開く
volcam = driver.find_element(By.LINK_TEXT,select_volcano)
# volcamのデータ型をtype関数で調べてみる
#  <class 'selenium.webdriver.remote.webelement.WebElement'> 

そしてvolcamのデータ型をtype関数で調べてみると、WebElementという答えが返ってきます。このデータ型に対応するメソッドがclick()です。

# リンクをクリックする
volcam.click()
# click() 要素をクリックする

火山カメラのコントロールをする

火山カメラのページも観察してみよう

ここまでの作業によって目的の火山カメラページにアクセスできたかと思います。しかしまだまだ作業は続きます。最終的にやりたい事は画像の連続キャプチャです。そこに至るまでにはもう少しステップを踏む必要があります。次のコードがブロック4の最終部分です。

# スライダを左端に移動
driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/oldest.png']").click()

# 早送りボタンを取得
forward = driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/next-frame.png']")

この二行で要素のコントロール取得という二つの作業を行っています。では何故この作業が必要になるかということですが、それにはまず火山カメラのWebページを観察していく必要があります。

火山カメラ・浅間山(鬼押) (1)
火山カメラ・浅間山(鬼押) (1)

ここでは例として浅間山・鬼押(あさまやま・おにおし)観測点のカメラ画像を開いてみました。このように火山カメラのページは中央に火山カメラの映像が表示されており、その下にコントローラ的なボタン類が配置されていることがわかります。どうやらそのボタンから別の時間帯の画像に切り替えられそうです。

火山カメラ・浅間山(鬼押) (2)
火山カメラ・浅間山(鬼押) (2)
青い四角の位置と右下の撮影時刻、数字に注目です

確かに画像が切り替わっていることが確認できます。画像には日付と時刻の情報が埋め込まれていますね。また、連動して画像下の青い四角が動いています。また、画像エリアの右下にも撮影日時が表示されており、その右側には19/28と二つの数字がスラッシュで区切られています。

また左下を見ると、いかにもスライドショーが始まりそうなボタンがあります。これを押すとやはり画像が自動的に切り替わり始めます。実際にその動きを確認してみてください。

まず最初に右端にあった青い四角が左端に移動して数字は1/28に変化します。そして左から右へ移動するとともに画像が変わり、右下の数字が増えていき、右端に到達すると28/28となってスライドショーが終了することがチェックできたかと思います。

つまり、この数字はページアクセス時に用意された時系列に沿った火山画像のカウンター(現在の表示枚数/総枚数)と思われます。そして青い四角は今表示されている画像が全体の何枚目にあるかを教えてくれる目印であり、また任意の画像に移動できるスライダーの役目があるのではないかと推察できます。

そして撮影の間隔はおよそ2分間隔になっていますから、直近1時間ほどの画像(動画ではなく静止画)が閲覧できることになります(火山ごとに若干異なり、おおむね27~30枚になっているようです。12枚くらいのところもあります)。

さらに下のコントロール部分の挙動を確認してみましょう。先ほど推察したスライダーと思われる部分について、左端と右端にあるボタンをクリックするとどのような反応を示すでしょうか。

左端のボタンはスライダーが左端にある時は押しても反応せず、左端を離れた時に押すとスライダーが一つ左、つまり前の時間に戻ります。右端のボタンではその逆の挙動を見せますから、この二つのボタンは画像を一つ前の時間に戻す、あるいは一つ前の時間に進める機能があるのでしょう。

次に左下に三つのボタンがあります。再生ボタンは真ん中で間違いないでしょう。では左と右のボタンはどのような役割があるのでしょうか。こちらも実際にクリックしてみましょう。

その結果から、残りのボタンは画像を一番最初の時間帯、あるいは最新のものにジャンプする機能を持つことが推測できるかと思います。

そして右隣にある「遅、速」と書かれた青いバーがありますが、こちらも挙動から考えますと画像スライドショーの速度を変えることができると思われます。

実際に調べたところ(ストップウォッチでラップタイムを計測してその平均を算出してみました)、調節は5段階になっていて2秒(遅)、1.5秒、1秒、0.5秒、そしておそらく0.1秒(速)までスピードを可変できるようになっているようです。

ただ最速の場合は速すぎて画像の切り替わりを確認してからラップを刻む反応が間に合いませんでした。そこで全体の再生時間と枚数から計算してみたところ、0.1秒程度ではないかと思います。が、ちょっと断定はできません。とにかく速いです。

画像エリアの機能を整理する

以上の考察から、この画像エリアを次のように番号分けしてみました。

火山カメラの機能整理
火山カメラの機能整理

これを踏まえてそれぞれの機能を整理してみましょう。

火山カメラページ・画像エリアの機能

  1. 火山カメラ画像の表示
    • 火山名と撮影日時情報が埋め込まれている
  2. 早戻し・早送りボタンと現在の画像位置
    • 画像を一つ前、あるいは一つ後の時間のものに切り替える
    • スライダーを調節して(ドラッグでもクリックでも)任意の撮影日時の画像に移動する
  3. 先頭・最終画像への移動ボタンと再生ボタン
    • 設定した再生速度に応じて画像を切り替えて表示する
    • 最も撮影日時が古い、あるいは最新の画像に移動する
  4. 再生速度調節スライダー
    • 画像の再生速度を5段階で設定する
      2秒(遅)、1.5秒、1秒、0.5秒、(おそらく)0.1秒(速)
  5. 撮影日時と撮影枚数の表示
    • 現在表示されている画像の撮影日時を表示する
    • 現在の火山カメラ画像の枚数/火山カメラ画像の総枚数を表示

なかなか良い感じにまとまりましたね。そして最初に火山カメラ画像にアクセスしたときはこのような感じでした(先ほどの再掲です)。

火山カメラ・浅間山(鬼押) (1)
火山カメラ・浅間山(鬼押) (1)

よく見てみますと、5の撮影枚数が総枚数と一致しています。ということは、初期状態では最も最新のカメラ画像が読み込まれていることになります。

今このプログラムでやろうとしていることは、火山カメラ画像のキャプチャです。そして用意されている枚数のそれぞれについて実行したいと考えています。ならば最も撮影日時が古いものから最新のものまで順番に画像を表示させて逐一キャプチャしていくのが順当なやり方だと思われます。

となるとプログラムのキャプチャ手順として次のようなレシピが作れそうです。

火山カメラ画像キャプチャの手順

  1. 画像を最も古い撮影日時に切り替える
    • 撮影枚数が1の状態にする
    • 機能3のボタンで実行可能
  2. キャプチャを実行
  3. 機能2のスライダーを使って画像を一つ先のものに切り替える
  4. 2.に戻る

つまり手順1でキャプチャ準備に入り、手順2~4を全ての画像をキャプチャするまで繰り返し行うことになります。

そうすると先ほど整理したどの機能をブラウザ側から利用すれば良いかが見えてきます。

手順1については、撮影日時が最も古い画像にするのですから、機能3のボタンを一度押せば実現できます。また手順2~4ではキャプチャ処理は後述しますが、機能2のスライダーで次の画像に切り替えられるはずです。とはいってもスライダーをぐりぐりと動かすのではなく、一番右のボタンをクリックするだけです。

ロケータの選択 – CSSセレクタ

これらを押さえた上で再度ブロック4最後のコード二行をご覧ください。

# スライダを左端に移動
driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/oldest.png']").click()

# 早送りボタンを取得
forward = driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/next-frame.png']")

何となく意味が見えてきたのではないでしょうか。一行目は何をやっているかというと、機能3のボタンを押して最も撮影日時の古い画像に切り替えています。つまりスライダは左端に移動します。これは手順1に相当します。

また二行目では画像を一つ進めるためのボタンを取得しています。手順2~4の前準備となります。いずれもfind_element()メソッドを利用して要素を取り出しています。

そして今回はロケータにCSS_SELECTOR(CSSセレクタ)を用いることにします。これはByクラスの変数名でその値はcss selectorになります。先にちょっとだけ触れましたが、CSSとはCascading Style Sheet(カスケーディング・スタイル・シート)のことで、Webページのレイアウトや背景色などそのスタイルを決めるための言語になります。

CSSはWebページのスタイルを指定したい要素に対して、セレクタを利用して指定を行います。

/*<div>タグで囲まれた要素について、フォントサイズを14px(ピクセル)にする*/
div{font-size:14px;}

上の例で、中括弧の前にあるのがセレクタです。ここではタグを指定しており、HTMLファイル中で<div>タグが使われている要素全てに中括弧の中身で指定した内容が適用されます。

セレクタはかなりたくさんの種類があり、たとえばタグの属性、さらには属性値についても指定が可能です。

/*<div>タグで囲まれたclass名がgreenの要素について、フォントサイズを14px(ピクセル)にする*/
div[class="green"]{font-size:14px;}
/*大括弧(またはブラケット)で属性、属性値を指定する*/

つまり、Webページにおいて指定したい要素をCSSの目で探すというのがcss selectorの役割になります。

プログラムに戻りましょう。まずやりたい事は、画像を最も古い撮影日時のものにすることでした。そのためには機能3のボタンの要素を指定してあげれば良いことになります。

ではこの部分のタグをどのようにチェックすればよろしいのでしょうか。実はWebブラウザにはそれをサポートする強力な機能があるのです。試しにF12キーを押してみてください(またはCtrlとShiftとIキーを同時押し)。

開発者ツールを起動(2)
開発者ツールを起動
薩摩硫黄島 岩の上の監視カメラです。火映と思われる赤味がかった部分が見えます。

画面の右側が分割され、文字列の羅列が表示されたかと思います。これは開発者ツール(あるいはモード)と呼ばれ、Webページを分析することができる大変便利なツールです。基本的にChromeやEdge、Firefoxも同じキーで起動します。

この文字列はHTMLのソースコードであり、Webページを記述していますから、この中のどこかに件のボタンを示したタグがあるはずです。

とは言え、どこがどこにあるかちょっと見当が付きません。それにHTMLは入れ子構造が可能になっていてその部分は折り畳まれた状態で表示されています。それを一つ一つ展開して探すのは骨が折れる作業になってしまうでしょう。

そこで活躍するのが右クリックメニューです。件のボタンにカーソルを合わせて右クリックしてみてください。出てきたメニューに検証(Chromeの場合。Edgeですと「開発者ツールで調査する」がそれに当たります)という項目がありますのでそのままクリックします。

「検証」で要素を特定
「検証」で要素を特定
該当場所はimg要素でした

するとそのボタン要素を示すコード部分にフォーカスが移ります。つまり一発で要素が特定できるんです。特定部分を抜き出した部分がこちらです。

<img src="./icon/player/oldest.png" alt="最初へ" width="30px" height="30px" onclick="Oldest()">
<!--画像を表示するimgタグ。src属性=画像のURLを指定、alt属性=画像の代わりに表示する説明を記述-->

ということはimgタグで検索をすればボタンを見つけられそうですが、他のボタン類もみな画像であることから、imgタグが多そうです。さらに属性も指定して絞り込むのが確実そうです。

そこでsrc属性あるいはalt属性を利用します。それが一行目のコードということになります。

# スライダを左端に移動
driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/oldest.png']").click()

src属性はそのボタンの画像自体(の場所)を指しますから、他に同じ画像がなければこれで特定できます。一方でalt属性もボタンの機能が「最初へ」と説明されており、他に同様な機能を持つボタンがないため特定可能です。ここではsrc属性を採用しましたが、alt属性の方がわかりやすかったかも知れません。

また、一連の作業においてこのボタンの使用は一度きりです。ですので別の変数に格納せず、そのままclicl()メソッドを適用しています。

同様に二行目のコードについても検証から要素を特定します。次のようなコードが出てきました。

<img src="./icon/player/next-frame.png" alt="早送り" onclick="nextFrame(slider1)">

こちらもsrcあるいはaltの属性値が使えそうです。したがって

# 早送りボタンを取得
forward = driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/next-frame.png']")

と記述しました。またこのボタンは画像の切り替えに何度も使用しますから早送りを意味するforwardという変数名に格納しておきました。

以上の作業をもってキャプチャに必要な要素のコントロールと取得を行うことができました。
これにてブロック4は終了です。

まとめです

Pythonによる火山カメラのキャプチャプログラムの解説について、ブロック4の解説を二回に渡って行いました。いよいよ次のブロック5キャプチャ作業が実装されます。

基本的にブロック一つにつき、一回の解説を考えていたのですが思ったよりも分量が膨らんでしまい、結構な回数になってしまいました。またまとまった時期に記事を投稿できず申し訳ありません。次回はなるべく早めに上げられればと思います。

なお、今回の投稿で気が付いたのですが、一部に必要のないコードが紛れていました。このプログラムの前身であるものを作成した時の名残が入っていました。具体的には以下の部分です。

# ブロック1
from selenium.webdriver.common.action_chains import ActionChains
# ブロック4
actions = ActionChains(driver)

これはキーボード操作に関係するコードです。モジュールを読み込み、変数にdriverを入れただけなので実行しても全く影響はありません。最後のブロック解説を行った時に今までの記事で関連する部分をまとめて訂正する予定です。よろしくお願いします。

タイトルとURLをコピーしました
/* クリッカブル用コード */