乐于分享
好东西不私藏

从零搭建:用 OpenClaw 每天给微信推送一份电脑硬件价格简报

从零搭建:用 OpenClaw 每天给微信推送一份电脑硬件价格简报
写在前面:这是一份基于真实实施记录整理的教程。我们用 OpenClaw 搭了一条全自动链路:每天凌晨 2 点,机器人自动抓取 Amazon US 上 12 个固定硬件 SKU 的历史价格,算出品牌趋势和综合价格指数,生成一份图文简报,推送到自己的微信。最终验证结果:2026-08-26 02:00 无人值守定时推送成功。文中所有代码、命令、报错和解决方案都来自真实踩坑过程,已全部脱敏。

本文含有AI辅助生成内容,请读者结合自身需求甄别参考。

先看最终效果
每天 02:00 自动送达

简报由两大块组成:主流品牌价格趋势(内存 / SSD / 显卡各 4 个品牌,共 12 个固定 SKU)和月度 / 年度综合价格指数(基期 2025-03 = 100)。所有内容渲染成一张长图,微信里点开就能看。

最终推送到微信的长图简报:标题、摘要、KPI、三类品牌趋势、月度/年度综合指数一图到底

最终链路总览
验证通过的架构

整条链路的分工原则只有一句话:Python 负责计算,Qwen 只负责文字表达,OpenClaw 负责调度与发送。分工清晰是后面所有环节稳定的前提。

1
Keepa 采集:Amazon US 历史价格 API,固定 12 个 ASIN
3 类 × 4 品牌,Buy Box 含运费口径
2
Python 处理:价格解析 → 趋势图 → 综合指数 → summary.json
fetch_prices.py / plot_categories.py / build_indices.py / build_summary.py
3
Qwen 写文案:读取 summary.json,只表达、不算数
report_prompt.md 硬规则约束
4
渲染长图:Jinja2 + HTML/CSS → Playwright 截图
build_report_html.py / report.html.j2 / render_report_png.py
5
微信推送:OpenClaw Agent + openclaw-weixin 主动发送
send_test_report.sh
6
定时调度:OpenClaw Cron,每天 02:00(Asia/Shanghai)
openclaw cron create "0 2 * * *"
0
准备工作
开工前备齐
运行环境
一台 Linux 机器(本教程用 systemd user service 常驻 gateway)+ Node.js
数据处理
Python 3.12 + .venv 虚拟环境(pandas / matplotlib / jinja2 / playwright,不做全局安装)
凭据
Keepa API token · 阿里云百炼 API Key(Qwen)
通道
微信(用于 openclaw-weixin channel 绑定)
1
安装 OpenClaw,配好模型
约 20 分钟

先装 Node.js,再装 OpenClaw。首次启动进入Crestodian 初始化界面,按 QuickStart 走完:确认 workspace、gateway、systemd user service。装完还不算完——此时 Agent 还不会说话。

⚠️ 坑 1:No usable model provider is configuredsetup 完成后看到这个提示属正常现象:OpenClaw 本体不带模型,必须自己配置一个可用的 model provider。
⚠️ 坑 2:OpenAI OAuth 报 403 unsupported_country_region_territory尝试 OpenAI Codex OAuth 时,token exchange 直接返回 403。这是区域 / 网络路由限制,不是你的操作问题。不要纠缠,直接换可用的 provider。

解法:改用 Qwen provider(阿里云百炼凭据),装好插件并重启 gateway:

openclaw plugins install @openclaw/qwen-provider openclaw gateway restart

然后把默认模型切到qwen/qwen3.7-plus,重启并重新进入 TUI。当 TUI 顶部显示该模型、发一句话能正常回复,模型链路就通了。

2
接通微信,先验证主动推送
最小化验证

用 OpenClaw 的Weixin channel绑定微信。绑定成功后,微信里会出现一个可聊天的机器人会话——"你 → 机器人"方向已通。但每日简报需要反方向:机器人无人值守时主动发消息。

绑定成功后,微信里出现了“芝士虾1号”——这是它和主人的第一次对话

技巧一次性测试任务
不要直接上正式任务。先创建一个几分钟后触发的一次性短时 cron 任务,内容只发"定时推送测试成功",执行完自动删除——用最小成本同时验证 cron 调度和微信 channel 两个环节,不污染正式配置。
✅ 验证标准到点微信收到"定时推送测试成功",说明 cron → Agent → 微信 channel 的主动链路完全打通,可以进入正文开发。

实际聊天记录:吩咐“2 分钟后发一条定时推送测试成功,执行后删除”,23:18 准点收到

