Raspberry Pi のサイネージ化 … Playwright で Chromium を最大化して自動操作する

2026-02-18 03:00 (9 days ago)
Raspberry Pi のサイネージ化 … Playwright で Chromium を最大化して自動操作する

概要

Raspberry Pi を「サイネージ端末」として使い、社内の Web アプリケーションを常時フルスクリーン表示する仕組みを構築した。

ブラウザの自動操作には Playwright (Python) を使用し、Chromium の --kiosk モードでフルスクリーン表示を実現している。

この仕組みでできること

  • Raspberry Pi 起動時に、自動的に Chromium が開き、指定した Web ページがフルスクリーンで表示される
  • ログインが必要なページでも、自動で認証を行う
  • Cookie を永続化し、再起動後もログイン状態を維持する
  • 開発時は --no-fullscreen オプションでウインドウモードに切り替え可能

環境

  • Raspberry Pi 5 / Raspberry Pi OS (Debian bookworm)
  • Python 3.13
  • uv (Python パッケージマネージャ)
  • Playwright

セットアップ

uv のインストール

curl -LsSf https://astral.sh/uv/install.sh | sh

プロジェクトの依存関係

pyproject.toml:

[project]
name = "rpi-signage"
version = "0.1.0"
description = "Automated Web page launcher for Raspberry Pi using Playwright"
requires-python = "==3.13.*"
dependencies = [
    "playwright>=1.57.0",
    "python-dotenv>=1.2.1",
]

Playwright と Chromium のインストール

uv sync
uv run playwright install chromium

playwright install chromium で、Playwright がブラウザを自動操作するために必要な Chromium バイナリがダウンロードされる。Raspberry Pi OS にプリインストールされている Chromium とは別のもの。

ただし、OS にインストール済みの Chromium がある場合はそちらを優先して使う (後述の get_browser_channel())。

Python コード

メインスクリプト

#!/usr/bin/env python3

import argparse
import json
import shutil
import time
from pathlib import Path

import dotenv
from playwright._impl._errors import TargetClosedError
from playwright.sync_api import BrowserContext, sync_playwright

COOKIES_FILE = Path(__file__).parent / "cookies.json"


def save_cookies(context: BrowserContext):
    """Cookie を JSON ファイルに保存する"""
    try:
        cookies = context.cookies()
        COOKIES_FILE.write_text(json.dumps(cookies, ensure_ascii=False))
        print(f"Saved {len(cookies)} cookies.")
    except Exception as e:
        print(f"Failed to save cookies: {e}")


def load_cookies(context: BrowserContext):
    """保存済みの Cookie を読み込んでブラウザコンテキストに適用する"""
    if not COOKIES_FILE.exists():
        return
    try:
        cookies = json.loads(COOKIES_FILE.read_text())
        context.add_cookies(cookies)
        print(f"Loaded {len(cookies)} cookies.")
    except Exception as e:
        print(f"Failed to load cookies: {e}")


def get_browser_channel() -> str | None:
    """OS にインストールされた Chromium/Chrome を検出する。
    なければ None を返し、Playwright バンドル版を使用する。"""
    if shutil.which("chromium") or shutil.which("chromium-browser"):
        return "chromium"
    if shutil.which("google-chrome") or shutil.which("chrome"):
        return "chrome"
    return None


def main(args):
    with sync_playwright() as p:
        channel = get_browser_channel()
        chromium_args = [
            "--window-size=1920,1040",
            "--window-position=0,0",
        ]
        if not args.no_fullscreen:
            chromium_args.append("--kiosk")

        context = p.chromium.launch_persistent_context(
            user_data_dir="./browser_data",
            headless=False,
            channel=channel,
            args=chromium_args,
            no_viewport=True,
            ignore_https_errors=True,
        )
        page = context.pages[0] if context.pages else context.new_page()
        load_cookies(context)

        page.goto("https://your-webapp.example.com/")
        time.sleep(2)

        # ログインページにリダイレクトされた場合の認証処理
        if page.locator("#username").count() > 0:
            print("Logging in...")
            page.fill("#username", dotenv.get_key(".env", "USERNAME"))
            page.fill("#password", dotenv.get_key(".env", "PASSWORD"))
            page.click("button[type='submit']")
            time.sleep(3)
            save_cookies(context)

        # Ctrl+C で終了するまで無限待機
        print("Press Ctrl+C to exit.")
        try:
            while True:
                time.sleep(2)
        except KeyboardInterrupt:
            save_cookies(context)
        except TargetClosedError:
            print("Browser window was closed.")

        context.close()


if __name__ == "__main__":
    arg_parser = argparse.ArgumentParser()
    arg_parser.add_argument(
        "--no-fullscreen",
        action="store_true",
        help="フルスクリーンにしない。開発時に指定する。",
    )
    args = arg_parser.parse_args()
    main(args)

コードのポイント

launch_persistent_context

