Coze+Trae+掘金MCP = "✨掘金微热榜"

1. 引言

🤡 "MCP " 和 "Vibe Coding" 这两个词刷屏好一阵子了,搜了下资料,好像不是什么高深的东西,🤷‍♀️ 搞不懂为啥这么火,简单聊聊~

1.1. MCP

  • Model Context Protocol,模型上下文协议 ,由 Anthropic 公司于 2024年11月推出推出的开源协议,旨在为 大语言模型 (LLM) 与 外部数据源、工具及服务之间建立标准化、安全的双向连接
  • 在它之前 LLM与外部资源交互 通过 Function Call 来实现,需先实现 自定义协议 (如 JSON Schema),模型直接生成调用指令 (如特定JSON),开发者需实自行实现 函数编排层-解析 (验证格式、匹配函数、提取参数) 和 函数处理逻辑 -执行 (调用 API、处理权限和错误),并将处理结果以适当的形式 反馈给用户 (格式化数据、维护上下文),跨平台兼容麻烦。
  • 而在 MCP架构 中,"函数编排层 " 被下沉到 宿主应用(Host,连接用户、LLM和外部工具的核心枢纽) ,开发者无需手动实现协议解析、服务发现、参数校验等底层逻辑,而是专注于业务功能的实现。

Talk is cheap,show you the Code,以调用天气服务为例,Function Call 开发者 需实现完整编排逻辑:

python 复制代码
# 1. 定义函数 Schema(JSON 结构)
schema = {
    "name": "get_weather",
    "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
    }
}

# 2. 解析模型生成的 JSON 请求
def parse_request(raw_json):
    # 验证 Schema、提取参数、错误处理
    if "city" not in raw_json["arguments"]:
        raise ValueError("缺少城市参数")

# 3. 调用外部服务
def call_weather_api(city):
    # 处理 API 密钥、网络请求、重试逻辑
    response = requests.get(f"https://api.weather.com?key=XXX&city={city}")

# 4. 格式化结果并返回给模型
def format_result(api_data):
    return f"温度:{api_data['temp']}℃,天气:{api_data['condition']}"

# 5. 整合到 LLM 对话流程
def handle_query(user_input):
    llm_response = model.generate(user_input)  # 模型可能返回 JSON 请求
    parsed = parse_request(llm_response)
    data = call_weather_api(parsed["arguments"]["city"])
    return format_result(data)

而 MCP 开发者只需用 @mcp.tool() 声明功能,然后实现业务逻辑,协议层 由宿主应用自动处理:

python 复制代码
# 1. 定义 MCP 工具(声明式语法)
from mcp.server import MCPTool

@MCPTool(name="get_weather", description="获取天气")
def get_weather(city: str) -> str:
    # 直接编写业务逻辑,无需处理协议细节
    return f"温度:{query_weather_api(city)}℃,天气:{get_condition(city)}"

# 2. 启动 MCP 服务器(宿主应用自动处理通信)
if __name__ == "__main__":
    server = MCPServer()
    server.register_tool(get_weather)
    server.run()  # 宿主应用负责协议解析、安全校验、多工具路由

MCP 协议的核心架构除了上面定义的 MCP Server 外,还有 MCP Client ,前者 暴露具体功能 (如文件操作、API调用),实现业务逻辑 (如数据库查询、邮件发送),业务逻辑千差万别,须定制开发,大部分案例都是捣鼓这个。后者实现 协议转换、服务发现、结果整合,需处理协议解析、多工具路由、安全校验,协议层高度标准化 (JSON-RPC 2.0),已有成熟实现 (😀Trae、Cursor 等AI编程助手基本都内置,支持所有标准协议,开发者无需重复实现)。举个 Trae + MCP Server 调用天气服务的执行链条🌰:

python 复制代码
=== MCP Server 配置 ===

添加MCP Server配置 (直接市场安装或手动配置),然后一般是要配置API密钥的,如Figma的配置:
{
  "mcpServers": {
    "Figma AI Bridge": {
      "command": "npx",
      "args": [
        "-y",
        "figma-developer-mcp",
        "--stdio"
      ],
      "env": {
        "FIGMA_API_KEY": ""
      }
    }
  }
}

=== MCP Server 调用 ===


用户输入提示词 ------ 如:"北京今天天气怎么样?需要带伞吗?"
  │
  ▼
Trae的AI模型解析意图 ------ 识别出需要调用「天气查询」功能,并提取参数(城市=北京)
  │
  ▼
Trae 内置的 MCP Client 查找工具
【动态服务发现】自动向所有已配置的 MCP Server 发送 Capability Exchange 请求,获取可用工具列表。
【匹配逻辑】根据提示词中的关键词(如 "天气"),筛选出已安装的 Weather MCP Server。
  │
  ▼
