#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
获嘉县人民政府 - 环境管理 爬虫
栏目: https://www.huojia.gov.cn/htmls/huanjingguanli/list-1.html
内容: 新乡市生态环境局获嘉分局 环评批复/审批意见公示 (工业项目环评)
CMS: 获嘉门户 dreamer-pagination (list-N.html 静态分页, 10条/页)
列表: <li class="list-li"><span class="date">YYYY-MM-DD HH:MM</span><a href="/htmls/huanjingguanli/YYYYMMDD/ID.html">完整标题</a>
  标题列表即完整 (无截断省略号); URL 雪花 ID 19位 (新) / 短 ID (旧)
分页: list-1.html 首页, list-2.html ... 越界空页自动停; CUTOFF 3年 → list-13
详情: https://www.huojia.gov.cn/htmls/huanjingguanli/YYYYMMDD/ID.html
  h1#title 完整标题; <i id="pubDate">2025-09-05 15:51</i> 日期
  正文: <div class="content"> ... <p><span style="仿宋">段落</span></p> ...
  ⚠️ div.content 后紧跟 上/下一篇 导航 (dreamer-prevnext) → 正文截到该标记前
  正文内附件 <a href="*.pdf"> 相对路径 urljoin(详情页URL) 绝对化
用法:
  python3 crawl_huojia_hjgl.py --pages=1
  python3 crawl_huojia_hjgl.py --pages=13   # CUTOFF 3年窗口
