Memo:用 n8n 搭建高尔夫新闻自动抓取 + AI 改写 + WordPress 草稿发布流水线

起因

我有一个高尔夫网站 golforfun.net,想让它每天自动产出一些新闻内容。VPS 上已经跑着 n8n,正好可以用它搭一条自动化流水线。

目标:

  • 每天自动抓取高尔夫新闻
  • 用 AI 改写成中文文章
  • 自动配图
  • 创建 WordPress 草稿(人工审核后再发布)

整体架构

text

Schedule Trigger(每天定时)
    ↓
RSS Read(抓取高尔夫新闻)
    ↓
Limit(限制数量)
    ↓
Code(清洗 RSS 内容)
    ↓
Code(组装请求体)
    ↓
HTTP Request(调 One API → Kimi K3 改写)
    ↓
Code(拆分标题正文 + 提取图片 URL)
    ↓
HTTP Request(下载图片)
    ↓
HTTP Request(上传到 WordPress 媒体库)
    ↓
WordPress(创建草稿,带特色图片)

节点配置

节点 1:Schedule Trigger

  • Trigger Interval: Days
  • Days Between Triggers: 1
  • Trigger at Hour: 9

节点 2:RSS Read

  • URL: https://golf.com/feed/

Golf.com 的 RSS 里包含 enclosure.url 字段,就是文章的特色图片。这是后续配图的关键。

节点 3:Limit

  • Max Items: 3

节点 4:Code(清洗 RSS 内容)

RSS 抓到的内容里经常有换行符、引号,直接插入 JSON 会破坏格式。这个节点负责清洗:

javascript

const items = $input.all();

