我维护的 stock MCP Server 有上百个工具——实时行情、K线、龙虎榜、北向资金、涨停梯队……但某天我意识到:这些数据只喂给了自己,没有变成”作品”。于是花了 72 小时,给 Agent 接上一条「收盘后自动写市场综述并发到雪球」的管线。本文完整记录:6 大平台选型、阿里云 WAF 攻防(curl_cffi 全指纹被拦 → Playwright 页面内 fetch 穿透)、三个接口逆向坑,以及”LLM 写财经文数字必漂移”的数据零幻觉方案。第一篇文章已真实上线


1. 缘起:一堆好工具,却没发挥出价值

我有一套股票分析系统:一个 stock MCP Server(通过 Cloudflare Workers 网关暴露上百个工具),加上每天 11:00 的盘中简报、16:10 的收盘深度分析。数据链路非常完整——开盘、收盘、资金流向、涨停梯队,全部自动化。

但有一天我看着这些日报,突然问了自己一个问题:

这些分析只发给了我自己。它们能不能变成对外发布的作品

网络上正好有专门的股票社区——雪球、东方财富、掘金。能不能让 Agent 每天收盘后,用 MCP 拉数据、写一篇市场综述、自动发上去?这既能让数据资产产生二次价值,也是对我那套 MCP 工具的”压力测试”。

从 8 月 1 日提出想法,到 8 月 4 日第一篇文章成功发布——72 小时,中间踩遍了平台选型、WAF 攻防、接口逆向、数据校验四座山。


2. 核心方法

2.1 平台选型:先看受众匹配,再看接入难度

第一步是调研 6 个目标平台的发文接口。调研结果很有意思:

平台 发文接口 认证方式 风险
雪球 部分(行情/财务) xq_a_token cookie 🔴 需登录+人工探索
东方财富 无公开接口 需登录 🔴 Choice API 付费 500 万门槛
知乎 部分创作 API OAuth 🟡 需申请创作权限
头条号 ✅ 完整开放 API AppKey+Secret 🟢 反爬友好
掘金 ✅ 有开放接口 Access Token 🟢 技术社区
CSDN 有限 需 Selenium 🟡 自动化成本高

按”接入难度”排序,最优解似乎是掘金——有开放 API、有 SDK。于是先定了掘金 + 半自动 + 财经评论风。

然后撞上一堵看不见的墙:掘金是技术社区,分类是后端/前端/Android/iOS/人工智能/代码人生/阅读——没有财经分类。 一篇 A 股复盘挂在”阅读”分类下,等于没人看。

这时我意识到选型顺序错了。于是问了一句:”既然掘金也需要 cookie 登录,为什么不直接选雪球?”——雪球才是股票受众真正聚集的地方。

教训 #1:平台选型先看”受众匹配度”,再看”接入难度”。 接口最容易的平台,如果受众不匹配,等于白做。

2.2 第一道墙:阿里云 WAF 攻防战

定了雪球,真正的硬仗才开始。雪球的防护不是 Cloudflare,而是阿里云 WAF

  • 页面带 JS 挑战,动态下发 acw_tc cookie
  • curl_cffi 的 chrome110/120/124/131 全部指纹都被拦,返回 aliyun_waf_aa/oo 挑战页
  • 更狠的是:即使你手动用浏览器访问拿到动态 acw_tc cookie,再用 curl_cffi 发请求依然被拦——因为 WAF 校验的是 TLS 指纹,不只是 cookie

纯 HTTP 这条路彻底死了。唯一可行的路径是:

Playwright 启动真实浏览器 → 注入登录 cookie → 在页面上下文里用 page.evaluate(fetch(...)) 发请求。

真实浏览器环境 WAF 直接放行,页面内的 fetch 自动携带所有 cookie 和指纹,不需要手工处理任何挑战逻辑。代码长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True, args=[
"--disable-blink-features=AutomationControlled", "--no-sandbox"])
ctx = await browser.new_context(
user_agent="Mozilla/5.0 ... Chrome/120.0.0.0 Safari/537.36")