3
数据采集:Keepa + 固定商品篮子
核心设计决策

数据源选Keepa:成熟的 Amazon 价格历史生态,直接拿到历史价格序列,不用自己写爬虫长期蹲守。比数据源更重要的是篮子设计——三固定原则

固定规格
内存只选 DDR5 32GB 6000;SSD 只选 2TB PCIe 4.0 NVMe;显卡锁定 RTX 5070 12GB 同芯片档
固定品牌
每类 4 个代表品牌
固定 SKU
每品牌 1 个固定 ASIN,每次采集都是这 12 个,样本永不漂移
为什么不用"品牌均价"把不同档次、不同规格的 SKU 混进"品牌均价",指数会被产品结构变化污染——便宜型号卖得多指数就"跌",这不是真实价格变化。固定 SKU 才能保证每次比较的都是同一个东西。
类别
4 品牌 × 1 固定 SKU
内存 RAM
DDR5 32GB 6000
Kingston FURY Beast · Corsair Vengeance · Crucial Pro · G.Skill Flare X5
固态 SSD
2TB PCIe 4.0 NVMe
Samsung 990 PRO · WD Black SN850X · Crucial T500 · Kingston KC3000
显卡 GPU
RTX 5070 12GB
ASUS TUF OC · MSI Gaming Trio OC · Gigabyte Gaming OC · Zotac Solid OC

12 个 SKU 固化在products.json里,之后每次采集都原样复用这份清单。

⚠️ 坑 3:Keepa 字段与 tokenKeepa 返回的 csv 数组有 36 个索引位置,Buy Box 价格藏在特定索引里,历史字段要请求足够的 token 才给全。第一次解析只拿到 1931 个 Amazon 价格点且 Buy Box 为空;重新请求足够 token 后,单个 SKU 就有约5971 个历史点。后续主口径选Buy Box(含运费)——最贴近用户实际能买到的价格。

同一 SKU 的三种价格口径:Amazon 自营 / New / Buy Box,最终以 Buy Box 为准

单个 SKU 的历史价格序列(Samsung 990 PRO 2TB),验证数据链路可用

环境方面记得先apt install python3.12-venv,重建 .venv 后装依赖。最后跑 fetch_prices.py,12 个 ASIN 全部 Validation OK(tokensConsumed: 36),原始响应落盘,采集层完成。

4
画图与综合指数:必须避开的统计陷阱
Python 计算层

第一块产出:3 张过去 12 个月的品牌价格趋势图

DDR5 32GB 6000 · 四品牌 12 个月走势

2TB PCIe 4.0 NVMe · 四品牌 12 个月走势

RTX 5070 12GB · 四品牌 12 个月走势

第二块产出:综合价格指数。基期固定为2025-03 = 100,各类别先算类别指数,三类再等权合成 Overall。就在这一步,踩了全项目最有价值的一个坑:

⚠️ 坑 4(重点):Keepa 是事件序列,"没有记录" ≠ "没有价格"第一版指数算出来大量 NaN,年度只剩零碎月份。原因:Keepa 只在价格发生变化时写记录,"某月没有新 event"被错误当成了"无价格"。正确做法:把事件序列 resample 到每日,取每日最后状态并forward-fill延续到今天,再在完整的每日序列上算月均。缺失是被填充消除的,而不是被丢弃的。

核心逻辑在 build_summary.py 的 load_daily() 里,值得逐行看懂:

# Keepa 事件序列 → 连续每日价格 state = df["price_usd"].fillna(-1.0)  # -1 表示明确无 Buy Box series = pd.Series(state.values,                    index=df["datetime_utc"]).sort_index()  daily = series.resample("1D").last().ffill()  # 价格没变化时 Keepa 不会每天重复写记录,# 因此把最后状态延续到今天。 today = pd.Timestamp.now(tz="UTC").normalize() if len(daily) and daily.index.max() < today:     full_index = pd.date_range(         daily.index.min(), today, freq="D", tz="UTC")     daily = daily.reindex(full_index).ffill()

修正后,月度 Overall 从 2025-03 起连续不断档。年度图还要明确标注2025 partial2026 YTD,避免把不完整年份当完整年度解读。以 2026-08 为例:

RAM 月度指数
543.6
基期 2025-03=100
SSD 月度指数
249.5
基期 2025-03=100
GPU 月度指数
94.4
低于基期
Overall / 2026 YTD
295.8
YTD 267.5

月度综合指数:RAM 与 SSD 拉升,GPU 回落

年度视角:2025 partial · 2026 YTD

