Felo API PlatformFelo API Platform

研究

把一个搜索词变成 SEO 内容简报

看当前哪些页面在回答这个搜索词,把它们读成 Markdown,梳理它们回答和遗漏的问题,再写一份编辑可以直接拿去列大纲的简报。

构建时间25 分钟难度进阶更新于2026-10-30
适合谁内容营销SEO 专家编辑

复制 prompt

把方括号里的字段替换掉,然后粘贴到 Claude Code、Codex 或任何能访问终端的 AI。它会安装 Felo skill、在花额度前询问,并执行这套 workflow。

为[产品或站点]写一份针对查询[目标查询]的 SEO 内容简报。

读者:[读者]。市场与语言:[地区]、[语言]。角度:[指南 / 对比 / 产品页]。它支持的决策:[新文章 / 更新 / 补缺口]。

先读当前有哪些页面在回答这个查询,再列大纲。输出一份 Markdown 简报,包含:
1. 目标查询和你推断的搜索意图,并给出这一判断的依据。
2. 当前页面覆盖了什么:标题、URL、开篇章节。
3. 当前页面回答了哪些问题,漏掉了哪些问题。
4. 建议大纲,每个章节用一句话说明它必须证明什么。
5. 来自来源的证据和例子,附链接。
6. 完整来源列表。

规则:
- 不要声称搜索量、关键词难度、排名位置或流量。这个工作流不测量这些;用“此处未测量”代替数字。
- 不要编造统计、引文或关于竞品内容的说法。
- 把意图描述为对页面的解读,而不是测量出的事实。
- 引文要短、注明出处并附链接。
- 把地区和语言落实为明确值。
- 消耗额度前,先展示计划执行的 API 调用并等待我批准。
- 不要发布、排期或发送任何内容。
- 将结果写入 ./seo-brief.md,并在对话中总结。

复制的内容也包含安装步骤和安全规则。

你会得到什么

一次运行的示例输出,不是实时生成。

简报示例(节选)

SEO 简报 — “如何自动化支持分诊”

目标查询和意图

查询:“如何自动化支持分诊”。返回的页面以实施步骤开头,而不是产品页,所以意图读作 how-to。这是对页面的解读,不是测量出的意图标签。

当前页面覆盖什么

页面URL开头内容
自动化支持分诊:实用指南https://example.com/guide路由规则,然后是标签
分诊自动化清单https://example.com/checklist清单本身

页面回答的问题

  • 分诊自动化改变了什么
  • 从哪些路由规则开始

页面漏掉的问题

  • 如何衡量分诊是否真的改善
  • 分类器判断错误时怎么办

建议大纲

  1. 分诊自动化改变了什么——证明它关乎队列,而不是人头。
  2. 值得自动化的前三条路由规则——每条用实例证明。
  3. 第一个月如何衡量——用运营负责人能取到的指标证明。
  4. 自动化出错时——证明人工兜底路径。

证据

  • 路由规则示例,引自 https://example.com/guide(2026-10-30 核对)

此处未测量

搜索量、关键词难度和排名位置。这个工作流读取公开页面,不查询搜索索引或排名工具。

来源

  • https://example.com/guide
  • https://example.com/checklist

实际调用

给开发者:同一套 workflow 的可复制代码,按执行顺序排列。