# 注入登录 cookie(domain 必须带点前缀,否则 mp 子域不生效)
cookies = [{"name": n.split("=")[0].strip(), "value": n.split("=", 1)[1].strip(),
"domain": ".xueqiu.com", "path": "/"}
for n in cookie_str.split(";") if "=" in n]
await ctx.add_cookies(cookies)

page = await ctx.new_page()
await page.goto("https://mp.xueqiu.com/write/", wait_until="domcontentloaded")

# 页面内 fetch 调用发布接口 —— 绕过 WAF 的 TLS 指纹校验
result = await page.evaluate("""async (params) => {
const body = new URLSearchParams();
for (const [k, v] of Object.entries(params)) body.append(k, v);
const r = await fetch('/xq/statuses/update.json', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: body.toString()
});
return await r.text();
}""", publish_body)

这个”真实浏览器 + 页面内请求“的模式,是所有强风控平台自动化的通用终局——别在 TLS 指纹模拟上死磕,直接给平台一个”真人”。

2.3 接口逆向:三个坑

无文档平台的逆向套路其实很固定:

  1. Playwright 打开页面,page.on("request") 监听所有非 GET 请求
  2. 手动触发一次写操作(点发布/删除按钮),从捕获的请求里读 URL + post_data
  3. 删改类 API 通常藏在打包 JS 里——拉 main.*.js / list.*.js,grep destroy|delete|remove 附近字符串

逆向出三个核心接口,每个都埋着坑:

1
2
3
4
5
6
7
8
9
POST /xq/statuses/text_check.json   # 内容预检(敏感词/合规)
# 参数名是 text 不是 status —— 用错返回 error_code 10020

POST /xq/statuses/update.json # 发布长文
# 需要动态 session_token;draft_id 留空 = 直接发布
# 参数: title / status(HTML) / cover_pic / show_cover_pic / original / allow_reward

POST /xq/statuses/destroy/{id}.json # 删除长文
# 路径必须带 .json 后缀,否则 404

