返回 Skill 列表
extension
分类: 数据与分析无需 API Key

监管动态周报生成

Use this skill when the user asks to generate a weekly regulatory enforcement

person作者: user_213bfd3bhubcommunity

监管动态周报生成

按周生成上市公司监管动态周报(Word 文档),涵盖全国各省市证监局、上交所、深交所的监管处分案例。

工作流程概览

① 确定日期范围 → ② 分省市+交易所搜索 → ③ 解析过滤案例
→ ④ 补充详情 → ⑤ 生成 Word 文档 → ⑥ 注入超链接 → ⑦ 输出

前置条件

  • baidu-search 技能已安装并配置 API Key(pass api/baidu-search/key 🔐)
  • Python 依赖python-docxlxmlrequests
  • 模板文件:一个已有的 .docx 周报模板,含汇总表 + 案例详情表格
  • pass 密码管理器:用于读取 API Key 和其他凭据

步骤详解

1. 确定日期范围

与用户确认周报覆盖的日期范围(通常是上周一至周日)。默认格式示例:2026.6.22-2026.6.28

如果用户说"本周"或"上周",调用 get_current_time 计算具体日期。

2. 分省市 + 交易所搜索

2a. 确定搜索范围

全国 36 个证监局(31 省市区 + 5 计划单列市):

北京、天津、河北、山西、内蒙古、辽宁、大连、吉林、黑龙江、上海、江苏、浙江、宁波、安徽、福建、厦门、江西、山东、青岛、河南、湖北、湖南、广东、深圳、广西、海南、重庆、四川、贵州、云南、西藏、陕西、甘肃、青海、宁夏、新疆

交易所:上交所(沪市主板 + 科创板)、深交所(深市主板 + 创业板)

2b. 执行搜索

使用 baidu-search 技能,对每个目标证监局/交易所发起搜索。

推荐搜索关键词模板:

# 按证监局搜索(监管措施)
sh skills/baidu-search/scripts/search.sh '{"query":"上海证监局 2026年6月 警示函 责令改正 上市公司"}'

# 搜索行政处罚
sh skills/baidu-search/scripts/search.sh '{"query":"上海证监局 2026年 行政处罚 上市公司"}'

# 搜索立案调查
sh skills/baidu-search/scripts/search.sh '{"query":"上海证监局 2026年 立案调查 上市公司"}'

# 搜索上交所处分
sh skills/baidu-search/scripts/search.sh '{"query":"上交所 2026年6月 通报批评 公开谴责 监管警示 上市公司"}'

# 搜索深交所处分
sh skills/baidu-search/scripts/search.sh '{"query":"深交所 2026年6月 通报批评 公开谴责 监管警示 上市公司"}'

