设为首页收藏本站

 找回密码
 立即注册

只需一步,快速开始

搜索
查看: 153|回复: 21

[实用软件] 百度脑图即将关停了,做了个脚本可以批量保存,若有数据请尽快保存。

 火... [复制链接]
累计签到:28 天
连续签到:1 天
灌水成绩
7
55
4074
主题
帖子
积分

等级头衔

ID : 621

助理工程师

积分成就 测量币 : 4074
在线时间 : 0 小时
注册时间 : 2026-2-8
最后登录 : 2026-5-20

勋章
UID勋章测量学徒测量员
发表于 2026-4-18 10:39:12 | 显示全部楼层 |阅读模式 IP:北京
百度脑图将于2026年3月31日正式停服,核心是长期无商业变现、资源投入停滞、功能与生态掉队、战略优先级低,最终被“断舍离”。
但是奇怪的是现在还没停服,但估计是快了,应该是给我们留存保留数据的时间。

在百度脑图上有数据的朋友可以借助这个油猴脚本把自己账号上的脑图数据下载下来,转向使用开源的百度脑图(https://github.com/NaoTu/DesktopNaotu)继续使用。


依旧是使用油猴脚本,在AI的帮助下做了这个脚本。
安装链接:https://greasyfork.org/zh-CN/scripts/574394-%E7%99%BE%E5%BA%A6%E8%84%91%E5%9B%BE%E6%89%B9%E9%87%8F%E5%AF%BC%E5%87%BA-km-zip
AI查毒:https://www.doubao.com/thread/wad340797b7f572c9这是效果图:

[JavaScript] 纯文本查看 复制代码// ==UserScript==// @name         百度脑图批量导出(.km + ZIP)// @namespace    https://naotu.baidu.com/// @version      2.3.3// @description  在百度脑图主页添加【批量导出】按钮,支持导出所有文件及文件夹内的文件,输出为 .km 格式,最终打包成 ZIP 压缩包下载。// @AuThor       帅气的小莲// @match        https://naotu.baidu.com/home*// @match        http://naotu.baidu.com/home*// @grant        none// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js// ==/UserScript==(function () {    'use strict';    console.log('[脑图导出] 脚本启动,JSZip 是否可用:', typeof JSZip !== 'undefined');    // ─── 样式 ───────────────────────────────────────────────────────────────────    const styleEl = document.createElement('style');    styleEl.textContent = `        #nm-export-btn {            position: fixed; bottom: 32px; right: 32px; z-index: 99999;            padding: 12px 22px; background: linear-gradient(135deg, #3b7de8, #1a56c4);            color: #fff; font-size: 14px; font-weight: bold; border: none;            border-radius: 28px; cursor: pointer;            box-shadow: 0 4px 18px rgba(59,125,232,0.45); transition: all 0.2s ease; user-select: none;        }        #nm-export-btn:hover { background: linear-gradient(135deg, #4e8ef7, #2460d6); box-shadow: 0 6px 24px rgba(59,125,232,0.6); transform: translateY(-2px); }        #nm-export-btn:disabled { background: #999; cursor: not-allowed; box-shadow: none; transform: none; }        #nm-export-panel {            position: fixed; bottom: 90px; right: 32px; z-index: 99999;            width: 360px; background: #fff; border-radius: 14px;            box-shadow: 0 8px 40px rgba(0,0,0,0.18); overflow: hidden; display: none; font-size: 13px;        }        #nm-export-panel .nm-panel-header {            background: linear-gradient(135deg, #3b7de8, #1a56c4); color: #fff;            padding: 12px 18px; font-weight: bold; font-size: 14px;            display: flex; justify-content: space-between; align-items: center;        }        #nm-export-panel .nm-panel-header span.nm-close { cursor: pointer; font-size: 18px; line-height: 1; opacity: 0.8; }        #nm-export-panel .nm-panel-header span.nm-close:hover { opacity: 1; }        #nm-export-panel .nm-panel-body { padding: 14px 18px; }        #nm-export-panel .nm-log {            background: #f5f7fb; border-radius: 8px; padding: 10px 12px;            max-height: 220px; overflow-y: auto; font-family: monospace;            font-size: 12px; color: #333; line-height: 1.6; word-break: break-all;        }        #nm-export-panel .nm-log .nm-ok   { color: #22a355; }        #nm-export-panel .nm-log .nm-warn { color: #e07c00; }        #nm-export-panel .nm-log .nm-err  { color: #d32f2f; }        #nm-export-panel .nm-progress-bar-wrap { margin-top: 10px; background: #e8edf5; border-radius: 999px; overflow: hidden; height: 8px; }        #nm-export-panel .nm-progress-bar { height: 100%; width: 0%; background: linear-gradient(90deg, #3b7de8, #7ab4ff); border-radius: 999px; transition: width 0.25s ease; }        #nm-export-panel .nm-phase-label { margin-top: 6px; font-size: 11px; color: #3b7de8; font-weight: bold; }        #nm-export-panel .nm-progress-text { float: right; margin-top: -15px; color: #666; font-size: 11px; }    `;    document.head.appendChild(styleEl);    // ─── UI ─────────────────────────────────────────────────────────────────────    const btn = document.createElement('button');    btn.id = 'nm-export-btn';    btn.textContent = '📦 批量导出';    document.body.appendChild(btn);    const panel = document.createElement('div');    panel.id = 'nm-export-panel';    panel.innerHTML = `                    📦 批量导出进度            ✕        
                    等待开始…
            

            
            0 / 0
        
    `;    document.body.appendChild(panel);    panel.querySelector('.nm-close').addEventListener('click', () => { panel.style.display = 'none'; });    // ─── 工具 ──────────────────────────────────────────────────────────────────    const logBox = document.getElementById('nm-log-box');    const progressBar = document.getElementById('nm-progress-bar');    const progressText = document.getElementById('nm-progress-text');    const phaseLabel = document.getElementById('nm-phase-label');    function log(msg, type = '') {        const line = document.createElement('div');        if (type) line.className = `nm-${type}`;        line.textContent = msg;        logBox.appendChild(line);        logBox.scrollTop = logBox.scrollHeight;        console.log('[脑图导出] ' + msg);    }    function clearLog() { logBox.innerHTML = ''; }    function setProgress(cur, total, phase) {        const pct = total > 0 ? Math.round((cur / total) * 100) : 0;        progressBar.style.width = pct + '%';        if (phase === 'zip') {            phaseLabel.textContent = '📦 打包中 ' + pct + '%';            progressText.textContent = '';        } else {            phaseLabel.textContent = '📥 采集文件';            progressText.textContent = cur + ' / ' + total;        }    }    // ─── CSRF ──────────────────────────────────────────────────────────────────    function getCsrf() {        const el = document.getElementById('km-csrf');        if (el && el.value) return el.value;        const m = document.cookie.match(/(?:^|;\s*)csrfToken=([^;]+)/);        if (m) return decodeURIComponent(m[1]);        const meta = document.querySelector('meta[name="csrf-token"]');        if (meta) return meta.getAttribute('content');        return '';    }    // ─── API(使用页面 jQuery) ─────────────────────────────────────────────────    function api(path, data = {}) {        const csrf = getCsrf();        const body = Object.assign({}, data);        if (csrf) body.csrf_token = csrf;        return new Promise((resolve, reject) => {            const jq = (typeof jQuery !== 'undefined') ? jQuery : (typeof $ !== 'undefined' ? $ : null);            if (!jq) { reject(new Error('jQuery 不可用')); return; }            jq.ajax({                url: path, type: 'POST', cache: false, data: body, dataType: 'json',                success(json) {                    if (json.errno !== 0 && json.errno != null) {                        reject(new Error('API ' + path + ' errno=' + json.errno));                    } else {                        resolve(json.data !== undefined ? json.data : json);                    }                },                error(xhr, status, err) { reject(new Error('API ' + path + ' 失败: ' + status)); }            });        });    }    function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }    function sanitize(name) { return (name || 'untitled').replace(/[\\/:*?"|]/g, '_').trim() || 'untitled'; }    // ─── 递归遍历 ──────────────────────────────────────────────────────────────    async function walkDir(dirGuid, zipFolder, counter) {        let items;        try {            const res = await api('bos/ls', { dirGuid });            if (Array.isArray(res)) items = res;            else if (Array.isArray(res.list)) items = res.list;            else if (Array.isArray(res.children)) items = res.children;            else items = [];        } catch (e) {            log('⚠ 读取目录失败: ' + e.message, 'warn');            return;        }        const files = items.filter(i => i.file_type === 'file');        const dirs  = items.filter(i => i.file_type === 'directory');        counter[1] += files.length;        setProgress(counter[0], counter[1], 'fetch');        for (const dir of dirs) {            const subFolder = zipFolder.folder(sanitize(dir.file_name));            log('📁 进入目录:' + dir.file_name);            await walkDir(dir.file_guid, subFolder, counter);            await sleep(200);        }        for (const file of files) {            try {                const fileData = await api('bos/open', { fileGuid: file.file_guid });                let content = fileData.content;                if (typeof content === 'object') content = JSON.stringify(content);                let fileName = fileData.file_name || file.file_name || 'untitled';                const ext = fileData.ext_name || '.km';                if (!fileName.endsWith(ext)) fileName += ext;                zipFolder.file(sanitize(fileName), content);                counter[0]++;                setProgress(counter[0], counter[1], 'fetch');                log('✓ ' + fileName, 'ok');            } catch (e) {                log('✗ ' + (file.file_name || file.file_guid) + ' 失败: ' + e.message, 'err');                counter[0]++;                setProgress(counter[0], counter[1], 'fetch');            }            await sleep(300 + Math.random() * 200);        }    }    // ─── 主流程 ────────────────────────────────────────────────────────────────    async function startExport() {        btn.disabled = true;        btn.textContent = '⏳ 导出中…';        panel.style.display = 'block';        clearLog();        setProgress(0, 0, 'fetch');        log('🚀 开始批量导出…');        try {            // 1) 获取根目录            log('正在获取根目录…');            const rootData = await api('bos/get_root_dir');            const rootGuid = rootData.file_guid || rootData.dirGuid;            const creatorName = rootData.creater_name || 'naotu';            if (!rootGuid) throw new Error('无法获取根目录 GUID');            log('根目录 GUID: ' + rootGuid + ',用户: ' + creatorName, 'ok');            // 2) 创建 ZIP            const zip = new JSZip();            console.log('[脑图导出] JSZip 实例创建成功, typeof zip.generateAsync =', typeof zip.generateAsync);            const counter = [0, 0];            // 3) 递归遍历            await walkDir(rootGuid, zip, counter);            if (counter[1] === 0) { log('⚠ 未找到任何脑图文件', 'warn'); return; }            // 4) 打包 — 使用 STORE(不压缩)避免 DEFLATE 卡死,先确保流程通畅            log('📦 正在打包 ZIP(' + counter[0] + ' 个文件)…');            setProgress(0, 100, 'zip');            console.log('[脑图导出] 即将调用 zip.generateAsync...');            console.log('[脑图导出] zip 内文件数:', Object.keys(zip.files).length);            let zipBlob = null;            // 先尝试无回调 + STORE 模式            try {                console.log('[脑图导出] 尝试方式1: generateAsync + STORE + onUpdate 回调');                zipBlob = await zip.generateAsync(                    { type: 'blob', compression: 'STORE' },                    function onUpdate(meta) {                        // 回调内部不能抛异常,否则会中断 generateAsync                        try {                            var pct = Math.round(meta.percent);                            setProgress(pct, 100, 'zip');                            if (pct % 10 === 0) {                                console.log('[脑图导出] 打包进度: ' + pct + '%');                            }                        } catch (e) {                            console.warn('[脑图导出] onUpdate 内部错误:', e);                        }                    }                );                console.log('[脑图导出] 方式1成功! blob size =', zipBlob.size);            } catch (e1) {                console.error('[脑图导出] 方式1失败:', e1);                // 方式2:完全无回调,用 arraybuffer 再转 blob                try {                    console.log('[脑图导出] 尝试方式2: generateAsync + arraybuffer(无回调)');                    var ab = await zip.generateAsync({ type: 'arraybuffer', compression: 'STORE' });                    zipBlob = new Blob([ab], { type: 'application/zip' });                    console.log('[脑图导出] 方式2成功! blob size =', zipBlob.size);                } catch (e2) {                    console.error('[脑图导出] 方式2失败:', e2);                    // 方式3:用 nodebuffer / uint8array                    try {                        console.log('[脑图导出] 尝试方式3: generateAsync + uint8array');                        var u8 = await zip.generateAsync({ type: 'uint8array', compression: 'STORE' });                        zipBlob = new Blob([u8], { type: 'application/zip' });                        console.log('[脑图导出] 方式3成功! blob size =', zipBlob.size);                    } catch (e3) {                        console.error('[脑图导出] 方式3失败:', e3);                        throw new Error('所有打包方式均失败: ' + e1.message + ' | ' + e2.message + ' | ' + e3.message);                    }                }            }            setProgress(100, 100, 'zip');            console.log('[脑图导出] 打包完成,blob size =', zipBlob.size, 'type =', zipBlob.type);            // 5) 下载            const now = new Date();            const datePart = now.getFullYear() + '_' + String(now.getMonth() + 1).padStart(2, '0') + '_' + String(now.getDate()).padStart(2, '0');            const fileName = creatorName + '_脑图备份_' + datePart + '.zip';            console.log('[脑图导出] 开始下载,文件名:', fileName);            const url = URL.createObjectURL(zipBlob);            console.log('[脑图导出] createObjectURL 成功:', url);            const a = document.createElement('a');            a.href = url;            a.download = fileName;            a.style.display = 'none';            document.body.appendChild(a);            a.click();            console.log('[脑图导出] a.click() 已执行');            setTimeout(() => {                URL.revokeObjectURL(url);                if (a.parentNode) a.parentNode.removeChild(a);                console.log('[脑图导出] 下载链接已清理');            }, 10000);            log('🎉 导出完成!共 ' + counter[0] + ' 个文件,已打包下载:' + fileName, 'ok');        } catch (e) {            log('❌ 导出失败:' + e.message, 'err');            console.error('[脑图导出] 错误详情:', e);        } finally {            btn.disabled = false;            btn.textContent = '📦 批量导出';        }    }    btn.addEventListener('click', startExport);    console.log('[脑图导出] 脚本已加载,点击右下角【📦 批量导出】按钮开始。');})();
累计签到:56 天
连续签到:46 天
灌水成绩
4
230
14545
主题
帖子
积分

等级头衔

ID : 566

中级工程师

积分成就 测量币 : 14545
在线时间 : 530 小时
注册时间 : 2026-1-23
最后登录 : 2026-7-5

勋章
UID勋章测量学徒测量员
发表于 2026-4-21 12:38:13 | 显示全部楼层 IP:美国
很多细节都讲到了,非常用心。
回复

使用道具 举报

累计签到:60 天
连续签到:40 天
灌水成绩
3
235
15225
主题
帖子
积分

等级头衔

ID : 578

中级工程师

积分成就 测量币 : 15225
在线时间 : 533 小时
注册时间 : 2026-3-12
最后登录 : 2026-7-5

勋章
UID勋章测量学徒测量员
发表于 2026-4-22 17:13:46 | 显示全部楼层 IP:美国
很多细节都讲到了,非常用心。
回复

使用道具 举报

累计签到:61 天
连续签到:45 天
灌水成绩
3
226
15382
主题
帖子
积分

等级头衔

ID : 516

中级工程师

积分成就 测量币 : 15382
在线时间 : 519 小时
注册时间 : 2025-10-29
最后登录 : 2026-7-5

勋章
UID勋章测量学徒测量员
发表于 2026-4-25 00:04:44 | 显示全部楼层 IP:北京
对项目实战很有指导意义。
回复

使用道具 举报

累计签到:62 天
连续签到:47 天
灌水成绩
3
254
16210
主题
帖子
积分

等级头衔

ID : 571

中级工程师

积分成就 测量币 : 16210
在线时间 : 532 小时
注册时间 : 2026-4-9
最后登录 : 2026-7-5

勋章
UID勋章测量学徒测量员
发表于 2026-4-26 03:51:45 | 显示全部楼层 IP:美国
代码注释很详细,容易理解。
回复

使用道具 举报

累计签到:61 天
连续签到:48 天
灌水成绩
4
210
12918
主题
帖子
积分

等级头衔

ID : 511

中级工程师

积分成就 测量币 : 12918
在线时间 : 523 小时
注册时间 : 2025-11-2
最后登录 : 2026-7-5

勋章
UID勋章测量学徒测量员
发表于 2026-4-30 02:09:19 | 显示全部楼层 IP:广东东莞
语言平实,容易接受。
回复

使用道具 举报

累计签到:59 天
连续签到:40 天
灌水成绩
4
241
16525
主题
帖子
积分

等级头衔

ID : 535

中级工程师

积分成就 测量币 : 16525
在线时间 : 527 小时
注册时间 : 2026-2-19
最后登录 : 2026-7-5

勋章
UID勋章测量学徒测量员
发表于 2026-5-1 01:10:33 | 显示全部楼层 IP:广东东莞
希望以后多分享这类实战经验。
回复

使用道具 举报

累计签到:65 天
连续签到:47 天
灌水成绩
3
257
16340
主题
帖子
积分

等级头衔

ID : 576

中级工程师

积分成就 测量币 : 16340
在线时间 : 530 小时
注册时间 : 2025-12-5
最后登录 : 2026-7-5

勋章
UID勋章测量学徒测量员
发表于 2026-5-1 01:10:33 | 显示全部楼层 IP:广东东莞
很适合入门学习,推荐给朋友了。
回复

使用道具 举报

累计签到:52 天
连续签到:47 天
灌水成绩
3
236
15725
主题
帖子
积分

等级头衔

ID : 531

中级工程师

积分成就 测量币 : 15725
在线时间 : 524 小时
注册时间 : 2026-1-28
最后登录 : 2026-7-5

勋章
UID勋章测量学徒测量员
发表于 2026-5-1 01:10:33 | 显示全部楼层 IP:广东东莞
希望以后多写这类实战教程。
回复

使用道具 举报

累计签到:55 天
连续签到:47 天
灌水成绩
3
262
16957
主题
帖子
积分

等级头衔

ID : 589

中级工程师

积分成就 测量币 : 16957
在线时间 : 531 小时
注册时间 : 2026-3-19
最后登录 : 2026-7-5

勋章
UID勋章测量学徒测量员
发表于 2026-5-1 01:41:59 | 显示全部楼层 IP:广东东莞
希望以后多分享这类实战经验。
回复

使用道具 举报

快速回复换一批
好贴帮顶
先赞后看,养成习惯! 感谢大佬指路,回帖留名以备日后查阅。 📝🚀
业界良心! 楼主这波操作太牛了,细节拉满,顶起来让更多人看到! 🌟👏
学到了! 请问楼主,这个方法在实际操作中有什么需要特别注意的坑吗? 🧐❓
博大精深。 关注楼主了,以后这类硬核文章请务必多发一些! 🔔❤️
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|精密测量技术论坛 ( 桂ICP备2026007449号-1 )

GMT+8, 2026-7-5 15:01 , Processed in 0.757558 second(s), 52 queries .

Powered by 精密测量技术论坛

© 2025-2026 联系站长

快速回复 返回顶部 返回列表