为政策雷达 skill 添加11个政府网站的 Python 抓取工具

覆盖全部11个数据源的可复用 scraper 模块 tools/scrapers.py:
- gov.cn: 国务院政策文件库(HTML 解析)
- xinhuanet (news.cn): 新华社受权发布(HTML 解析)
- scio (pbc镜像): 国新办新闻发布会文字实录
- pbc: 央行货币政策/公告
- mof: 财政部政策
- ndrc: 发改委产业政策
- csrc: 证监会公告(HTML 解析)
- mofcom: 商务部政策(JSON API)
- people: 人民日报社论(HTML 解析)
- cctv: 央视新闻联播(JSONP API)
- ce: 经济日报政策解读

更新 SKILL.md 引用新工具,统一入口 scrape_list()/scrape_article()。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
推理链专家
2026-07-21 06:52:54 +00:00
parent 55f1e3be07
commit e492965ba1
5 changed files with 1003 additions and 0 deletions
+371
View File
@@ -0,0 +1,371 @@
---
name: policy-radar
description: 政策雷达 — 从中国政府公开信息源抓取最新政策原文,按「李铁四步法」进行结构化深度分析,输出投资方向的判断报告。输入关键词或时间范围,自动搜索→抓取→解读→输出。
---
# 政策雷达 (Policy Radar)
> 可执行的政策解读智能体:从政策原文抓取到投资方向判断的完整工作流。
> 基于李铁(铁哥)在中国资本市场的政策解读方法论。
---
## 1. 角色与目标
**角色:** 你是一个政策分析师,专为中国A股市场的投资者提供政策解读服务。你不会凭空猜测——所有判断都基于政策原文。
**目标产物:** 一份 `policy-analysis.md`,包含:
1. 本期抓取到的所有相关政策清单(附原文链接/引文)
2. 每条政策的四步法解读
3. 政策连续性验证(对比上一期分析)
4. 投资方向判断(大方向/中方向/小方向)
---
## 2. 可执行 SOP(六步)
### 阶段 A: 搜集政策原文
**Step 1 — 确认搜索范围。**
- 用户输入:关键词(如「科技金融」「消费」「房地产」)或时间范围(如「最近一周」「本月」)
- 默认:最近7天所有重要政策
**Step 2 — 搜索并抓取政策原文。**
按以下顺序依次搜索和抓取:
```
优先级 1: 中央政府门户
├── 中国政府网 (www.gov.cn) → 国务院政策文件库
├── 国新办官网 (www.scio.gov.cn) → 新闻发布会文字实录
└── 新华社 (www.xinhuanet.com) → 受权发布/时政
优先级 2: 部委官网
├── 中国人民银行 (www.pbc.gov.cn) → 货币政策/公告
├── 财政部 (www.mof.gov.cn) → 财政政策/预算报告
├── 证监会 (www.csrc.gov.cn) → 主席讲话/政策公告
├── 发改委 (www.ndrc.gov.cn) → 产业政策/规划
└── 商务部 (www.mofcom.gov.cn) → 外贸/消费政策
优先级 3: 权威媒体转载
├── 人民日报 (www.people.com.cn) → 社论/政策解读
├── 央视新闻 (news.cctv.com) → 新闻联播文字版
└── 经济日报 (www.ce.cn) → 部委政策解读
```
**搜索方法:** 使用 WebSearch 工具,搜索格式为:
- `site:gov.cn {关键词} {年份}`
- `国新办 新闻发布会 {年份}年{月份}`
- `中共中央政治局会议 {年份}年{月份}`
- `site:scio.gov.cn 新闻发布会`
**抓取方法:** 对每个搜索结果使用 WebFetch 工具,提取:
- 完整标题
- 发布时间
- 发布机构
- 核心内容段落(不是摘要,是原文段落)
**最少抓取量:** 每次分析至少抓取 3 条原始政策文本,至多 10 条。如无新政策,显式报告「本期无重大新政策」。
**Step 3 — 排除「非政策」。**
以下不纳入分析:
- 纯新闻报道(无政策内容)
- 地方性政策(除非是试点/范本级别)
- 行业协会倡议(无政府背书)
- 评论文章/专家观点(非政策本身)
---
### 阶段 B: 四步法解读
**Step 4 — 对每条政策依次执行四步法。**
#### 4.1 读原文:定位核心段落
从抓取到的原文中,找出以下关键信息:
- **政策目标**:这份文件要解决什么问题?
- **具体措施**:提到了哪些实际手段?(财政补贴/税收优惠/牌照放开/准入限制/金融支持……)
- **措辞强度**:用了什么级别的动词?(「支持」<「推动」<「加快」<「举国」<「志在必得」)
- **量化指标**:有没有具体数字?
#### 4.2 找新增:与上一期对比
将本期政策与上一期分析(如果存在)逐条对比:
| 对比维度 | 判断标准 |
|----------|----------|
| **新增表述** | 以前没出现过的新词/新概念 → 标记为「新方向」 |
| **排序变化** | 同一议题在不同文件中位置前移/后移 |
| **措辞升级** | 「支持」→「推动」→「举国体制」 |
| **新增措施** | 以前没有的具体手段首次出现 |
| **删除内容** | 以前强调的本期不再提及 |
**基线建立规则(硬步骤,不可跳过):**
- **非首次运行**: 将本期政策与上一期 `policy-analysis-{上期日期}.md` 的结论逐条对比,标注每条政策的「新增表述/措辞变化/排序变化/新增措施/删除内容」。
- **首次运行**: 必须执行「基线扫描」子步骤——搜索过去 3 个月的相关政策(至少覆盖本次关键词领域),建立一份简短的基线摘要(每条政策的核心方向+关键措辞),作为 4.2 的对比锚点。基线摘要写入报告的「基线对比」字段,不得省略。
- **后续运行输入**: 将上一期 `policy-analysis-{日期}.md` 作为上下文输入,使 4.2 的增量判断可验证,而非依赖 agent 的历史知识。
#### 4.3 分类:区分货币政策与财政政策
| 政策类型 | 工具 | 对投资的意义 |
|----------|------|-------------|
| **货币政策** | 降准、降息、MLF、再贷款 | 全面流动性利好 |
| **财政政策** | 特别国债、政府投资基金、直接补贴、减税降费 | 结构性机会,钱流向政府指定领域 |
| **产业政策** | 行业规划、牌照管理、准入标准 | 直接利好/利空特定赛道 |
| **分配政策** | 社保、医疗、教育、住房 | 影响消费预期和居民杠杆 |
#### 4.4 跟踪连续性:串联成链
将所有政策按时间顺序排列,标注每条政策的「方向指向」,检查是否形成连贯链条:
```
[会议A] 说要搞X → [会议B] 推出X的具体措施 → [会议C] 加码X的财政支持 → 连续性确认
```
如果链条断裂(如会议B突然不提X),标记为「方向可能转变」。
**跨赛道分支标注**: 当多条政策分属不同赛道但共享同一宏观主题(如绿色转型、扩大内需)时,用分支箭头标注关联关系,避免孤立分析。格式见第 4 节模板中的链条图示例。
---
### 阶段 C: 输出分析
**Step 5 — 生成结构化分析报告。**
使用以下模板(第4节)生成 `policy-analysis.md`
**Step 6 — 写入文件并汇报。**
将报告写入工作目录,并向用户汇报核心结论(不超过300字)。
---
## 3. 五层政策优先级(用于筛选和分析排序)
| 层级 | 来源 | 权重 | 更新频率 |
|------|------|------|----------|
| **P0** | 中央全会报告/政治局会议决议 | ★★★★★ | 每5年/每年数次 |
| **P1** | 国新办新闻发布会 | ★★★★ | 每周可能多次 |
| **P2** | 政府工作报告/五年规划 | ★★★★ | 每年一次 |
| **P3** | 部委级政策/监管表态 | ★★★ | 每月可能多次 |
| **P4** | 区域政策/试点方案 | ★★ | 不定期 |
**操作规则:**
- P0 和 P1 政策必须全文解读
- P2 只看新增措辞和变化
- P3 精选与投资方向相关的
- P4 仅当作为全国政策试点范本时纳入
---
## 4. 输出模板
```markdown
# 政策雷达分析报告 — {日期}
> 分析范围:{关键词/时间范围}
> 政策抓取数量:{数量} 条
> 基线对比:{本期/上一期日期}
## 1. 政策清单
| # | 政策名称 | 发布机构 | 日期 | 优先级 | 原文链接 |
|---|----------|----------|------|--------|----------|
| 1 | ... | ... | ... | P0 | ... |
| 2 | ... | ... | ... | P1 | ... |
## 2. 四步法逐条解读
### 政策 1: {标题}
**原文摘要:** {200字以内的原文核心段落}
**4.1 读原文:**
- 政策目标: ...
- 具体措施: ...
- 措辞强度: {动词+级别}
- 量化指标: ...
**4.2 找新增(vs 上期):**
- 新增表述: ...(如首次出现则标注「新方向」)
- 措辞变化: ...
- 是否首次提出: 是/否
**4.3 政策类型:** 货币/财政/产业/分配
**4.4 连续性验证:**
- 与前序政策的关系: {延续/加码/转向}
- 方向一致性: {一致/存疑/转向}
## 3. 连续性总览
```
{时间线图表,支持分支标注——跨赛道政策关系用分支箭头表示}
```
格式要求:
- **主线链条**: 同一议题的政策按时间顺序串联(延续 → 加码 → 转向)
- **分支关联**: 跨赛道政策用分支箭头标注(如环保 ←→ 新能源同属绿色转型),格式:`↗ [赛道名] 政策X: 关联描述`
- **每条节点**: 标注政策编号 + 方向措辞 + 赛道标签
示例:
```
[政策7: 适度宽松+供强需弱] (货币)
↓ 延续
[政策6: 宽松+框架改革] (货币)
↓ 一致
[政策5: 零碳园区100个] (产业·新能源)
│ ↗ [环保] 政策1: 同属绿色转型链条
↓ 一致
[政策2: 消费60万亿] (财政·消费)
│ ↗ [医疗] 政策3: 同日批复,同属15FYP批量落地
↓ 一致
[政策1: 县乡水体治理] (产业·环保)
```
**方向是否恒定:** 是/否
**转变信号(如有):** ...
**跨赛道关联(如有):** 政策X ←→ 政策Y 同属 {赛道名} 链条
## 4. 投资方向判断
### 大方向(市场整体)
- 判断: ...
- 依据: {政策编号} 中的 ...
### 中方向(赛道选择)
- 看好的赛道: ...
- 依据: {政策编号} 中的 ...
- 看空的赛道: ...
- 依据: {政策编号} 中的 ...
### 小方向(赛道内部)
- 具体机会: ...
- 风险提示: ...
## 5. 关键词
`关键词1` `关键词2` `关键词3` ...
## 6. 发布前自检(内嵌,发布前必须逐项打勾)
- [ ] **原文可追溯:** 每条政策都有原文链接或原文引文(部分/降级已标注)
- [ ] **四步法齐全:** 每条政策都经过 4.1→4.2→4.3→4.4 四步,缺失项已标注「原文不可得」
- [ ] **基线对比完成:** 非首次运行时对比了上一期报告;首次运行时执行了 3 个月基线扫描
- [ ] **方向判断有依据:** 每个投资判断(含看空赛道)都指向具体政策编号
- [ ] **无臆测:** 所有分析基于原文,推测部分标注「(推测,非原文)」
- [ ] **优先级标注:** 每条政策标注了 P0-P4 优先级
- [ ] **降级标注:** 原文获取不完整的政策已添加 ⚠️ 降级标注
```
---
## 5. 抓取工具与方法
### 5.1 可复用抓取工具(推荐)
skill 目录下提供了预构建的 Python 抓取模块 `tools/scrapers.py`,覆盖全部 11 个数据源。**优先使用这些工具,而非临时编写 curl 脚本。**
```python
import sys
sys.path.insert(0, '.claude/skills/policy-radar')
from tools.scrapers import scrape_list, scrape_article
# 获取某站点的最新列表
items = scrape_list("gov.cn") # 返回 [{title, date, url}, ...]
items = scrape_list("pbc") # 央行货币政策/公告
items = scrape_list("people") # 人民日报社论
# 抓取某篇文章正文
article = scrape_article("gov.cn", url) # 返回 [{text}, ...]
```
**支持的站点名称:** `gov.cn`, `xinhuanet`, `pbc`, `mof`, `ndrc`, `csrc`, `mofcom`, `people`, `cctv`, `ce`, `scio`
### 5.2 工具选择
| 工具 | 适用场景 | 缺点 |
|------|----------|------|
| **tools/scrapers.py(推荐)** | 直接调用预置抓取器,覆盖11个源 | 网站结构变更时需更新正则 |
| WebSearch | 通用搜索,找最新政策链接 | 依赖配额,可能 403 |
| WebFetch | 获取单个页面全文 | 依赖配额,部分站点证书问题 |
| curl + python3 | 直接 HTTP 请求 + HTML 解析 | 需处理编码和动态页面 |
**当预置工具不可用时,使用 curl + python3 作为降级方案。**
### 5.3 原文获取质量与降级标注
当抓取工具无法获取完整原文时,**必须显式降级标注**,不得让读者误以为分析覆盖了全文:
| 获取程度 | 判定条件 | 标注格式 |
|----------|----------|----------|
| **完整原文** | 获取到政策正文 ≥ 5 个核心段落 | 无需额外标注 |
| **部分原文** | 仅获取到摘要、解读页、或 < 5 个段落 | `> ⚠️ 本文基于 gov.cn 政策解读页整理,全文见原文链接` |
| **仅标题/元数据** | 链接不可达或返回 404/空白 | `> ⚠️ 原文不可得(链接失效/需登录),本条基于二手分析,结论可信度降低` |
**规则:**
- 降级标注写在每条政策的「原文摘要」字段开头。
- 仅标题/元数据时,该政策的「4.1 读原文」中缺失项标注「原文不可得」,**不得臆测填充**。
- 原文获取程度直接影响该政策在投资方向判断中的权重——仅标题的政策不应用于看空/看涨的核心依据。
### 5.4 抓取注意事项
1. **编码**: gov.cn 和 pbc.gov.cn 均使用 UTF-8curl 直接读取二进制再 decode 即可。
2. **超时**: 建议 `--max-time 15`,避免慢站点阻塞。
3. **反爬**: gov.cn 无严格反爬,但连续抓取间隔 1-2 秒为佳。
4. **动态内容**: gov.cn 政策列表页是服务端渲染,直接 curl 可获取。部委子页面(如 pbc.gov.cn 发文列表)同上。
5. **404 风险**: 政策 URL 可通过列表页 `/zhengce/``<a href>` 确认,不要猜测 content ID 序列。
6. **政策解读页**: gov.cn 政策解读(`/zhengce/jiedu/`)的链接格式与政策原文(`/zhengce/content/`)不同,注意区分。
---
## 6. 数据源 URL 速查
| 来源 | URL | 内容 | 工具名 |
|------|-----|------|--------|
| 中国政府网 | https://www.gov.cn/zhengce/ | 国务院政策文件库 | `gov.cn` |
| 新华社 | https://www.news.cn/ | 受权发布/时政 | `xinhuanet` |
| 国新办 | https://www.scio.gov.cn/xwfbh/ | 新闻发布会文字实录 | `scio` (镜像: pbc.gov.cn) |
| 央行 | https://www.pbc.gov.cn/goutongjiaoliu/113456/ | 货币政策/公告 | `pbc` |
| 财政部 | https://www.mof.gov.cn/zhengwuxinxi/ | 财政政策 | `mof` |
| 发改委 | https://www.ndrc.gov.cn/xxgk/ | 产业政策/规划 | `ndrc` |
| 证监会 | https://www.csrc.gov.cn/csrc/c100028/ | 证监会公告 | `csrc` |
| 商务部 | https://www.mofcom.gov.cn/ | 外贸/消费政策 | `mofcom` |
| 人民日报 | http://opinion.people.com.cn/ | 社论/评论 | `people` |
| 央视新闻 | https://news.cctv.com/ | 新闻联播文字版 | `cctv` |
| 经济日报 | http://www.ce.cn/ | 部委政策解读 | `ce` |
**注意事项:**
- 国新办 (scio.gov.cn) 有 JS 反爬挑战,工具使用央行镜像站获取国新办发布会实录
- 新华社内容已迁移至 www.news.cn(旧域名 xinhuanet.com 仅有历史内容)
- 人民日报社论版使用 opinion.people.com.cn 子域名
---
## 7. 发布前自检说明
> 自检清单已内嵌到报告模板第 6 节。发布前必须逐项打勾,全部通过方可提交。本节保留各检查项的详细说明供参考。
1. **原文可追溯** — 每条政策都有原文链接或原文引文。原文获取不完整时须有 ⚠️ 降级标注。
2. **四步法齐全** — 每条政策都经过 4.1→4.2→4.3→4.4 四步。缺失项不得臆测填充,应标注「原文不可得」。
3. **基线对比** — 非首次运行对比上一期 `policy-analysis-{日期}.md`;首次运行执行 3 个月基线扫描并建立基线摘要。
4. **方向判断有依据** — 每个投资判断(含看空赛道)指向具体政策编号,格式为 `{政策编号} 中的 ...`
5. **无臆测** — 所有分析基于原文。推测部分必须标注「(推测,非原文)」。
6. **优先级标注** — 每条政策在清单中标注了 P0-P4 优先级。
---
## 8. 反模式(出现即重写)
1. **凭空解读:** 没有抓取到原文就开始分析 → 必须至少3条原文
2. **只看标题:** 没有读原文段落就下判断 → 必须读核心段落
3. **忽略连续性:** 孤立分析单条政策 → 必须串联对比
4. **混淆政策类型:** 把财政政策和货币政策混为一谈 → 必须分类
5. **过度推断:** 原文没说但分析中写了 → 删除或标注「(推测,非原文)」
---
## 9. 一句话调用
> 「运行政策雷达,搜索{关键词/时间范围},抓取政策原文,按四步法解读,输出 policy-analysis.md。」
@@ -0,0 +1,632 @@
"""
政策雷达 - 政府网站抓取工具集
每个函数返回标准化格式: {"title": str, "date": str, "url": str, "content": str}
列表函数返回: [{"title": str, "date": str, "url": str}, ...]
"""
import re
import json
import urllib.request
import urllib.error
import ssl
from html import unescape
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"
TIMEOUT = 15
# 忽略 SSL 证书验证(部分政府站点使用自签名证书)
_SSL_CTX = ssl.create_default_context()
_SSL_CTX.check_hostname = False
_SSL_CTX.verify_mode = ssl.CERT_NONE
def _fetch(url, headers=None):
"""通用 HTTP GET,返回解码后的 HTML 文本"""
hdrs = {"User-Agent": USER_AGENT}
if headers:
hdrs.update(headers)
req = urllib.request.Request(url, headers=hdrs)
with urllib.request.urlopen(req, timeout=TIMEOUT, context=_SSL_CTX) as resp:
raw = resp.read()
for enc in ("utf-8", "gbk", "gb2312"):
try:
return raw.decode(enc)
except UnicodeDecodeError:
continue
return raw.decode("utf-8", errors="replace")
def _extract_paragraphs(html):
"""从 HTML 中提取正文段落(通用)"""
paragraphs = re.findall(r"<p[^>]*>(.*?)</p>", html, re.DOTALL)
result = []
for p in paragraphs:
text = re.sub(r"<[^>]+>", "", p).strip()
text = unescape(text)
if text and len(text) > 10:
result.append(text)
return result
# ============================================================
# 优先级 1: 中央政府门户
# ============================================================
def gov_search(keyword, page=1):
"""
中国政府网 - 搜索 API(可搜索新闻发布会等)
URL: https://sousuo.www.gov.cn/search-gov/data?t=zhengcelibrary&q=...
返回 JSON,包含 title/url/summary
注意: 索引有 1-2 周延迟,最新内容可能尚未收录
"""
import urllib.parse
q = urllib.parse.quote(keyword)
api_url = (
f"https://sousuo.www.gov.cn/search-gov/data?t=zhengcelibrary"
f"&q={q}&p={page}&n=20&timetype=timeqb&mintime=&maxtime="
)
html = _fetch(api_url)
try:
data = json.loads(html)
except json.JSONDecodeError:
return []
results = []
search_result = data.get("searchVO", {}).get("catMap", {}).get("zhengcelibrary", {})
items = search_result.get("listVO", []) if isinstance(search_result, dict) else []
for item in items:
title = re.sub(r"<[^>]+>", "", item.get("title", "")).strip()
url = item.get("url", "")
date = item.get("pubtime", "")[:10] if item.get("pubtime") else ""
if title and url:
results.append({"title": title, "date": date, "url": url})
return results
def gov_zhengce_list():
"""
中国政府网 - 国务院政策文件库
URL: https://www.gov.cn/zhengce/
结构: 服务端渲染,<li><a href="...">标题</a><span>日期</span></li>
"""
html = _fetch("https://www.gov.cn/zhengce/")
items = re.findall(
r'<li>\s*<a href="([^"]+)"[^>]*>\s*([^<]+)\s*</a>\s*<span>\s*([\d-]+)\s*</span>',
html,
)
results = []
for url, title, date in items:
title = title.strip()
if not title:
continue
if url.startswith("./"):
url = "https://www.gov.cn/zhengce/" + url[2:]
results.append({"title": title, "date": date, "url": url})
return results
def gov_article(url):
"""
中国政府网 - 政策文章正文
正文在 <div class="pages_content"> 内的 <p> 标签中
"""
html = _fetch(url)
# 提取标题
title_match = re.search(r'<title>([^<]+)</title>', html)
title = title_match.group(1).strip() if title_match else ""
# 提取发布日期
date_match = re.search(r"发布时间[:]\s*([\d年\-./]+)", html)
date = date_match.group(1).strip() if date_match else ""
# 提取正文
content_paras = _extract_paragraphs(html)
return {
"title": title,
"date": date,
"url": url,
"content": "\n\n".join(content_paras),
}
def scio_press_list():
"""
国新办 - 新闻发布会文字实录
注意: scio.gov.cn 有反爬保护,此处使用 PBC 站点作为镜像
PBC 的 "国新办举行新闻发布会" 系列完整保留了国新办原文
URL: https://www.pbc.gov.cn/goutongjiaoliu/113456/113469/index.html
"""
html = _fetch(
"https://www.pbc.gov.cn/goutongjiaoliu/113456/113469/index.html"
)
items = re.findall(
r'<a[^>]*href="(/goutongjiaoliu/[^"]+)"[^>]*>([^<]{10,})</a>',
html,
)
results = []
seen = set()
for href, title in items:
title = title.strip()
if title and href not in seen and "国新办" in title:
seen.add(href)
results.append(
{
"title": title,
"date": "",
"url": "https://www.pbc.gov.cn" + href,
}
)
return results
def xinhuanet_list():
"""
新华社 - 受权发布/时政
注意: 使用 www.news.cn(新域名),旧 xinhuanet.com 只有 2021 年旧数据
URL: https://www.news.cn/
结构: 服务端渲染,标题在 <div class="tit"><a href="URL">标题</a></div>
文章链接格式: /{channel}/{YYYYMMDD}/{32-hex}/c.html
"""
html = _fetch("https://www.news.cn/")
pattern = r'<div class="tit"[^>]*><a[^>]*href=[\'"](https?://www\.news\.cn/[^"\']+/20\d{6}/[a-f0-9]{32}/c\.html)[\'"][^>]*>([^<]+)</a>'
items = re.findall(pattern, html)
results = []
seen = set()
for url, title in items:
title = title.strip()
if title and url not in seen:
seen.add(url)
date_match = re.search(r"/(20\d{6})/", url)
date = ""
if date_match:
d = date_match.group(1)
date = f"{d[:4]}-{d[4:6]}-{d[6:8]}"
results.append({"title": title, "date": date, "url": url})
return results
def xinhuanet_article(url):
"""
新华社 - 文章正文
标题: <h1><span class="title">...</span></h1>
正文: <div id="detailContent"> 内的 <p> 标签
"""
html = _fetch(url)
title_match = re.search(r'<h1[^>]*>.*?<span class="title"[^>]*>([^<]+)</span>', html, re.DOTALL)
if not title_match:
title_match = re.search(r'<title>([^<]+)</title>', html)
title = title_match.group(1).strip() if title_match else ""
# 提取正文
content_match = re.search(r'id="detailContent"[^>]*>(.*?)</div>\s*(?:<div|</)', html, re.DOTALL)
if content_match:
content_html = content_match.group(1)
content_paras = _extract_paragraphs(content_html)
else:
content_paras = _extract_paragraphs(html)
return {
"title": title,
"date": "",
"url": url,
"content": "\n\n".join(content_paras),
}
# ============================================================
# 优先级 2: 部委官网
# ============================================================
def pbc_list():
"""
中国人民银行 - 货币政策/公告/沟通
URL: https://www.pbc.gov.cn/goutongjiaoliu/113456/113469/index.html
结构: 服务端渲染,<a href="/goutongjiaoliu/...">标题</a>
"""
html = _fetch(
"https://www.pbc.gov.cn/goutongjiaoliu/113456/113469/index.html"
)
items = re.findall(
r'<a[^>]*href="(/goutongjiaoliu/[^"]+)"[^>]*>([^<]{10,})</a>',
html,
)
results = []
seen = set()
for href, title in items:
title = title.strip()
if title and href not in seen and len(title) > 5:
seen.add(href)
# 从 URL 提取日期 (格式: 2026071518015458230)
date_match = re.search(r"/(20\d{6})\d{11}/", href)
date = ""
if date_match:
d = date_match.group(1)
date = f"{d[:4]}-{d[4:6]}-{d[6:8]}"
results.append(
{
"title": title,
"date": date,
"url": "https://www.pbc.gov.cn" + href,
}
)
return results
def pbc_article(url):
"""中国人民银行 - 文章正文(正文在 <p> 标签中)"""
html = _fetch(url)
title_match = re.search(r"<title>([^<]+)</title>", html)
title = title_match.group(1).strip() if title_match else ""
content_paras = _extract_paragraphs(html)
return {
"title": title,
"date": "",
"url": url,
"content": "\n\n".join(content_paras),
}
def mof_list():
"""
财政部 - 财政新闻/政策
URL: https://www.mof.gov.cn/zhengwuxinxi/caizhengxinwen/
结构: 服务端渲染,<a href="t202607...">标题</a> + 日期
"""
html = _fetch("https://www.mof.gov.cn/zhengwuxinxi/caizhengxinwen/")
items = re.findall(
r'href="([^"]*t2026[^"]+\.htm)"[^>]*>\s*([^<]{10,80})\s*</a>\s*(?:.*?(\d{4}[-/]\d{2}[-/]\d{2}))?',
html,
re.DOTALL,
)
results = []
seen = set()
for url, title, date in items:
title = title.strip()
if title and url not in seen:
seen.add(url)
if url.startswith("./") or url.startswith("t20"):
url = "https://www.mof.gov.cn/zhengwuxinxi/caizhengxinwen/" + url.replace("./", "")
results.append({"title": title, "date": date or "", "url": url})
return results
def mof_article(url):
"""财政部 - 文章正文"""
html = _fetch(url)
title_match = re.search(r"<title>([^<]+)</title>", html)
title = title_match.group(1).strip() if title_match else ""
date_match = re.search(r"发布日期[:]\s*([\d年\-./]+)", html)
date = date_match.group(1).strip() if date_match else ""
content_paras = _extract_paragraphs(html)
return {
"title": title,
"date": date,
"url": url,
"content": "\n\n".join(content_paras),
}
def ndrc_list(page=1):
"""
发改委 - 产业政策/规划
URL: https://www.ndrc.gov.cn/xxgk/
结构: 服务端渲染,<ul class="u-list"><li><a>标题</a><span>日期(YYYY/MM/DD)</span></li></ul>
分页: index_{page-1}.html
"""
if page == 1:
list_url = "https://www.ndrc.gov.cn/xxgk/"
else:
list_url = f"https://www.ndrc.gov.cn/xxgk/index_{page - 1}.html"
html = _fetch(list_url)
items = re.findall(
r'<a[^>]*href="([^"]+)"[^>]*>\s*([^<]{10,})\s*</a>\s*<span>\s*([\d/]+)\s*</span>',
html,
)
results = []
for url, title, date in items:
title = title.strip()
if not title:
continue
if url.startswith("./"):
url = "https://www.ndrc.gov.cn" + url[1:]
results.append({"title": title, "date": date, "url": url})
return results
def ndrc_article(url):
"""发改委 - 文章正文(正文在 div.TRS_Editor 内)"""
html = _fetch(url)
title_match = re.search(r"<title>([^<]+)</title>", html)
title = title_match.group(1).strip() if title_match else ""
editor_match = re.search(r'class="TRS_Editor"[^>]*>(.*?)</div>\s*<', html, re.DOTALL)
if editor_match:
content_paras = _extract_paragraphs(editor_match.group(1))
else:
content_paras = _extract_paragraphs(html)
return {
"title": title,
"date": "",
"url": url,
"content": "\n\n".join(content_paras),
}
def csrc_list(page=1):
"""
证监会 - 主席讲话/政策公告
URL: https://www.csrc.gov.cn/csrc/c100028/
结构: HTML 页面(API 返回空结果,需用 common/searchList 获取 HTML
HTML URL: /common/searchList/{channelid}?_isAgg=true&_isJson=true&_pageSize=18&_template=index&page={N}
"""
channel_id = "c100028"
url = f"https://www.csrc.gov.cn/common/searchList/{channel_id}?_isAgg=true&_isJson=true&_pageSize=18&_template=index&page={page}"
html = _fetch(url)
items = re.findall(
r'<a[^>]*href="(/csrc/c100028/c\d+/content\.shtml)"[^>]*>([^<]{10,})</a>',
html,
)
results = []
seen = set()
for path, title in items:
title = title.strip()
if title and path not in seen:
seen.add(path)
results.append(
{
"title": title,
"date": "",
"url": "https://www.csrc.gov.cn" + path,
}
)
return results
def csrc_article(url):
"""证监会 - 文章正文"""
html = _fetch(url)
title_match = re.search(r"<title>([^<]+)</title>", html)
title = title_match.group(1).strip() if title_match else ""
content_paras = _extract_paragraphs(html)
return {
"title": title,
"date": "",
"url": url,
"content": "\n\n".join(content_paras),
}
def mofcom_list():
"""
商务部 - 外贸/消费政策
URL: https://www.mofcom.gov.cn/
结构: 政策列表页使用 JSON API
API: /api-gateway/jpaas-publish-server/front/page/build/unit
"""
html = _fetch("https://www.mofcom.gov.cn/")
# 首页新闻是 SSR
items = re.findall(
r'<a[^>]*href="([^"]+)"[^>]*>\s*([^<]{10,80})\s*</a>',
html,
)
results = []
seen = set()
for url, title in items:
title = title.strip()
if title and url not in seen and len(title) > 8:
seen.add(url)
if url.startswith("/"):
url = "https://www.mofcom.gov.cn" + url
results.append({"title": title, "date": "", "url": url})
return results
def mofcom_article(url):
"""商务部 - 文章正文(正文在 div.wms-con 内)"""
html = _fetch(url)
title_match = re.search(r"<title>([^<]+)</title>", html)
title = title_match.group(1).strip() if title_match else ""
content_match = re.search(r'class="wms-con"[^>]*>(.*?)</div>\s*<', html, re.DOTALL)
if content_match:
content_paras = _extract_paragraphs(content_match.group(1))
else:
content_paras = _extract_paragraphs(html)
return {
"title": title,
"date": "",
"url": url,
"content": "\n\n".join(content_paras),
}
# ============================================================
# 优先级 3: 权威媒体转载
# ============================================================
def people_editorial_list():
"""
人民日报 - 社论/观点
URL: http://opinion.people.com.cn/GB/8213/49160/index.html
结构: 主页面有多个子栏目,每个子栏目页面有 <li> 文章列表
文章URL格式: /n1/2026/0623/c223228-40745676.html (相对) 或完整URL
"""
base = "http://opinion.people.com.cn/GB/8213/49160/"
html = _fetch(base + "index.html")
# 获取所有子栏目链接(完整URL
sub_urls = re.findall(r'href="(http://opinion\.people\.com\.cn/GB/8213/49160/\d+/index\.html)"', html)
sub_urls = list(dict.fromkeys(sub_urls))
results = []
seen = set()
for sub in sub_urls:
try:
sub_html = _fetch(sub)
# 解析 <li> 项目(注意:人民网的链接使用单引号 href='...'
lis = re.findall(r"<li[^>]*>(.*?)</li>", sub_html, re.DOTALL)
for li in lis:
# 匹配单引号或双引号的 href
m = re.search(r"href=['\"](/n1/\d{4}/\d{4}/c[\d-]+\.html)['\"][^>]*>([^<]{10,})</a>", li)
if m:
url_path, title = m.groups()
title = title.strip()
url = "http://opinion.people.com.cn" + url_path
if title and url not in seen:
seen.add(url)
date_match = re.search(r"/n1/(20\d{2})/(\d{4})/", url)
date = ""
if date_match:
year, md = date_match.groups()
date = f"{year}-{md[:2]}-{md[2:]}"
results.append({"title": title, "date": date, "url": url})
except Exception:
continue
return results
def people_article(url):
"""人民日报 - 文章正文(正文在 <p> 标签中,class=rm_txt_zw 的容器内)"""
html = _fetch(url)
title_match = re.search(r"<title>([^<]+)</title>", html)
title = title_match.group(1).strip() if title_match else ""
# 正文在 p 标签中
content_paras = _extract_paragraphs(html)
return {
"title": title,
"date": "",
"url": url,
"content": "\n\n".join(content_paras),
}
def cctv_list(page=1):
"""
央视新闻 - 新闻联播文字版
URL: https://news.cctv.com/lbj/
结构: JSONP API
API: https://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/china_{page}.jsonp?callback=china
注意: 需要设置 Referer 头
"""
api_url = f"https://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/china_{page}.jsonp?callback=china"
raw = _fetch(api_url, headers={"Referer": "https://news.cctv.com/"})
# 解析 JSONP: china({...})
json_match = re.search(r"[^(]+\((\{.*\})\)", raw, re.DOTALL)
if not json_match:
return []
try:
data = json.loads(json_match.group(1))
except json.JSONDecodeError:
return []
items = data.get("data", {}).get("list", []) if isinstance(data.get("data"), dict) else []
results = []
for item in items:
title = item.get("title", "").strip()
url = item.get("url", item.get("link", ""))
date = item.get("focus_date", item.get("date", ""))
if title and url:
results.append(
{
"title": title,
"date": str(date)[:10],
"url": url,
}
)
return results
def cctv_article(url):
"""央视新闻 - 文章正文(正文在 div.content_area 内)"""
html = _fetch(url)
title_match = re.search(r"<title>([^<]+)</title>", html)
title = title_match.group(1).strip() if title_match else ""
content_match = re.search(r'class="content_area"[^>]*>(.*?)</div>\s*<', html, re.DOTALL)
if content_match:
content_paras = _extract_paragraphs(content_match.group(1))
else:
content_paras = _extract_paragraphs(html)
return {
"title": title,
"date": "",
"url": url,
"content": "\n\n".join(content_paras),
}
def ce_list():
"""
经济日报 - 部委政策解读
URL: http://www.ce.cn/
结构: 服务端渲染,文章链接格式: /xwzx/gnsz/gdxw/202607/t20260721_xxxxxxx.shtml
政策相关文章在 /gnsz/ (国内时政) 和 /xwzx/ (新闻) 栏目下
"""
html = _fetch("http://www.ce.cn/")
items = re.findall(
r'<a[^>]*href="([^"]*t2026[^"]+\.shtml)"[^>]*>([^<]{10,})</a>',
html,
)
results = []
seen = set()
for url, title in items:
title = title.strip()
# 过滤政策相关文章(国内时政/新闻栏目)
if "/gnsz/" not in url and "/xwzx/" not in url and "/syj/" not in url:
continue
if title and url not in seen:
seen.add(url)
if url.startswith("/"):
url = "http://www.ce.cn" + url
# 从 URL 提取日期
date_match = re.search(r"/(20\d{6})/", url)
date = ""
if date_match:
d = date_match.group(1)
date = f"{d[:4]}-{d[4:6]}-{d[6:8]}"
results.append({"title": title, "date": date, "url": url})
return results
def ce_article(url):
"""经济日报 - 文章正文(正文在 div.content.clearfix 内)"""
html = _fetch(url)
title_match = re.search(r'id="NewsArticleTitle"[^>]*>([^<]+)', html)
if not title_match:
title_match = re.search(r"<title>([^<]+)</title>", html)
title = title_match.group(1).strip() if title_match else ""
content_match = re.search(r'class="content clearfix"[^>]*>(.*?)</div>\s*<', html, re.DOTALL)
if content_match:
content_paras = _extract_paragraphs(content_match.group(1))
else:
content_paras = _extract_paragraphs(html)
return {
"title": title,
"date": "",
"url": url,
"content": "\n\n".join(content_paras),
}
# ============================================================
# 统一入口
# ============================================================
SCRAPERS = {
"gov.cn": {"list": gov_zhengce_list, "article": gov_article, "name": "中国政府网"},
"xinhuanet": {"list": xinhuanet_list, "article": xinhuanet_article, "name": "新华社"},
"pbc": {"list": pbc_list, "article": pbc_article, "name": "中国人民银行"},
"mof": {"list": mof_list, "article": mof_article, "name": "财政部"},
"ndrc": {"list": ndrc_list, "article": ndrc_article, "name": "发改委"},
"csrc": {"list": csrc_list, "article": csrc_article, "name": "证监会"},
"mofcom": {"list": mofcom_list, "article": mofcom_article, "name": "商务部"},
"people": {"list": people_editorial_list, "article": people_article, "name": "人民日报"},
"cctv": {"list": cctv_list, "article": cctv_article, "name": "央视新闻"},
"ce": {"list": ce_list, "article": ce_article, "name": "经济日报"},
"scio": {"list": scio_press_list, "article": pbc_article, "name": "国新办(PBC镜像)"},
}
def scrape_list(site_key, **kwargs):
"""统一列表抓取入口"""
scraper = SCRAPERS.get(site_key)
if not scraper:
return []
return scraper["list"](**kwargs)
def scrape_article(site_key, url):
"""统一文章抓取入口"""
scraper = SCRAPERS.get(site_key)
if not scraper:
return {"title": "", "date": "", "url": url, "content": ""}
return scraper["article"](url)