#!/usr/bin/env python3 """ 用 Browserless 抓 Yahoo 台股大盤(加權指數)即時行情。 用法: python3 stocks/yahoo-market-browserless.py python3 stocks/yahoo-market-browserless.py ^TWII 環境變數(可覆蓋): BROWSERLESS_ENDPOINT=http://192.168.42.124:13000 BROWSERLESS_TOKEN=6R0W53R135510 """ import os import re import sys import json import urllib.request import urllib.error from pathlib import Path def load_dotenv() -> None: """載入 .env(優先目前目錄,其次 workspace 根目錄)。""" candidates = [ Path.cwd() / ".env", Path(__file__).resolve().parents[3] / ".env", ] for env_path in candidates: if not env_path.exists(): continue for raw in env_path.read_text(encoding="utf-8").splitlines(): line = raw.strip() if not line or line.startswith("#") or "=" not in line: continue k, v = line.split("=", 1) k = k.strip() v = v.strip().strip('"').strip("'") if k and k not in os.environ: os.environ[k] = v break load_dotenv() ENDPOINT = os.getenv("BROWSERLESS_ENDPOINT") TOKEN = os.getenv("BROWSERLESS_TOKEN") if not ENDPOINT or not TOKEN: raise RuntimeError("缺少必要環境變數:BROWSERLESS_ENDPOINT / BROWSERLESS_TOKEN") SYMBOL = sys.argv[1] if len(sys.argv) > 1 else "^TWII" def fetch_html(symbol: str) -> str: url = f"https://tw.stock.yahoo.com/quote/{symbol}" api = f"{ENDPOINT}/content?token={TOKEN}" payload = { "url": url, "gotoOptions": {"waitUntil": "domcontentloaded", "timeout": 60000}, } req = urllib.request.Request( api, data=json.dumps(payload).encode("utf-8"), headers={"Content-Type": "application/json"}, method="POST", ) try: with urllib.request.urlopen(req, timeout=90) as resp: text = resp.read().decode("utf-8", errors="replace") status = resp.getcode() except urllib.error.HTTPError as e: body = e.read().decode("utf-8", errors="replace") if hasattr(e, "read") else str(e) raise RuntimeError(f"Browserless HTTP {e.code}: {body[:200]}") from e if status != 200: raise RuntimeError(f"Browserless HTTP {status}: {text[:200]}") if "TimeoutError" in text: raise RuntimeError("Browserless 導航逾時(TimeoutError)") return text def pick(html: str, pattern: str, default: str = "-") -> str: m = re.search(pattern, html, flags=re.S) return m.group(1) if m else default def to_float(num: str) -> float | None: try: if not num or num == "-": return None return float(num.replace(",", "")) except Exception: return None def parse_market(html: str, symbol: str) -> dict: # 名稱:優先取 title(加權指數(^TWII) 走勢圖 - Yahoo股市) name = pick(html, r"