"""爬取中医方剂大全的方剂列表：方剂名、详情页链接、所属分类，保存到 csv。

页面结构（2026-07 实测）：和中药大全一致——div.p_content 下按 h2 分成
20 个分类（解表剂、泻下剂、和解剂 ...），每个 h2 之后跟着若干
<a href="/wiki/xxx">方剂名</a>。共 721 条，其中 18 条是同一方剂
挂在两个分类下。

用法（在项目根目录执行）：
    python -m _001__clawler.03_formula_list
"""

import csv
import time

import requests
from bs4 import BeautifulSoup

from common.path_utils import get_file_path

BASE_URL = "https://zhongyibaike.com"
LIST_URL = "https://zhongyibaike.com/wiki/%E4%B8%AD%E5%8C%BB%E6%96%B9%E5%89%82"
OUTPUT_CSV = get_file_path("_001__clawler/formula_list.csv")

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
    )
}


def fetch_html(url, retries=3, timeout=30):
    """下载页面 html，失败时重试。"""
    for attempt in range(1, retries + 1):
        try:
            response = requests.get(url, headers=HEADERS, timeout=timeout)
            response.raise_for_status()
            # 站点返回 utf-8，显式指定避免 requests 猜错编码导致中文乱码
            response.encoding = "utf-8"
            return response.text
        except requests.RequestException as exc:
            print(f"第 {attempt}/{retries} 次请求失败: {exc}")
            if attempt == retries:
                raise
            time.sleep(2 * attempt)


def parse_formula_list(html):
    """从列表页 html 中解析出 [{分类, 方剂名, 链接}, ...]，并按链接去重。

    用 descendants 顺序遍历而不是 find_all('a')，这样才能知道每个链接
    落在哪个 h2 分类下面。
    """
    soup = BeautifulSoup(html, "html.parser")
    content = soup.find("div", class_="p_content")
    if content is None:
        raise RuntimeError("找不到 div.p_content，页面结构可能已经改版")

    formulas = []
    seen = set()
    category = None
    for element in content.descendants:
        name = getattr(element, "name", None)
        if name == "h2":
            category = element.get_text(strip=True)
        elif name == "a" and element.get("href", "").startswith("/wiki/"):
            formula_name = element.get_text(strip=True)
            href = element["href"]
            if not formula_name or href in seen:
                continue
            seen.add(href)
            formulas.append(
                {
                    "分类": category or "",
                    "方剂名": formula_name,
                    "链接": BASE_URL + href,
                }
            )
    return formulas


def save_to_csv(formulas, output_path):
    """写 csv。utf-8-sig 让 Excel 打开中文不乱码。"""
    with open(output_path, "w", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=["分类", "方剂名", "链接"])
        writer.writeheader()
        writer.writerows(formulas)


def main():
    print(f"正在抓取列表页: {LIST_URL}")
    html = fetch_html(LIST_URL)
    #分类 ：方剂  链接
    formulas = parse_formula_list(html)
    categories = {formula["分类"] for formula in formulas}
    print(f"解析到 {len(formulas)} 个方剂，{len(categories)} 个分类")

    save_to_csv(formulas, OUTPUT_CSV)
    print(f"已保存到: {OUTPUT_CSV}")


if __name__ == "__main__":
    main()
