用n8n搭建AI自动化工作流:5步实现每日AI资讯自动收集+推送到微信

· 小白基础技术分享

每天手动刷TechCrunch、Hacker News、GitHub Trending获取AI资讯?这篇教程教你用n8n搭建一个自动化工作流,定时抓取多源AI资讯、AI筛选过滤、自动推送到微信。全程可视化拖拽,无需写后端代码。

为什么选择n8n?

n8n是一个开源的工作流自动化平台,相比Zapier/Make等商业工具:

完全自托管:数据不离开你的服务器

300+原生集成:HTTP请求、RSS、数据库、Webhook全支持

代码节点:复杂逻辑用JavaScript/Python自定义

免费无限制:自托管版无执行次数限制

第一步:部署n8n

最简单的方式——Docker一键启动:

docker run -d \
  --name n8n \
  --restart always \
  -p 5678:5678 \
  -v ~/n8n-data:/home/node/.n8n \
  -e N8N_SECURE_COOKIE=false \
  n8nio/n8n:latest

打开 http://你的服务器IP:5678,创建管理员账号,完成初始设置。

第二步:创建定时触发器

在n8n编辑器中:

1. 点击 Add first step

2. 搜索 Schedule Trigger

3. 设置参数:

Trigger Times: 0 8 *(每天早上8点执行)

– 也可以是 Every hour(测试阶段更方便)

第三步:添加多源数据抓取

以三个AI资讯源为例:

节点1:Hacker News Best

– 添加 HTTP Request 节点

– Method: GET

– URL: http://localhost:1200/hackernews/best

– Response Format: String

节点2:GitHub Trending

– 添加第二个 HTTP Request 节点

– Method: GET

– URL: https://github.com/trending?since=daily

– 从Schedule Trigger再拖一条线连接

节点3:自定义RSS

– 添加第三个 HTTP Request 节点

– URL: https://techcrunch.com/category/artificial-intelligence/feed/

第四步:AI筛选与汇总

在三个数据源后面添加一个 Code 节点(模式:Run Once for All Items):

// 合并三个数据源的结果
const items = $input.all();
let allHeadlines = [];

for (const item of items) {
  const data = item.json;
  // 每个源的数据结构不同,根据实际格式提取
  if (data.body) {
    // RSS/XML格式
    const titles = (data.body.match(/(.*?)<\/title>/g) || [])
      .map(t => t.replace(/<\/?title>/g, ''));
    allHeadlines = allHeadlines.concat(titles.slice(0, 5));
  }
}

// 去重
allHeadlines = [...new Set(allHeadlines)];

return [{
  json: {
    date: new Date().toISOString().split('T')[0],
    count: allHeadlines.length,
    headlines: allHeadlines,
    summary: allHeadlines.slice(0, 10).join('\n')
  }
}];</code></pre>
<p>如果需要AI筛选(如只保留AI相关),可以使用DeepSeek API:</p>
<pre><code>const resp = await this.helpers.httpRequest({
  method: 'POST',
  url: 'https://api.deepseek.com/chat/completions',
  headers: {
    'Authorization': 'Bearer «redacted:sk-…»',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    model: 'deepseek-chat',
    messages: [{
      role: 'user',
      content: `从以下标题中选出与AI/LLM/开源最相关的5条:\n${headlines}`
    }]
  })
});