调用天气 MCP Server
【协议通信】使用 JSON-RPC 2.0 格式发送请求:{
  "method": "get_weather",
  "params": {
    "city": "北京",
    "include_rain": true  // 用户隐含需求:是否需要带伞
  }
}
【服务器执行】调用第三方天气 API,解析返回的 JSON 数据,提取天气状况,返回结构化结果:{
  "temperature": "28℃",
  "condition": "多云转晴",
  "rain_probability": 10
}
  │
  ▼
结果反馈
【成功】Trae 收到数据后,生成自然语言回复:"北京今天多云转晴,气温28℃,降水概率10%。无需带伞。"
【失败】若 API 超时,返回 { "error": "无法连接天气服务" },Trae提示:"暂时无法获取天气信息,请稍后再试"

顺带分享几个 MCP Server 聚合 站点:

1.2. Vibe Coding

😄 MCP 了解完,接着说下 Vibe Coding (氛围编程) ------ 2025年初由 OpenAI 联合创始人Andrej Karpathy 提出的概念,一种通过 自然语言提示驱动AI生成可执行代码软件开发范式 。与传统AI辅助编程工具不同,其强调开发者对生成代码的 "有限理解接受度 ",只要功能实现符合预期,无需深究具体实现细节 🤡。

传统编程要求开发者建立 双重认知映射先将业务需求转化为算法逻辑,再将逻辑表达为特定语法结构 。而在Vibe Coding范式下,这种双重映射被简化为 单阶段语义传递 ,开发者只需 确保需求描述的准确性,其余转换工作由AI模型完成。Vibe Coding 的技术实现依赖于两个关键要素的协同作用:

  • 专用代码生成模型 :以Claude 3.7等为代表的代码专用LLM,通过海量开源代码库和技术文档训练,具备 理解编程语义和生成合规代码 的能力。这些模型在参数规模 (普遍超过500B) 和训练数据量 (涵盖GitHub前1%优质仓库) 上远超早期代码生成工具,能够处理复杂的技术需求。
  • 自然语言交互框架 :开发工具提供 对话式编程界面 ,允许开发者通过 渐进式提示 来完善描述,典型交互模式包括:初始需求陈述、功能细化追问、异常处理补充

🐶 简单点说,以前写个 简单程序 (网页、APP等),你得先学对应的编程语言,学点基础的数据结构和算法,然后报错得自己定位,上网检索答案。

🤡 现在你只需要会 好好说人话 就行 (写 提示词 ,,甚至通过Superwhisper等工具语音转文字直接battle),如 "给我做一个XXX App",AI 就会啪啪啪一顿生成,搭建完基本架构,你要做的是继续 描述清楚需求 (如"点击按钮后跳转XX页"),完善APP的过程可能会报错,纠错也是靠 口嗨 ,如 "这里显示红色不对"。🤔 这种编程范式确实减低了开发门槛,开发仔能提效,普通人也能快速实现自己的想法,有种"人人都是产品经理的趋势"~

1.3. 掘金MCP

😏 掘金前些天也推出了自己的 MCP Server《通过 Trae 等 AI IDE 配置 MCP一键发布到掘金》,配置起来非常简单,先 生成下Token

这个Token是固定的,跟掘金账号绑定,忘记了也没关系,下次获取返回的值也是一样的,但也注意别泄露,以免引起不必要的麻烦。接着在 Trae、Cursor支持MCP 的AI编程工具,找到添加MCP配置的入口,粘贴下配置

json 复制代码
{
    "mcpServers": {
        "juejin-deploy-mcp": {
            "command": "npx",
            "args": [
                "--registry=https://registry.npmjs.org",
                "-y",
                "@juejin-team/mcp-server@latest"
            ],
            "env": {
                "JUEJIN_TOKEN": "粘贴上面生成的Token"
            }
        }
    }
}

配置完确认:

💁‍♂️ 这个 MCP Server 的作用:将纯前端项目(HTML/CSS/JS) 一键部署到掘金 ,注意是 "纯前端项目 " ❗️❗️❗️ 依赖Node.js 或是 需要编译的框架 (如 React/Vue 未打包成静态文件) 都是不支持的哈!!!

😄 本质 :将 纯前端项目 作为 静态资源 托管到 CDN好处 :无需繁琐配置,调下MCP就能完成部署。通过不同 项目文件夹名 区分不同项目,只要不改名字,每次调MCP都会覆盖。😐 用到的 图片 等资源文件不会上传!!!需要你自己找 图床 或者 静态托管网站 ,生成 远程url 访问,最简单的可以用 GithubGitee

😳 端午前,掘金酱发了篇《掘金 AI 编程社区- 人人都是 AI 编程家竞赛》,省流:

使用 Trae+掘金 MCP 实现 VibeCoding 创意作品参赛,奖励丰厚,截止日期:6月30日

