Python で Playwright アプリをexe化する時のコツ

# launch_inspector.py　というplaywrightを使うアプリをexe化した際にコツがあったので、シェアします。

まずは、exe化するツールのインストール
```bash
pip install pyinstaller
```
# ポイントは、自分のPCのchrome.exeを活用すること！！　(chromiumを使おうとすると全然うまくいかなかった！！( ﾉД`)ｼｸｼｸ…)

        async with async_playwright() as p:
            browser = await p.chromium.launch(
                headless=False,
                executable_path="C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",  # 絶対パス
                args=['--disable-blink-features=AutomationControlled']
            )

# launch_inspector.py　のコード

```python
# launch_inspector.py
import asyncio
import argparse
import sys
from urllib.parse import urlparse
from playwright.async_api import async_playwright

# --- playwright-stealth のインポート試行 ---
try:
    from playwright_stealth import stealth_async
    _STEALTH_AVAILABLE = True
except ImportError:
    _STEALTH_AVAILABLE = False
# --- ここまで ---


# --- ロギング関数 ---
def log_info(msg):
    print(f"INFO: {msg}")


def log_warning(msg):
    print(f"WARNING: {msg}")


def log_error(msg):
    print(f"ERROR: {msg}")


async def launch_browser(target_url: str, use_stealth: bool):
    """
    指定されたURLでブラウザを起動し、Playwright Inspectorを表示します。

    Args:
        target_url: 開くURL
        use_stealth: stealth_async を使用するかどうか
    Returns:
        正常終了時は0、エラー発生時は1
    """
    try:
        async with async_playwright() as p:
            browser = await p.chromium.launch(
                headless=False,
                executable_path="C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",  # 絶対パス
                args=['--disable-blink-features=AutomationControlled']
            )
            context = await browser.new_context(
                viewport={'width': 1280, 'height': 720},
                locale='ja-JP',
                user_agent=(
                    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                    'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
                ),
                timezone_id='Asia/Tokyo',
                java_script_enabled=True,
                extra_http_headers={'Accept-Language': 'ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7'}
            )

            if use_stealth and _STEALTH_AVAILABLE:
                await stealth_async(context)
                log_info("Stealth mode enabled.")
            elif use_stealth and not _STEALTH_AVAILABLE:
                log_warning("Stealth mode requested, but library not found.")

            page = await context.new_page()

            try:
                await page.goto(target_url, timeout=60000, wait_until="domcontentloaded")
            except Exception as goto_error:
                log_error(f"Failed to navigate to {target_url}: {goto_error}")
                await browser.close()
                return 1  # エラーコードを返す

            print("-" * 50)
            print(">>> Playwright Inspector を起動します <<<")
            if use_stealth and _STEALTH_AVAILABLE:
                print("(高度なステルスモードが有効です)")
            else:
                print("(基本的なステルス対策のみ有効です)")
            print("Inspectorウィンドウの [Record ▶️] ボタンを押して操作を記録・確認できます。")
            print("記録や確認が終わったら、Inspectorウィンドウを閉じるか、")
            print("   再生ボタン (Resume) ▶️ を押すとスクリプトが終了します。")
            print("-" * 50)

            await page.pause()
            await browser.close()
            log_info("Browser closed.")

    except Exception as e:
        log_error(f"An error occurred: {e}")
        return 1  # エラーコードを返す

    return 0  # 正常終了コードを返す


def is_valid_url(url):
    """
    URLが有効かどうかをチェックします。

    Args:
        url: チェックするURL

    Returns:
        有効なURLの場合はTrue、それ以外はFalse
    """
    try:
        result = urlparse(url)
        return all([result.scheme, result.netloc])
    except:
        return False


def main():
    """
    コマンドライン引数を解析し、ブラウザを起動します。

    Returns:
        正常終了時は0、エラー発生時は1
    """
    parser = argparse.ArgumentParser(
        description="Launch Chromium with Playwright Inspector."
    )
    parser.add_argument(
        "url",
        help="The target URL to open (e.g., https://www.google.com)"
    )
    parser.add_argument(
        "--no-stealth",
        action="store_true",
        help="Disable applying playwright-stealth modifications."
    )

    args = parser.parse_args()

    if not is_valid_url(args.url):
        print("Error: Invalid URL provided.")
        return 1

    use_stealth = not args.no_stealth
    return asyncio.run(launch_browser(args.url, use_stealth))


if __name__ == "__main__":
    sys.exit(main())
```


—
# ###
以下のコマンドで、launch_inspector.spec　を作成する。
```bash
pyi-makespec --onefile --console --noupx launch_inspector.py
```
# ###

—

# ###
launch_inspector.spec　の内容 
以下の通り作成したら、上手くいった。
ポイントは、自分のchromeを使うこと。
        # Chromeの実行ファイル
        ('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', '.'),
# ###

```python
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(
    ['launch_inspector.py'],
    pathex=[],
    binaries=[],
    datas=[
        # Chromiumの実行ファイル
        ('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', '.'),
        # Playwrightのディレクトリ全体
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright', 'playwright'),
        # Playwrightに必要なその他のデータファイル (必要に応じて追加)
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\chrome.app.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\chrome.csi.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\chrome.hairline.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\chrome.load.times.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\chrome.runtime.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\generate.magic.arrays.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\iframe.contentWindow.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\media.codecs.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\navigator.hardwareConcurrency.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\navigator.languages.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\navigator.permissions.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\navigator.platform.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\navigator.plugins.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\navigator.userAgent.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\navigator.vendor.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\utils.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\webgl.vendor.js', 'playwright_stealth\\js'),
        ('C:\\Users\\sinzy\\playwright-record-scraper3\\venv312\\Lib\\site-packages\\playwright_stealth\\js\\window.outerdimensions.js', 'playwright_stealth\\js'),

    ],
    hiddenimports=[],
    hookspath=[],  # hookspathを削除
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False
)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)

exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='launch_inspector',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=False,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True,
          disable_windowed_traceback=False,
          target_arch=None,
          codesign_identity=None,
          entitlements_file=None )
```


---

# 以下のコマンドで、exe化
```shell
pyinstaller launch_inspector.spec
```

これで、成功した！！
セキュリティソフトにも誤検知されなかった！！