const filtered = JSON.parse(resp).choices[0].message.content;</code></pre>
<h2>第五步:推送到微信</h2>
<p>由于微信个人号没有公开API,这里提供两种方案:</p>
<p>### 方案A:企业微信机器人(推荐,详细配置)</p>
<p>企业微信(WeCom)的群机器人是官方支持的推送渠道,配置简单且稳定。</p>
<p><strong>获取Webhook地址:</strong></p>
<p>1. 下载并登录企业微信客户端(企业微信官网:work.weixin.qq.com)</p>
<p>2. 在目标群聊中,点击右上角 <code>···</code> → <strong>群机器人</strong> → <strong>添加群机器人</strong></p>
<p>3. 为机器人命名(如”AI资讯助手”),点击<strong>添加</strong></p>
<p>4. 复制生成的 Webhook 地址,格式为:</p>
<pre><code>   https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</code></pre>
<p>5. 妥善保存这个 key,它等同于密码,泄露后任何人可向你的群发消息</p>
<p><strong>配置n8n节点:</strong></p>
<p>添加 <strong>HTTP Request</strong> 节点:</p>
<p>– Method: <code>POST</code></p>
<p>– URL: <code>https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=你的机器人key</code></p>
<p>– Headers: <code>Content-Type: application/json</code></p>
<p>– Body (JSON):</p>
<pre><code>{
  "msgtype": "markdown",
  "markdown": {
    "content": "## 🤖 今日AI资讯({{ $json.date }})\n> 共抓取 {{ $json.count }} 条资讯\n\n{{ $json.summary }}\n\n---\n*由n8n自动生成*"
  }
}</code></pre>
<p><strong>企业微信机器人消息类型:</strong></p>
<p style="font-family:monospace;white-space:pre">| 类型 | msgtype值 | 适用场景 |</p>
<p style="font-family:monospace;white-space:pre">|——|———–|———-|</p>
<p style="font-family:monospace;white-space:pre">| 文本消息 | <code>text</code> | 简短通知 |</p>
<p style="font-family:monospace;white-space:pre">| Markdown消息 | <code>markdown</code> | 格式化资讯推送(推荐) |</p>
<p style="font-family:monospace;white-space:pre">| 图片消息 | <code>image</code> | 截图、图表 |</p>
<p style="font-family:monospace;white-space:pre">| 图文消息 | <code>news</code> | 带封面图的文章列表 |</p>
<p style="font-family:monospace;white-space:pre">| 文件消息 | <code>file</code> | 发送PDF/Excel报告 |</p>
<p><strong>注意事项:</strong></p>
<p>– 每个机器人每分钟最多发送20条消息,超出会被限流</p>
<p>– Markdown内容不支持HTML标签,@人需使用 <code><@userid></code> 格式</p>
<p>– 如需@所有人,在markdown内容中添加 <code>@all</code> 即可</p>
<p>### 方案B:Telegram Bot(备选)</p>
<pre><code>await this.helpers.httpRequest({
  method: 'POST',
  url: `https://api.telegram.org/bot${token}/sendMessage`,
  body: JSON.stringify({
    chat_id: chatId,
    text: summary,
    parse_mode: 'Markdown'
  })
});</code></pre>
<h2>第六步:PostgreSQL去重存储</h2>
<p>仅靠内存去重不够可靠——重启n8n后历史数据丢失,可能导致重复推送。用PostgreSQL持久化存储可以彻底解决这个问题。</p>
<p><strong>准备PostgreSQL:</strong></p>
<p>如果还没有PostgreSQL,可以用Docker快速启动:</p>
<pre><code>docker run -d \
  --name n8n-pg \
  --restart always \
  -e POSTGRES_USER=n8n \
  -e POSTGRES_PASSWORD=your_password \
  -e POSTGRES_DB=n8n_dedup \
  -p 5432:5432 \
  -v ~/pg-data:/var/lib/postgresql/data \
  postgres:16</code></pre>