"""
import os, sys, re, time, json, html as html_mod
import requests
from urllib.parse import urljoin

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from crawler_lib import push_to_searchdb

BASE_URL = "https://www.huojia.gov.cn"
LIST_TPL = BASE_URL + "/htmls/huanjingguanli/list-%d.html"
SITE_NAME = "获嘉县人民政府-环境管理"
CATEGORY = "环境管理"
CUTOFF = "2023-08-11"   # 3 年窗口

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Referer": BASE_URL + "/htmls/huanjingguanli/list-1.html",
}
TIMEOUT = 30


def parse_args():
    pages = 0
    for a in sys.argv[1:]:
        if a.startswith("--pages="):
            pages = int(a.split("=")[1])
        elif a.startswith("--pages") and len(sys.argv) > sys.argv.index(a) + 1:
            pages = int(sys.argv[sys.argv.index(a) + 1])
    return pages


def clean_title(t):
    t = html_mod.unescape(t or "")
    t = re.sub(r"[\u200b\u200e\u200f\ufeff\xa0]", "", t)
    t = re.sub(r"\s+", " ", t)
    t = re.sub(r"^[•·\-—]\s*", "", t)
    return t.strip()


def http_get(url, timeout=TIMEOUT):
    for attempt in range(3):
        try:
            r = requests.get(url, headers=HEADERS, timeout=timeout)
            if r.status_code == 200:
                r.encoding = "utf-8"
                return r.text
        except Exception as e:
            if attempt == 2:
                print(f"  [WARN] GET failed {url}: {e}", file=sys.stderr)
                return None
        time.sleep(1.5)
    return None


def _abs_url(u, page_url):
    if u.startswith(("http://", "https://", "javascript:", "mailto:", "tel:", "data:", "#")):
        return u
    return urljoin(page_url, u)


def clean_content(raw_html, page_url):
    """正文清洗: 保留 <p> 段落 HTML, 剥 span/font 内联样式, 附件/图片绝对化"""
    if not raw_html:
        return ""
    raw_html = re.sub(r"<script[\s\S]*?</script>", "", raw_html)
    raw_html = re.sub(r"<style[\s\S]*?</style>", "", raw_html)
    # 附件/图片相对路径绝对化 (以详情页 URL 为基址)
    raw_html = re.sub(r'<a\s+href="([^"]+)"', lambda m: f'<a href="{_abs_url(m.group(1), page_url)}"', raw_html)
    raw_html = re.sub(r'<img\s+src="([^"]+)"', lambda m: f'<img src="{_abs_url(m.group(1), page_url)}"', raw_html)
    # 规范化 <p> 标签
    raw_html = re.sub(r"<p[^>]*>", "<p>", raw_html)
    # 剥 span 标签 (保留文本)
    raw_html = re.sub(r"</?span[^>]*>", "", raw_html)
    # 其余标签去属性
    raw_html = re.sub(r"<(table|tbody|thead|tr|td|th|br)([^>]*)>", lambda m: f"<{m.group(1)}>", raw_html)
    raw_html = re.sub(r"</(table|tbody|thead|tr|td|th|br)>", lambda m: f"</{m.group(1)}>", raw_html)
    # 剥残留标签 (⚠️ \b 边界防误伤 <img>)
    raw_html = re.sub(r"</?(?:font|b|strong|em|u|sup|sub)\b[^>]*>", "", raw_html, flags=re.I)
    # 空白清理
    raw_html = re.sub(r"[\u3000\xa0]", " ", raw_html)
    raw_html = re.sub(r"<p>\s*(?:&nbsp;)?\s*</p>", "", raw_html)
    raw_html = re.sub(r"\r\n", "\n", raw_html)
    return raw_html.strip()


def extract_attachments(content, page_url):
    """从正文提取附件链接, 返回 JSON 数组字符串"""
    atts = []
    seen = set()
    for am in re.finditer(r'<a\s+href="([^"]+)"[^>]*>(.*?)</a>', content, re.S):
        ahref, atext = am.group(1), re.sub(r"<[^>]+>", "", am.group(2)).strip()
        if re.search(r"\.(pdf|doc|docx|xls|xlsx|zip|rar|wps|7z|txt)(\?|$)", ahref, re.I) or "download" in ahref.lower():
            u = _abs_url(ahref, page_url)
            if u not in seen:
                seen.add(u)
                atts.append({"name": atext or u.split("/")[-1], "url": u})
    return json.dumps(atts, ensure_ascii=False) if atts else "[]"


def fetch_detail(url):
    """抓详情页: 返回 (正文HTML, 附件列表)"""
    html_text = http_get(url)
    if not html_text:
        return "", []
    atts = []
    # 正文: div.content 截到 上/下一篇 导航前
    body = ""
    m = re.search(r'<div class="content">([\s\S]*?)(?:dreamer-prevnext|<div[^>]*class="dis"|<div[^>]*class="share|分享：)', html_text)
    if m:
        body = clean_content(m.group(1), url)
    else:
        m2 = re.search(r'<div class="content">([\s\S]*?)</div>\s*</div>', html_text)
        if m2:
            body = clean_content(m2.group(1), url)
    # 正文内附件
    for a in re.finditer(r'<a\s+href="([^"]+)"[^>]*>(.*?)</a>', body, re.S):
        ahref = a.group(1)
        if re.search(r"\.(pdf|doc|docx|xls|xlsx|zip|rar|wps|7z)(\?|$)", ahref, re.I):
            atext = re.sub(r"<[^>]+>", "", a.group(2)).strip() or ahref.split("/")[-1]
            atts.append({"name": atext, "url": ahref})
    return body, atts


def main():
    pages = parse_args()
    if not pages:
        pages = 5

    all_items = []
    seen = set()
    for pg in range(1, pages + 1):
        html_text = http_get(LIST_TPL % pg)
        if not html_text:
            print(f"[PAGE {pg}] fetch failed, stopping")
            break
        items = re.findall(
            r'<li class="list-li">\s*<span class="date">([^<]+)</span>\s*<a href="([^"]+)"[^>]*>([^<]+)</a>',
            html_text)
        if not items:
            print(f"[PAGE {pg}] 0 items (分页边界), stopping")
            break
        for date_raw, href, title_raw in items:
            url = urljoin(BASE_URL, href)
            if url in seen:
                continue
            seen.add(url)
            title = clean_title(title_raw)
            if not title:
                continue
            pub_date = date_raw.strip()[:10]
            if pub_date and pub_date < CUTOFF:
                continue
            # 详情页: 完整标题 + 正文 + 附件
            body, atts = fetch_detail(url)
            content = body
            # 正文空/仅残留容器 (源站旧格式公告无正文) → 回退标题段落
            # ⚠️ 用 <[^>]*>? 宽松剥标签 (content 可能含未闭合标签残片 <ul class=' 剥不掉)
            plain = re.sub(r"<[^>]*>?", "", content or "")
            if not content or (len(plain.strip()) < 10 and "<img" not in content):
                content = f"<p>{title}</p>"
            attachments = json.dumps(atts, ensure_ascii=False) if atts else "[]"
            all_items.append({
                "site_name": SITE_NAME,
                "title": title,
                "pub_date": pub_date,
                "content": content,
                "source_url": url,
                "url": url,
                "attachments": attachments,
                "industry": "",
            })
        print(f"[PAGE {pg}] +{len(items)} items (total {len(all_items)})")
        time.sleep(0.6)

    print(f"\n有效 {len(all_items)} 条, 入库...")
    push_to_searchdb(all_items, CATEGORY)
    print(f"完成! 新增/更新 {len(all_items)} 条")


if __name__ == "__main__":
    main()
