#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
crawl_jshbgz_hpgs.py — 江苏环保公众网「环评公示」栏目
http://www.jshbgz.cn/hpgs/
TRS WCM 系:
- 列表: ./YYYYMM/tYYYYMMDD_ID.html 链接 + 标题
- 分页: TRS createPageHTML(50,0,"index","html") → index_N.html (20条/页, 共50页≈1000条)
- 详情: /hpgs/YYYYMM/tYYYYMMDD_ID.html
    标题 td.black21, 日期 发布时间：YYYY-MM-DD, 正文 td#Zoom > div.TRS_Editor > div.Custom_UnionStyle
- 附件: 正文内 ./P0*.docx (公众意见表等), 内嵌URL保留
"""
import argparse
import html as html_lib
import os
import re
import sqlite3
import time
import urllib.parse

import requests

BASE = "http://www.jshbgz.cn"
SITE_NAME = "江苏环保公众网"
SCRIPT_NAME = "crawl_jshbgz_hpgs.py"
GROUP_NAME = "江苏"
HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36",
    "Accept-Language": "zh-CN,zh;q=0.9",
}
TIMEOUT = 30


def clean_title(t):
    """标题清洗: strip &middot;&nbsp;&#32; 实体前缀与空白"""
    if not t:
        return ""
    t = t.strip()
    t = re.sub(r"&middot;|&nbsp;|&#160;|&#32;", "", t, flags=re.I)
    t = t.strip()
    t = re.sub(r"\s+", " ", t)
    return t


def html_to_text(html_seg):
    """HTML -> 保留 <p> 段落 HTML（search_app 渲染依赖 <p>/<table> 检测），段内剥标签,
    附件内嵌 <a href> 绝对URL, 去 script/style, 删空段落"""
    seg = html_seg
    # 去 script/style
    seg = re.sub(r"<(script|style)[^>]*>.*?</\1>", "", seg, flags=re.S | re.I)
    # 段落规范化: 块级标签 -> <p> 段落边界
    seg = re.sub(r"<(p|div|li|tr|h[1-6])[^>]*>", "\n", seg, flags=re.I)
    seg = re.sub(r"</(p|div|li|tr|h[1-6])>", "\n", seg, flags=re.I)
    seg = re.sub(r"<br\s*/?>", "\n", seg, flags=re.I)
    # 附件链接先提取占位（防剥标签误伤）
    links = []
    def _link_repl(m):
        href, txt = m.group(1), m.group(2)
        txt = re.sub(r"<[^>]+>", "", txt)
        txt = html_lib.unescape(txt).strip()
        if not txt or href.startswith("#") or href.startswith("javascript"):
            return txt or ""
        abs_url = urllib.parse.urljoin(BASE, href)
        links.append((href, abs_url, txt))
        return f"__LINK__{len(links)}__"
    seg = re.sub(r'<a\s[^>]*href="([^"]+)"[^>]*>(.*?)</a>', _link_repl, seg, flags=re.S | re.I)
    # 图片保留
    def _img_repl(m):
        src = m.group(1)
        abs_src = urllib.parse.urljoin(BASE, src)
        return f'<img src="{abs_src}">'
    seg = re.sub(r'<img[^>]*src="([^"]+)"[^>]*>', _img_repl, seg, flags=re.I)
    # 表格保留（占位防剥标签）
    tables = []
    def _table_repl(m):
        tables.append(m.group(0))
        return f"__TABLE__{len(tables)}__"
    seg = re.sub(r"<table.*?</table>", _table_repl, seg, flags=re.S | re.I)
    # 剥剩余标签
    seg = re.sub(r"<[^>]+>", "", seg)
    seg = html_lib.unescape(seg)
    # 还原链接/表格
    for i, (href, abs_url, txt) in enumerate(links, 1):
        seg = seg.replace(f"__LINK__{i}__", f'<a href="{abs_url}" target="_blank">{txt}</a>')
    for i, t in enumerate(tables, 1):
        seg = seg.replace(f"__TABLE__{i}__", t)
    # 按 \n 切段，逐段清洗成 <p>
    parts = []
    for line in seg.split("\n"):
        line = line.strip()
        if not line:
            continue
        line = re.sub(r"[ \t\u00a0]+", " ", line)
        if not line:
            continue
        # 段内含表格/图片/链接 → 直接 <p> 包裹
        parts.append(f"<p>{line}</p>")
    return "\n".join(parts), links


def parse_list(html_src):
    """列表页 -> [(title, art_url, pub_date)]"""
    items = re.findall(
        r'<a href="(\./20\d{4}/t\d{8}_\d+\.html)"[^>]*>(.*?)</a>',
        html_src, re.S)
    out = []
    for href, t in items:
        title = clean_title(t)
        if not title:
            continue
        art_url = urllib.parse.urljoin(BASE + "/hpgs/", href)
        # 日期从 URL 提取 tYYYYMMDD
        m = re.search(r"t(\d{4})(\d{2})(\d{2})_", href)
        pub = "%s-%s-%s" % (m.group(1), m.group(2), m.group(3)) if m else ""
        out.append((title, art_url, pub))
    return out


