- twse-monthly-revenue.py: fetch monthly revenue from Yahoo Finance TW via Browserless (MOPS blocks direct API) - twse-pe-pbr.py: fetch PE ratio, PBR, dividend yield from TWSE BWIBBU API Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
113 lines
3.6 KiB
Python
113 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
查詢 TWSE 個股本益比/股價淨值比/殖利率
|
||
|
||
用法:
|
||
python3 skills/stocks-query/scripts/twse-pe-pbr.py 2330
|
||
python3 skills/stocks-query/scripts/twse-pe-pbr.py 3481
|
||
|
||
資料來源:證交所 TWSE 公開 API(個股日本益比、殖利率及股價淨值比)
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import sys
|
||
import urllib.request
|
||
from datetime import datetime, timedelta
|
||
|
||
|
||
def fetch_pe_pbr(stock_no: str, date_str: str | None = None) -> dict:
|
||
"""從 TWSE 抓本益比/股價淨值比,自動往前回溯最多 3 個月找到有資料的月份。"""
|
||
if date_str:
|
||
dates_to_try = [date_str]
|
||
else:
|
||
today = datetime.now()
|
||
dates_to_try = []
|
||
for i in range(4):
|
||
dt = today - timedelta(days=30 * i)
|
||
dates_to_try.append(dt.replace(day=1).strftime("%Y%m%d"))
|
||
|
||
for d in dates_to_try:
|
||
url = (
|
||
f"https://www.twse.com.tw/rwd/zh/afterTrading/BWIBBU"
|
||
f"?date={d}&stockNo={stock_no}&response=json"
|
||
)
|
||
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
||
try:
|
||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||
data = json.loads(resp.read().decode("utf-8", errors="replace"))
|
||
except Exception:
|
||
continue
|
||
|
||
if data.get("stat") != "OK":
|
||
continue
|
||
|
||
rows = data.get("data", [])
|
||
fields = data.get("fields", [])
|
||
if not rows or not fields:
|
||
continue
|
||
|
||
# 取最新一筆
|
||
latest = rows[-1]
|
||
result = {}
|
||
for i, field in enumerate(fields):
|
||
val = str(latest[i]).strip() if i < len(latest) else "-"
|
||
result[field.strip()] = val
|
||
|
||
# 附上近期歷史
|
||
result["_history"] = []
|
||
for row in rows:
|
||
entry = {}
|
||
for i, field in enumerate(fields):
|
||
val = str(row[i]).strip() if i < len(row) else "-"
|
||
entry[field.strip()] = val
|
||
result["_history"].append(entry)
|
||
|
||
return result
|
||
|
||
raise RuntimeError(f"找不到 {stock_no} 的本益比/淨值比資料(已回溯 3 個月)")
|
||
|
||
|
||
def print_summary(stock_no: str, d: dict) -> None:
|
||
print(f"{stock_no}|本益比/股價淨值比摘要")
|
||
|
||
date_val = d.get("日期", "-")
|
||
pe = d.get("本益比", "-")
|
||
pbr = d.get("股價淨值比", "-")
|
||
dividend_yield = d.get("殖利率(%)", "-")
|
||
fiscal_year = d.get("股利年度", "-")
|
||
fiscal_quarter = d.get("財報年/季", "-")
|
||
|
||
print(f"• 最新日期:{date_val}")
|
||
print(f"• 本益比:{pe}")
|
||
print(f"• 股價淨值比:{pbr}")
|
||
if dividend_yield != "-" and "%" not in str(dividend_yield):
|
||
print(f"• 殖利率:{dividend_yield}%")
|
||
else:
|
||
print(f"• 殖利率:{dividend_yield}")
|
||
print(f"• 股利年度:{fiscal_year}")
|
||
print(f"• 財報年/季:{fiscal_quarter}")
|
||
|
||
# 近期趨勢
|
||
history = d.get("_history", [])
|
||
if len(history) > 1:
|
||
recent = history[-5:]
|
||
print(f"\n近期趨勢(最近 {len(recent)} 筆):")
|
||
for entry in recent:
|
||
h_date = entry.get("日期", "-")
|
||
h_pe = entry.get("本益比", "-")
|
||
h_pbr = entry.get("股價淨值比", "-")
|
||
h_dy = entry.get("殖利率(%)", "-")
|
||
print(f" {h_date}|PE {h_pe}|PBR {h_pbr}|殖利率 {h_dy}%")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
stock_no = sys.argv[1] if len(sys.argv) > 1 else "2330"
|
||
try:
|
||
data = fetch_pe_pbr(stock_no)
|
||
print_summary(stock_no, data)
|
||
except Exception as e:
|
||
print(f"取得失敗:{e}", file=sys.stderr)
|
||
sys.exit(1)
|