【実現したいこと(Must要件)】
①Yahoo!カレンダーのURLからログインフォームへ飛ぶこと
②Yahoo!のエコシステムにログインすること(二段階認証をすること)
③認証後はセッションを切らないよう、一定間隔でWEB画面を更新(リフレッシュ)すること
④WEB画面の更新と同時に、予定を取得し、外部モニターに描画する
(Pythonのbeautifulsoupライブラリを利用して、WEB画面をスクレイピングする)
【考慮しなければならないこと】
①Raspberry PiからYahoo!カレンダー(WEB版)にアクセスする際には、毎回セッションが
切られてしまうため、二段階認証が毎回必要になる。
→二段階認証は最初の1回のみで済むように、セッションを保ち続けるように工夫する。
つまり、プログラム実行後、一定時間内に管理者が二段階認証という操作をし、
一定時間後にプログラムはWEB版Yahoo!カレンダーの情報をスクレイピングする、という
挙動となるようにプログラム側に待機時間を設ける。そして一度二段階認証をしたら
WEB画面を閉じずに、リフレッシュをさせるようにして無理やりセッションタイムアウトや
セッションの切断をさせないようにプログラムを設計する。
②データスクレイピングでの予定の取得では、Yahoo!カレンダー(WEB版)のHTMLが一部変わった
だけで予定の取得ができなくなるのではないか。API等の代替案はあるか?
→その懸念は正しいです。が、代替案はないため、Yahoo!カレンダーの改修がされるたびに
データスクレイピング定義を修正する必要があります。
上記の内容を踏まえて実装したプログラムは下記のようになります。
import os import time import datetime import pygame from itertools import cycle from PIL import Image, ImageDraw, ImageFont, UnidentifiedImageError 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 threading import logging import schedule # ヘッドレスモード(ブラウザを表示しない)でChromeを起動するオプションを設定 chrome_options = Options() # chrome_options.add_argument("--headless") # ヘッドレスモードを有効にするにはコメントを外す chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") try: service = Service("/usr/bin/chromedriver") # Linuxの例 driver = webdriver.Chrome(service=service, options=chrome_options) except Exception as e: print(f"Error starting ChromeDriver: {e}") exit(1) def wait_for_login(timeout=120): time.sleep(timeout) def switch_to_day_view(): try: # 指定されたCSSセレクタを使って「日」ボタンを探してクリックする day_view_button = driver.find_element(By.CSS_SELECTOR, '.ToolBar__viewButton.ToolBar__viewButton--first') if day_view_button.text == "日": day_view_button.click() time.sleep(10) # ボタンを押下した後に10秒待機 else: print("Day view button not found.") except Exception as e: print(f"Error switching to day view: {e}") def get_schedule(): url = "https://calendar.yahoo.co.jp/" try: driver.get(url) # Yahoo!カレンダーのページを取得 wait_for_login() # ログイン完了を待機(120秒間待機) switch_to_day_view() # 「日」ビューに切り替え tasks = driver.find_elements(By.CSS_SELECTOR, '.bc-event-inner') schedule_texts = [f'予定: {task_elem.text.strip()}' for task_elem in tasks] return schedule_texts except Exception as e: print(f"Error fetching schedule: {e}") return [] def update_schedule(): global schedule_texts schedule_texts = get_schedule() # 5分ごとにスケジュールを取得し更新する schedule.every(5).minutes.do(update_schedule) # 5分ごとにupdate_scheduleを実行 # Pygameを初期化 pygame.init() # 日本語対応フォントのパスを指定 font_path = "/home/hayate/Desktop/album/NotoSansCJKjp-Regular.otf" # フォントファイルのパスを指定 font_size = 36 date_font = pygame.font.Font(font_path, font_size) time_font = pygame.font.Font(font_path, font_size) schedule_font = pygame.font.Font(font_path, 25) # ディスプレイの設定 display_width = 800 display_height = 400 screen = pygame.display.set_mode((display_width, display_height)) pygame.display.set_caption("Display Image Example") # 背景を薄いベージュに設定 background_color = (245, 245, 220) screen.fill(background_color) # 画像の準備 image_files = [f for f in os.listdir('.') if f.lower().endswith(('jpg', 'jpeg', 'png'))] img_iter = cycle(image_files) image_rect = pygame.Rect(display_width // 2, 0, display_width // 2, display_height) def display_image(screen, img_iter, rect, background_color): img_path = next(img_iter) try: img = Image.open(img_path) img.thumbnail((rect.width, rect.height), Image.Resampling.LANCZOS) img_surface = pygame.image.fromstring(img.tobytes(), img.size, img.mode).convert() img_rect = img_surface.get_rect(center=rect.center) # 右半分をベージュで塗りつぶす pygame.draw.rect(screen, background_color, rect) # 画像を描画 screen.blit(img_surface, img_rect.topleft) except (UnidentifiedImageError, ValueError) as e: print(f"Cannot display image {img_path}: {e}") # メインループ running = True last_update = time.time() try: schedule_texts = get_schedule() # 初回スケジュール取得 display_image(screen, img_iter, image_rect, background_color) # 初回画像表示 while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 現在の日付、曜日、時刻を取得 now = datetime.datetime.now() date_str = now.strftime("%Y-%m-%d-%A") time_str = now.strftime("%H:%M:%S") # 背景の上に年月日と時刻を再描画 pygame.draw.rect(screen, background_color, (0, 0, display_width // 2, display_height)) date_surface = date_font.render(date_str, True, (0, 0, 0)) time_surface = time_font.render(time_str, True, (0, 0, 0)) # テキストの表示位置 screen.blit(date_surface, (10, 10)) screen.blit(time_surface, (10, 60)) # スケジュールを表示 for i, text in enumerate(schedule_texts): schedule_surface = schedule_font.render(text, True, (0, 0, 0)) screen.blit(schedule_surface, (10, 120 + i * 40)) # 20秒ごとに画像を切り替え if time.time() - last_update > 20: display_image(screen, img_iter, image_rect, background_color) last_update = time.time() pygame.display.flip() # 画面更新 schedule.run_pending() # スケジュールを実行 time.sleep(1) except KeyboardInterrupt: print("Script interrupted by user.") finally: pygame.quit() driver.quit() # ブラウザを閉じる