return items.map(item => {
  const title = (item.json.title || '').replace(/[\n\r\t]/g, ' ').trim();
  const content = (item.json.contentSnippet || item.json.content || '')
    .replace(/[\n\r\t]/g, ' ')
    .replace(/"/g, '\\"')
    .trim();

  return {
    json: {
      cleanTitle: title,
      cleanContent: content.slice(0, 2000)
    }
  };
});

节点 5:Code(组装请求体)

关键点:不要在 HTTP Request 节点里手写 JSON。用 Code 节点组装对象,让 n8n 自动序列化,避免转义问题。

javascript

const item = $input.first().json;

const prompt = `请根据以下新闻,用中文写一篇高尔夫资讯文章。

严格要求:
1. 必须忠实于原始新闻内容,只能使用原文提供的事实
2. 禁止添加原文没有的人物、赛事、时间、数据
3. 如果原文信息不足,就写短文,不要编造
4. 第一行输出标题(不要加引号、星号或 Markdown 标记)
5. 空一行后输出正文,200-400字
6. 只输出文章内容,不要任何解释

原始标题:${item.cleanTitle}
原始内容:${item.cleanContent}`;

return [{
  json: {
    body: {
      model: "moonshotai/kimi-k3",
      messages: [{ role: "user", content: prompt }],
      max_tokens: 4096,
      stream: false
    }
  }
}];

节点 6:HTTP Request(调 LLM)

  • Method: POST
  • URL: http://172.17.0.1:3000/v1/chat/completions
  • Headers:
    • AuthorizationBearer sk-你的OneAPI令牌
    • Content-Typeapplication/json
  • Send Body: 开启
  • Body Content Type: JSON
  • Specify Body: Using JSON
  • JSON: {{ $json.body }}
  • Options → Timeout: 600000

节点 7:Code(拆分标题正文 + 提取图片 URL)

javascript

const input = $input.first().json;

const text = input.choices[0].message.content
  .replace(/\\n/g, '\n')
  .replace(/\*\*/g, '')
  .replace(/^#+\s*/gm, '');

const lines = text.split('\n').map(l => l.trim()).filter(l => l);
const title = lines[0].replace(/^["'"'""'']|["'"'""'']$/g, '').trim();
const content = lines.slice(1).join('\n\n').trim();

const rssItem = $('RSS Read').item.json;
const imageUrl = rssItem.enclosure?.url || '';

return [{ json: { title, content, imageUrl } }];

节点 8:HTTP Request(下载图片)

  • Method: GET
  • URL: {{ $json.imageUrl }}
  • Options → Response → Response Format: File
  • Options → Response → Put Output in Field: data

节点 9:HTTP Request(上传到 WordPress 媒体库)

  • Method: POST
  • URL: https://golforfun.net/wp-json/wp/v2/media
  • Authentication: Generic Credential Type → Basic Auth
    • User: WordPress 用户名
    • Password: 应用密码
  • Send Body: 开启
  • Body Content Type: n8n Binary File
  • Input Data Field Name: data
  • Send Headers:
    • Content-Dispositionattachment; filename="featured.webp"
    • Content-Typeimage/webp

返回的 id 就是 Media ID。

节点 10:WordPress(创建草稿)

  • Credential: WordPress 凭据
  • Resource: Post
  • Operation: Create
  • Title: {{ $('Code').item.json.title }}
  • Content: {{ $('Code').item.json.content }}
  • Status: draft
  • Additional Fields → Featured Media: {{ $json.id }}

踩过的坑

坑 1:模型选择

最初用 mistralai/mistral-nemotron,中文创作质量差,还会编造人物和年份(把 Carlota Ciganda 写成 Linnéa Ström,把 2023 年写成 2022 年)。

换成 moonshotai/kimi-k3 后,中文流畅、事实准确、不编造。这是关键。

坑 2:JSON Body 控制字符错误

在 HTTP Request 节点里手写 JSON 时,如果 prompt 里有真实换行符,会报:

text

Bad control character in string literal in JSON

解决:用 Code 节点组装对象,HTTP Request 里只填 {{ $json.body }}

坑 3:messages 被当成字符串

最初用 “Using Fields Below” 模式,messages 数组被 n8n 当成字符串发出,报:

text

json: cannot unmarshal string into Go struct field ... messages of type []model.Message

解决:改用 “Using JSON” 模式,配合 Code 节点组装。

坑 4:节点位置放错

「拆分标题正文」的 Code 节点必须放在 HTTP Request 之后,因为它依赖 LLM 的返回(choices[0].message.content)。如果放在 Limit 之后,会报:

text

Cannot read properties of undefined (reading '0')

坑 5:单独执行 Code 节点报错

n8n 里单独执行某个 Code 节点时,不会自动执行它前面的节点,所以上游数据是空的。

解决:点画布右上角的 Execute Workflow 跑整条链,或从上游节点开始执行。

坑 6:RSS 只有摘要

Golf.com 的 RSS 只提供一句话摘要,LLM 只能基于这一句话写文章,内容空洞。

当前状态:接受这个限制,生成短资讯。后续可以加全文抓取节点优化。

坑 7:WordPress 应用密码

自托管 WordPress 必须用 Application Password(基础认证),不能用 OAuth2。OAuth2 只适用于 WordPress.com 托管的站点。

在 WordPress 后台 → 用户 → 个人资料 → 应用密码,生成一个专用密码。

当前状态

环节状态
RSS 抓取✅ Golf.com feed
内容清洗
LLM 改写✅ Kimi K3
图片提取✅ RSS enclosure
图片下载 + 上传
WordPress 草稿✅ 带特色图片

待优化

  • □ 全文抓取:RSS 只有摘要,加 HTTP Request 抓文章详情页
  • □ 去重:检查标题是否已存在,避免重复
  • □ 加分类和标签:在 WordPress 节点里自动设置
  • □ 多源聚合:加多个 RSS 源,每天产出更多内容
  • □ 自动发布:确认质量稳定后,把 draft 改成 publish
  • □ 错误通知:如果某天抓取或 LLM 失败,发 Telegram 通知

关键文件位置

项目位置
n8nhttp://162.43.92.249:5678
One APIhttp://172.17.0.1:3000
WordPresshttps://golforfun.net
Kimi K3 模型moonshotai/kimi-k3

一句话总结

n8n 定时触发 → RSS 抓新闻 → Code 清洗 → Kimi K3 改写 → 提取图片 → 上传媒体库 → WordPress 草稿。 每天自动产出 3 篇草稿,人工审核后发布。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注