搜索技巧:

  • 监管措施行政处罚立案调查三个类型分别搜索
  • 时间范围缩小到当周前后(如 2026年6月22日至28日
  • 每轮搜索取前 5-10 条结果
  • 覆盖尽可能多的证监局,优先搜索经济发达地区(上交所官网、深交所官网也是好来源)
  • 对于"证监局"搜索结果不足的地区,尝试用"证监会 XX 监管局"作为关键词

2c. 【推荐】浏览器搜东方财富「立方风控鸟」(替代百度 API)

当百度搜索 API 触发限流(429)时,推荐使用浏览器直接搜索东方财富的 「立方风控鸟」 栏目。这是每日监管汇总专栏,效率远高于逐个省搜索:

优势对比:

| 对比项 | 百度 API | 浏览器搜东方财富 ✅ | |--------|----------|-------------------| | 速率限制 | 10次/分钟,连续搜索被429限流 | 无限制 | | 覆盖范围 | 逐个省市搜索 × 36 = 上百次 | 1篇汇总 = 当日全国案例 | | 搜索质量 | 结果掺杂不相关 | 每日编辑精选,直接列处罚原文链接 | | 反爬机制 | 无验证码 | 无验证码 (东方财富对浏览器友好) |

操作步骤:

# 1. 打开浏览器
browser_use(action="start")
browser_use(action="open", url="https://so.eastmoney.com/Web/s?keyword=立方风控鸟+6月26日+警示函")

# 2. 浏览搜索结果,找到当日汇总文章
browser_use(action="open", url="http://finance.eastmoney.com/a/202606263785036421.html")

# 3. 阅读文章内容,提取监管案例
browser_use(action="snapshot")
# 从文章内容中提取:公司名、处罚机构、处罚类型、违规事实

# 4. 覆盖整周:分别搜索 6月22日 到 6月28日的立方风控鸟文章
# 通常每天有 早报 + 晚报 两篇汇总

搜索关键词模板:

# 搜索特定日期的立方风控鸟
keyword=立方风控鸟+6月24日+早报
keyword=立方风控鸟+6月24日+晚报

# 搜索警示函汇总
keyword=立方风控鸟+警示函

# 搜索某地区证监局案例
keyword=立方风控鸟+安徽证监局

注意:

  • 东方财富搜索对 headless 浏览器友好,不需要滑块验证
  • 立方风控鸟文章路径格式:http://finance.eastmoney.com/a/YYYYMMDD<数字>.html
  • 百度网页版(baidu.com)会触发滑块验证码,headless 浏览器无法绕过,请勿使用

3. 解析过滤案例

3a. 过滤规则

从搜索结果中提取案例,按以下规则筛选:

✅ 保留:

  • 处罚对象为上市公司本身、控股股东、实际控制人、董事、监事、高级管理人员
  • 处罚类型:警示函、责令改正、通报批评、公开谴责、监管警示、监管谈话、出具监管关注函
  • 行政处罚:没收违法所得、罚款、市场禁入
  • 立案调查:证监会/证监局立案调查
  • 板块:沪深主板、科创板、创业板(含 ST、*ST)

❌ 排除:

  • 处罚对象为中介机构(券商、会计师事务所、律所、评估机构等)
  • 对象为个人投资者或非上市公司关联人员
  • 纯债券违约、银行间市场处分
  • 重复案例(同公司同一事项只保留一次)
  • 新闻简讯无具体处罚信息

❌ 特别注意排除(避免混淆):

  • 行政处罚事先告知书(尚未正式处罚,只是告知)
  • 搜索结果中标题含"事先告知书"的条目

3c. 精选去重(Curated Selection)

文档结构为 两部分

  • 第一部分(汇总表):显示所有搜索到的案例统计总数(包括立案调查、行政处罚、监管措施、上交所、深交所数量)。汇总表的数据反映全部搜索结果,而不是精选后的子集。
  • 第二部分(案例详情):仅展示精选后的代表案例(6-8个),覆盖不重复的违规类型。

精选规则:

  1. 按违规类型分组:将案例按违规类型分组(业绩预告违规、会计处理/收入确认、资金占用、信披遗漏、关联交易、一致行动人未披露、立案调查、行政处罚等)
  2. 同类去重:同一类型有多个案例时,只保留最典型的一个(选受影响最大、处罚最重、或信息最完整的)
  3. 强制保留
    • ✅ 立案调查(每期至少保留1个)
    • ✅ 行政处罚(每期至少保留1个)
  4. 覆盖多样性:精选后总数控制在 6-8 个,尽量覆盖不同省市证监局和交易所

示例: 搜到 13 个案例,其中业绩预告违规 5 个(超讯通信、*ST宝馨、*ST冀凯、*ST未名、*ST实达),精选时只保留最典型 1 个(如超讯通信),其余类型各保留 1 个,最终精选 7 个案例。

3d. 案例整合

同一公司的同一违规事项同时被证监局和交易所处罚时:

  • 分别记录两个处罚来源的链接
  • 在表格中合并为一行,体现双重处罚
  • 链接列应包含两个链接(证监局原文、交易所原文),不要只写一个

4. 补充详细内容

对每个保留的案例,用 browser_use 打开原文链接,提取以下信息:

【处罚结果】
处罚机构、日期、具体措施、处罚对象

【违规事实】
公司违反了哪些规定、具体违规行为描述、涉及金额等

【违规事项摘要】
一句话概括:XX公司因XX问题被XX处罚(用于表格第二列)

操作示例:

browser_use(action="open", url="<原文链接>")
browser_use(action="snapshot")
→ 阅读内容,提取处罚结果和违规事实

5. 生成 Word 文档

使用 python-docx 和模板生成周报文档。

模板结构

模板包含两个表格:

  • 表0(汇总表):统计各类型案例数量(立案调查、行政处罚、监管措施、上交所、深交所)
  • 表1(案例详情表):每3行为一个案例(第1行:股票名称、违规类型+链接、原文链接;第2-3行:处罚详情)

生成步骤

from docx import Document
from docx.shared import Pt
from docx.oxml.ns import qn

# 1. 复制模板
shutil.copy2(template_path, output_path)
doc = Document(output_path)

# 2. 更新汇总表
t0 = doc.tables[0]
t0.rows[0].cells[2].text = '立案调查数量'
t0.rows[1].cells[2].text = '行政处罚数量'
t0.rows[2].cells[2].text = '监管措施数量'
# ...

# 3. 填充案例表
# 使用标记法插入超链接:!!HLINK!!<url>!!<显示文本>!!
# 详见第6步

# 4. 保存中间文件
doc.save(output_path)

重要:python-docx 保存时会丢失超链接关系,所以用标记法 + 后处理注入。

标记格式

python-docx 填充时,不要直接创建超链接,而是插入标记字符串:

def sl_marker(cell, url, txt):
    """插入标记,后续用 ZIP 注入替换为真实超链接"""
    marker = f'!!HLINK!!{url}!!{txt}!!'
    run = cell.paragraphs[0].add_run(marker)
    run.font.name = '楷体'
    run.font.size = Pt(10.5)

对于同一案例有两个链接(证监局+交易所),插入两个标记:

sl_marker(cell, url1, '证监局原文')
# 在同一单元格再接一个
sl_marker(cell, url2, '交易所原文')

6. ZIP 后处理注入超链接

这是关键技术:用 zipfile + lxml 直接操作 .docx(本质是 ZIP 包),替换标记为真实超链接。

⚠️ 为什么需要这一步: python-docx 保存文档时会丢失 <w:hyperlink> 元素和对应的 document.xml.rels 关系条目。所以必须保存后重新打开 ZIP 包手动注入。

核心逻辑:

import zipfile, re, io
from lxml import etree

NS_W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
NS_R = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
NS_REL = 'http://schemas.openxmlformats.org/package/2006/relationships'

buf = io.BytesIO()
with zipfile.ZipFile(docx_path, 'r') as zin:
    with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zout:
        dx = zin.read('word/document.xml')
        rx = zin.read('word/_rels/document.xml.rels')
        dt = etree.fromstring(dx)
        rt = etree.fromstring(rx)

        pattern = re.compile(r'!!HLINK!!(.+?)!!(.+?)!!')
        nhl = 0

        for te in dt.iter('{' + NS_W + '}t'):
            if te.text and '!!HLINK!!' in te.text:
                m = pattern.search(te.text)
                if m:
                    url, disp = m.group(1), m.group(2)
                    te.text = disp  # Replace marker text with display text
                    pr = te.getparent()
                    pp = pr.getparent()
                    if pp is not None:
                        nhl += 1
                        rid = f'rIdHL{nhl}'
                        # Add relationship
                        rel = etree.SubElement(rt, '{' + NS_REL + '}Relationship')
                        rel.set('Id', rid)
                        rel.set('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink')
                        rel.set('Target', url)
                        rel.set('TargetMode', 'External')
                        # Create hyperlink element
                        hl = etree.Element('{' + NS_W + '}hyperlink')
                        hl.set('{' + NS_R + '}id', rid)
                        hr = etree.SubElement(hl, '{' + NS_W + '}r')
                        rp = etree.SubElement(hr, '{' + NS_W + '}rPr')
                        # font, size, color, underline
                        rf = etree.SubElement(rp, '{' + NS_W + '}rFonts')
                        rf.set('{' + NS_W + '}ascii', '楷体')
                        rf.set('{' + NS_W + '}hAnsi', '楷体')
                        rf.set('{' + NS_W + '}eastAsia', '楷体')
                        sz = etree.SubElement(rp, '{' + NS_W + '}sz')
                        sz.set('{' + NS_W + '}val', '21')
                        sc = etree.SubElement(rp, '{' + NS_W + '}szCs')
                        sc.set('{' + NS_W + '}val', '21')
                        co = etree.SubElement(rp, '{' + NS_W + '}color')
                        co.set('{' + NS_W + '}val', '0563C1')
                        un = etree.SubElement(rp, '{' + NS_W + '}u')
                        un.set('{' + NS_W + '}val', 'single')
                        tt = etree.SubElement(hr, '{' + NS_W + '}t')
                        tt.text = disp
                        tt.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
                        idx = list(pp).index(pr)
                        pp.remove(pr)
                        pp.insert(idx, hl)

        # Write back
        dxb = etree.tostring(dt, xml_declaration=True, encoding='UTF-8', standalone=True)
        rxb = etree.tostring(rt, xml_declaration=True, encoding='UTF-8', standalone=True)
        for item in zin.infolist():
            data = zin.read(item.filename)
            if item.filename == 'word/document.xml': data = dxb
            elif item.filename == 'word/_rels/document.xml.rels': data = rxb
            zout.writestr(item, data)

⚠️ 注意 URL 中的下划线: 正则 (.+?) 匹配任何字符(非贪婪),所以 URL 中包含下划线、连字符、斜杠等都 OK。使用 !! 作为分隔符时,确保 URL 中不包含 !!(正常情况下不会)。如果 URL 含 !!,需换分隔符。

验证: 注入完成后,检查超链接数量是否等于总链接数:

rels = z.read('word/_rels/document.xml.rels').decode()
targets = re.findall(r'Target="(https?://[^"]+)"', rels)
assert len(targets) == expected_link_count

7. 输出文档

将最终 .docx 文件发送给用户:

send_file_to_user(file_path="监管动态周报(2026.XX.XX-2026.XX.XX).docx")

辅助脚本

本技能附带两个脚本,可简化重复性工作。

scripts/gen_report.py — 文档生成 + 超链接注入一体化

接收 JSON 案例数据,自动完成模板填充和超链接注入。

使用方式:

python3 <skill_dir>/scripts/gen_report.py \n  --template <template_path> \n  --output <output_path> \n  --cases cases.json \n  --stats '{"立案调查":0,"行政处罚":0,"监管措施":6,"上交所":5,"深交所":3}' \n  --date_range "2026.6.22-2026.6.28"

cases.json 格式:

[
  {
    "stock": "超讯通信(603322)",
    "type": "【上交所】:通报批评\n【对象】:公司、董事长梁建华等",
    "links": [
      {"url": "https://...", "text": "原文链接"}
    ],
    "detail": "【处罚结果】\n2026年6月22日...\n\n【违规事实】\n..."
  }
]

对于同一案例有证监局和交易所双重处罚的情况,links 数组包含两个对象:

{
  "stock": "上海物贸(600822)",
  "type": "【上海证监局】:责令改正\n【上交所】:通报批评",
  "links": [
    {"url": "https://...", "text": "证监局原文"},
    {"url": "https://...", "text": "交易所原文"}
  ],
  "detail": "..."
}

脚本会自动处理双链接,在同一单元格中依次插入两个 !!HLINK!! 标记。

scripts/search_regions.py — 证监局清单管理

python3 <skill_dir>/scripts/search_regions.py --list                    # 列出36个证监局
python3 <skill_dir>/scripts/search_regions.py --queries --date "2026年6月"  # 生成搜索查询
python3 <skill_dir>/scripts/search_regions.py --queries --type "行政处罚"    # 按类型过滤
python3 <skill_dir>/scripts/search_regions.py --queries --date "2026年6月22日至28日" --type "监管措施" --output queries.json

常见问题

超链接丢失

问题: 打开生成好的 .docx 发现超链接不可点击。 原因: python-docx 保存时丢掉了 <w:hyperlink>document.xml.rels 中的关系条目。 解决: 坚持使用标记法 + ZIP 后处理注入。不要试图在 python-docx 中直接创建超链接。

搜索结果不够全或触发限流

问题: 某些省证监局搜不到案例,或 Baidu Search API 返回 429 Too Many Requests原因:

  • 百度搜索覆盖有限,或该局当周确实没有公开案例
  • 频繁请求触发 Baidu AI Search 的速率限制(429 错误)

解决:

  • 尝试不同的关键词组合(如"监管措施"→"出具警示函"、"证监局"→"证监会XX监管局")
  • 分批次搜索,每轮间隔一段时间(遭遇 429 后等待 30 分钟以上再试)
  • 优先搜索交易所官网(上交所、深交所官网公告无需调用百度 API)
  • 直接使用 browser_use 访问交易所官网获取公告信息
  • 如实汇报:向用户说明"百度 API 限流中,剩余 XX 个证监局待搜索",并用手头已有的案例生成文档

模板案例槽位不足

问题: 模板只有 10 个案例槽位(30行表格),但需要展示更多案例。 解决(两种方式):

  • 方式一(推荐):扩展模板表格行数,使用 extend_template.py 脚本
  • 方式二:精选案例时控制在 10 个以内,直接用原模板

扩展模板方法:

python3 <skill_dir>/../temp/extend_template.py  # 将模板从30行扩展到39行(13案例)

空案例槽残留旧数据

问题: 精选后案例数少于模板槽位数,空槽残留模板旧内容。 解决: 在生成文档后,清除空槽的文本内容:

for slot in range(curated_count, total_slots):
    for row in [slot*3, slot*3+1, slot*3+2]:
        for tc in trs[row].findall(...):
            t.text = ''

同一案例多个来源

问题: 证监局和交易所对同一公司同一事项都处罚了。 解决: 合并为一个案例,在 type 列同时显示两个处罚来源,links 数组包含两个链接。不要创建两个重复案例条目。

输出格式

ALWAYS 使用以下格式:

  • 文件名:监管动态周报(2026.XX.XX-2026.XX.XX).docx
  • 汇总表头:证监会(局)立案调查/行政处罚/监管措施、上交所自律监管措施及纪律处分、深交所自律监管措施及纪律处分
  • 案例表:每3行为一个案例(第1行:股票名称、违规类型、原文链接;第2-3行:处罚详情)
  • 超链接:蓝色带下划线,点击可跳转原文
  • 双链接案例:原文链接列显示两个链接(证监局原文 + 交易所原文)