Playwright の通常の launch() ではなく launch_persistent_context() を使っている。 user_data_dir にブラウザのプロファイルデータ (キャッシュ、ローカルストレージなど) が永続化される。

Cookie の永続化

save_cookies() / load_cookies() で、Cookie を JSON ファイルに保存・復元する。 launch_persistent_context でもある程度セッションは維持されるが、明示的に Cookie を保存することで確実にログイン状態を復元できる。

get_browser_channel()

Raspberry Pi OS には chromium-browser がプリインストールされている場合がある。 shutil.which() でシステムにインストールされた Chromium / Chrome を検出し、あればそれを使う。なければ Playwright バンドル版の Chromium を使用する。

OS ネイティブの Chromium の方が、Raspberry Pi 上でのパフォーマンスが良い傾向にある。

no_viewport=True

Playwright はデフォルトでビューポートサイズを固定する。no_viewport=True を指定すると、ウインドウサイズに応じてビューポートが動的に変わるようになる。サイネージ用途では画面全体を使いたいのでこの設定が必要。

.env からの認証情報読み込み

パスワードなどの認証情報は .env ファイルに記載し、python-dotenv で読み込む。

USERNAME=your-username
PASSWORD=your-password

フルスクリーン化の試行錯誤

Raspberry Pi 上で Chromium をフルスクリーン表示するために、いくつかの方法を試した。

失敗した方法

1. xdotool key F11

シェルスクリプトから遅延実行で F11 キーを送る方法。

sleep 10 && xdotool key F11 &

タイミングの問題で安定しなかった。ブラウザが完全に起動する前に送信されたり、フォーカスが合わなかったりする。

2. --start-maximized

Chromium の起動オプション。ウインドウは最大化されるが、アドレスバーやタブバーは残る。サイネージ用途には不十分。

3. JavaScript requestFullscreen()

Playwright から JavaScript を実行してフルスクリーンにする方法。

page.evaluate("document.documentElement.requestFullscreen()")

ユーザーのジェスチャー (クリックなど) を伴わないプログラム的な呼び出しは、ブラウザのセキュリティポリシーによりブロックされる場合がある。

採用した方法: --kiosk

chromium_args.append("--kiosk")

Chromium の --kiosk フラグは、アドレスバー・タブバーを含むすべての UI 要素を非表示にし、完全なフルスクリーンでページを表示する。

キオスク端末 (公共の端末) 用に設計されたモードで、サイネージ用途にも最適。

開発時は --no-fullscreen オプションを付けることで --kiosk を無効にし、通常のウインドウモードで動作確認できる。

# 通常起動 (キオスクモード)
uv run ./open_signage.py

# 開発時 (ウインドウモード)
uv run ./open_signage.py --no-fullscreen

デスクトップ自動起動の設定

シェルスクリプト

自動起動時に実行するシェルスクリプト:

#!/usr/bin/env bash
set -e
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting signage"
cd "$(dirname "$0")"
export DISPLAY=:0
/home/pi/.local/bin/uv run ./open_signage.py
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Signage finished"

export DISPLAY=:0 は、GUI のない autostart 環境から X Window System にアクセスするために必要。

.desktop ファイル

~/.config/autostart/.desktop ファイルを配置することで、Raspberry Pi のデスクトップ起動時に自動的にスクリプトが実行される。

[Desktop Entry]
Type=Application
Name=Web Signage
Exec=/bin/bash -c "/home/pi/signage/autostart.sh >> /tmp/signage.log 2>&1"
Terminal=false
X-GNOME-Autostart-enabled=true

ログを /tmp/signage.log にリダイレクトしているので、問題が発生した場合はこのファイルを確認する。

Mac からラズパイへのデプロイ

scp でファイルを転送するシンプルなデプロイスクリプト:

#!/usr/bin/env zsh
set -e
cd "$(dirname "$0")"

FILES=(
  autostart.sh
  open_signage.py
  .env
  pyproject.toml
)

for FILE in "${FILES[@]}"; do
  scp "$FILE" pi@rpi-signage.local:~/signage/
done

scp signage.desktop pi@rpi-signage.local:~/.config/autostart/signage.desktop

デプロイ後、ラズパイを再起動すれば新しいスクリプトが自動起動される。

ラズパイ側で初回のみ以下を実行しておく:

mkdir -p ~/signage
mkdir -p ~/.config/autostart
cd ~/signage
uv sync
uv run playwright install chromium

まとめ

  • Playwright + Python で Chromium を自動操作し、サイネージ端末を構築した
  • --kiosk フラグが Chromium のフルスクリーン化には最も確実
  • Cookie の永続化により、認証が必要なページも自動ログインできる
  • .desktop ファイルで OS 起動時に自動起動
  • uv でシンプルに依存関係を管理
  • scp ベースのデプロイスクリプトで Mac からラズパイに簡単にデプロイ
まだ評価がありません
著者は、アプリケーション開発会社 Cyberneura を運営しています。
開発相談をお待ちしています。

アーカイブ