session_token 是发布时动态生成的短 token,获取方式有三种:优先从 window.session_token / localStorage 提取;没有则从页面 HTML 正则 session_token["']?\s*[:=]\s*["']([A-Za-z0-9]{10,}) 提取;最稳的是调独立的 token.json?api_path=... 接口。

2.4 数据零幻觉:财经文的红线

接口通了,下一个问题更致命:LLM 写财经内容,数字一定会漂移。 指数点位、涨停家数、行业涨跌幅——LLM 大概率会”合理推断”出一些看起来很像真的、实际上是编的数字。财经评论数据错了,就是事故。

我做了三层防线(后来证明必须全上):

第一层:事实块程序化渲染。 关键数据段落根本不经过 LLM 的”创作”——指数、涨停梯队、强势股题材、北向资金、行业涨跌,全部由程序从数据快照直接渲染成文本,作为【数据事实块】嵌入 prompt,强制 LLM”只引用、不得自造”:

1
2
3
4
lines.append("【数据事实块 - 2026-08-04】以下所有数据来自真实市场数据源,必须原样引用,不得修改任何数字。")
lines.append("- 深证成指: 13885.71(+3.25%)")
lines.append("- 创业板指: 3488.97(+5.64%)")
lines.append("- 涨停 200 家,首板 177 家,炸板 8 家,最高连板 3 板")

第二层:verify_numbers 全覆盖校验。 文章生成后,用正则把文章里的关键数字全部扫出来和快照对比——指数价格容差 0.01、行业涨跌幅容差 0.05、涨停数必须精确相等。任何不一致都记录在案:

1
2
3
4
5
for idx in sources["market_overview"]["indices"]:
pattern = rf"{re.escape(name)}(?:收报|报|收于|至)?\s*([\d]+\.[\d]+)"
for m in re.finditer(pattern, md):
if abs(float(m.group(1)) - float(idx["price"])) > 0.01:
fixes.append(f"指数[{name}]: 文章={m.group(1)} → 快照={idx['price']}")

第三层:fix_numbers_programmatically 兜底纠正。 校验不过的数字直接程序化替换回快照值,不给 LLM 二次”修正”的机会。

第四层(发布前终检): publish_xueqiu.py 在真正调发布接口之前,再跑一次 verify_numbers,与最新数据快照比对——不一致直接拒绝发布。这条”数据准确性红线”贯穿生成到发布的全链路。

效果:8 月 4 日发布的文章里,深证成指 13885.71、创业板指 3488.97、涨停 200 家等数据全部与快照一致,页面级验证通过。

2.5 半自动流水线:人留一道闸

整条管线设计成半自动——机器写,人确认

1
2
3
4
5
6
7
collect_market_data.py   # 6 数据源(指数/涨停梯队/强势股/北向/行业/宏观)
↓ 存带时间戳快照(审计用)
generate_article.py # 事实块注入 + verify_numbers + fix_numbers_programmatically

daily_pipeline.py # cron 交易日 16:30 运行 → 飞书推送预览

用户回复「发布」→ publish_xueqiu.py # 发布前终检 → text_check → update.json

为什么不全自动?因为对外发布的内容是”作品”不是”内参”——机器生成、人确认,既保证质量,也把风险控制在一道人工闸门内。实际运行时还发现:自动化发布的长文可能被平台标记”内容不适合展示”,测试文发布后要立即用 destroy 接口删除。这些都要求发布流程可控、可回滚。


3. 踩坑记录:六个真实的坑

  1. text_check 参数名是 text 不是 status —— 用错返回 error_code 10020。逆向时抓包抓到的字段名和直觉不一致,是这类坑的常态。

  2. destroy 接口路径必须带 .json 后缀/xq/statuses/destroy/403375350.json),否则 404。从打包 JS 里 grep 出来的路径经常被省略后缀。

  3. cookie domain 必须带点前缀".xueqiu.com",写成 "xueqiu.com" 则 mp 子域不生效,页面永远显示”立即登录”。

  4. 成功误报失败的经典 bugpage.evaluate 里对响应 .slice(0, 800) 截断后返回,Python 端 json.loads 解析失败 → 误报”非 JSON 响应”——实际上文章已经发布成功了(返回体里有 id: 403604975)。修复:在 JS 端先 JSON.parse 再返回,判定才可靠。这个坑的教训是:响应截断和解析要放在同一端完成

  5. LLM 内容审查误判:某些行业词+数字组合(如”铁路公路 1.16”)会概率性触发 LLM 服务端 500。解法:重试 3 次(5s/10s/15s 退避),仍失败则用消毒词表把”铁路公路”替换为”交运”等安全同义词。

  6. 数据源混入脏数据:指数接口会把个股混进 indices(平安银行 000001 赫然在列)——必须按名称/代码前缀过滤。

什么时候这套方案不适用?

  • 平台有强设备指纹校验 + 验证码(如滑块)→ 纯 Playwright 也可能被识破,风险陡增
  • 登录态丢失(xq_a_token 有效期约一周)→ 需要定期人工刷新 cookie
  • 财经/金融内容有合规红线(不荐股、不承诺收益)→ prompt 里必须写死合规约束,本文的 7 段模板就内置了免责声明

4. 总结

72 小时,从”想法”到”雪球上真实上线一篇文章”(A股复盘 | 2026-08-04:市场观察),这套管线沉淀了四条可复用的经验:

  1. 平台选型先看受众匹配,再看接口难度——掘金的 API 再开放,没有财经分类也白搭
  2. 强风控平台的自动化终局是”真实浏览器 + 页面内请求”——别在 TLS 指纹模拟上死磕,直接给平台一个”真人”
  3. LLM 写数据密集型内容必须程序化兜底——事实块渲染 → 全量校验 → 程序化纠正 → 发布前终检,四层防线缺一不可
  4. 发布类操作坚持半自动——机器写、人确认,质量与风险都握在自己手里

一句话 takeaway:给 Agent 接外部平台,不是”调个 API”那么简单——它本质是 数据管线 × 反爬工程 × 内容治理 三件事的合体。

对想复刻的朋友,核心代码模式(Playwright 穿透 WAF + 页面内 fetch + 数字校验正则)都已在上文给出,把域名换成你的目标平台即可。如果这篇文章对你有帮助,欢迎在评论区分享你的”Agent 接外部平台”经历。


本文由龙天涯(颂雅风)原创。文中提及的雪球文章链接为真实发布内容,仅作技术分享,不构成任何投资建议。