口径声明(写简报时必须带上)指数上涨 ≠ 整个市场价格上涨同样幅度。这是固定代表 SKU 篮子的指数,反映的是这 12 个具体商品的走势。
5
让 Qwen 写简报:只表达,不算数
AI 文案层

build_summary.py 把现价、7 日变化、月度指数、年度/YTD 指数全部算好,连同图路径写进 summary.json。然后 Qwen 读取它生成中文简报——但只允许做文字表达。约束全部写在 report_prompt.md 里,这几条硬规则是简报可信度的根基:

1. 只能使用 summary.json 中已有的数据,    不得自行搜索、猜测、补充或重新计算数字。 2. null 表示当前没有有效报价,    不得沿用旧价格冒充当前价格。 3. change_7d_pct 为 null 时,只能写"7日变化暂无数据"    或不讨论变化,绝对不能写"持平""上涨""下跌"。 4. 只有 change_7d_pct 明确等于 0.0 时,才允许写"持平"。 5. 不做购买建议,不写新闻,不扩展到其他硬件。 6. 正文中绝对不要输出 MEDIA:、本地文件路径。
✅ 为什么要这么"苛刻"大模型天生爱补全:缺数据时它会"合理推测"一个涨跌幅,看起来通顺,实际是在编数。把计算全部留在 Python、表达全部交给 LLM、用 prompt 硬规则封死推测空间,是数据类自动简报最重要的工程纪律。先用 openclaw agent --message-file report_prompt.md 单独试跑(不发微信),确认输出合规后再进入发送环节。

Qwen 按硬规则生成的文字简报(微信内实际效果):有数据才说话,null 一律写"暂无数据"

6
微信发送:从"5 张图"到"1 张长图"
本项目最大的坑

最初的需求很合理:简报要有标题、文字说明,每张图可单独点开。于是第一版方案是"正文 + 5 张独立图片"。接下来是一连串真实踩坑:

尝试 1一条回复附 5 条 MEDIA:
让 Agent 在回复末尾输出 5 行 MEDIA:路径。结果:微信只收到 1 张图,文字混成一团。改 prompt 强制"每个 MEDIA 独占一行、用绝对路径"再试——还是只有 1 张。结论:单回复多媒体在当前微信插件路径上不可靠。
尝试 2拆成 5 次独立发送
用 openclaw sessions --active 120 --json 拿到微信会话的 session-key,再让 Agent 用 --deliver 分 5 次回复、每次只带 1 条 MEDIA。前 3 张到了,但月度、年度两张持续报错:
GatewayClientRequestError: OutboundDeliveryError: sendMessage ret=-2 errmsg=prepare failed
⚠️ 坑 5:ret=-2 prepare failed 与会话状态加重试(慢速 + 3 次)依然失败。关键判别测试:先在微信里主动发一句"继续发图"刷新会话,立刻重试——两张都收到了。据此判断:当前 openclaw-weixin 的主动媒体发送受 context / prepare 状态影响,完全无人值守一次发 5 图,可靠性不够

真实记录:Agent 回复“会话已刷新~”,但高清长图依然“Media failed.”

于是换思路:把所有文字和图片渲染成一张长图,只主动发送 1 次。对比 PDF、HTML 链接后选方案 A:Jinja2 模板 + HTML/CSS + Playwright 截图——排版质量顺便解决。实现只有三个文件,流水线是 summary.json → report.html → report.jpg:

文件
作用
build_report_html.py
读 summary.json,图表 base64 内嵌进 Jinja2 模板,生成自包含 HTML
templates/report.html.j2
简报版式:标题、摘要、4 个 KPI、三类品牌说明+图、月度/年度图、注释
render_report_png.py
Playwright 无头浏览器整页截图,输出 JPG
⚠️ 坑 6:浏览器路径Playwright 默认找 ~/.cache/ms-playwright/,而本机装的是 Chrome for Testing。必须在脚本里显式写死 executable_path,否则一直报找不到浏览器:
CHROME = "/home/<USER>/.cache/chrome-for-testing/           chrome-linux64/chrome"  browser = await p.chromium.launch(     executable_path=CHROME, headless=True) page = await browser.new_page(     viewport={"width": 1400, "height": 2000},     device_scale_factor=2.0) await page.goto(url, wait_until="networkidle") await page.screenshot(path=str(REPORT_IMAGE),                       full_page=True,                       type="jpeg", quality=92)
⚠️ 坑 7:微信图片的"尺寸夹缝"(务必知道)长图生成后,高清版反而发不出去:· 4500×14005 PNG(2.8 MB)→ Media failed· 降到 2800×9614 JPG → 仍然 Media failed(瓶颈是像素尺寸而非文件字节数)· 缩到 1400×4807 →发送成功,但文字偏小、可读性打折当前微信通道存在"可发送尺寸上限"和"可读性下限"的夹缝。本项目的务实选择:先保留半尺寸版本把每日链路跑通;若追求高清,可把长图按版块切成 2~3 段发送,或换更宽松的通道。
7
定时任务:每天 02:00 自动推送
最后一公里

