#!/usr/bin/env python3 """ 新矿集团供应链平台 公告抓取工具 数据源:http://120.70.100.128:8096/(新站,2026-10 切换) - 纯服务端渲染(layui),无 JSON API,抓 HTML 列表页解析 - 列表页:/front/notice/cgxx?category=bid|procure|auction(GET 表单分页,pageNo 从 0 起,每页 10 条) - 详情页:/front/index/v_notice_view?id=<32hex>(含正文 + 附件 PDF 直链) 板块类型标签全集: bid 招标公告 / 变更&澄清公告 / 资格预审公告 / 中标候选人公示 / 中标公告 / 废标公告 procure 采购公告 / 变更公告 / 结果公示 / 其他公告 auction 竞价公告 / 竞价流拍 默认抓取"商机类 + 流标类"(对应旧站 C 寻源 + D 流标语义): 商机类 = 招标公告/采购公告/竞价公告/资格预审公告 流标类 = 废标公告/竞价流拍 --all-types 额外抓结果/其他类(中标候选人公示/中标公告/变更&澄清/结果公示/其他公告) 用法: python3 fetch-xkgroup.py # 差量更新(默认回溯到上次运行/7 天内) python3 fetch-xkgroup.py --all-types # 抓取所有公告类型 python3 fetch-xkgroup.py --from 2026-10-01 # 指定起始日期 python3 fetch-xkgroup.py --force # 强制重新抓取(不跳过已下载) python3 fetch-xkgroup.py --keyword 水泥 # 按关键词过滤(标题/发布单位) python3 fetch-xkgroup.py --max-pages 10 # 每板块最大翻页数(默认 5 页 = 50 条) 状态文件 xkgroup_downloaded.json 格式与旧版一致,CrawlerService.runGenericScript 依赖字段: id/title/org/publish_time/detail_url/amount/deadline/region/region_detail/downloaded_at """ import argparse import html as html_lib import json import os import re import sys from datetime import datetime, timedelta, date try: import requests except ImportError: print("缺少 requests 库,请安装: pip3 install requests") sys.exit(1) # ============ 配置区 ============ BASE_URL = "http://120.70.100.128:8096" LIST_URL = f"{BASE_URL}/front/notice/cgxx" PAGE_SIZE = 10 # 新站固定每页 10 条 MAX_PAGES_DEFAULT = 5 # 每板块默认最多翻 5 页(日增量远小于此,历史回填可加大) # 板块与类型标签 CATEGORIES = ["bid", "procure", "auction"] # 商机类 + 流标类(默认抓取,对应旧版 C+D) BUSINESS_TAGS = {"招标公告", "采购公告", "竞价公告", "资格预审公告"} FLOWN_TAGS = {"废标公告", "竞价流拍"} # 结果/其他类(--all-types 才抓) RESULT_TAGS = {"中标候选人公示", "中标公告", "变更&澄清公告", "变更公告", "结果公示", "其他公告"} # 多次询价标题正则 (匹配 二次/三次/四次/.../九次) RETRY_PATTERN = re.compile(r"[二三四五六七八九]次") # 发布组织白名单 (新矿供应链平台关注的发布方) ORG_WHITELIST = ( "新疆宝地矿业股份有限公司", "新疆火烧云铅锌矿有限责任公司", ) OUTPUT_BASE = os.path.expanduser("~/desktop/招标公告") HEADERS = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", "Accept": "text/html,application/xhtml+xml,*/*", "Referer": f"{BASE_URL}/", } # ============ 抓取层(新站 HTML 解析) ============ def fetch_list_page(category, page_no, timeout=30): """抓取列表页 HTML(GET 表单,pageNo 从 0 起)""" resp = requests.get( LIST_URL, params={"category": category, "pageNo": page_no}, headers=HEADERS, timeout=timeout, ) resp.raise_for_status() return resp.text def parse_list_html(html_text): """解析列表页 tbody#listTbody 中的公告条目 返回 [{"id", "url_path", "title", "tag", "publish_date", "org"}] """ items = [] tbody_m = re.search(r'
([\s\S]*?)', html_text) rows = re.split(r"