<p><strong>建表:</strong></p>
<pre><code>CREATE TABLE IF NOT EXISTS ai_news_dedup (
    id SERIAL PRIMARY KEY,
    title_hash VARCHAR(64) UNIQUE NOT NULL,
    title TEXT NOT NULL,
    source VARCHAR(100),
    url TEXT,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_title_hash ON ai_news_dedup(title_hash);
CREATE INDEX idx_created_at ON ai_news_dedup(created_at);</code></pre>
<p><strong>在n8n中配置PostgreSQL节点:</strong></p>
<p>1. 添加 <strong>Postgres</strong> 节点(n8n原生支持)</p>
<p>2. 在 Credentials 中新增数据库连接:</p>
<p>– Host: <code>localhost</code>(如果PG与n8n在同一台机器,或用Docker network中的容器名)</p>
<p>– Database: <code>n8n_dedup</code></p>
<p>– User: <code>n8n</code></p>
<p>– Password: <code>your_password</code></p>
<p>– Port: <code>5432</code></p>
<p>3. 操作模式选择 <strong>Execute Query</strong></p>
<p>4. 在 Code 节点之后、推送节点之前插入去重逻辑:</p>
<pre><code>-- 查询今天是否已经推送过该标题
INSERT INTO ai_news_dedup (title_hash, title, source)
VALUES (MD5({{ $json.title }}), {{ $json.title }}, {{ $json.source }})
ON CONFLICT (title_hash) DO NOTHING
RETURNING id;</code></pre>
<p><strong>Code节点去重过滤:</strong></p>
<pre><code>const pgItems = $input.all();
const seen = new Set();

// 查询已有记录
for (const item of pgItems) {
  if (item.json.id) seen.add(item.json.title_hash);
}

// 过滤掉已存在的标题
const deduped = items.filter(item => {
  const hash = md5(item.json.title); // 需要引入crypto库或自行实现
  return !seen.has(hash);
});

return deduped.map(item => ({ json: item }));</code></pre>
<p><strong>高级策略:内容指纹去重</strong></p>
<p>对于标题相似但措辞不同的情况(如”GPT-5正式发布”和”OpenAI发布GPT-5″),可以存储每条标题的向量嵌入,用余弦相似度判断。n8n的OpenAI Embeddings节点配合pgvector扩展可以实现,但会增加API调用成本,建议从标题哈希去重开始。</p>
<h2>第七步:Webhook触发器替代方案</h2>
<p>除了定时触发,Webhook方式更适合需要即时响应或手动控制的场景。</p>
<p><strong>使用场景:</strong></p>
<p>– 收到特定GitHub Star数达标事件后立即推送</p>
<p>– 手动通过curl/脚本触发一次抓取</p>
<p>– 与其他系统(飞书、钉钉、Slack)联动</p>
<p><strong>配置Webhook触发:</strong></p>
<p>1. 将 <strong>Schedule Trigger</strong> 替换为 <strong>Webhook</strong> 节点</p>
<p>2. 配置参数:</p>
<p>– <strong>HTTP Method</strong>: <code>GET</code> 或 <code>POST</code></p>
<p>– <strong>Path</strong>: <code>/ai-news-trigger</code>(自定义路径)</p>
<p>– <strong>Response Mode</strong>: <code>When Last Node Finishes</code></p>
<p>3. 激活工作流后,n8n会生成一个生产级URL:</p>
<pre><code>   https://你的n8n域名/webhook/ai-news-trigger</code></pre>
<p>4. 外部系统向这个URL发请求即可触发工作流:</p>
<pre><code># 手动触发
curl -X POST https://n8n.example.com/webhook/ai-news-trigger

# 带参数触发(可通过$input.first().json.query获取)
curl -X POST https://n8n.example.com/webhook/ai-news-trigger \
  -H "Content-Type: application/json" \
  -d '{"source": "hackernews", "limit": 20}'</code></pre>
<p><strong>安全加固:</strong></p>
<pre><code>// 在Webhook节点后添加Code节点做签名验证
const secret = '你的共享密钥';
const signature = $input.first().headers['x-webhook-signature'];

// 简单HMAC验证
const crypto = require('crypto');
const expected = crypto
  .createHmac('sha256', secret)
  .update(JSON.stringify($input.first().json))
  .digest('hex');

if (signature !== expected) {
  throw new Error('签名验证失败,拒绝执行');
}

return $input.all();</code></pre>
<p><strong>同时支持定时+Webhook:</strong></p>
<p>n8n支持一个工作流同时配置多个触发器——添加 <strong>Schedule Trigger</strong> 和 <strong>Webhook</strong> 节点并列作为第一层,任一触发都会执行后续流程。两个节点用 <strong>Merge</strong> 节点汇合即可。</p>
<h2>故障排查与调试技巧</h2>
<p>工作流出问题时,按以下顺序排查:</p>
<p>### 1. 利用n8n内置调试功能</p>
<p>– <strong>单步执行</strong>:点击节点 → <strong>Execute Node</strong>,单独测试每个节点的输入输出,快速定位出错位置</p>
<p>– <strong>Pin Data</strong>:右键节点 → <strong>Pin Data</strong>,固定测试数据后多次重跑下游节点,避免反复请求外部API</p>
<p>– <strong>日志面板</strong>:点击底部 <strong>Executions</strong> 标签,查看每次执行的完整日志和错误堆栈</p>
<p>### 2. HTTP请求常见错误及修复</p>
<p style="font-family:monospace;white-space:pre">| 错误 | 原因 | 解决方案 |</p>
<p style="font-family:monospace;white-space:pre">|——|——|———-|</p>
<p style="font-family:monospace;white-space:pre">| <code>ETIMEDOUT</code> / <code>ECONNREFUSED</code> | 目标服务不可达或网络不通 | 检查URL是否正确,确认服务器能访问外网 |</p>
<p style="font-family:monospace;white-space:pre">| <code>429 Too Many Requests</code> | 触发频率限制 | 见下文”速率限制”章节 |</p>
<p style="font-family:monospace;white-space:pre">| <code>403 Forbidden</code> | API Key无效或权限不足 | 检查Authorization头,确认API Key未过期 |</p>
<p style="font-family:monospace;white-space:pre">| 响应为空/格式错误 | 目标网站返回HTML而非JSON | 使用 <strong>HTML Extract</strong> 节点代替HTTP Request直接解析 |</p>
<p style="font-family:monospace;white-space:pre">| XML解析失败 | RSS Feed格式不规范 | 先用HTTP Request获取原始文本,再在Code节点中用正则提取 |</p>
<p>### 3. 企业微信推送失败排查</p>
<p>– <strong><code>errcode: 93000</code></strong>:key已失效或被删除,需重新添加机器人</p>
<p>– <strong><code>errcode: 45009</code></strong>:接口调用超过频率限制(20条/分钟)</p>
<p>– <strong><code>errcode: 40001</code></strong>:access_token过期(如使用应用消息而非群机器人)</p>
<p>– <strong>消息发不出但无报错</strong>:检查markdown格式是否正确,特殊字符(<code>></code> <code>#</code> <code>*</code>)需转义</p>
<p>### 4. 生产环境监控建议</p>
<p>在推送节点后添加一个 <strong>Error Trigger</strong> 工作流:当主工作流执行失败时自动发送通知(邮件/钉钉/企业微信),包含失败原因和执行ID:</p>
<pre><code>// 错误通知Code节点
const error = $input.first().json;
return [{
  json: {
    msgtype: 'text',
    text: {
      content: `⚠️ AI资讯工作流执行失败\n时间:${error.startedAt}\n错误:${error.error?.message}\n执行ID:${error.id}`
    }
  }
}];</code></pre>
<h2>速率限制与优化</h2>
<p>API和推送渠道都有频率上限,不做保护会导致数据丢失或封禁。</p>
<p>### DeepSeek / OpenAI API限流</p>
<p>– DeepSeek免费额度:500万tokens/月,并发限制约30 RPM</p>
<p>– 在n8n的HTTP Request节点设置中开启 <strong>Retry on Fail</strong>:</p>
<p>– Max Retries: <code>3</code></p>
<p>– Retry Wait (ms): <code>5000</code>(等5秒后重试)</p>
<p>– 使用 <strong>Wait</strong> 节点在每批API调用之间插入1秒延迟</p>
<p>### 企业微信机器人限流</p>
<p>– 每个机器人:<strong>20条/分钟</strong></p>
<p>– 对于多条资讯,合并为一条Markdown消息推送而非逐条发送</p>
<p>– 如果内容超过4096字节限制,分批发送并在每批之间插入 <strong>Wait</strong> 节点(3秒)</p>
<p>### 源站限流保护</p>
<p>– Hacker News API:无官方限制但建议不超过10 req/s</p>
<p>– GitHub Trending:频繁抓取可能触发Cloudflare验证,建议间隔≥1小时</p>
<p>– TechCrunch RSS:属于CDN缓存内容,频率限制宽松,但建议≥30分钟</p>
<p>### 优化建议</p>
<pre><code>// 在Code节点中添加退避逻辑
async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const resp = await this.helpers.httpRequest({ method: 'GET', url });
      return resp;
    } catch (err) {
      if (i < maxRetries - 1 && err.statusCode === 429) {
        const delay = Math.pow(2, i) * 2000; // 指数退避:2s, 4s, 8s
        await new Promise(r => setTimeout(r, delay));
        continue;
      }
      throw err;
    }
  }
}</code></pre>
<h2>执行与部署</h2>
<p>1. 点击 <strong>Execute Workflow</strong> 测试完整流程</p>
<p>2. 检查每个节点的输入输出,确认数据流转正确</p>
<p>3. 确认企业微信/Telegram收到消息</p>
<p>4. 开启PostgreSQL连接,验证去重逻辑正常工作</p>
<p>5. 点击右上角 <strong>Active</strong> 开关激活工作流</p>
<p>6. 在生产环境建议设置环境变量 <code>N8N_ENCRYPTION_KEY</code> 以加密存储敏感信息</p>
<h2>进阶扩展</h2>
<p>这个基础工作流可以无限扩展:</p>
<p>– <strong>接入更多源</strong>:36氪RSS、微博热搜、Reddit r/MachineLearning、Product Hunt AI分类</p>
<p>– <strong>存入数据库</strong>:用Postgres节点保存历史,Chart节点生成资讯趋势图</p>
<p>– <strong>AI摘要</strong>:用DeepSeek对5篇头条做200字摘要,减少阅读负担</p>
<p>– <strong>多平台分发</strong>:同时推送到飞书、钉钉、Discord、Slack,一个工作流覆盖全部渠道</p>
<p>– <strong>RAG知识库</strong>:将每日资讯写入向量数据库(如Qdrant),构建可检索的AI资讯档案</p>
<p>– <strong>情感分析</strong>:在AI筛选后接入情感分析节点,标注每条资讯的正负面倾向</p>
<p>整套工作流的月成本:VPS约$5/月 + DeepSeek API(每天几十条分析约¥0.1)+ PostgreSQL(同VPS无需额外费用)+ n8n完全免费 = <strong>几乎为零的运营成本</strong>,却能每天自动为你整理AI领域的最新动态。</p>
<hr>
<p style="text-align:center;color:#999;font-size:14px">— END —<br />LC 智趣厅 · 科技与生活的交点<br />ihygg.cn</p>
            </div>

            <!-- Bottom Navigation -->
            <nav class="news-bottom-nav">
                <a href="https://ihygg.cn/" class="news-btn-back">← 返回首页</a>
                                    <a href="https://ihygg.cn/category/xiaobai-tech/" class="news-btn-cat">更多 小白基础技术分享 →</a>
                            </nav>

        </article>

        <!-- Previous / Next Post -->
        <nav class="news-post-nav">
            <div class="news-nav-links">
                <div class="news-nav-prev">
                    <a href="https://ihygg.cn/xiaobai-tech/%e9%9b%b6%e6%88%90%e6%9c%ac%e9%83%a8%e7%bd%b2%e5%bc%80%e6%ba%90%e5%a4%a7%e6%a8%a1%e5%9e%8b%e6%8e%a8%e7%90%86api%ef%bc%9a%e7%94%a8huggingface-tgi%e6%90%ad%e5%bb%ba%e5%85%bc%e5%ae%b9openai%e6%a0%bc/" rel="prev">← 零成本部署开源大模型推理API:用HuggingFace TGI搭建兼容OpenAI格式的服务</a>                </div>
                <div class="news-nav-next">
                    <a href="https://ihygg.cn/ai-news/openai%e8%87%aa%e6%9b%9dastra%e6%a8%a1%e5%9e%8b%e8%83%bd%e7%8b%ac%e7%ab%8b%e5%8f%91%e8%b5%b7%e7%bd%91%e7%bb%9c%e6%94%bb%e5%87%bb%ef%bc%8c%e4%b8%bb%e5%8a%a8%e6%8c%89%e4%b8%8b%e6%9a%82%e5%81%9c%e9%94%ae/" rel="next">OpenAI自曝Astra模型能独立发起网络攻击,主动按下暂停键 →</a>                </div>
            </div>
        </nav>


    </main>
