Files
cpc-price-cli/cpc_price.py
2026-03-20 01:17:01 +08:00

167 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""
中油歷史油價查詢 CLI
資料來源: https://www.cpc.com.tw/historyprice.aspx?n=2890
"""
import argparse
import csv
import json
import sys
from datetime import datetime
try:
import requests
from bs4 import BeautifulSoup
except ImportError:
print("請先安裝依賴套件: pip install requests beautifulsoup4", file=sys.stderr)
sys.exit(1)
URL = "https://www.cpc.com.tw/historyprice.aspx?n=2890"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
}
def roc_to_ad(roc_date: str) -> str:
"""將民國年份 (115/03/16) 轉換為西元年份 (2026/03/16)"""
parts = roc_date.strip().split("/")
if len(parts) != 3:
return roc_date
year_ad = int(parts[0]) + 1911
return f"{year_ad}/{parts[1]}/{parts[2]}"
def fetch_prices() -> list[dict]:
"""抓取油價資料,回傳 list of dict"""
try:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
resp = requests.get(URL, headers=HEADERS, timeout=15, verify=False)
resp.raise_for_status()
resp.encoding = "utf-8"
except requests.RequestException as e:
print(f"連線失敗: {e}", file=sys.stderr)
sys.exit(1)
soup = BeautifulSoup(resp.text, "html.parser")
table = soup.find("table")
if not table:
print("找不到價格表,頁面結構可能已變更。", file=sys.stderr)
sys.exit(1)
rows = table.find_all("tr")
headers = [th.get_text(strip=True) for th in rows[0].find_all(["th", "td"])]
if not headers:
print("找不到表頭。", file=sys.stderr)
sys.exit(1)
records = []
for row in rows[1:]:
cells = [td.get_text(strip=True) for td in row.find_all("td")]
if len(cells) == len(headers):
records.append(dict(zip(headers, cells)))
return records
def print_table(records: list[dict], use_ad: bool) -> None:
"""以對齊表格格式輸出"""
if not records:
print("無資料")
return
headers = list(records[0].keys())
col_widths = [max(len(h), max(len(r[h]) for r in records)) + 2 for h in headers]
def fmt_row(values):
return " ".join(v.ljust(w) for v, w in zip(values, col_widths))
separator = " ".join("-" * w for w in col_widths)
print(fmt_row(headers))
print(separator)
for r in records:
row = list(r.values())
if use_ad:
row[0] = roc_to_ad(row[0])
print(fmt_row(row))
def print_csv(records: list[dict], use_ad: bool) -> None:
"""以 CSV 格式輸出"""
if not records:
return
writer = csv.DictWriter(sys.stdout, fieldnames=records[0].keys())
writer.writeheader()
for r in records:
if use_ad:
r = dict(r)
date_key = list(r.keys())[0]
r[date_key] = roc_to_ad(r[date_key])
writer.writerow(r)
def print_json(records: list[dict], use_ad: bool) -> None:
"""以 JSON 格式輸出"""
if use_ad:
out = []
for r in records:
r = dict(r)
date_key = list(r.keys())[0]
r[date_key] = roc_to_ad(r[date_key])
out.append(r)
else:
out = records
print(json.dumps(out, ensure_ascii=False, indent=2))
def main():
parser = argparse.ArgumentParser(
description="查詢台灣中油歷史油價",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
輸出格式範例:
%(prog)s # 表格(預設)
%(prog)s -f csv # CSV
%(prog)s -f json # JSON
%(prog)s --ad # 日期轉西元年
%(prog)s -f json --ad # JSON + 西元年
""",
)
parser.add_argument(
"-f", "--format",
choices=["table", "csv", "json"],
default="table",
help="輸出格式 (預設: table)",
)
parser.add_argument(
"--ad",
action="store_true",
help="將民國年份轉換為西元年份",
)
parser.add_argument(
"--latest",
action="store_true",
help="只顯示最新一筆資料",
)
args = parser.parse_args()
records = fetch_prices()
if args.latest:
records = records[:1]
if args.format == "table":
print_table(records, args.ad)
elif args.format == "csv":
print_csv(records, args.ad)
elif args.format == "json":
print_json(records, args.ad)
if __name__ == "__main__":
main()