发送脚本 send_test_report.sh 只有一件事:让 Agent 在指定微信会话里回复一条 MEDIA 并 deliver:

#!/usr/bin/env bash set -e  openclaw agent \   --session-key 'agent:main:openclaw-weixin:direct:<WECHAT_TARGET>@im.wechat' \   --message '只回复下面这一行,不要任何其他文字: MEDIA:/home/<USER>/hardware-price-tracker/report/report_test_half.jpg' \   --deliver

再用OpenClaw Cron挂到每天 02:00(注意显式指定时区):

# 每天 02:00(Asia/Shanghai) openclaw cron create "0 2 * * *" \   --name "每日硬件价格长图测试" \   --tz "Asia/Shanghai" \   --exact \   --command "./send_test_report.sh" \   --command-cwd "/home/<USER>/hardware-price-tracker" \   --no-deliver  # 查看 / 清理 openclaw cron list openclaw cron remove <CRON_JOB_ID>
✅ 最终验证2026-08-26 02:00,无人值守定时推送成功,微信准点收到简报长图。测试完成后用 cron remove 清掉测试任务。正式上线时,把 --command 换成完整流水线脚本(fetch → plot → indices → summary → html → render → send)。

也可以直接在微信里吩咐 Agent 建排程——图中是另一个每日 00:00 的测试任务,创建后准点触发

避坑清单(建议收藏)
7 个真实坑
现象
原因
解法
No usable model provider
OpenClaw 不带模型
装 provider 插件并配置凭据
OpenAI OAuth 403
区域/网络路由限制
换 Qwen 等可用 provider,别纠缠
Buy Box 无数据
历史字段需足够 token
重发请求拿全 csv 数组,按索引解析
指数大量 NaN
把"无新事件"当"无价格"
事件序列 forward-fill 成每日状态再算月均
微信只收到 1 张图
单回复多媒体不可靠
改单张长图,只主动发 1 次
ret=-2 prepare failed
主动媒体发送受会话状态影响
避免无人值守多图连发;单图最稳
高清长图 Media failed
像素尺寸超通道上限
1400×4807 可发;高清需分段或换通道
项目文件地图
照单组建即可
文件
职责
products.json
固定商品篮子:12 个 SKU 的类别 / 品牌 / 型号 / ASIN
fetch_prices.py
按篮子拉取 Keepa 历史价格,保存原始响应
plot_categories.py
生成 RAM / SSD / GPU 三张品牌趋势图
build_indices.py
计算月度与年度/YTD 综合指数
build_summary.py
汇总现价、7 日变化、指数与图路径 → summary.json
report_prompt.md
Qwen 简报硬规则:只表达、不算数、null 不推测
build_report_html.py + report.html.j2
把数据与图渲染成自包含 HTML
render_report_png.py
Playwright + Chrome for Testing 截整页长图
send_test_report.sh + Cron
主动推送到微信,每天 02:00 定时
写在最后

这条链路最值钱的不是某个具体命令,而是三个工程决策:固定 SKU 篮子保证指数不漂移,Python 算数 / Qwen 写字的分工保证数字可信,单张长图 + 单次发送绕开微信通道的可靠性雷区。

后续可扩展:国产品牌价格篮子(已留作下一步)、长图按版块切段兼顾清晰度、或把 products.json 换成你关心的任何品类——整条链路原样复用。

数据来源与说明

· 本文所有实施步骤、代码、报错与结果均整理自《OpenClaw 硬件价格每日简报——实施过程对话整理》(2026-08-26,脱敏版),敏感字段(API Key、微信内部 ID、IP、路径用户名等)已泛化处理。

· 价格数据口径:Amazon US · Keepa 历史价格 · Buy Box 含运费;指数基期 2025-03 = 100;2025 为 partial,2026 为 YTD。指数仅反映固定代表 SKU 篮子走势,不构成任何购买建议。

· 早期脚本(parse_price.py / fetch_prices.py / plot_categories.py / build_indices.py)在原始记录中已折叠,本文按记录保留其文件名与行为,未凭空补写源码。

本文含有AI辅助生成内容,请读者结合自身需求甄别参考。