❗️ 提醒下:发布完记得报名填下参赛信息:vibecoding

😏 刚好有个💡,妥妥得参加一波,虽然没有前端开发经验,但刚花3刀开了 Trae Pro 我的感觉强得可怕🔥。

1.4. 灵感乍现

😶 随着 AI编程工具 的逐渐普及,以前那种喜欢 钻研技术细节 的热情也逐渐褪去,现在打开掘金,也就签个到,然后刷下 掘金热榜

🤔 很多时候,我并不能直接从文章的 标题 ,得知这是一篇 写什么内容的文章 ,我得点开,粗略看下写的啥,是不是自己感兴趣的内容,再选择是否做详细阅读,😐 感觉这样获取信息的效率有点低。😏 我突然有个想法:为什么不自己写一个 "掘金丶微热榜" 呢?思路立马就有:

  • 写脚本定时爬热榜接口,获取热榜文章数据。
  • AI做文章预读,批量生成标题和内容摘要。
  • Trae 生成我想要的页面,然后利用 掘金MCP 部署成一个静态网站。

2. 实践

2.1. 接口分析

浏览器 F12 打开开发者模式,过滤 XHR,截取不同分类的url:

😄 明显只是 category_id 不一样,试了下不传uuid,一样可以获取到数据,OK,Trae 直接新建一个 py 文件,写提示词尝试数据爬取:

😂 然后生成结果惊到我了,我前面有个链接复制是重复的,Trae 觉得可能有错,给我贴心的标注出来了:

🙂 category_id 修正后,直接运行,数据解析,一步到位,雀食🐂🍺:

2.2. Coze

2.2.1. 代码节点-模拟请求

Coze 空间 直接新起一个 工作流 ,拖一个 代码节点 ,不支持pip装三方库,没有 requests 库,但提供了 requests_async 库,用法基本一样,导包设置下别名:import requests_async as requests ,把上面 Trae 生成的爬取代码CV过来,改改就能用:

python 复制代码
import requests_async as requests

async def main(args: Args) -> Output:
    category = args.params['category']
    category_map = {
        '综合': '1',
        '后端': '6809637769959178254',
        '前端': '6809637767543259144',
        'Android': '6809635626879549454',
        'iOS': '6809635626661445640',
        '人工智能': '6809637773935378440',
        '开发工具': '6809637771511070734',
        '代码人生': '6809637776263217160',
        '阅读': '6809637772874219534'
    }
    # 检查分类是否有效
    if category not in category_map:
        raise ValueError(f"无效的分类: {category},有效分类为: {', '.join(category_map.keys())}")

    # 构建请求URL
    category_id = category_map[category]
    url = f"https://api.juejin.cn/content_api/v1/content/article_rank?category_id={category_id}&type=hot&aid=2608&spider=0"

    # 发送请求
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    }
    response = await requests.get(url, headers=headers)
    response.raise_for_status()  # 如果响应状态码不是200,将引发HTTPError异常
    data = response.json()
    # 构建输出对象
    ret: Output = {
        "result": data,
    }
    return ret

接着点下 测试代码,传入category值为 "综合",运行,ok,数据正常输出:

这里为了方便调试,直接输出 接口返回的json字符串 ,后续处理,需要转换成 Array 的形式返回:

**

💡 Tips :如果修改输出参数后报错,点右上角X,关掉节点,然后再点 在IDE中编辑 就好了。
再次试运行:

👏 Nice,数据解析完毕,这里加了三个字段:order (排行榜序号)、ai_title (ai生成的文章标题)、ai_desc (ai生成的摘要)。

2.2.2. LinkReaderPlugin插件-读取文章内容

如题,添加一个 LinkReaderPlugin的插件,用于读取文章内容,简单做下配置:

配完试运行:

🐂 啊,50个页面只花了13s,我还想着会很慢,然后用老套路,拖五个插件 ,分任务并发执行呢🤣。时代变了,现在最多支持 200次批处理+10个并行任务

2.2.3. LLM节点-生成文章标题和摘要

接着拉个大模型节点,批处理生成标题和摘要,先配下批处理和输出参数:

接着简单写下提示词,让AI自动优化:

AI优化后的提示词:

🤣 中规中矩,补全输出参数,试运行:

😳 卧槽,6s干完,🐂啊,不过输出效果不是特别满意,再自己微调下提示词,最终效果:

2.2.4. 代码节点-数据整合

😶 拖个代码节点,用 LLM节点返回的标题和摘要,更新下前面代码节点返回数据的对应字段:

🐶别问我为什么这么写,因为这样写不报错,我也想直接返回origin_data ...
结束节点,返回变量,输出变量,引用下代码节点输出的值就行啦~

试运行无误后,点击右上角 发布 按钮,填下 版本号版本描述,进行发布~

2.2.5. API调用

发布完就可以通过API来调用工作流了,打开 执行工作流 页面进行测试,先整个Token,点授权 → 添加新令牌:

输入Token名称,设置过期时间,勾选工作流,工作空间选个人,点确定:

会生成一个Token,只会显示一次,自己保存好哈,不然就得删了重建了~

回到工作流、执行工作流,依次输入:token工作流id (编辑工作流的url中workflow_id后面跟着的数字字符串就是) 和 参数:

点击运行,等待片刻,返回结果没啥问题,接着让 Traecurl 命令模拟请求翻译成 python 实现:

😀 Trae 如约写出py请求代码,试了下一把过,数据能正常获取到,理论上Coze部分应该就到这,但实践过程还是遇到了一些问题...

2.2.6. 问题:资源点额度 & 插件调用

😶 Coze 现在是 算资源点 的,个人免费版一天只有 500点,工作流试运行也扣资源点,加了LLM节点,没玩几次就说免费资源点用尽:

😳 资源点那么不经用的吗?后面看了下计费标准才知道,这玩意主要是调LLM扣的:

右上角看到跑一次工作流消耗 30多w的tokens,后面找到消费历史:

我去,跑一次100点,怪不得没玩两下就没了,🤡 唉,想继续体验只能氪金,个人进阶版限时特惠 9.9元/月~

🐶 搞一次得调8次工作流,1000 点也扛不住造,赶紧找个最便宜的模型:

调了两次工作流花了88点左右,感觉一次差不多50点,一天更新两次热榜还是够用的:

😐 当我满怀期待跑 py调工作流api 的脚本时,后台却给我返回了这个:

啥玩意?工具请求超过限制?接着我打开工作流又是运行了一下:

🙂 想找个客服问问,才发现 个人版不配

🐶 加了交流群没人鸟,只能老老实实 反馈-提工单

🤡 只有两个选项... 直接选的第二个:


过了十分钟有个技术工程师让我提供下异常logid,提供完过了快一小时都没回我...

🤡 等了一个多小时,就得到一个 免费插件稳定性没法保证,请用付费插件或自定义插件 的回复,问题是 《文档中心:插件费用》也没有 链接读取 的插件,🙃 这才刚开始,就结束了?
🙂 地铁回家路上,我试了写提示词让 Kimi 分析url的内容并提取标题和摘要,竟然可以:

😆 那是不是我 不用插件,直接调豆包模型也可以解析url生成标题和摘要?直接开试:

分别试下两个模型的输出:

🤡 Lite 直接胡说八道,明明是 Trae Pro付费的文章,Pro 说无法访问链接让我手贴内容,看来是模型不支持联网,但支持 技能配置 ,选下 链接读取 插件就正常了:

🤔 能否通过 模型自己调插件的方式 绕开上面 用户调用插件 的限制呢?同样试试:

🤣 卧槽,果然可以,机智如我:

😄 之前为了避免那个访问限制,只能同步加延时(5分钟请求一次),但依旧报错:

现在:

😏 而且速度快多了,不过一次还是得等上几分钟,8个不得半个小时?用毛 同步request ,直接上 并发协程Trae 听我号令🔥:

哗哗哗,没几分钟就搞完了

😳 正当我以为万事大吉准备收工,却瞥见输出结果不对劲:


😭 难受,这个方法也行不通,那就只能自己写个插件了...

2.2.7. 写个获取文章内容的插件


😄 这里支持安装三方依赖包,直接装一波 requests

接着先配 元数据 (插件的输入/输出参数)

接着Trae写
接着Ctrl + I 写提示词让AI写代码:


运行结果:

OK,都打印出来了,接着用 Trae 用正则写提取代码 (🐶 Cluade 4.0 靠谱一点):


🤡 试了下,单纯靠正则不太行,有些正文会获取不到,还是让 Trae 抠 HTML 标签吧:

加了bs4+lxml库,运行后生成结果还可以,代码复制到 Coze:

python 复制代码
from runtime import Args
from typings.juejin_article_content.juejin_article_content import Input, Output
import requests
import re
from bs4 import BeautifulSoup


def extract_article_content(url):
"""
从URL获取掘金文章内容并提取正文,使用BeautifulSoup解析HTML

Args:
url: 文章URL地址

Returns:
str: 提取后的文章内容字符串
"""
try:
# 发送HTTP请求获取网页内容
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
response = requests.get(url, headers=headers)
response.raise_for_status() # 如果请求不成功则抛出异常
html_content = response.text

# 使用BeautifulSoup解析HTML
soup = BeautifulSoup(html_content, 'lxml')

# 提取文章标题
title_element = soup.select_one('h1.article-title')
title = title_element.text.strip() if title_element else "未找到标题"

# 提取文章作者
author_element = soup.select_one('span.name')
author = author_element.text.strip() if author_element else "未知作者"

# 提取文章内容 - 查找文章主体部分
# 尝试多种可能的选择器,增强通用性
content_element = None
possible_selectors = [
'div.article-viewer.markdown-body', # 常规文章页面
'div.markdown-body', # 可能的备选结构
'article.article-content', # 另一种可能的结构
'div.article-content' # 再一种可能的结构
]

for selector in possible_selectors:
content_element = soup.select_one(selector)
if content_element:
break

if content_element:
# 提取所有段落和列表项
paragraphs = []
list_items = []

# 提取段落
for p in content_element.find_all('p'):
# 移除style标签
for style in p.find_all('style'):
style.decompose()

# 获取文本内容
text = p.get_text().strip()
if text:
paragraphs.append(text)

# 提取列表项
for li in content_element.find_all('li'):
text = li.get_text().strip()
if text:
list_items.append(text)

# 去重处理
unique_paragraphs = remove_duplicates(paragraphs)
unique_list_items = remove_duplicates(list_items)

# 构建返回内容 - 只包含正文
content_parts = []

# 添加段落
for p in unique_paragraphs:
content_parts.append(p)

# 添加列表项
for li in unique_list_items:
content_parts.append(f"• {li}")

# 合并所有内容
return "\n\n".join(content_parts)
else:
# 尝试更通用的方法提取内容
# 获取所有段落和列表项,不限定在特定容器内
paragraphs = [p.get_text().strip() for p in soup.find_all('p') if p.get_text().strip()]
list_items = [li.get_text().strip() for li in soup.find_all('li') if li.get_text().strip()]

if paragraphs or list_items:
# 去重处理
unique_paragraphs = remove_duplicates(paragraphs)
unique_list_items = remove_duplicates(list_items)

# 构建返回内容
content_parts = []
content_parts.extend(unique_paragraphs)
content_parts.extend([f"• {li}" for li in unique_list_items])

return "\n\n".join(content_parts)
else:
return "未找到文章内容"

except Exception as e:
return f"处理URL时出错: {e}"


def clean_html_content(html):
"""
清理HTML内容,移除标签但保留文本和表情符号
注意:此函数在使用BeautifulSoup后不再需要,但保留以兼容旧代码

Args:
html: HTML内容

Returns:
清理后的文本内容
"""
# 使用BeautifulSoup清理HTML
soup = BeautifulSoup(html, 'lxml')
# 获取纯文本
clean_text = soup.get_text()
# 移除多余空白
clean_text = re.sub(r'\s+', ' ', clean_text).strip()
return clean_text


def remove_duplicates(items):
"""
移除列表中的重复项

Args:
items: 文本列表

Returns:
去重后的列表
"""
unique_items = []
seen = set()

for item in items:
# 规范化文本以便更好地去重
normalized = re.sub(r'\s+', ' ', item).strip()
# 使用规范化文本作为唯一标识
if normalized and normalized not in seen:
seen.add(normalized)
unique_items.append(item)

return unique_items

def handler(args: Args[Input])->Output:
url = args.input.url
content = extract_article_content(url)
return {"content": content}

运行:

😄 试了好几个url,都能正常获取,接着发布,成功后回到工作流,LLM节点将 工具 替换成我们写的插件:

改下提示词:

接着试运行,成功拿到数据,接着本地跑脚本,发现高频访问还是触发了掘金的反爬,导致获取不到页面内容,🐶 先暂时加了一个错误延时重试,🌚 这个方法可行,但我没留意今天的 1000点 都花完,还在那里跑脚本试。

😳 结果没过多久,手机就收到欠款短信:说我欠款0.07元,我充了0.1元,没隔多久又提示我欠款0.46元,我擦:


🤡 这... 一时没找到设置额度用完就不能用的地方,又怕充完又扣,忍痛又氪了 10块 买了10000点...

2.3. Gitee 文件托管

😄 就是把上面coze解析返回的内容保存成 json文件 ,发到托管网站,可以 直接通过链接进行访问 ,比较 简单且免费 的就 GithubGitee , 这里选后者,国内访问会顺畅些🐶。Gitee 直接新建一个 公共仓库 ,接着到 设置→安全设置→个人访问令牌→生成新令牌,只给这三个权限:

提交创建:

这个key要保存好,只会显示一次 !!!同样是忘了只能删掉重建,怎么把文件push到gitee?🤣不需要我们知道,写提示词让Trae代劳就好了:

啪啪啪,立马给我生成:

结果运行报错,复制粘贴给Trae纠错,纠错过程:


Trae 666,修改后自己验证,失败联网搜索,纠正后再验证,👏

提交成功,Gitee仓库也可以看到上传的文件:

接着让Trae完善脚本,调 Coze API,获取响应数据,保存文件,然后push到Git仓库,最终生成代码:

python 复制代码
import json
from datetime import datetime
import os
import base64
import time
import asyncio
import aiohttp
from typing import Dict, List, Any, Optional

# 请求Coze工作流
async def fetch_coze_workflow(category: str, session: aiohttp.ClientSession) -> List[Any]:
"""异步请求Coze工作流获取掘金热门文章数据"""
url = "https://api.coze.cn/v1/workflow/run"
headers = {
"Authorization": "Bearer pat_hVoztrF1YdHVrsEtYJR83WqZYgFxL0IZgV5WeogED7VOcaabUNqkwawGHfFRRuxQ",
"Content-Type": "application/json"
}
params = {
"parameters": {
"category": category
},
"workflow_id": "7511534131105251378"
}
try:
async with session.post(url, headers=headers, json=params) as response:
if response.status == 200:
data = await response.json()
print(f"Coze API返回: {data}") # 打印Coze API的响应
if data.get('code') == 0:
# 从data字段获取output并解析为Python对象
output_data = json.loads(data['data'])
return output_data.get('output', [])
return []
return []
except Exception as e:
print(f"调用Coze API失败: {str(e)}")
return []

# 生成JSON文件
async def gen_json_file(result: List[Any], category: str, category_id: str, json_dir: str) -> None:
"""异步更新JSON文件中的掘金热门文章数据"""
# 根据分类生成文件名
category_filename = os.path.join(json_dir, f"{category_id}.json")
try:
# 使用异步文件操作库可能更好,但为简单起见,使用loop.run_in_executor执行同步IO操作
def write_file():
with open(category_filename, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)

# 在线程池中执行IO操作
await asyncio.get_event_loop().run_in_executor(None, write_file)
print(f"成功更新 {category} 分类的掘金热门文章到 {category_filename}")
except Exception as e:
print(f"更新JSON文件失败: {str(e)}")

# 上传json文件夹到Gitee仓库
async def upload_json_to_gitee(json_dir: str) -> None:
"""异步上传json文件夹到Gitee仓库"""
# 配置信息(根据用户提供的信息)
ACCESS_TOKEN = ""
OWNER = "coder-pig"
REPO = "juejin_file_save"
BRANCH = "master"
REMOTE_DIR = "hot_articles"
BASE_URL = 'https://gitee.com/api/v5'

# 扫描json目录
json_files = [f for f in os.listdir(json_dir) if f.endswith('.json')]
if not json_files:
print("📂 当前目录下没有找到JSON文件")
return

print(f"📋 找到 {len(json_files)} 个JSON文件:")
for i, file in enumerate(json_files, 1):
print(f" {i}. {file}")

# 定义辅助函数
async def get_file_content(file_path: str) -> Optional[str]:
"""异步获取文件内容并转换为base64编码"""
try:
# 使用run_in_executor执行同步IO操作
def read_file():
with open(file_path, 'rb') as f:
content = f.read()
return base64.b64encode(content).decode('utf-8')

return await asyncio.get_event_loop().run_in_executor(None, read_file)
except FileNotFoundError:
print(f"文件不存在: {file_path}")
return None
except Exception as e:
print(f"读取文件失败: {e}")
return None

async def check_file_exists(remote_path: str, session: aiohttp.ClientSession) -> Optional[Dict]:
"""异步检查远程文件是否存在"""
url = f"{BASE_URL}/repos/{OWNER}/{REPO}/contents/{remote_path}"
params = {
'access_token': ACCESS_TOKEN,
'ref': BRANCH
}

try:
async with session.get(url, params=params) as response:
if response.status == 200:
file_info = await response.json()
# 如果返回的是列表(目录内容),说明这是目录而不是文件
if isinstance(file_info, list):
# 空列表说明目录存在但文件不存在
return None
elif isinstance(file_info, dict) and 'sha' in file_info:
# 返回的是文件信息且包含sha字段
return file_info
else:
# 其他情况视为文件不存在
return None
elif response.status == 404:
return None
else:
response_text = await response.text()
print(f"检查文件存在性时出错: {response.status} - {response_text}")
return None
except Exception as e:
print(f"检查文件存在性时发生异常: {e}")
return None

async def upload_file(local_file_path: str, remote_path: str, session: aiohttp.ClientSession, commit_message: Optional[str] = None) -> Dict:
"""异步上传文件到Gitee仓库"""
# 获取文件内容
file_content = await get_file_content(local_file_path)
if not file_content:
return {'success': False, 'error': '获取文件内容失败'}

# 检查文件是否已存在
existing_file = await check_file_exists(remote_path, session)

# 打印调试信息
if existing_file:
print(f"📝 检测到文件已存在,将进行更新操作")
else:
print(f"✨ 文件不存在,将进行新建操作")

# 构建API URL
url = f"{BASE_URL}/repos/{OWNER}/{REPO}/contents/{remote_path}"

# 生成提交信息
if not commit_message:
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
action = "更新" if existing_file else "添加"
commit_message = f"{action}文件 {os.path.basename(local_file_path)} - {timestamp}"

# 构建请求数据
data = {
'access_token': ACCESS_TOKEN,
'content': file_content,
'message': commit_message,
'branch': BRANCH
}

# 如果文件已存在,需要提供sha值
if existing_file and 'sha' in existing_file:
data['sha'] = existing_file['sha']

try:
# 根据文件是否存在选择不同的HTTP方法
if existing_file and 'sha' in existing_file:
# 更新已存在的文件,使用PUT方法
async with session.put(url, json=data) as response:
if response.status in [200, 201]:
result = await response.json()
return {
'success': True,
'message': f"文件上传成功: {remote_path}",
'commit_sha': result.get('commit', {}).get('sha'),
'download_url': result.get('content', {}).get('download_url')
}
else:
response_text = await response.text()
error_msg = f"上传失败: {response.status} - {response_text}"
return {'success': False, 'error': error_msg}
else:
# 创建新文件,使用POST方法
async with session.post(url, json=data) as response:
if response.status in [200, 201]:
result = await response.json()
return {
'success': True,
'message': f"文件上传成功: {remote_path}",
'commit_sha': result.get('commit', {}).get('sha'),
'download_url': result.get('content', {}).get('download_url')
}
else:
response_text = await response.text()
error_msg = f"上传失败: {response.status} - {response_text}"
return {'success': False, 'error': error_msg}

except Exception as e:
return {'success': False, 'error': f"上传过程中发生异常: {e}"}

# 批量上传文件
print("🚀 开始批量上传JSON文件到Gitee仓库...")
results = []
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

# 创建一个新的HTTP会话用于上传文件
async with aiohttp.ClientSession() as session:
# 创建所有上传任务
upload_tasks = []
for file in json_files:
local_path = os.path.join(json_dir, file)
remote_path = f"{REMOTE_DIR}/{file}"
commit_msg = f"更新掘金热门文章JSON数据 - {file} - {timestamp}"

task = upload_file(local_path, remote_path, session, commit_msg)
upload_tasks.append((file, local_path, remote_path, task))

# 等待所有上传任务完成
for file, local_path, remote_path, task in upload_tasks:
result = await task
results.append({
'local_path': local_path,
'remote_path': remote_path,
'result': result
})

# 打印结果
if result['success']:
print(f"✅ {local_path} -> {remote_path} 上传成功")
else:
print(f"❌ {local_path} -> {remote_path} 上传失败: {result['error']}")

# 统计结果
success_count = sum(1 for r in results if r['result']['success'])
total_count = len(results)
print(f"\n📊 上传完成: {success_count}/{total_count} 个文件成功")

async def process_category(category: str, category_id: str, session: aiohttp.ClientSession, json_dir: str) -> None:
"""异步处理单个分类的文章"""
print(f"开始处理 {category} 分类的文章...")
result = await fetch_coze_workflow(category, session)
if result:
await gen_json_file(result, category, category_id, json_dir)


async def main():
"""主函数,协调所有异步任务"""
category_map = {
'综合': '1',
'后端': '6809637769959178254',
'前端': '6809637767543259144',
'Android': '6809635626879549454',
'iOS': '6809635626661445640',
'人工智能': '6809637773935378440',
'开发工具': '6809637771511070734',
'代码人生': '6809637776263217160',
'阅读': '6809637772874219534'
}

# 确保json目录存在
json_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'json')
if not os.path.exists(json_dir):
os.makedirs(json_dir)

# 创建一个共享的HTTP会话
async with aiohttp.ClientSession() as session:
# 创建所有分类的任务列表
tasks = []
for category, category_id in category_map.items():
task = process_category(category, category_id, session, json_dir)
tasks.append(task)

# 并发执行所有任务
await asyncio.gather(*tasks)

print("所有分类的文章处理完成!")
# 上传JSON文件到Gitee(异步操作,但在所有文章处理完成后执行)
await upload_json_to_gitee(json_dir)

if __name__ == "__main__":
# 运行异步主函数
asyncio.run(main())

运行结果:

2.4. 嘴遁-🔥静态页面生成术

文档先行,简单写写开发文档:

python 复制代码
# 掘金微热榜

开个一个静态的纯前端项目(不依赖Node.js,或是需要编译的框架),用于掘金微热榜的展示。

# 接口API

category_map = {
'综合': '1',
'后端': '6809637769959178254',
'前端': '6809637767543259144',
'Android': '6809635626879549454',
'iOS': '6809635626661445640',
'人工智能': '6809637773935378440',
'开发工具': '6809637771511070734',
'代码人生': '6809637776263217160',
'阅读': '6809637772874219534'
}

