#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
潞城区人民政府(www.lc.gov.cn, 山西长治潞城) - 潞城经济技术开发区公告公示
URL: http://www.lc.gov.cn/ztzl/33025/9652/
CMS: 自定义 + TRS_Editor 正文
列表: div.aon-con > ul > li > a + span(日期)
分页: index_N.html (JS 生成, countPage=2)
详情: div.article-con > div.trs_editor_view
生产库 schema: gov_raw(id, 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)
gov_search: fts5(title, site_name, summary, tokenize=trigram) rowid 对齐 gov_raw.id
"""
import argparse
import hashlib
import os
import re
import sqlite3
import time
from html import unescape
from urllib.parse import urljoin

import requests

BASE_URL = "http://www.lc.gov.cn/ztzl/33025/9652/"
LIST_URL = "http://www.lc.gov.cn/ztzl/33025/9652/"
SITE_NAME = "潞城区人民政府-经开区公告公示"
SCRIPT_NAME = "crawl_lc_tzgg.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": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.9",
}


def clean_title(t):
    """清洗标题: strip &middot;&nbsp; 等实体前缀"""
    t = unescape(t or "")
    t = re.sub(r"^\s*(?:&middot;|·|&nbsp;|\s)+", "", t)
    t = re.sub(r"\s+", " ", t).strip()
    return t


def parse_list(html_text, base_url):
    """解析列表页, 返回 [(title, url, date), ...]
    注意: 列表页标题被站点截断(带省略号), 此处 title 仅作占位,
    入库前从详情页 <h2> 提取完整标题.
    """
    items = []
    pat = re.compile(
        r'<li>\s*<a href="([^"]+)"[^>]*>\s*([^<]+?)\s*</a>\s*<span>([^<]+?)</span>\s*</li>',
        re.S,
    )
    for m in pat.finditer(html_text):
        href, title, date = m.group(1), m.group(2), m.group(3).strip()
        if not href.startswith(("http://", "https://")):
            href = urljoin(base_url, href)
        # 只抓本域本专题 t 开头的文章链接
        if "/ztzl/33025/9652/" not in href or "t2026" not in href and "t2025" not in href:
            continue
        title = clean_title(title)
        if not title:
            continue
        # 日期格式 2026-07-29
        date = re.sub(r"\s+", " ", date).strip()
        items.append((title, href, date))
    seen = set()
    out = []
    for it in items:
        if it[1] not in seen:
            seen.add(it[1])
            out.append(it)
    return out


def extract_full_title(html_text, fallback=""):
    """从详情页提取完整标题 (div.con1 > div.t > h2)"""
    m = re.search(r'<div class="t">\s*<h3></h3>\s*<h2>(.*?)</h2>', html_text, re.S)
    if not m:
        m = re.search(r'<h2>(.*?)</h2>', html_text, re.S)
    if m:
        t = re.sub(r'<[^>]+>', '', m.group(1))
        t = clean_title(t)
        if t:
            return t
    return fallback


def parse_detail(html_text, url):
    """解析详情页, 返回 (content_html, attachments)"""
    # 正文: div.article-con > div.trs_editor_view (排除注释块)
    m = re.search(r'<div class="article-con">\s*<div class="trs_editor_view[^"]*"[^>]*>(.*?)</div>\s*</div>', html_text, re.S)
    if not m:
        # 回退: 任意 trs_editor_view
        m = re.search(r'<div class="trs_editor_view[^"]*"[^>]*>(.*?)</div>', html_text, re.S)
    if not m:
        return "", []
    body = m.group(1)
    # 移除注释块(如果有)
    body = re.sub(r'<!--.*?-->', '', body, flags=re.S)
    # 附件
    atts = re.findall(r'<a[^>]+href="([^"]+)"[^>]*>', body)
    atts = [urljoin(url, a) for a in atts if re.search(r'\.(?:pdf|docx?|xlsx?|zip|rar|wps|et|dps)(?:\?|$)', a, re.I)]
    return body, atts


def html_to_text(html_text):
    """HTML -> 文本, 保留表格、\n\n 分段"""
    tables = []
    def _save_tbl(m):
        tables.append(m.group(0))
        return f"@@TABLE{len(tables)-1}@@"
    html_text = re.sub(r'<table[\s\S]*?</table>', _save_tbl, html_text)
    html_text = re.sub(r'</?(?:p|div|tr|br|h[1-6]|li)[^>]*>', '\n', html_text)
    html_text = re.sub(r'</?(?:span|font|b|strong|i|em|u|o:p|o|a)[^>]*>', '', html_text)
    html_text = re.sub(r'\n{3,}', '\n\n', html_text)
    lines = [ln.strip() for ln in html_text.split('\n')]
    out = [ln for ln in lines if ln]
    text = '\n\n'.join(out)
    for i, t in enumerate(tables):
        text = text.replace(f"@@TABLE{i}@@", t)
    text = re.sub(r'\n{3,}', '\n\n', text)
    return text.strip()


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

    for page in range(1, args.pages + 1):
        if page == 1:
            url = LIST_URL
        else:
            url = f"http://www.lc.gov.cn/ztzl/33025/9652/index_{page-1}.html"
        try:
            r = requests.get(url, headers=HEADERS, timeout=30)
            r.encoding = "utf-8"
        except Exception as e:
            print(f"[{page}] 请求失败 {url}: {e}")
            err_count += 1
            continue
        items = parse_list(r.text, url)
        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 source_url=? AND script_name=?", (art_url, script_name))
                if cur.fetchone()[0] > 0:
                    dup_count += 1
                    continue
                dr = requests.get(art_url, headers=HEADERS, timeout=30)
                dr.encoding = "utf-8"
                # 完整标题优先从详情页 h2 提取(列表页标题被站点截断带省略号)
                full_title = extract_full_title(dr.text, title)
                content_html, atts = parse_detail(dr.text, art_url)
                if not content_html.strip():
                    print(f"  跳过空正文: {title[:30]}")
                    continue
                content = html_to_text(content_html)
                att_txt = "\n\n".join(atts) if atts else ""
                md5 = hashlib.md5((full_title + content[:500]).encode("utf-8")).hexdigest()
                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, full_title, pub_date, 0, summary, "", "", 0, content, "",
                     "other", att_txt, GROUP_NAME, 1 if "<table" in content else 0, script_name),
                )
                cur.execute(
                    "INSERT INTO gov_search (rowid, title, site_name, summary) VALUES (?,?,?,?)",
                    (cur.lastrowid, full_title, SITE_NAME, summary),
                )
                conn.commit()
                new_count += 1
                print(f"  + {pub_date} {full_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()
