# メルカリ検索サーバー（Firefox 常駐）

## 概要

メルカリはSPAかつBot検知が厳しいため、通常のHTTPリクエストではスクレイピングできない。
Firefox を常駐起動し、HTTP サーバーとして待機することで、Laravelからの検索リクエストに即座に応答する。
商品データの抽出は JavaScript 一括実行（`execute_script`）で高速化している。

## 仕組み

```
[ユーザー] → [Laravel] → HTTP GET → [mercari_server.py (port 5100)] → [常駐Firefox] → メルカリ検索
                                   ← JSON レスポンス ←
```

- ブラウザは起動時に1回だけ立ち上げ、以降はそのまま使い回す
- `browser_lock` による排他制御で同時リクエストも安全
- GUI モード（`--gui`）で起動すれば CAPTCHA を手動解除可能

## 起動方法

```bash
# ヘッドレスモード（デフォルト）
python python/mercari/mercari_server.py --port 5100

# GUIモード（CAPTCHA手動対応が必要な場合）
python python/mercari/mercari_server.py --port 5100 --gui

# 外部からのアクセスを許可（Docker/LAN内利用時）
python python/mercari/mercari_server.py --port 5100 --bind 0.0.0.0
```

起動後、Firefox がメルカリトップページを開いた状態で待機する。
あとは通常通り Laravel アプリ（価格比較など）を使えば、自動的にこのサーバー経由で検索される。

## API エンドポイント

### `GET /search`

メルカリ検索を実行して結果を返す。

| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
| `keyword` | string | ○ | 検索キーワード |
| `min_price` | int | - | 最低価格 |
| `max_price` | int | - | 最高価格 |
| `max_items` | int | - | 最大取得件数（デフォルト: 20） |
| `status_filter` | string | - | `on_sale`（販売中）/ `sold_out`（売り切れ） |

レスポンス例:
```json
[
  {
    "platform": "mercari",
    "title": "商品名",
    "url": "https://jp.mercari.com/item/m12345",
    "price": 3000,
    "image": "https://static.mercdn.net/...",
    "shop": "メルカリ",
    "condition": "中古",
    "status": "on_sale"
  }
]
```

### `GET /health`

サーバーの稼働状態を確認。

### `GET /debug`

デバッグ用。ブラウザの現在のページタイトル・URL・HTML長を返す。

### `POST /shutdown`

サーバーを停止してブラウザを閉じる。

## 外部サーバー（Lightsail等）からの接続

メルカリサーバーがLAN内（例: `192.168.11.200`）で動作している場合、外部サーバーから直接アクセスできない。
Cloudflare Tunnel を使ってセキュアに公開する。

### 設定手順

1. Cloudflare Tunnel の「Published application routes」で新しいルートを追加:

| 項目 | 値 |
|---|---|
| Subdomain | `mercari` |
| Domain | `koh-devvv.com` |
| Path | （空） |
| Service | `http://192.168.11.200:5100` |

2. Lightsail側の `.env` を更新:

```
MERCARI_SERVER_URL=https://mercari.koh-devvv.com
```

### セキュリティ

Cloudflare Tunnel 経由で公開する場合、Cloudflare Access Policy でアクセス制限を設定すること:
- Lightsail の固定IPからのみ許可する
- またはサービストークン認証を設定する

### 接続パターン一覧

| 環境 | MERCARI_SERVER_URL | 備考 |
|---|---|---|
| ローカル開発 | `http://127.0.0.1:5100` | 同一マシンで起動 |
| LAN内 | `http://192.168.11.200:5100` | NAS/別PCで起動 |
| 外部（Lightsail等） | `https://mercari.koh-devvv.com` | Cloudflare Tunnel経由 |
| Docker（同一ホスト） | `http://localhost:5100` | コンテナのポートマッピング |

## 技術構成

### Python（サーバー側）

| ファイル | 説明 |
|---|---|
| `python/mercari/mercari_server.py` | Firefox 常駐 HTTP サーバー |
| `python/mercari/test_mercari_server.py` | ユニットテスト（29件） |
| `python/mercari/mercari_search.py` | 旧版（headless Chrome、現在は未使用） |

### Laravel（クライアント側）

| ファイル | 説明 |
|---|---|
| `app/Services/Shopping/MercariService.php` | HTTP で mercari_server に問い合わせ |
| `app/Http/Controllers/Shopping/PriceCompareController.php` | 価格比較画面から呼び出し |
| `tests/Feature/MercariServiceTest.php` | MercariService のテスト（13件） |

### 処理フロー

1. `mercari_server.py` を起動（Firefox 常駐）
2. ユーザーが価格比較画面で検索
3. `PriceCompareController` → `MercariService::search()`
4. `MercariService` が `MERCARI_SERVER_URL/search` に HTTP GET
5. `mercari_server.py` が JavaScript 一括実行で商品データを抽出
6. 結果を JSON で返却

## 必要な環境変数

| 変数名 | 説明 | デフォルト |
|---|---|---|
| `MERCARI_SERVER_URL` | サーバーURL | `http://127.0.0.1:5100` |
| `MERCARI_SERVER_TIMEOUT` | タイムアウト秒数 | `30` |

## 必要なPythonパッケージ

```bash
pip install selenium webdriver-manager
```

## テスト

```bash
# Python側（ブラウザ不要、モック使用）
cd python/mercari && python -m pytest test_mercari_server.py -v

# Laravel側（HTTP通信をモック化）
php artisan test tests/Feature/MercariServiceTest.php
```

## 注意事項

- サーバーは **事前に手動で起動** しておく必要がある
- サーバーが停止している場合、Laravel側でエラーメッセージが表示される（`MercariService::isAvailable()` で事前チェック可能）
- GUIモードの場合、Firefox ウィンドウは閉じないこと（閉じるとブラウザセッションが切れる）
- 長時間放置するとメルカリ側でセッションが切れる場合がある → ブラウザ自動再起動機能（`ensure_browser()`）で対応済み
- Yahoo!フリマ検索は `PayPayFrimaService.php`（PHP直接取得）に移行済み。Python側のYahoo!フリマ機能は削除済み
