Raspberry piでデジタルサイネージを作ってみた ~拡張編~

 2021.03.11  株式会社テリロジー 技術統括部

Raspberry piでデジタルサイネージを作ってみた ~拡張編~

みなさまお久しぶりです。Tachanです。

前回はラズパイでデジタルサイネージを開発した記事を書きました!

今回はその追加機能について書いていきたいと思います٩( 'ω' )و

追加機能の概要

今回追加した機能は

  • 出勤状況確認アプリの表示
  • 保守対応ダッシュボードの表示

になります。

それぞれ説明しますと、

出勤状況確認アプリは昨年Lさんが開発したもので、部員の出勤状況をリアルタイムで確認することができます。

保守対応ダッシュボードは、お問い合わせいただいたお客様からのフィードバックを表示しています。

今回はどちらもWebでアクセスするもののため、Seleniumを使ってみることにしました。

システム構成

今回は、PDFを表示する前にヘッドレスブラウザでスクリーンショットを撮る処理を追加しました。


システム構成


selenium処理の流れは
①指定URLにアクセス
②スクリーンショットを取得
③アルファチャンネルを取り除く作業
④③で作成した画像からPDFを作成

になります。

使ったモジュール

今回使ったモジュールは以下の3つです。

①selenium

Webアプリケーションをテストするためのソフトウェアです。
これを使ってブラウザ処理をヘッドレスで行うことができます。

②img2pdf

画像を加工できるモジュールです。
取得したスクリーンショットをPDFに変換する際に、アルファチャンネル(画像処理で、透過度の情報(アルファ値)を格納するデータ)を取り除かないといけなかったため、
こちらのモジュールを使って削除する処理を書きました。

③shutil

ファイルコピー、ディレクトリ階層のコピーや削除、ファイルやディレクトリの移動を行うことができるモジュールです。
スクリーンショットの画像や、加工した画像が溜まっていくので、それらのファイルを移動させるために使いました。

コード