</div>

	</div> <!-- ast-container -->
	</div><!-- #content -->
<footer
class="site-footer" id="colophon" itemtype="https://schema.org/WPFooter" itemscope="itemscope" itemid="#colophon">
			<div class="site-below-footer-wrap ast-builder-grid-row-container site-footer-focus-item ast-builder-grid-row-full ast-builder-grid-row-tablet-full ast-builder-grid-row-mobile-full ast-footer-row-stack ast-footer-row-tablet-stack ast-footer-row-mobile-stack" data-section="section-below-footer-builder">
	<div class="ast-builder-grid-row-container-inner">
					<div class="ast-builder-footer-grid-columns site-below-footer-inner-wrap ast-builder-grid-row">
											<div class="site-footer-below-section-1 site-footer-section site-footer-section-1">
								<div class="ast-builder-layout-element ast-flex site-footer-focus-item ast-footer-copyright" data-section="section-footer-builder">
				<div class="ast-footer-copyright"><p>Copyright © 2026 LC智趣厅导航站   <a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow noopener" style="color:inherit">京ICP备2025120804号</a></p>
</div>			</div>
						</div>
										</div>
			</div>

</div>
	</footer><!-- #colophon -->
	</div><!-- #page -->

<div id="ast-scroll-top" tabindex="0" class="ast-scroll-top-icon ast-scroll-to-top-right" data-on-devices="both">
	<span class="ast-icon icon-arrow"><svg class="ast-arrow-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" width="26px" height="16.043px" viewBox="57 35.171 26 16.043" enable-background="new 57 35.171 26 16.043" xml:space="preserve">
                <path d="M57.5,38.193l12.5,12.5l12.5-12.5l-2.5-2.5l-10,10l-10-10L57.5,38.193z" />
                </svg></span>	<span class="screen-reader-text">Scroll to Top</span>
