研究3:Yahoo!カレンダーの予定の取得と可視化(二段階認証の突破)

Hayate.Labに戻る

 【実現したいこと(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()  # ブラウザを閉じる

Hayate.Labに戻る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA