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 访问,最简单的可以用 Github 或 Gitee。
😳 端午前,掘金酱发了篇《掘金 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后面跟着的数字字符串就是) 和 参数:
点击运行,等待片刻,返回结果没啥问题,接着让 Trae 将 curl 命令模拟请求翻译成 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文件 ,发到托管网站,可以 直接通过链接进行访问 ,比较 简单且免费 的就 Github 和 Gitee , 这里选后者,国内访问会顺畅些🐶。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的细化,有什么建议欢迎评论区留言💐~**