使用 1Panel + Docker 部署全自动 Bing Rewards 积分脚本
Microsoft Bing 每日搜索就能获得 Rewards 积分,既然手里有一台 24 小时开机的 NAS / 1Panel 面板,不妨直接部署在服务器上自动挂机,既省电又方便。
Docker 容器化edge浏览器 实现了在无头 Linux 服务器上运行带 GUI 的浏览器,完美绕过了微软的反爬机制,配合改造后的 JavaScript 脚本实现。
想法:纯 Python 无头浏览器?
一开始,我的思路非常直接:用 Python + Playwright 写一个脚本,放在后台跑不就行了?
于是根据原本在 PC 上跑的油猴(Tampermonkey)脚本逻辑,写出了 Python 版本的自动化搜索爬虫。但最大的问题出现了:账号登录。
微软的防机器人的风控非常严格。在没有显示器的 Headless Linux 服务器上,想要让脚本自动过验证码、处理 2FA 双重认证几乎是不可能的。
唯一的解法是:在本地电脑登录好,导出 Cookies,再传到服务器上给 Python 脚本吃。
但 Cookies 是会过期的。这意味着每隔一段时间,我就得手动抓一次 Cookies 上传。
最终方案:Docker 化的 Web 浏览器
最终发现Dockers hub上已经有现成的 Firefox 和 Edge 浏览器镜像。
我们可以通过 Docker 跑一个完整的 Firefox 或 Edge 浏览器,并通过 HTML5 的 VNC 技术,把浏览器的画面实时推送到网页上。
这意味着,我可以用极其轻量的资源,在毫无图形界面的 Linux 服务器上,获得一个可以通过网页随时随地访问的“虚拟浏览器”
部署过程 (基于 1Panel)
在 1Panel 面板中,整个部署过程异常丝滑:
- 打开 1Panel,进入 容器 -> 编排 -> 创建编排。
- 填入以下
docker-compose.yml 配置:
version: '3'
services:
edge:
image: jlesage/edge
container_name: bing-rewards
restart: unless-stopped
ports:
- "<port>:5800" # 映射到宿主机的访问端口
volumes:
- /opt/1panel/local/edge:/config:rw # 持久化配置
environment:
- LANG=zh_CN.UTF-8 # 中文界面
shm_size: "1gb" # 给足共享内存,防止崩溃
yaml
- 点击启动!
- 在浏览器输入
http://服务器IP:5800访问
在这个容器化的浏览器里,像往常一样打开 Bing 登录账号,安装 Tampermonkey 插件,一切行云流水,没有任何反爬阻碍。
终极改造:让脚本成为真正的“全自动收割机”
浏览器有了,脚本也装了。但原来的脚本有一个缺陷:必须要手动点击菜单才能开始运行。
既然是挂机,我当然希望它能够每天自动开始,完全不需要我的干预。
为了让它能够真正“无人值守”,我对原版的 JavaScript 脚本进行了深度改造,加入了时间检测机制和日志记录功能。
核心改造代码片段
1. 自动唤醒机制
我加入了一段守护循环,只要检测到过了早上 6:00,并且今天还没运行过,脚本就会自动把计数器归零并开始执行。
function checkAndStartDailyTask() {
let now = new Date();
let today = now.toLocaleDateString();
let lastRunDate = GM_getValue('LastRunDate', '');
// 每天 6:00 之后自动重置任务
if (today !== lastRunDate && now.getHours() >= 6) {
GM_setValue('Cnt', 0);
GM_setValue('LastRunDate', today);
// ... 初始化日志 ...
if (!location.href.includes("[bing.com/?br_msg=Auto-Start](https://bing.com/?br_msg=Auto-Start)")) {
location.href = "[https://www.bing.com/?br_msg=Auto-Start](https://www.bing.com/?br_msg=Auto-Start)";
return true;
}
}
return false;
}
// 挂机守候:每 1 分钟检查一次时间
setInterval(checkAndStartDailyTask, 60000);
javascript
2. 积分抓取与可视化日志
光跑完不行,我还想知道它到底拿了多少分。我加入了 DOM 元素抓取功能,在任务开始前记录初始积分,结束后记录最终积分,并利用 Blob 自动生成一个精美的 HTML 日志页面。
// 任务刚开始时抓取
if (currentSearchCount === 0) {
let pts = await getPoints();
GM_setValue('StartPoints', pts);
addLog(`<b>任务正式开始</b> 🚀 初始积分: ${pts}`);
}
// 任务结束时展示日志
else if (currentSearchCount === max_rewards) {
let endPts = await getPoints();
// ... 拼接 HTML 日志 ...
let blob = new Blob([finalLog], { type: 'text/html;charset=utf-8' });
let url = URL.createObjectURL(blob);
GM_openInTab(url, { active: true }); // 自动弹出日志标签页
}
javascript
成果验收与结语
现在,我的方案完美运转:
那个跑在 Docker 里的浏览器常年挂在后台,停留在 Bing 的首页。每天清晨 6 点,脚本自动启动,自己翻找热门词汇,模拟人类习惯进行滑动和搜索,并在触发风控阈值前聪明地暂停休息。
当我下课或者闲暇时,只需连上我的 Tailscale 虚拟局域网,打开手机浏览器访问 http://服务器内网IP:5800。映入眼帘的不是枯燥的代码报错,而是一个排版精美、记录着详细积分收益的日志页面.
附:完整脚本代码
// ==UserScript==
// @name Microsoft Bing Rewards每日任务脚本 (6:00自动/带日志/带积分)
// @version V4.2.0
// @description 每天6:00后自动执行,获取热门词搜索,记录详细日志并抓取初始和结束积分。
// @note 基于V4.1.0深度修改
// @author 怀沙2049 (Modified)
// @match https://*.bing.com/*
// @exclude https://rewards.bing.com/*
// @license GNU GPLv3
// @icon https://www.bing.com/favicon.ico
// @connect gumengya.com
// @run-at document-end
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @grant GM_openInTab
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @namespace https://greasyfork.org/zh-CN/scripts/477107
// ==/UserScript==
var max_rewards = 25; // 重复执行的次数
var pause_time = 2000; // 暂停时长 (根据原脚本设置, 若需16分钟请改为 960000)
var search_words = []; // 搜索词
var appkey = "b7a782741f667201b54880c925faec4b";
var Hot_words_apis = "https://api.gmya.net/Api/";
var keywords_source = ['TouTiaoHot','DouYinHot','WeiBoHot','BaiduHot'];
var current_source_index = 0;
var default_search_words = ["盛年不重来,一日难再晨", "千里之行,始于足下", "少年易学老难成", "敏而好学,不耻下问", "海内存知已", "三人行必有我师", "天生我材必有用", "海纳百川", "读书破万卷", "学而不思则罔", "莫等闲", "少壮不努力", "近朱者赤", "学无止境", "己所不欲勿施于人", "天下兴亡", "为中华之崛起", "淡泊明志", "宁静致远", "卧龙跃马"];
// -----------------------------------------------------
// 核心逻辑 1:6:00 自动唤醒与重置检查
// -----------------------------------------------------
function checkAndStartDailyTask() {
let now = new Date();
let today = now.toLocaleDateString();
let lastRunDate = GM_getValue('LastRunDate', '');
// 每天 6:00 之后,且今天还没运行过
if (today !== lastRunDate && now.getHours() >= 6) {
console.log("达到 6:00,自动重置任务并开始!");
GM_setValue('Cnt', 0); // 计数器归零
GM_setValue('LastRunDate', today);
// 初始化精美 HTML 日志模板
let logTemplate = `
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; padding: 30px; background-color: #f3f2f1; color: #323130; min-height: 100vh;">
<div style="max-width: 800px; margin: 0 auto; background: white; padding: 25px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);">
<h2 style="color: #0078d4; border-bottom: 2px solid #0078d4; padding-bottom: 10px; margin-top: 0;">Bing Rewards 自动搜索日志 - ${today}</h2>
<ul style="line-height: 2; font-size: 15px; list-style-type: none; padding-left: 0;">
`;
GM_setValue('DailyLog', logTemplate);
GM_setValue('StartPoints', '等待获取...');
// 如果当前不在首页,跳转回首页以确保干净的启动环境
if (!location.href.includes("bing.com/?br_msg=Auto-Start")) {
location.href = "https://www.bing.com/?br_msg=Auto-Start";
return true;
}
}
return false;
}
// 页面加载时执行一次检查,如果是为了启动而跳转,则中止后续脚本
if (checkAndStartDailyTask()) return;
// 挂机守候:每 1 分钟检查一次时间(专门针对 Docker 长期挂在某一页面的情况)
setInterval(checkAndStartDailyTask, 60000);
// -----------------------------------------------------
// 核心逻辑 2:日志记录与积分抓取工具
// -----------------------------------------------------
function addLog(msg) {
let log = GM_getValue('DailyLog', '');
let timeStr = new Date().toLocaleTimeString('zh-CN', { hour12: false });
log += `<li style="border-bottom: 1px dashed #edebe9; padding: 5px 0;"><span style="color: #605e5c; margin-right: 15px;">[${timeStr}]</span> ${msg}</li>`;
GM_setValue('DailyLog', log);
}
// 异步抓取积分 (重试 5 次,等待页面加载)
async function getPoints() {
for (let i = 0; i < 5; i++) {
let el = document.getElementById('id_rc'); // Bing 页面顶部积分元素的固定 ID
if (el && el.innerText) return el.innerText;
await new Promise(r => setTimeout(r, 1000));
}
return '获取失败 (可能未登录或网络延迟)';
}
// -----------------------------------------------------
// 搜索词获取与执行入口
// -----------------------------------------------------
async function douyinhot_dic() {
while (current_source_index < keywords_source.length) {
const source = keywords_source[current_source_index];
let url = appkey ? `${Hot_words_apis}${source}?format=json&appkey=${appkey}` : `${Hot_words_apis}${source}`;
try {
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
if (data.data && data.data.some(item => item)) {
return data.data.map(item => item.title);
}
}
} catch (error) {
console.error('搜索词来源请求失败:', error);
}
current_source_index++;
}
return default_search_words;
}
douyinhot_dic().then(names => {
search_words = names;
exec();
}).catch(error => console.error(error));
// -----------------------------------------------------
// 核心逻辑 3:主执行函数 (包含完成后的日志展示)
// -----------------------------------------------------
function AutoStrTrans(st) { /* ...原函数简化... */
let yStr = st, rStr = "", zStr = "", prePo = 0;
for (let i = 0; i < yStr.length;) {
let step = parseInt(Math.random() * 5) + 1;
if (i > 0) { zStr += yStr.substr(prePo, i - prePo) + rStr; prePo = i; }
i += step;
}
return zStr + yStr.substr(prePo, yStr.length - prePo);
}
function generateRandomString(length) {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let result = '';
for (let i = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * characters.length));
return result;
}
async function exec() {
if (GM_getValue('Cnt') == null) GM_setValue('Cnt', max_rewards + 10);
let currentSearchCount = GM_getValue('Cnt');
// 刚开始第一波:抓取初始积分
if (currentSearchCount === 0) {
let pts = await getPoints();
GM_setValue('StartPoints', pts);
addLog(`<b>任务正式开始</b> 🚀 初始积分: <span style="color: #107c10; font-weight: bold;">${pts}</span>`);
}
// 执行过程
if (currentSearchCount < max_rewards) {
document.title = "[" + currentSearchCount + " / " + max_rewards + "] " + document.title;
document.documentElement.scrollIntoView({ behavior: 'smooth', block: 'end' });
GM_setValue('Cnt', currentSearchCount + 1);
let nowtxt = search_words[currentSearchCount] || default_search_words[currentSearchCount % default_search_words.length];
addLog(`搜索进度 <b>(${currentSearchCount + 1}/${max_rewards})</b>: 关键词 <span style="color: #0078d4;">${nowtxt}</span>`);
nowtxt = AutoStrTrans(nowtxt);
let randomDelay = Math.floor(Math.random() * 6000) + 2000;
let randomString = generateRandomString(4);
let randomCvid = generateRandomString(32);
// 动态决定域名 (复刻原版逻辑)
let domain = (currentSearchCount > max_rewards / 2) ? "cn.bing.com" : "www.bing.com";
let targetUrl = `https://${domain}/search?q=${encodeURI(nowtxt)}&form=${randomString}&cvid=${randomCvid}`;
setTimeout(function () {
if ((currentSearchCount + 1) % 5 === 0) {
addLog(`<span style="color: #d13438;"><i>触发防封控长暂停,休息 ${pause_time / 1000} 秒...</i></span>`);
setTimeout(() => { location.href = targetUrl; }, pause_time);
} else {
location.href = targetUrl;
}
}, randomDelay);
}
// 任务刚刚结束
else if (currentSearchCount === max_rewards) {
let endPts = await getPoints();
let startPts = GM_getValue('StartPoints', '未知');
addLog(`<b>任务圆满完成!</b> 🎉 结束积分: <span style="color: #107c10; font-weight: bold; font-size: 18px;">${endPts}</span> (初始: ${startPts})`);
// 闭合 HTML 标签
let finalLog = GM_getValue('DailyLog') + "</ul><div style='text-align: center; margin-top: 20px; font-size: 13px; color: #a19f9d;'>© Bing Rewards 自动助手 (运行在 Docker)</div></div></div>";
// 将日志生成为网页文件并打开新标签页
let blob = new Blob([finalLog], { type: 'text/html;charset=utf-8' });
let url = URL.createObjectURL(blob);
GM_openInTab(url, { active: true });
// 设置为极大值,防止无限循环打开标签
GM_setValue('Cnt', max_rewards + 10);
}
}
// 菜单支持
GM_registerMenuCommand('强制开始(忽略时间)', function () {
GM_setValue('Cnt', 0);
location.href = "https://www.bing.com/?br_msg=Manual-Start";
}, 'o');
GM_registerMenuCommand('停止', function () {
GM_setValue('Cnt', max_rewards + 10);
}, 'o');
javascript