不同类别,对应不同的json文件地址,如"综合" 对应 https://gitee.com/coder-pig/juejin_file_save/blob/master/hot_articles/1.json,项目最终会托管到服务器上。请使用这些json文件作为数据源,不要使用假数据!

# 接口返回Json数据样例

```
[{
"ai_desc": "2025年5月30日,由rolldown驱动的vite,rolldown-vite正式内测。作者用前端项目实测,替换后编译打包4231个文件,操作10次,打包性能提升约2倍,暂未发现问题,可供尝鲜。",
"ai_title": "实测:下一代Vite速度快一倍",
"author_id": "1028798615918983",
"author_name": "粥里有勺糖",
"collect": 0,
"comment_count": 0,
"content_id": "7510477725476339766",
"content_url": "https://juejin.cn/post/7510477725476339766",
"like": 1,
"order": 9,
"title": "视野修炼-技术周刊第121期 | Rolldown-Vite",
"view": 79
}],
```

# 页面需展示的字段

ai_title (没有的话显示title)、ai_desc、view、like、comment_count、collect、content_url

# 交互逻辑

点击文章,打开新标签页跳转到content_url对应的页面

写提示词让 Trae 开干:

以及各种 截图标注,指出问题,让Trae改改改,最终本地预览效果:

本地预览没啥问题,接着发布到 掘金MCP ,切下Agent,然后键入 "发布到掘金MCP"


发布成功后,可以点开上面的预览链接进行预览,没啥问题点击右上角的 "发布 ",依次填下:作品名描述封面对话记录项目Rules 然后发布等审核就行啦,作品链接:🌟 掘金微热榜

3. 小结

🤡 花了差不多2天 (🙂1.5天整Coze+0.5天指挥Trae写页面 ),仓促弄完,以为很简单一个东西,结果踩坑不少,最受伤的莫过于😭 荷包-20 (都够在 东哥那里喝一月奶茶了😐桑心... )

💁‍♂️ 项目基本支棱起来了,但还存在下面这三个主要问题:

  • 并发执行Coze工作流爬掘金文章太快,会触发反爬,导致获取不到内容🤡,AI 自然生成不了标题和摘要。细水长流,最重要 ,还是改成 同步爬取+长延时策略,慢没关系,热榜数据变化不会太频繁,🐶 而且1000点的额度,一天也就够搞2~3次。
  • Gitee 托管文件不行 🐶,也不知道 json 文件哪里有敏感信息,有几个文件访问几次后就报 "The content may contain violation information ",🤷‍♀️ 妥妥需要换一个,🙇大佬 有什么免费/便宜,操作起来简单,不用搞域名备案啥的 文件托管网站, 请务必到评论区安利一波😋~
    🤡 浏览器的同源策略 坑了我一把,本地预览正常,部署了却一直加载不出来,我 F12 抓了一波包:

    🤡 我以为是这两个文件没有上传上去,还把这两玩意也放到gitee上了,然后通过URL的方式引用,结果还是404 ,后面群里问大佬,说是我 浏览器问题

    🙂 看了下,还真是:

    🤔 虽然 404 ,但也不影响展示,真的不显示的原因还是 "跨域问题 ",搜了下 Chrome如何关闭CORS策略 ,试了好几种方法都不行,没咋搞过前端,这玩意我还真不了解,问了下 Trae ,给了一个最简单的解决方法------第三方CORS代理服务,通过中间层转发机制,将客户端请求经代理服务器中转至目标服务器,并在响应中添加必要的CORS头信息。用的 api.allorigins.win,🤡 暂时解决了问题~

    😃 其它就是一些功能和UI的细化,有什么建议欢迎评论区留言💐~**
相关推荐
cpp加油站1 小时前
Anthropic断供Claude只是续集!AI编程的剿杀战,早被微软按下启动键
ai编程·claude·trae
it董卓1 小时前
Trae 产品体验:开启 AI 编程新旅程
trae
君若雅2 小时前
我如何借助 Trae 三分钟搞定开源项目中的隐藏 BUG
java·后端·trae
平行绳2 小时前
零基础玩转 Coze 数据库,看这篇就够了!
数据库·人工智能·coze
波点兔2 小时前
【亲测有效 | Cursor Pro每月500次快速请求扩5倍】(Windows版)Cursor中集成interactive-feedback-mcp
windows·mcp·cursor pro
CodeAgent4 小时前
【MCP 第二篇】实现一个简易的MCP
ai编程·mcp
lowcode5 小时前
MCP协议在LLM系统中的架构与实现原理研究
人工智能·llm·mcp
Goboy5 小时前
Trae 与颜色板生成器,为前端开发提供智能配色解决方案
ai编程·trae
Goboy5 小时前
Trae 开发文本大小写转换器,结合 MCP Server 自动部署
ai编程·trae
银空飞羽17 小时前
再学学MCP间接提示词注入
安全·mcp·trae