#!/usr/bin/env python3 """ 用 Browserless 抓 Yahoo 股利政策頁,輸出精簡摘要 用法: python3 skills/stocks-query/scripts/yahoo-dividend-browserless.py 2330 python3 skills/stocks-query/scripts/yahoo-dividend-browserless.py 0050 環境變數(可覆蓋): BROWSERLESS_ENDPOINT=http://192.168.42.124:13000 BROWSERLESS_TOKEN=6R0W53R135510 """ import json import os import re import sys import urllib.error import urllib.request 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") STOCK_NO = sys.argv[1] if len(sys.argv) > 1 else "2330" def fetch_html(stock_no: str) -> str: url = f"https://tw.stock.yahoo.com/quote/{stock_no}.TW/dividend" 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 parse_dividend(html: str, stock_no: str) -> dict: name = pick(html, r'property="og:title" content="([^"(]+)\([0-9]+\.TW\)', "").strip() if not name: name = pick(html, r"