展开实际 API 调用
  1. 先看当前谁在回答

    用 Chat 问这个搜索词,拿到答案和它引用的页面;这些页面就是当前的竞争内容。

    seo-brief.ts
    const BASE = "https://openapi.felo.ai";
    const headers = {
      Authorization: `Bearer ${process.env.FELO_API_KEY}`,
      "Content-Type": "application/json",
    };
    
    async function felo<T>(path: string, body?: unknown): Promise<T> {
      const res = await fetch(`${BASE}${path}`, {
        method: body ? "POST" : "GET",
        headers,
        body: body ? JSON.stringify(body) : undefined,
      });
      if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
      return res.json() as Promise<T>;
    }
    
    type ChatData = {
      data: {
        answer: string;
        resources: { link: string; title: string; snippet: string }[];
      };
    };
    
    const query = "how to automate support triage";
    const ourSite = "example.com";
    
    const coverage = await felo<ChatData>("/v2/chat", {
      query: `What pages currently answer the search query "${query}"? List the pages and what each one covers.`,
    });
    
    const candidates = coverage.data.resources
      .filter((resource) => !resource.link.includes(ourSite))
      .slice(0, 5)
      .map((resource) => ({ url: resource.link, title: resource.title }));

    示例输出

    {
      "answer": "Readers searching this usually want a step-by-step …",
      "sources": [
        { "link": "https://example.com/guide", "title": "…", "snippet": "…" }
      ]
    }
  2. 把页面读成 Markdown

    用 Web Fetch 把最强的三到五个页面抓成 Markdown,保留标题和问题。

    seo-brief.ts
    type ExtractData = { data: { content: string } };
    
    const pages = await Promise.all(
      candidates.map(async (page) => {
        const res = await felo<ExtractData>("/v2/web/extract", {
          url: page.url,
          output_format: "markdown",
          with_readability: true,
          with_cache: true,
        });
        const checkedAt = new Date().toISOString().slice(0, 10);
        return { ...page, checkedAt, content: res.data.content };
      }),
    );

    示例输出

    {
      "url": "https://example.com/guide",
      "checkedAt": "2026-10-30",
      "content": "# Automating support triage\n\n## Routing rules\n…"
    }
  3. 梳理覆盖和遗漏

    把每页的 H2/H3 和 FAQ 问题抽出来,做一张覆盖表:哪些问题被回答,哪些没有。

    seo-brief.ts
    type Heading = { page: string; heading: string };
    
    const headings: Heading[] = pages.flatMap((page) =>
      page.content
        .split("\n")
        .filter((line) => line.startsWith("## "))
        .map((line) => ({
          page: page.title,
          heading: line.replace(/^##\s+/, ""),
        })),
    );
    
    const questions = headings.filter((item) => item.heading.includes("?"));
    
    const byTopic = new Map<string, string[]>();
    for (const item of headings) {
      byTopic.set(item.heading, [...(byTopic.get(item.heading) ?? []), item.page]);
    }

    示例输出

    // questions
    [
      { "page": "…", "heading": "What routes should you automate first?" }
    ]
  4. 写简报

    写目标词、意图、覆盖对比、遗漏问题、建议大纲和来源列表。

    seo-brief.ts
    const today = new Date().toISOString().slice(0, 10);
    
    const brief = [
      `# SEO brief — "${query}"`,
      `Checked ${today}. Site: ${ourSite}.`,
      "",
      "## Intent read",
      "The pages returned for this query lead with: " +
        headings.slice(0, 6).map((item) => item.heading).join("; ") +
        ". This is a reading of their structure, not a measured intent label.",
      "",
      "## What the pages cover",
      "| Page | URL | Checked |",
      "| --- | --- | --- |",
      ...pages.map((page) => `| ${page.title} | ${page.url} | ${page.checkedAt} |`),
      "",
      "## Questions the pages answer",
      ...questions.map((item) => `- ${item.heading} (${item.page})`),
      "",
      "## Topics more than one page covers",
      ...[byTopic.entries()]
        .filter(([, pageTitles]) => pageTitles.length > 1)
        .map(([heading, pageTitles]) => `- ${heading} (${pageTitles.length} pages)`),
      "",
      "## Proposed outline",
      ...questions.slice(0, 5).map((item, index) => `${index + 1}. ${item.heading}`),
      "",
      "## Questions they miss",
      "<!-- Only questions none of the fetched pages answer. Delete this comment when filled. -->",
      "",
      "## Not measured here",
      "Search volume, keyword difficulty, and ranking position. This workflow reads public pages; it does not query a search index or a rank tracker.",
      "",
      "## Sources",
      ...pages.map((page) => `- ${page.title}: ${page.url}`),
    ].join("\n");
    
    console.log(brief);

    示例输出

    # SEO brief — "how to automate support triage"
  5. 存进 LiveDoc

    把简报和页面链接存进 LiveDoc,下次更新只抓变化的部分。

    seo-brief.ts
    type LiveDocData = { data: { short_id: string } };
    
    const briefDoc = await felo<LiveDocData>("/v2/livedocs", {
      name: `SEO briefs: ${ourSite}`,
      description: "One document resource per target query",
    });
    
    await felo(`/v2/livedocs/${briefDoc.data.short_id}/resources/urls`, {
      urls: pages.map((page) => ({ url: page.url, title: page.title })),
    });
    
    await felo(`/v2/livedocs/${briefDoc.data.short_id}/resources/doc`, {
      title: `SEO brief — ${query}`,
      content: brief,
    });
    
    console.log(briefDoc.data.short_id); // the next query adds to this archive

    示例输出

    { "short_id": "PvyKouzJirXjFdst4uKRK3" }

包含什么

  • 来自 Chat 的有来源页面列表,以及答案背后的链接
  • 当前排名靠前的页面,转成 Markdown
  • 这些页面覆盖的标题和问题
  • 一份可直接列大纲的简报
  • 保存来源的 LiveDoc,下次更新可以接着用

不做什么

  • 不测排名、搜索量、关键词难度或流量
  • 只读公开页面;登录墙和付费墙可能失败
  • 不会替你发布或改写整篇文章
  • 一个搜索词对应一次运行;批量词需要你自己循环
  • 不保证内容一定排名

常见问题

它测量搜索量或排名吗?

不测。它只读公开页面,判断当前哪些内容在回答这个搜索词;排名和搜索量请用你现有的 SEO 工具。

数据从哪里来?

Chat 返回有来源的页面,Web Fetch 把页面转成 Markdown。简报里的每个判断都指向具体页面和日期。

能处理多个搜索词吗?

可以,但每次只跑一个词。批量处理时,为每个词重复同样的步骤,并各自保存一份简报。

适合什么类型的搜索词?

适合信息型、比较型和“怎么做”类查询,因为公开页面能提供足够信号。交易型词还需要你自己的产品与转化数据。

会直接写文章吗?

不会。它产出的是简报和大纲,真正写作仍由编辑或你的写作 agent 完成。

一次要花多少?

Chat 和页面抽取都会消耗按量计费的额度;当前费率在价格页面。prompt 会在花额度前请求确认。

相关 workflow

改成你的版本

同一套配方,按你实际的交付方式调整。

只做对比型搜索词

把范围限定在“X vs Y”“最佳 X”这类词,覆盖表会更容易看出差异化角度。

加入你自己的产品页

把产品页作为额外来源加进去,让大纲能落到你的功能和定价上。

按季度刷新

同一个 LiveDoc 重跑,观察竞争页面和大纲建议怎么变化。

这套配方背后的参考页面,以及下一步可以看什么。