有道云笔记作为一款常用的云端笔记工具,承载了我们大量的资料和创作。然而,其在内容复制、文章目录显示等方面的一些限制,有时会给我们的使用带来不便。为了更自由地管理和迁移我的笔记,我找到了将笔记导出为通用 Markdown 格式的方法。
本文将介绍两种行之有效的方法:
油猴脚本 :适合单篇笔记的即时导出,尤其适用于那些经常需要修改和分享的文章。Python 脚本 :一个功能强大的批量导出方案,适合一次性备份或迁移全部笔记。 方法一:使用油猴脚本(单篇、即时导出)这个方法通过在浏览器中安装一个简单的脚本,为你的有道云笔记分享页面增加一个“导出为 MD”的按钮,实现一键下载。
1. 安装油猴(Tampermonkey)扩展油猴是一款强大的浏览器扩展,它允许用户运行自定义脚本来增强网页功能。
国内用户建议选择第二个或第三个链接进行安装
2. 导入导出脚本安装好油猴扩展后,你有两种方式来导入脚本
方式 A:直接安装 (推荐)
直接点击下方链接即可一键安装:-> 国外 点击此处安装脚本 -> 国内 点击此处安装脚本
方式 B:手动创建 (备用方案)
如果无法访问上述链接,请按以下步骤手动创建脚本:
在浏览器右上角找到“篡改猴”扩展图标,点击它,然后选择“添加新脚本”。
在打开的编辑器页面中,清空所有默认内容,然后将下面的完整代码粘贴进去。
按下 Ctrl + S 保存脚本,即可完成安装。
脚本代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 (function ( ) { "use strict" ; const richTextParser = { parseNode : function (node ) { if (node.nodeType === 3 ) return node.textContent ; if (node.nodeType !== 1 ) return "" ; let innerMarkdown = Array .from (node.childNodes ) .map ((child ) => this .parseNode (child)) .join ("" ); const isBold = node.style .fontWeight === "bold" || node.classList .contains ("bold" ); if (isBold && innerMarkdown.trim () !== "" ) { if (!innerMarkdown.startsWith ("**" ) && !innerMarkdown.endsWith ("**" )) { innerMarkdown = `**${innerMarkdown} **` ; } } if (node.tagName .toLowerCase () === "a" && node.hasAttribute ("href" )) { return `[${innerMarkdown} ](${node.getAttribute("href" )} )` ; } if (node.tagName .toLowerCase () === "br" ) { return "\n" ; } return innerMarkdown; }, convertHeading : function (block ) { const level = parseInt (block.dataset .headingLevel .replace ("h" , "" ), 10 ); return `${"#" .repeat(level)} ${this .parseNode(block).trim()} ` ; }, convertParagraph : function (block ) { const text = this .parseNode (block).trim (); return text === "" && (block.innerHTML .includes (" " ) || block.textContent .trim () === "" ) ? "" : text; }, convertImage : function (block ) { const img = block.querySelector ("img" ); return img ? `} )` : "" ; }, convertListItem : function (block ) { const li = block.querySelector ("li" ); if (!li) return null ; const text = this .parseNode (li).trim (); let prefix = "* " ; const placeholder = block.querySelector (".item-list-placeholder" ); let indent = "" ; if (placeholder && placeholder.style .left ) { const left = parseInt (placeholder.style .left , 10 ); if (left > 0 ) indent = " " .repeat (Math .round (left / 28 )); } if (block.hasAttribute ("start" )) { prefix = `${placeholder ? placeholder.textContent.trim() : "1." } ` ; } return `${indent} ${prefix} ${text} ` ; }, convertCodeBlock : function (block ) { const langElement = block.querySelector (".change-language .css-u30aqu" ); const lang = langElement ? langElement.textContent .trim ().toLowerCase () : "" ; const codeLines = Array .from ( block.querySelectorAll ('pre[data-block-type="code-line"]' ) ).map ((pre ) => pre.textContent ); return `\`\`\`${lang} \n${codeLines.join("\n" )} \n\`\`\`` ; }, run : function (editor ) { const blocks = Array .from (editor.children ); const markdownLines = []; blocks.forEach ((block ) => { let mdLine = null ; const blockType = block.dataset .blockType ; if (block.tagName .toLowerCase () === "ul" && blockType === "list-item" ) { mdLine = this .convertListItem (block); } else { switch (blockType) { case "heading" : mdLine = this .convertHeading (block); break ; case "paragraph" : mdLine = this .convertParagraph (block); break ; case "image" : mdLine = this .convertImage (block); break ; case "code" : mdLine = this .convertCodeBlock (block); break ; default : if (block.textContent && block.textContent .trim ()) { mdLine = this .parseNode (block); } break ; } } if (mdLine !== null ) markdownLines.push (mdLine); }); return formatMarkdownOutput (markdownLines); }, }; const markdownParser = { run : function (editor ) { const TurndownService = window .TurndownService || (function ( ) { alert ( "Turndown library not found. Please add `@require https://unpkg.com/turndown/dist/turndown.js` to the script header." ); return function ( ) { this .turndown = (text ) => text; }; })(); const turndownService = new TurndownService ({ codeBlockStyle : "fenced" }); return turndownService.turndown (editor.innerHTML ); }, }; function formatMarkdownOutput (lines ) { let fullMarkdown = "" ; for (let i = 0 ; i < lines.length ; i++) { if (lines[i].trim () === "" && (i === 0 || lines[i - 1 ].trim () === "" )) continue ; fullMarkdown += lines[i]; const isCurrentList = /^((\s*[-*]|\s*\d+\.)\s)/ .test (lines[i]); const isNextList = i + 1 < lines.length && /^((\s*[-*]|\s*\d+\.)\s)/ .test (lines[i + 1 ]); fullMarkdown += isCurrentList && isNextList ? "\n" : "\n\n" ; } return fullMarkdown.trim (); } function exportToMarkdown ( ) { const iframe = document .getElementById ("content-body" ); if (!iframe) { alert ("错误:未找到笔记内容的iframe框架!" ); return ; } const iframeDoc = iframe.contentDocument || iframe.contentWindow .document ; if (!iframeDoc) { alert ("错误:无法访问iframe的文档内容!" ); return ; } let markdownContent = "" ; const noteTitle = document .querySelector (".file-name" )?.title || "youdao-note" ; if (iframeDoc.querySelector (".bulb-editor-inner" )) { console .log ("检测到富文本笔记,使用富文本解析器。" ); const editor = iframeDoc.querySelector (".bulb-editor-inner" ); markdownContent = richTextParser.run (editor); } else if (iframeDoc.querySelector (".markdown-body" )) { console .log ("检测到Markdown笔记,使用Turndown解析器。" ); loadScript ("https://unpkg.com/turndown/dist/turndown.js" , () => { const editor = iframeDoc.querySelector (".markdown-body" ); markdownContent = markdownParser.run (editor); downloadMarkdown (markdownContent, noteTitle); }); return ; } else { alert ("错误:未识别的笔记类型或内容未加载!" ); return ; } downloadMarkdown (markdownContent, noteTitle); } function downloadMarkdown (content, title ) { const safeFilename = title.replace (/[\\/:*?"<>|]/g , "-" ).replace (/\s+/g , "_" ) || "youdao-note" ; downloadFile (content, `${safeFilename} .md` ); } function downloadFile (content, filename ) { const blob = new Blob ([content], { type : "text/markdown;charset=utf-8" }); const url = URL .createObjectURL (blob); const a = document .createElement ("a" ); a.href = url; a.download = filename; document .body .appendChild (a); a.click (); document .body .removeChild (a); URL .revokeObjectURL (url); } function loadScript (url, callback ) { if (window .TurndownService ) { callback (); return ; } const script = document .createElement ("script" ); script.src = url; script.onload = callback; script.onerror = () => alert ("加载Turndown库失败,请检查网络连接或脚本设置。" ); document .head .appendChild (script); } function addExportButton ( ) { if (document .getElementById ("export-md-button" )) return ; const button = document .createElement ("button" ); button.id = "export-md-button" ; button.textContent = "导出为MD" ; button.style .position = "fixed" ; button.style .bottom = "20px" ; button.style .right = "20px" ; button.style .zIndex = "9999" ; button.style .padding = "10px 15px" ; button.style .backgroundColor = "#4CAF50" ; button.style .color = "white" ; button.style .border = "none" ; button.style .borderRadius = "5px" ; button.style .cursor = "pointer" ; button.style .fontSize = "14px" ; button.style .boxShadow = "0 2px 5px rgba(0,0,0,0.2)" ; button.addEventListener ( "mouseover" , () => (button.style .backgroundColor = "#45a049" ) ); button.addEventListener ( "mouseout" , () => (button.style .backgroundColor = "#4CAF50" ) ); button.addEventListener ("click" , exportToMarkdown); document .body .appendChild (button); } const observer = new MutationObserver ((mutations, obs ) => { const iframe = document .getElementById ("content-body" ); if (iframe) { const iframeDoc = iframe.contentDocument || iframe.contentWindow .document ; if ( iframeDoc && (iframeDoc.querySelector (".bulb-editor-inner" ) || iframeDoc.querySelector (".markdown-body" )) ) { addExportButton (); obs.disconnect (); } } }); observer.observe (document .body , { childList : true , subtree : true , }); })();
3. 开始使用完成以上步骤后,打开任意一篇有道云笔记的分享链接。你会发现页面右下角出现了一个绿色的“导出为 MD”按钮。点击它,当前笔记就会被自动转换为 .md 文件并下载到本地。
方法二:使用 Python 脚本(批量、完整备份)当你需要导出大量笔记,或者希望进行一次完整的本地备份时,使用由 Github 里其他大佬编写的 Python 脚本会是更高效的选择
1. 准备环境并下载项目首先,确保你的电脑上安装了 Git 和 Python 3
然后,打开命令行工具(如 Terminal 或 PowerShell),克隆项目代码:
1 2 git clone https://github.com/chunxingque/youdaonote-pull.git cd youdaonote-pull
2. 安装项目依赖进入项目目录后,使用 pip 安装所需的依赖库。
Windows:
1 pip install -r requirements.txt
macOS / Linux:
1 pip3 install -r requirements.txt
提示 :建议使用虚拟环境(如 venv)来管理项目依赖,以避免与系统全局环境冲突。
3. 配置登录 Cookies由于有道云笔记的登录机制更新,该脚本目前不支持账号密码登录,需要通过配置 Cookies 来获取访问权限。
获取 Cookies 的便捷方法:
手动在开发者工具中寻找 Cookies 字段既繁琐又容易出错。你可以使用以下 JavaScript 脚本快速生成所需的 JSON 配置。
在浏览器中登录你的有道云笔记网页版。
按 F12 打开开发者工具,切换到 Network (网络) 标签页。
刷新页面,找到一个主请求(通常是第一个),在 Headers (标头) 下找到 Request Headers (请求标头) 中的 Cookie 字段,并复制其完整的字符串值 。
切换到 Console (控制台) 标签页,粘贴以下代码,并将 '这里把上图Cookie属性的值丢进来' 替换为你刚刚复制的字符串 ,然后按回车
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var tmp_cookie = "这里把上图Cookie属性的值丢进来" ;function getCookies ( ) { var cookies = tmp_cookie.split (";" ); var result = []; for (var i = 0 ; i < cookies.length ; i++) { var cookie = cookies[i].trim (); var parts = cookie.split ("=" ); var name = parts[0 ]; var value = parts[1 ]; if ( name === "YNOTE_CSTK" || name === "YNOTE_LOGIN" || name === "YNOTE_SESS" ) { result.push ([name, value, ".note.youdao.com" , "/" ]); } } return result; } var formattedCookies = { cookies : getCookies () };console .warn (JSON .stringify (formattedCookies, null , 2 ));
控制台会输出一段格式化好的 JSON 内容。将其完整复制 在项目根目录下创建一个名为 cookies.json 的文件,并将复制的内容粘贴进去 4. 配置脚本参数在项目根目录下,找到并编辑 config.json 文件,根据你的需求进行设置
1 2 3 4 5 6 7 8 { "local_dir" : "" , "ydnote_dir" : "" , "smms_secret_token" : "" , "is_relative_path" : true , "del_spare_file" : false , "del_spare_dir" : false }
local_dir: 本地存储目录 。选填,导出的笔记将存放在这里,不填则默认为当前项目文件夹。ydnote_dir: 有道云笔记目录 。选填,指定要导出的云端文件夹路径,如 根目录/子目录。不填则默认导出所有笔记。smms_secret_token: SM.MS 图床 Token 。选填,用于将笔记中的图片上传到 SM.MS 图床。不填则图片会下载到本地。is_relative_path: 图片/附件路径 。默认为 true,在 Markdown 文件中使用相对路径引用本地图片和附件。del_spare_file: 删除多余文件 。设为 true 时,脚本会删除本地存在但云端已删除的文件。del_spare_dir: 删除多余目录 。设为 true 时,脚本会删除本地存在但云端已删除的目录。注意 :del_spare_file 和 del_spare_dir 是为了同步本地与云端的状态。建议先开启 del_spare_file 自动清理,目录则根据情况手动清理
5. 执行导出与后续更新首次运行
配置完成后,在命令行中运行脚本:
1 2 3 4 5 # Windows python pull_notes.py # macOS / Linux python3 pull_notes.py
脚本将开始拉取并转换你的笔记。如果遇到某个笔记拉取失败,可能是其格式较旧,可以尝试在有道云中新建一篇笔记,将旧内容复制过去再重新运行脚本
后续更新
再次运行相同的命令即可。脚本会根据文件的最后修改时间智能判断,只更新那些在云端被修改过或新增的笔记,不会覆盖你在本地修改过的文件
重要提示 :请避免同时在有道云和本地修改同一篇笔记,这可能导致本地的修改在同步时被覆盖丢失
总结本文介绍了两种将有道云笔记导出为 Markdown 的方法:
油猴脚本 :轻量、便捷,非常适合即时导出单篇文章Python 脚本 :强大、全面,是进行批量迁移和完整备份的不二之选根据你的具体需求,选择合适的方法,即可轻松地将你的知识库掌握在自己手中