CloudFlare Worker 免费部署 VPS 补货监控:实时追踪心仪小鸡 + 微信通知全攻略
前言
此工具致力于帮你监控心仪小鸡补货情况,并在补货时给你发送微信通知,避免没逛论坛错过买 🐔
手把手教程 + 原理讲解,面向新手/小白/中手。
操作更加详细具体,即使以前没用过 CloudFlare Works 也能跟着操作使用。
不需要配置 Telegram,直接发送通知到微信。
实现原理
根据网页中是否包含特定的缺货关键词进行是否缺货的判定, 当网页内容不包含缺货标志词时判定为有货。
例如:
- 对于 BWG/DMIT/HostDZire 网页, 如果页面中包含 "out of stock" 则判定为缺货
- 对于 Colocrossing, 如果页面中包含 "this service is not available" 则判定为缺货
因此你在添加监控对象时, 可以手动在浏览器中打开购买链接, 查看网页中缺货状态显示的关键词是什么, 添加到代码中的 TARGETS
数组中, 参考原有格式添加即可。
例如:对于 DMIT 和搬瓦工的网页, 如果页面中包含 "out of stock" 则判定为缺货。
TARGETS
数组中包含了: 监控名称, 链接, 缺货关键词, 补货提醒文字,后续会讲到。
操作步骤
登录或注册 CloudFlare,切换到 Workers & Pages
如果你找不到路径,也可以替换 account_id
快速访问:
登录之后浏览器链接中有一串很长的字符,例如:
https://dash.cloudflare.com/{account_id}/home/domains
复制 account_id
替换链接访问即可:
https://dash.cloudflare.com/{account_id}/workers-and-pages
创建 Worker
Get Started
选择第三个选项 "Start with Hello World!"
取名
取一个 Worker name, 例如: "VPS 补货微信通知",然后点击右下角 Deploy
编辑代码
替换代码
复制以下代码,进行替换
export default {
async fetch(request, env, ctx) {
// 默认页面响应
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Stock 监控</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { color: #333; }
p { color: #666; }
</style>
</head>
<body>
<h1>Stock 监控运行中</h1>
<p>每分钟检查补货情况,如果补货则通过 Server 酱发送微信通知</p>
</body>
</html>
`;
await handleRequest(env);
return new Response(html, {
headers: { "Content-Type": "text/html; charset=utf-8" },
status: 200,
});
},
async scheduled(event, env, ctx) {
ctx.waitUntil(handleRequest(env));
},
};
async function handleRequest(env) {
// Server 酱配置 - 获取环境变量中的 SCKEY
const SCKEY = env.SCKEY;
if (!SCKEY) {
console.error("SCKEY environment variable not set");
return new Response("SCKEY not configured", { status: 500 });
}
const SERVER_API = `https://sctapi.ftqq.com/${SCKEY}.send`;
// 目标地址、对应的缺货标志词、名称和描述
const TARGETS = [
{
name: "hostdzire 32刀",
url: "https://hostdzire.com/billing/index.php?rp=/store/indian-cloudvps/in-cloudvps-5-nodeseek-special",
outOfStockText: "out of stock",
description: `hostdzire 32刀闪购补货了。 `,
},
{
name: "colocrossing E3-2124G",
url: "https://portal.colocrossing.com/register/order/service/592",
outOfStockText: "this service is not available",
description: `colocrossing E3-2124G有货了。`,
},
];
// 存储有货的目标信息
const inStockTargets = [];
async function checkStock() {
// 记录循环开始
console.log("Starting stock check for all targets...");
for (const target of TARGETS) {
console.log(`Checking target: ${target.name} (${target.url})`);
try {
// 设置超时(例如 10 秒)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await fetch(target.url, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
},
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const text = await response.text();
// 检查页面是否包含对应的缺货标志词
if (!text.toLowerCase().includes(target.outOfStockText.toLowerCase())) {
console.log(`Stock found for ${target.name}`);
// 添加到有货列表
inStockTargets.push({
name: target.name,
message: `🎉 ` + target.description + `\n\n🔗 ${target.url}`,
});
} else {
console.log(`No stock for ${target.name}`);
}
} catch (error) {
console.error(
`Error checking ${target.name} (${target.url}):`,
error.message
);
// 发送错误通知到 Server 酱
await fetch(SERVER_API, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
title: "Stock监控错误",
desp: `监控错误 (${target.name}): ${error.message}`,
}),
});
// 继续循环,不中断
continue;
}
}
console.log("Completed stock check for all targets.");
// 如果有货,发送合并通知
if (inStockTargets.length > 0) {
console.log(
`Found ${inStockTargets.length} targets in stock, sending combined notification...`
);
// 合并消息
const combinedMessage = inStockTargets
.map((target) => target.message)
.join("\n\n ----- \n\n");
console.log(combinedMessage);
const serverResponse = await fetch(SERVER_API, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
title: "🎉 Stock补货通知",
desp: combinedMessage,
}),
});
if (!serverResponse.ok) {
console.error(
`Failed to send Server 酱 notification: ${serverResponse.status}`
);
} else {
console.log("Combined Server 酱 notification sent successfully.");
}
} else {
console.log("No targets in stock, no Server 酱 notification sent.");
}
console.log(
"Completed stock check for all targets with Server 酱 notification."
);
}
// 立即执行一次检查
await checkStock();
// 返回响应(Cloudflare Workers需要返回Response对象)
return new Response("Monitor running", { status: 200 });
}
点击左上角返回 Project 之后点击 Settings, 在 Variables and Secrets 中添加 Server 酱 Token
点击 Add 之后,环境变量类型选择 Secret
,Variable name
填写 SCKEY
,Value
填你的 Server 酱 Token
设置检测间隔时间
Trigger Events ~ Cron Triggers
提示: 时间间隔不要太短,避免被限制访问
像搬瓦工的购买页面就没有限制,几分钟监控一次也没关系。DMIT 不要太频繁监控,如果你查看日志发现 403,建议创建一个新的 Worker 把时间间隔改长,然后删除旧的 Worker。
即使像 DMIT 这种比较严格的,设置成 30 分钟检测一次也是可以的(下图左侧可见持续正常运行
)。
每家服务商情况不同,大部分都可以隔几分钟监控,具体可自行测试。日志查看方法见下文。
提示:或者你可以将限制严格和宽松的服务商分成(创建)两个不同的 Workers 运行,实现不同的检测时间间隔
Deploy 部署
点击 Deploy Version 之后继续点击 Deploy 即可部署修改后的版本。
添加或修改监控对象
如需添加监控对象, 参考原有格式编辑代码, 在 TARGETS 数组中添加监控对象即可
点击右上角(上图中标注位置)进入代码编辑界面,修改后示例:
const TARGETS = [
{
name: "hostdzire 32刀",
url: "https://hostdzire.com/billing/index.php?rp=/store/indian-cloudvps/in-cloudvps-5-nodeseek-special",
outOfStockText: "out of stock",
description: `hostdzire 32刀闪购补货了。 `,
},
{
name: "colocrossing E3-2124G",
url: "https://portal.colocrossing.com/register/order/service/592",
outOfStockText: "this service is not available",
description: `colocrossing E3-2124G有货了。`,
},
{
name: "DMIT",
url: "https://www.dmit.io/cart.php?a=add&pid=205",
outOfStockText: "out of stock",
description: "DMIT xxx 补货了"
}
];
提示:Cloudflare work 代码编辑界面也可以选中代码后按 Command + Shift + ↓ 或者 Ctrl + Shift + ↓ 进行复制, 复制一个监控对象之后再进行修改不容易出错
查看日志
点击查看具体日志内容
实现优势和局限性
优点: 免费+门槛低, 无需服务器, 运维成本低, 支持定时执行
缺点: 持久化缺失, CloudFlare Workers 无状态, 不能区分 "刚补货" 和 "持续有货", 补货之后就会发送通知, 甚至会持续发送通知。
使用 CloudFlare KV 可以解决但会增加操作(后续可能考虑,暂不),对新手没那么友好。
// 建议添加状态管理
const KV_NAMESPACE = env.STOCK_STATUS; // 使用 CloudFlare KV 存储
async function checkStock() {
for (const target of TARGETS) {
// 获取上次状态
const lastStatus = await KV_NAMESPACE.get(target.name);
// 检查当前状态
const currentStatus = !text.toLowerCase().includes(target.outOfStockText.toLowerCase());
// 只在状态变化时发送通知
if (lastStatus === 'out_of_stock' && currentStatus) {
// 从缺货变为有货,发送通知
await sendNotification(target);
}
// 保存当前状态
await KV_NAMESPACE.put(target.name, currentStatus ? 'in_stock' : 'out_of_stock');
}
}
不过补货之后实现了它的用途,修改或停用代码即可。