Kylaan

Back

使用 1Panel + Docker 部署全自动 Bing Rewards 积分脚本Blur image

使用 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 面板中,整个部署过程异常丝滑:

  1. 打开 1Panel,进入 容器 -> 编排 -> 创建编排
  2. 填入以下 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
  1. 点击启动!
  2. 在浏览器输入 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。映入眼帘的不是枯燥的代码报错,而是一个排版精美、记录着详细积分收益的日志页面.

附:完整脚本代码

最终 JS 脚本完整代码

// ==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
使用 1Panel + Docker 部署全自动 Bing Rewards 积分脚本
https://kylaan.top/blog/bingrewards/rewards
Author Kylaan
Published at 2026年2月21日
Comment seems to stuck. Try to refresh?✨