def parse_detail(html_src, art_url):
    """详情页 -> (title, publish_date, body_html, links)"""
    # 标题
    m = re.search(r'<td align="center" class="black21">(.*?)</td>', html_src, re.S)
    title = clean_title(m.group(1)) if m else ""
    if not title:
        m = re.search(r"<title>(.*?)</title>", html_src, re.S)
        if m:
            title = clean_title(m.group(1).split("-")[0])
    # 日期 发布时间：</td><td>2026-08-06</td>（td 标签隔开）
    m = re.search(r"发布时间[：:]\s*(?:</td>)?\s*(?:<td[^>]*>)?\s*(\d{4})-(\d{1,2})-(\d{1,2})", html_src)
    publish = ""
    if m:
        publish = "%s-%02d-%02d" % (m.group(1), int(m.group(2)), int(m.group(3)))
    # 正文: td#Zoom 深度计数到闭合 </td>
    body_html = ""
    zi = html_src.find('id="Zoom"')
    if zi < 0:
        zi = html_src.find("id='Zoom'")
    if zi >= 0:
        seg = html_src[zi:]
        # 跳过 td 开标签
        start = seg.find(">")
        if start >= 0:
            start += 1
            depth = 1  # td 本身
            pos = start
            while pos < len(seg):
                # 找下一个 <td 或 </td>
                td_open = seg.find("<td", pos)
                td_close = seg.find("</td>", pos)
                if td_close < 0:
                    break
                if td_open >= 0 and td_open < td_close:
                    depth += 1
                    pos = td_open + 3
                else:
                    depth -= 1
                    if depth == 0:
                        body_html = seg[start:td_close].strip()
                        # 附件区: Zoom td 闭合后 <tr><td><a href="./P0*.docx">附件名</a></td></tr>
                        att_seg = seg[td_close:td_close + 2000]
                        for m in re.finditer(
                                r'<a href="(\./[^"]+\.(?:docx?|pdf|xlsx?|rar|zip|7z))"[^>]*>(.*?)</a>',
                                att_seg, re.S | re.I):
                            ahref = m.group(1)
                            atxt = clean_title(re.sub(r"<[^>]+>", "", m.group(2)))
                            if atxt:
                                abs_a = urllib.parse.urljoin(BASE + "/hpgs/", ahref)
                                body_html += f'\n<p><a href="{abs_a}" target="_blank">{atxt}</a></p>'
                        break
                    pos = td_close + 5
    return title or None, publish or None, body_html, []


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--pages", type=int, default=1, help="抓取页数")
    ap.add_argument("--db", default="/mnt/data/search.db", help="SQLite 数据库路径")
    ap.add_argument("--script-name", default=SCRIPT_NAME)
    args = ap.parse_args()

    db_path = args.db
    script_name = args.script_name
    if not db_path.startswith("/mnt/"):
        db_path = os.path.expanduser(db_path)

    conn = sqlite3.connect(db_path, timeout=60)
    cur = conn.cursor()
    new_count = 0
    dup_count = 0
    err_count = 0

    session = requests.Session()

    for page in range(1, args.pages + 1):
        if page == 1:
            url = f"{BASE}/hpgs/"
        else:
            url = f"{BASE}/hpgs/index_{page}.html"
        try:
            r = session.get(url, headers=HEADERS, timeout=TIMEOUT)
            r.encoding = "utf-8"
        except Exception as e:
            print(f"[{page}] 请求失败 {url}: {e}")
            err_count += 1
            continue
        items = parse_list(r.text)
        print(f"[{page}] 列表 {len(items)} 条 {url}")
        if not items:
            continue
        for title, art_url, pub_date in items:
            try:
                cur.execute("SELECT COUNT(*) FROM gov_raw WHERE page_url=? AND script_name=?", (art_url, script_name))
                if cur.fetchone()[0] > 0:
                    dup_count += 1
                    continue
                dr = session.get(art_url, headers=HEADERS, timeout=TIMEOUT)
                dr.encoding = "utf-8"
                d_title, d_date, content_html, links = parse_detail(dr.text, art_url)
                if not content_html.strip():
                    print(f"  跳过空正文: {title[:30]}")
                    dup_count += 1
                    continue
                if d_title:
                    title = d_title
                if d_date:
                    pub_date = d_date
                content, links_out = html_to_text(content_html)
                att_txt = "\n\n".join(a[2] for a in links_out) if links_out else ""
                summary = content[:500] if content else ""
                cur.execute(
                    "INSERT INTO gov_raw (site_name, source_url, page_url, title, publish_date, date_rank, summary, status, category, visits, content, tags, industry, attachments, group_name, has_table, script_name) "
                    "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
                    (SITE_NAME, art_url, art_url, title, pub_date, 0, summary, "", "", 0, content, "",
                     "other", att_txt, GROUP_NAME, 1 if "<table" in content else 0, script_name),
                )
                # ⚠️ 2026-08-11 修复: 删除手动 INSERT gov_search —— FTS 触发器 trg_gov_raw_fts_ins
                # 已自动插入 gov_search（rowid=gov_raw.id），手动再插撞主键 IntegrityError 批量失败
                conn.commit()
                new_count += 1
                print(f"  + {pub_date} {title[:40]}")
            except Exception as e:
                err_count += 1
                print(f"  ERR {title[:30]}: {e}")
        time.sleep(0.3)

    conn.close()
    print(f"完成: 新增 {new_count} / 重复 {dup_count} / 错误 {err_count}")


if __name__ == "__main__":
    main()
