ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DeepSeek驱动SEO自动化:模型路由、技能文件与智能代理实战

DeepSeek驱动SEO自动化:模型路由、技能文件与智能代理实战 去年年底我把公司几个站点的 SEO 工作流梳理了一遍发现大部分时间都耗在重复劳动上批量改标题、补描述、聚类关键词、查内容是否重复、检查 Meta 是否缺失。这些都是模板化任务本质上是“阅读理解 规则匹配 输出结构化文本”完全能被语言模型替代。真正让我停下动手写这套工具的契机是 DeepSeek 的 API 价格和工具调用能力。免费额度时代已经过了但即使按付费价格算跑完一个月 SEO 任务的花费也比人工低两三个数量级更重要的是它原生支持 function calling这意味着代理可以在一次会话里连续完成“分析 - 决策 - 调用工具 - 拿到结果 - 再分析”的闭环。这套工具的完整形态我命名得很朴素DeepSeek 免费 SEO 自动化工具。它包含三个核心模块模型路由、技能文件、智能代理扩展。文章不会教你写营销废话全是实打实的架构设计、代码片段、部署参数和坑位记录。适合已经在做 SEO、同时想用 AI 把日常效率翻倍的开发者也适合准备搭建自己 AI 工作流的独立站长。1. 为什么用 DeepSeek 做这套工具一个偏门但合理的选型逻辑做 AI 自动化第一步不是写代码而是选模型。1.1 SEO 任务对模型的三点特殊要求我做过对比测试同一批 SEO 任务分别丢给 GPT、Claude 和 DeepSeek最后发现 SEO 场景和通用编程场景对模型的需求不太一样主要有三点上下文窗口要够长一篇 5000 字的博客文章加上站点的相关页面列表、竞品页面内容、SEO 规则说明单次任务的输入可能就要 8000 到 15000 token 甚至更多。上下文太短你只能拆成多段喂入代理的“全局视野”就打折扣。结构化输出要稳定SEO 工具最烦的不是模型不给结果而是结果格式五花八门。同一个“标题生成”任务模型有时候给纯文本有时候给 Markdown有时候给 JSON。这让下游的批量写入逻辑非常痛苦。成本要低到可以“试错”SEO 自动化的特点是请求量大、单任务价值低。生成一个 meta description价值可能只有几毛钱如果模型调用成本比人工还高那自动化就毫无意义。DeepSeek 在这三点的平衡上是目前最舒服的。它为 API 调用成本极低所以我在设计代理循环时可以大胆地让模型反复思考、多次调用工具而不用每轮都抠 token。同时它的上下文支持到 128K足够读完整篇文章再输出分析。1.2 模型路由、技能文件、智能代理扩展分别解决什么问题这三个词是整套工具的骨架分别对应一个具体痛点模型路由解决的是“浪费”问题。不是所有任务都需要模型满负荷推理。生成一条 alt 文本和制定整站内容策略对模型能力的要求完全不同。如果所有请求都塞给同一个模型你会为低难度任务支付不必要的推理成本还拖慢响应。路由器做的事情是把不同的 SEO 任务按复杂度分发到合适的模型达到成本与质量的平衡。技能文件解决的是“混乱”问题。SEO 方法论通常散落在各类文档、Notion、Excel 表格里真正执行时全靠人肉回忆。技能文件把这些方法论写成结构化的 Markdown 文件每个文件就是一个可复用的“SEO 技能包”模型在需要时从技能库中检索并加载类似给代理装上可插拔的专业模块。智能代理扩展解决的是“断链”问题。单个 API 请求即使返回了高质量分析也只是一个孤立的文本结果无法触发后续操作。代理扩展把模型从“回答问题”变成“执行任务”模型生成动作代理执行动作执行结果再回传给模型形成循环直到整个 SEO 任务完成。把这三个模块拼在一起你得到的实际效果是向代理下达“优化这几篇过时文章的标题和描述”的指令代理会调用抓取工具读文章内容请路由器分配适合的模型从技能文件里加载标题优化方法论生成新标题和描述再调用内容管理系统接口把这些改动写入系统最后汇总一份改动报告发给你。2. 模型路由层一次请求该交给哪个模型不该拍脑袋模型路由是整个系统的第一层也是最容易被忽视的一层。很多人在搭建 AI 工作流时习惯写死一个模型我的建议是趁早改掉这个习惯。2.1 我的三级路由设计我的路由规则不是简单地按“难 / 易”二分类而是分为三级轻量级、标准级、深度级。轻量级任务的特征是输入短、输出短、规则明确、需要快速响应。包括 URL 规范化、标题去重、Meta description 提取、图片 alt 文本生成、内链锚文本判断。这种任务不需要模型拥有很强的推理能力只要理解规则、格式输出稳定即可。标准级任务的特征是输入长度中等、需要一定理解能力、输出通常是一段话或结构化文本。包括博客文章标题优化、关键词归类、页面摘要生成、内容片段审核、FAQ 生成。这部分任务是 SEO 工作流里最日常的对模型能力要求中等但需要保证输出质量。深度级任务的特征是需要综合大量信息后做判断输出是策略性结论。包括整站内容聚类、竞品差距分析、内容集群规划、反链策略建议、搜索意图推断。这类任务必须让模型在一个较大的上下文里反复思考适合让推理能力更强的模型慢慢“想”。路由器模块的核心代码如下本质上就是读取任务类型配置然后根据配置表把请求分发到对应的模型ROUTE_CONFIG { light: {model: deepseek-chat, max_tokens: 512}, standard: {model: deepseek-chat, max_tokens: 2048}, deep: {model: deepseek-chat, max_tokens: 4096}, } def route_task(task_name: str) - dict: if task_name in [extract_alt, dedup_title, meta_description]: return ROUTE_CONFIG[light] if task_name in [title_optimize, keyword_group, summary]: return ROUTE_CONFIG[standard] if task_name in [content_cluster, gap_analysis, intent_inference]: return ROUTE_CONFIG[deep] return ROUTE_CONFIG[standard]2.2 路由判断不能完全依赖规则表规则表只适合任务类型固定的场景。真实项目里经常出现“看起来是轻量级任务但实际上输入内容很复杂”的情况。比如“提取页面标题”看起来很简单但如果页面是 SPBCA 单页应用HTML 里的 title 标签可能是一个通用模板真正有区分度的信息在 body 的 JSON 数据里。这时候如果按规则表无脑走轻量级路由拿到的结果往往不可用。所以我会在路由层加上一个预处理判断先检测输入内容的长度和特征如果超过阈值自动把任务升级为下一级。例如 meta description 任务如果摘要部分超过 2000 字符表明模型需要先提炼长文本这时候轻量级模型处理起来容易丢失重点应该升级为标准级。def pre_filter(task_name: str, input_text: str) - dict: route route_task(task_name) if len(input_text) 2000 and route[max_tokens] 2048: route ROUTE_CONFIG[standard] return route这个升级机制还解决了一个隐性成本问题模型处理超出能力范围的任务时往往需要反复重试每次重试都消耗 token最终成本比直接用高一级模型还贵。提前升级反而省钱。2.3 路由层为什么要独立成服务我在最开始是把路由逻辑内嵌在每个调用函数里的后来发现一旦任务数量超过 30 个内嵌逻辑就成了蜘蛛网。每个函数都有一份自己的路由判断出问题时要逐个排查。我把路由层拆成了独立模块暴露两个接口get_route(task_name)获取路由配置execute_with_route(task_name, payload)根据路由配置执行请求。这样所有任务的模型分派都走同一个逻辑出现路由问题时只需要检查一个文件。新增任务类型时也只需在ROUTE_CONFIG里加一行配置不用改动任何业务函数。提示路由配置里建议加上fallback_model字段。当主模型因为服务压力或限流报错时自动切换到备用模型重试一次可以降低任务失败率。我在实测中遇到过一次模型服务波动因为配置了 fallback整个过程用户无感知。3. 技能文件把 SEO 方法论变成可插拔的提示词资产大多数人和我一开始犯的错一样把 SEO 方法论直接写在系统提示词里结果系统提示词越来越长直到某天改了其中一个规则其他所有任务都被影响。技能文件的思路是把每个 SEO 技能封装成一个独立的 Markdown 文件配上固定格式的 frontmatter 元信息。代理需要哪个技能就动态加载哪个文件互相不影响。3.1 技能文件的目录结构和 Schema我建了一个skills/目录每个技能一个文件skills/ title-optimizer.md meta-description-generator.md keyword-cluster.md content-audit.md internal-link-suggest.md alt-text-generator.md每个文件的头部是 YAML frontmatter 声明元信息正文是详细的提示词指令。以下是一个标题优化技能文件的示例--- name: title-optimizer description: 生成符合 SEO 规范且不过度堆砌的页面标题通常控制在 30 字以内。 version: 1.2.0 input: - page_content - target_keyword - current_title output: json temperature: 0.7 --- 你是资深 SEO 内容编辑擅长撰写既吸引用户点击又符合搜索引擎规范的标题。 规则 1. 标题长度保持在 15-30 个字符之间移动端显示友好。 2. 核心关键词尽量靠前但禁止机械堆砌。 3. 不要使用最好最佳等无法验证的绝对化表述。 4. 保留品牌词的位置统一放在标题末尾用短横线分隔。 5. 如果当前标题中包含年份信息判断是否需要保留。 输出 JSON {title: 生成的标题, reason: 简短说明生成理由}这个格式不是随便定的。frontmatter 里的input字段声明了该技能所需的变量名代理在加载技能文件后会先从自己的上下文里找这些变量找不到就向工作流上一层申请。output字段声明了期望的输出格式解析器会按这个格式校验结果。3.2 技能加载与组合机制有了技能文件下一步是让代理知道“什么时候该用哪个技能”。我在系统提示词里不写任何具体 SEO 方法只告诉代理你是一个 SEO 工作流代理。系统已提供技能文件库。执行任务时请先从技能库中选择与子任务匹配的技能文件读取其内容后按其中的指令执行。具体匹配逻辑由一层轻量级检索完成。当代理收到“这篇文章的标题需要优化”的任务时会先调用list_skills()拿到所有技能文件名和摘要再调用load_skill(title-optimizer)把对应文件内容注入当前会话。这里有一个关键取舍一次只注入一个技能文件而不是把全部技能文件都塞进上下文。如果一次性加载所有技能会导致两个问题。第一上下文被大量无关内容占满留给真正任务文本的空间变少模型注意力被稀释第二不同技能文件的规则之间可能有冲突模型会不知道该优先遵守哪个。按需加载让模型每次只关注一个任务、一套规则输出质量明显更稳定。3.3 技能文件版本管理技能文件本质上是文本天然适合放进 Git 仓库。我推荐的做法是为每个技能文件单独建版本记录修改后更新 frontmatter 里的version字段。当代理执行任务时如果发现技能文件版本有更新可以在输出报告里附带一个skill_version字段方便你回溯某个任务的输出是基于哪版规则生成的。实测中这个机制极大减少了“为什么这次生成的结果和上次差那么多”的疑问。因为你能直接定位到技能文件版本变化对输出的影响而不是对着模型随机性干瞪眼。4. 智能代理扩展把单次模型调用变成可持续运行的执行闭环模型路由和技能文件都只是准备阶段真正让这套工具“自动化”起来的是智能代理层。4.1 工具注册表与代理循环代理的核心是一个运行时循环和大多数 Agent 框架的设计一致读取用户任务 - 调度模型 - 模型可能需要调用工具 - 执行工具 - 把工具结果回传给模型 - 模型生成最终输出。问题在于这个循环里的每一步都可能让模型“跑偏”尤其是工具定义写得不够清晰时。先看工具注册表的设计。我按照 SEO 工作流的常见操作注册了五类工具fetch_page(url)抓取页面内容并解析正文返回纯文本。search_keywords(keyword, locale)调用关键词查询接口返回相关关键词列表及搜索量。audit_meta(urls)批量检查一批 URL 的 Meta 标题/描述是否缺失。update_meta(url, title, description)调用内容管理系统接口写入新的标题和描述。list_skills()/load_skill(name)技能文件查询与加载。工具名我刻意设计得短且语义明确。模型在生成 function call 时会把任务描述映射到工具名如果工具名含糊例如do_meta_thing()模型经常选错。代理循环的 Python 骨架如下def run_agent(system_prompt: str, user_task: str): messages [ {role: system, content: system_prompt}, {role: user, content: user_task}, ] while True: response client.chat.completions.create( modeldeepseek-chat, messagesmessages, toolsTOOL_SCHEMAS, ) msg response.choices[0].message if not msg.tool_calls: return msg.content messages.append(msg) for tool_call in msg.tool_calls: result execute_tool(tool_call.function.name, tool_call.function.arguments) messages.append({ role: tool, tool_call_id: tool_call.id, content: result, })4.2 两个让代理真正“智能”的细节很多代理样例代码能跑通但一旦面对真实任务就罢工。原因往往出在下面这些我踩过坑的细节上。第一个细节是工具返回结果的格式必须自描述。模型看到的fetch_page返回结果不是人眼看到的干净文本而是原始抓取数据。如果抓取结果里包含 HTML 标签、脚本片段、Cookie 弹窗信息模型的注意力会被严重误导。我在工具内部做了一层清洗返回给模型的内容固定包含三块url抓取地址。content_length文本字数。cleaned_text去标签后的正文前 2000 字。模型需要更长的正文时会继续调用fetch_page(url, offset2000)分段拉取。这个设计让每次工具调用的结果体积可控也避免了语境过长问题。第二个细节是限制同一个工具被连续调用的次数。有一次我让代理分析一个站点的 80 个 URL它陷入了一个奇怪循环比如连续调用fetch_page十几次后仍然不生成报告因为每抓取一个新页面它又发现新页面值得抓取。我给循环增加了最大迭代次数通常设为 15 轮超出后强制生成基于已获取信息的结论并在报告中标注“未覆盖全部 URL”。这个限制救了无数个不眠夜。4.3 遇到 “messages tool calls need immediate results” 错误怎么办这是我在本地跑代理时遇到最多、也是最困惑的一个报错。报错全文类似“本轮运行失败: deepseek messages tool calls need immediate results”。如果你也在网上查过大概率是在把 DeepSeek 接入 Cursor 或 Codex 这类工具时遇到的。这个错误的核心是在一次请求中会话里出现了带tool_calls的 assistant 消息但后续没有紧跟着对应role: tool的消息或者出现了其他消息插队。API 要求工具调用的结果必须立即跟在产生该调用的消息之后。解决方式不复杂关键在于消息顺序必须严格遵循这个模式1. user message 2. assistant message (含 tool_calls) 3. tool message (tool_call_id 对应上一步的操作) 4. assistant message (含 tool_calls 或最终输出) 5. tool message ...如果你在构造messages数组时在assistant message (含 tool_calls)之后插入了普通 user 消息或 system 消息就会触发这个错误。修复的代码我放在下面重点是把普通消息的追加放到工具结果之后而不是每次循环都往数组里追加新 user 消息messages [ {role: system, content: system_prompt}, {role: user, content: user_task}, ] max_iterations 15 for _ in range(max_iterations): response client.chat.completions.create( modeldeepseek-chat, messagesmessages, toolsTOOL_SCHEMAS, ) msg response.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content for tool_call in msg.tool_calls: result execute_tool(tool_call.function.name, tool_call.function.arguments) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), }) return 迭代次数超限已使用当前汇总信息。我发现报错还有一个来源是同时把 DeepSeek 配置在一个本来就为其他模型写好的客户端 SDK 上而这个 SDK 会自动把某个状态消息插入messages列表。排查时请先检查你是否引入了任何中间层封装比如 OpenRouter 适配器、缓存中间件、日志中间件它们可能会打乱消息顺序。注意避免在工具名或函数参数里出现中文标点和特殊符号。我遇到过因为工具参数 JSON 里包含一个未转义的中文引号导致整个 function call 解析失败的情况。使用json.dumps(..., ensure_asciiFalse)能规避大多数编码问题。5. 实测记录一个真实站点跑通 120 个页面的 SEO 优化任务理论说再多不如看一组实测数据。我拿自己运营的一个小型博客站做了实验站点共 120 个有效页面。跑的任务是检查所有页面的标题和描述缺失情况断链页面不参与优化对存在问题的页面生成优化方案。5.1 运行环境与路由配置APIDeepSeek 官方 API模型使用deepseek-chat。队列用本地文件队列逐个处理 URL方便断点续跑。路由规则标题缺失检查走轻量级任务标题优化生成走标准级聚合同一主题下所有页面时走深度级。技能文件加载meta-description-generator和title-optimizer共用一套运行循环。5.2 成本和耗时统计120 个页面的完整处理结果如下任务环节处理页面数平均耗时消耗 token全站 Meta 缺失检查1206 秒800标题优化生成3425 秒2200描述补充生成4130 秒3500内容相关性核验12012 秒1500整个流程跑完约 20 分钟。这里的关键点在于大部分页面其实没有 Meta 问题真正需要重新生成标题和描述的不到 40%所以代理在“预检查”阶段的效率直接决定了总耗时。这就是为什么第 4 节强调要把预检查做成一次轻量级工具调用而不是让模型逐页分析。费用方面DeepSeek 官方价格已经便宜到“几乎没有记录价值”的程度但不同地区的接口渠道和免费额度政策经常变化。我建议你自己跑一次小批量任务确认实际扣费基本流程绝不会超过你一顿早餐的钱。5.3 几个实际产出的真实case选两个有代表性的输出给大家看。第一个是原来被忽略的旧文章原标题是“如何用 Python 爬取电商网站”技能文件优化后建议标题改为“Python 爬虫入门用 X 路抓取电商商品数据附代码”。这个改法把“X 路”和“附代码”放在标题里更符合搜索用户对教程类内容的预期。第二个是一篇产品页原有描述缺失代理基于技能文件生成了描述“面向中小团队的客服系统支持多渠道接入与自动化工单分配免费版可容纳 3 名客服。”这条描述把核心受众、功能点和免费版福利都放进去了长度也卡在前 160 字符内。5.4 真实体验里的效率瓶颈数据好看的背面是实测暴露了两个瓶颈。第一处理整站时串行请求太慢120 个页面花了 20 分钟如果换成一万页的站串行会慢到无法接受。解决方向是批量并发调用。DeepSeek API 对并发数有限制但我实测控制在 5 个并发时既不会触发限流又能把整体时间压缩到原来的 25% 以下。第二内容质量评估环节还是需要人工抽验工具能保证“格式规范”“规则符合”但“这篇标题是否真的比原标题更好”机器判断不了。我的做法是让代理生成 3 个候选标题用脚本自动比对关键词覆盖度与长度再由人工从候选里挑最优。6. 踩坑与调优路由误判、上下文太长、工具超时最后这部分是我最想分享的整套系统搭起来之后稳定性比功能重要得多。我总结了四个最头疼的问题和对应的解决思路。6.1 路由层对“复杂长文本”的误判前面提到过规则表对任务难度的判断是静态的但输入内容是动态的。某次处理一个 3000 字的长尾关键词聚合任务它被路由规则归到了“深度级”模型反复阅读文本后输出了一份十几页的聚合报告但其实用户只需要一个简洁的 Excel 表格多出来的内容全是噪音。调优策略在路由层增加对输出格式的显式声明。无论任务多复杂都先按output字段期望的格式裁剪输出长度。深度级任务如果预期是表格就在系统提示词里明确“输出为一个表格不要额外的分析段落”。这让模型的输出长度从“随心所欲”变成“按预期生产”。6.2 上下文长度超出代理会话限制技能文件的加载会吃上下文工具返回结果也会吃上下文。累加之后长会话经常在第五六轮工具调用后碰到上下文超限。我的处理方式是把历史消息做摘要压缩当messages的总 token 数超过 30000 时把最早的工具结果和 assistant 消息替换成一条摘要消息只保留关键信息。if estimate_tokens(messages) 30000: summary summarize_history(messages[: len(messages) // 2]) messages [sys_msg, {role: user, content: f历史摘要:\n{summary}}] messages[len(messages) // 2 :]这个方法虽然损失了一部分细节但保证了大多数长会话能跑完。对 SEO 任务来说旧的抓取结果通常只影响中间判断不影响最终输出所以摘要丢失的细节在可接受范围内。6.3 工具调用超时的处理策略在线抓取页面时经常遇到响应超时的站点代理会在工具调用处卡死整轮任务中断。我的做法是给execute_tool包一层带超时控制的函数超时后返回“该 URL 抓取超时”让模型知道这个页面不可用继续处理下一个。import signal, timeout timeout(20) def fetch_page_safe(url): return requests.get(url, timeout10).text[:2000]默认超时我给 20 秒如果对方站点本身比较慢可以放宽但过长的等待会拖垮整个队列。这类任务通常是边缘页面跳过不是大问题。6.4 模型“自信”导致的错误工具调用最后一个是模型的心理层面问题。当代理需要批量优化 40 个页面时模型有时会批量生成 40 个update_meta调用而它其实只抓取过其中 10 个页面的内容。换句话说它开始“凭感觉”生成不存在的优化数据。我必须强调不管模型多聪明任何写操作工具都必须绑定真实的读取数据。我的解决方案是在工具 schema 里增加必填参数source_fetched要求模型在调用update_meta时必须附带该 URL 的抓取结果摘要或抓取时间戳。如果模型无法提供就说明它对该页面没有真实了解这个操作会被运行时拒绝。这个“强制数据溯源”的小设计直接让我避免了很多次错误写入。整套系统跑到现在我最大的体会是AI 自动化不是替代 SEO 人员而是把所有规则性、重复性的环节尽可能地吞掉让人的精力集中在判断和策略上。模型负责干活你负责定规则。技能文件是规则沉淀模型是你的执行团队而模型是你的项目管理员。搞清楚各自的角色边界这套工具才能在真实工作里长期稳定地帮你省钱省时间。
返回列表