実際のコードは以下です。
拡張機能は別ファイルで書き、それをメインのファイルで呼び出すことにしました。

  1. #!/usr/bin/python
  2.  
  3. from selenium import webdriver
  4. from selenium.webdriver.chrome.options import Options
  5. from selenium.webdriver.common.action_chains import ActionChains
  6. import time
  7. import os
  8. import img2pdf
  9. from subprocess import check_output
  10. import subprocess
  11. import datetime
  12. import sys
  13. import shutil
  14. import glob
  15.  
  16.  
  17. class support_dashboard(object):
  18.      def __init__(self):
  19.           self.now = datetime.datetime.now()
  20.           self.Before_filename_1 = 'pic1.jpg'
  21.           self.Before_filename_2 = 'pic2.jpg'
  22.           self.After_filename_1 = 'z1' + str(self.now.strftime('%Y%m%d_%H%M')) + '.jpg'
  23.           self.After_filename_2 = 'z2' + str(self.now.strftime('%Y%m%d_%H%M')) + '.jpg'
  24.           self.pdfFileName_1 = 'z1' + str(self.now.strftime('%Y%m%d_%H%M')) + '.pdf'
  25.           self.pdfFileName_2 = 'z2' + str(self.now.strftime('%Y%m%d_%H%M')) + '.pdf'
  26.           self.ext = '.jpg'
  27.           self.path_1 = r'PDFを作成するディレクトリ' + str(self.After_filename_1)
  28.           self.path_2 = r'PDFを作成するディレクトリ' + str(self.After_filename_2)
  29.           self.options = Options()
  30.           self.options.add_argument('--headless')
  31.           self.browser = webdriver.Chrome(executable_path='/usr/bin/chromedriver', chrome_options=self.options)
  32.           self.url = '保守対応ダッシュボードのURL'
  33.  
  34.           self.userid = 'ユーザーID'
  35.           self.userpw = 'パスワード'
  36.  
  37.  
  38.  
  39.      def get_screenshot(self):
  40.           self.browser.get(self.url)
  41.           self.browser.set_window_size(1280,720)
  42.           self.browser.execute_script("document.body.style.zoom='90%'")
  43.           print(self.browser.current_url)
  44.           time.sleep(10)
  45.  
  46.           self.browser.switch_to_frame(self.browser.find_element_by_tag_name('iframe'))
  47.  
  48.           time.sleep(10)
  49.  
  50.           login_id = self.browser.find_element_by_name('user[email]')
  51.           login_pw = self.browser.find_element_by_name('user[password]')
  52.           login_id.send_keys(self.userid)
  53.           login_pw.send_keys(self.userpw)
  54.           login_btn = self.browser.find_element_by_name('commit')
  55.           login_btn.click()
  56.  
  57.           time.sleep(20)
  58.  
  59.           try:
  60.                move_DashBoard = self.browser.find_element_by_class_name('クラス名')
  61.                move_DashBoard.click()
  62.                time.sleep(30)
  63.           except:
  64.                pass
  65.  
  66.           display_DashBoard = self.browser.find_element_by_xpath('パス')
  67.           display_DashBoard.click()
  68.           time.sleep(20)
  69.  
  70.           sfile = self.browser.get_screenshot_as_file('保存するパス' + self.Before_filename_1)
  71.           print('get_screenshot1')
  72.           print(sfile)
  73.  
  74.           time.sleep(5)
  75.  
  76.           target = self.browser.find_element_by_css_selector('cssの要素')
  77.  
  78.           time.sleep(3)
  79.  
  80.           self.browser.execute_script('arguments[0].scrollIntoView()', target)
  81.           time.sleep(3)
  82.  
  83.           sfile = self.browser.get_screenshot_as_file('保存するパス' + self.Before_filename_2)
  84.           print('get_screenshot2')
  85.           print(sfile)
  86.  
  87.           time.sleep(3)
  88.  
  89.           menu = self.browser.find_element_by_class_name('クラス名')
  90.           menu.click()
  91.  
  92.           time.sleep(2)
  93.  
  94.           signout = self.browser.find_element_by_id('signout')
  95.           signout.click()
  96.  
  97.           time.sleep(2)
  98.  
  99.           self.browser.quit()
  100.           print('close browser')
  101.  
  102.  
  103.  
  104.      def edit_img(self):
  105.           args_imgMagick_1 = ['convert', self.Before_filename_1, '\( +clone -alpha opaque -fill white -colorize 100% \) +swap -geometry +0+0 -compose Over -composite -alpha off', self.After_filename_1]
  106.           res = subprocess.run(args_imgMagick_1)
  107.           args_imgMagick_2 = ['convert', self.Before_filename_2, '\( +clone -alpha opaque -fill white -colorize 100% \) +swap -geometry +0+0 -compose Over -composite -alpha off', self.After_filename_2]
  108.           res = subprocess.run(args_imgMagick_2)
  109.  
  110.      def make_pdf(self):
  111.           with open(self.pdfFileName_1, "wb") as f:
  112.                f.write(img2pdf.convert(self.path_1))
  113.  
  114.           with open(self.pdfFileName_2, "wb") as f:
  115.                f.write(img2pdf.convert(self.path_2))
  116.  
  117.  
  118.      def           move_files(self):
  119.           move_jpg_1 = self.After_filename_1
  120.           move_jpg_2 = self.After_filename_2
  121.           move_pdf_1 = self.pdfFileName_1
  122.           move_pdf_2 = self.pdfFileName_2
  123.           now_MMDD = datetime.date.today()
  124.           print(now_MMDD)
  125.           check_dir = 'ディレクトリのパス' + str(now_MMDD)
  126.           if os.path.isdir(check_dir) == True:
  127.                print('the dir has found')
  128.                pass
  129.  
  130.           else:
  131.                os.mkdir(check_dir)
  132.                print('made a dir')
  133.  
  134.           pickup_pdfs = glob.glob(os.path.dirname(os.path.abspath(__file__)) + '/PDF/z*.pdf')
  135.           print(pickup_pdfs)
  136.  
  137.           if pickup_pdfs != None:
  138.                for pickup_pdf in pickup_pdfs:
  139.                     shutil.move(str(pickup_pdf), check_dir + '/')
  140.           else:
  141.                pass
  142.  
  143.  
  144.           shutil.move('ディレクトリのパス' + str(move_jpg_1), check_dir + '/' + str(move_jpg_1))
  145.           shutil.move('ディレクトリのパス' + str(move_jpg_2), check_dir + '/' + str(move_jpg_2))
  146.           shutil.move('ディレクトリのパス' + str(move_pdf_1), "PDFが保存してあるディレクトリのパス" + str(move_pdf_1))
  147.           shutil.move('ディレクトリのパス' + str(move_pdf_2), "PDFが保存してあるディレクトリのパス" + str(move_pdf_2))
  148.  
  149.  
  150. def support_dashboard_loop():
  151.      SUPPORT_DASHBOARD = support_dashboard()
  152.      SUPPORT_DASHBOARD.get_screenshot()
  153.      SUPPORT_DASHBOARD.edit_img()
  154.      SUPPORT_DASHBOARD.make_pdf()
  155.      SUPPORT_DASHBOARD.move_files()
  156.  
  157.  
  158.  
  159. if __name__ == "__main__":
  160.  
  161.      support_dashboard_loop()
  162.  
  163.  

苦労したこと

ブラウザ処理、、全体的に難しかったです⊂⌒~⊃。Д。)⊃
特に

  • 要素の取得
  • ブラウザ終了処理

に時間がかかりました。

要素の取得では、ページの移動やスクロールする際にページの要素がうまく取得できないことが多くありました。
これはtime.sleepで数秒待機させることでうまくいきました。
ページが完全に移動する前に実行されてたみたいでした(´・ω・`)

ブラウザ終了処理では、最初にスクリーンショットを取得した後、ブラウザを閉じる処理を入れていなかったので
cronで定期的に実行するたびにchromeのプロセスがだるま式で増えていき、ラズパイが固まる…ってことが多々ありました…(;^ω^)
ブラウザの終了処理を追加しても「if __name__ == "__main__":」を入れておらず
メインスクリプトの多重起動チェックを行う前(importの時点)で処理が実行されてしまい、プロセスがだるま式に…ということもありました((((;゚Д゚))))

「if __name__ == "__main__":」、コレダイジ

さいごに

今回の開発を通して、ヘッドレスブラウザ処理の書き方やページの仕組みを学びました。
今後はバグの修正などに取り組んでいきます。

最後までお読みいただきありがとうございました。


RECENT POST「技術情報」の最新記事


技術情報

OCVS・Horizon構築してみた②

技術情報

Sumologicで目にするOpen Telemetry Collectorとは(中編)

技術情報

Sumo Logic アカウントを持っていないユーザへのダッシュボードの共有方法は?

技術情報

Sumo Logic ダッシュボードやフォルダ共有について

Raspberry piでデジタルサイネージを作ってみた ~拡張編~