</div>
<script id="astra-theme-js-js-extra">
var astra = {"break_point":"921","isRtl":"","is_scroll_to_id":"1","is_scroll_to_top":"1","is_header_footer_builder_active":"1","responsive_cart_click":"flyout","is_dark_palette":""};
</script>
<script src="https://ihygg.cn/wp-content/themes/astra/assets/js/minified/frontend.min.js?ver=4.13.4" id="astra-theme-js-js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js" id="prism-js-js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-python.min.js" id="prism-python-js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-bash.min.js" id="prism-bash-js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js" id="prism-javascript-js"></script>
			<script>
			/(trident|msie)/i.test(navigator.userAgent)&&document.getElementById&&window.addEventListener&&window.addEventListener("hashchange",function(){var t,e=location.hash.substring(1);/^[A-z0-9_-]+$/.test(e)&&(t=document.getElementById(e))&&(/^(?:a|select|input|button|textarea)$/i.test(t.tagName)||(t.tabIndex=-1),t.focus())},!1);
			</script>
			<script>
(function(){
  if (typeof Prism === 'undefined') return;

  /* ====== Copy Button ====== */
  document.querySelectorAll('.news-content pre').forEach(function(pre) {
    if (pre.querySelector('.code-copy-btn')) return;
    var btn = document.createElement('button');
    btn.className = 'code-copy-btn';
    btn.textContent = '复制';
    pre.style.position = 'relative';
    pre.appendChild(btn);
    btn.addEventListener('click', function() {
      var code = pre.querySelector('code');
      var text = code ? code.textContent : pre.textContent;
      text = text.replace(/^\n+|\n+$/g, '');
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(function() {
          btn.textContent = '已复制!'; btn.classList.add('copied');
          setTimeout(function(){ btn.textContent = '复制'; btn.classList.remove('copied'); }, 2000);
        });
      } else {
        var ta = document.createElement('textarea');
        ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
        document.body.appendChild(ta); ta.select();
        document.execCommand('copy'); document.body.removeChild(ta);
        btn.textContent = '已复制!'; btn.classList.add('copied');
        setTimeout(function(){ btn.textContent = '复制'; btn.classList.remove('copied'); }, 2000);
      }
    });
  });

  /* ====== Syntax Highlighting ====== */
  document.querySelectorAll('.news-content pre code').forEach(function(block) {
    if (block.classList.contains('language-')) return;
    var text = block.textContent.trim();
    var lang = 'python';
    if (/^(from|import |def |class |print\(|pip |python)/m.test(text)) lang = 'python';
    else if (/^(npm |node |const |let |var |import |export |function |=>|require\()/m.test(text)) lang = 'javascript';
    else if (/^(curl |wget |apt |brew |pip |npm |yarn |git |docker |cd |ls |mkdir |rm )/m.test(text)) lang = 'bash';
    var grammar = Prism.languages[lang];
    if (grammar) {
      block.innerHTML = Prism.highlight(block.textContent, grammar, lang);
      block.classList.add('language-' + lang);
    }
  });
})();
</script>
        <div class="mobile-qr">
        <div class="mobile-qr-inner">
            <img src="https://ihygg.cn/wp-content/themes/ai-nav-child/assets/wechat-qr.jpg" alt="微信公众号:LC智趣厅" width="100" height="100" class="mobile-qr-img">
            <p class="mobile-qr-text">扫码关注微信公众号<br><strong>LC智趣厅</strong></p>
        </div>
    </div>
    	</body>
</html>