导出时间:2026/2/25 05:51:45
# 对话标题(自己命名)
## 2026-02-24 你:
你问的内容
## 豆包:
回答内容
...
豆包-小说创作-20260224.md,按日期/主题归档(function(){
let chatItems = document.querySelectorAll('[data-testid="chat-item"]');
let result = [];
result.push("# 豆包对话备份");
result.push(new Date().toLocaleString());
result.push("");
chatItems.forEach(item => {
let role = item.querySelector('[data-testid="chat-role"]')?.textContent.trim();
let content = item.querySelector('[data-testid="chat-content"]')?.textContent.trim();
if (role && content) {
result.push(`## ${role}:`);
result.push(content);
result.push("");
}
});
let text = result.join("\n");
let blob = new Blob([text], { type: "text/plain;charset=utf-8" });
let url = URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "豆包对话备份_" + new Date().toISOString().slice(0,10) + ".txt";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
})();
.txt 扔到你的 Ubuntu 本地文件夹 + 云盘就算平台哪天没了、对话清了,你手里永远有完整原稿。(function(){
// 1. 找到所有对话气泡(新版选择器)
let chatItems = document.querySelectorAll('.chat-message');
let result = [];
result.push("# 豆包对话备份");
result.push(new Date().toLocaleString());
result.push("");
// 2. 遍历每一条消息
chatItems.forEach(item => {
// 判断是用户还是助手
let isUser = item.classList.contains('user-message');
let role = isUser ? "你" : "豆包";
// 提取内容(新版内容容器)
let content = item.querySelector('.message-content')?.textContent.trim();
if (content) {
result.push(`## ${role}:`);
result.push(content);
result.push("");
}
});
// 3. 生成并下载文件
let text = result.join("\n");
let blob = new Blob([text], { type: "text/plain;charset=utf-8" });
let url = URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "豆包对话备份_" + new Date().toISOString().slice(0,10) + ".txt";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
})();
.txt 文件,里面就是完整对话了(function(){
let result = [];
result.push("# 豆包对话备份");
result.push(new Date().toLocaleString());
result.push("");
// 直接遍历所有消息气泡,不管class
let allMessages = document.querySelectorAll('[role="article"], [data-role]');
allMessages.forEach(msg => {
let text = msg.textContent.trim();
if (text && text.length > 0) {
result.push(text);
result.push("---");
}
});
let text = result.join("\n");
let blob = new Blob([text], { type: "text/plain;charset=utf-8" });
let url = URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "豆包对话备份_" + new Date().toISOString().slice(0,10) + ".txt";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
})();
> 符号,这就是你粘贴代码的地方。(function() {
// 抓取页面中所有可见文本
const allText = document.body.innerText;
// 生成下载文件
const blob = new Blob([allText], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = "豆包对话全量备份_" + new Date().toISOString().slice(0,10) + ".txt";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
})();
.txt 文件,里面包含了当前页面上的所有文本,包括你的对话内容。(function() {
// 1. 找到所有对话消息
const messages = document.querySelectorAll('.chat-message');
let markdown = [];
// 2. 生成文件头
markdown.push(`# 豆包对话备份`);
markdown.push(`> 导出时间:${new Date().toLocaleString()}`);
markdown.push('---\n');
// 3. 遍历每条消息,转换为 Markdown
messages.forEach(msg => {
const isUser = msg.classList.contains('user-message');
const role = isUser ? '你' : '豆包';
const content = msg.querySelector('.message-content');
if (!content) return;
// 处理代码块
const codeBlocks = content.querySelectorAll('pre');
codeBlocks.forEach(block => {
const code = block.textContent.trim();
block.replaceWith(`\`\`\`\n${code}\n\`\`\``);
});
// 处理引用
const quotes = content.querySelectorAll('blockquote');
quotes.forEach(q => {
const text = q.textContent.trim();
q.replaceWith(`> ${text}`);
});
// 处理列表
const lists = content.querySelectorAll('ul, ol');
lists.forEach(list => {
const items = Array.from(list.querySelectorAll('li')).map(li => `- ${li.textContent.trim()}`).join('\n');
list.replaceWith(items);
});
// 提取最终文本
const finalText = content.innerHTML
.replace(/<strong>(.*?)<\/strong>/g, '**$1**') // 粗体
.replace(/<em>(.*?)<\/em>/g, '*$1*') // 斜体
.replace(/<a href="(.*?)".*?>(.*?)<\/a>/g, '[$2]($1)') // 链接
.replace(/<[^>]+>/g, ''); // 移除其他HTML标签
markdown.push(`## ${role}:`);
markdown.push(finalText);
markdown.push('---\n');
});
// 4. 生成并下载文件
const finalMarkdown = markdown.join('\n');
const blob = new Blob([finalMarkdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_${new Date().toISOString().slice(0,10)}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
})();
Ctrl + Shift + K 打开 Firefox 控制台。.md 文件,你可以用任何 Markdown 编辑器(如 Obsidian、VS Code、Typora)打开,就能看到格式清晰的对话了。# 豆包对话备份
> 导出时间:2026/2/24 18:30:00
---
## 你:
人就是既得陇又望蜀啊。那么现在这个 txt 版本呢,当然是好了。但是如果能够导出的是 Markdown 的格式的话...
---
## 豆包:
当然可以!导出为 Markdown 格式完全可行,而且能完美保留代码块、引用、列表等格式...
(function() {
// 1. 只抓取对话区域,排除左侧栏和顶部导航
const chatContainer = document.querySelector('[data-testid="chat-container"], .chat-container, main');
if (!chatContainer) {
alert('找不到对话容器,请确保在对话详情页');
return;
}
// 2. 找到所有对话消息
const messages = chatContainer.querySelectorAll('.chat-message, [data-role="assistant"], [data-role="user"]');
let markdown = [];
// 3. 生成文件头
markdown.push(`# 豆包对话备份`);
markdown.push(`> 导出时间:${new Date().toLocaleString()}`);
markdown.push('---\n');
// 4. 遍历每条消息,转换为 Markdown
messages.forEach(msg => {
// 判断角色
const isUser = msg.classList.contains('user-message') || msg.getAttribute('data-role') === 'user';
const role = isUser ? '你' : '豆包';
// 提取内容容器
const content = msg.querySelector('.message-content, [data-testid="message-content"]');
if (!content) return;
// 克隆内容,避免修改原页面
const contentClone = content.cloneNode(true);
// 处理代码块
const codeBlocks = contentClone.querySelectorAll('pre, code');
codeBlocks.forEach(block => {
const code = block.textContent.trim();
block.replaceWith(`\`\`\`\n${code}\n\`\`\``);
});
// 处理引用
const quotes = contentClone.querySelectorAll('blockquote');
quotes.forEach(q => {
const text = q.textContent.trim();
q.replaceWith(`> ${text}`);
});
// 处理列表
const lists = contentClone.querySelectorAll('ul, ol');
lists.forEach(list => {
const items = Array.from(list.querySelectorAll('li')).map(li => `- ${li.textContent.trim()}`).join('\n');
list.replaceWith(items);
});
// 处理粗体、斜体、链接
let finalText = contentClone.innerHTML
.replace(/<strong>(.*?)<\/strong>/g, '**$1**')
.replace(/<em>(.*?)<\/em>/g, '*$1*')
.replace(/<a href="(.*?)".*?>(.*?)<\/a>/g, '[$2]($1)')
.replace(/<[^>]+>/g, '') // 移除所有剩余HTML标签
.trim();
// 只添加有内容的消息
if (finalText) {
markdown.push(`## ${role}:`);
markdown.push(finalText);
markdown.push('---\n');
}
});
// 5. 生成并下载文件
const finalMarkdown = markdown.join('\n');
const blob = new Blob([finalMarkdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_${new Date().toISOString().slice(0,10)}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 已导出纯净版 Markdown 备份');
})();
Ctrl + Shift + K 打开 Firefox 控制台。.md 文件,里面只有你和豆包的对话,格式清晰,没有任何无关内容。# 豆包对话备份
> 导出时间:2026/2/24 19:00:00
---
## 你:
人就是既得陇又望蜀啊。那么现在这个 txt 版本呢,当然是好了。但是如果能够导出的是 Markdown 的格式的话...
---
## 豆包:
当然可以!导出为 Markdown 格式完全可行,而且能完美保留代码块、引用、列表等格式...
(function() {
// 抓取页面中所有可见文本
const allText = document.body.innerText;
// 生成下载文件
const blob = new Blob([allText], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = "豆包对话全量备份_" + new Date().toISOString().slice(0,10) + ".txt";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
})();
.txt 文件,里面包含了当前页面上的所有文本,包括你的对话内容。.txt 文件里有完整对话后,再运行下面这个脚本,它会把刚才导出的 .txt 文件内容,自动清洗成干净的 Markdown 格式。// 把刚才导出的 .txt 文件内容粘贴到这里
const rawText = `
# 这里粘贴你刚才导出的 .txt 文件的全部内容
`;
// 自动清洗成 Markdown
function cleanToMarkdown(text) {
// 1. 过滤掉左侧话题栏和顶部导航等无关内容
const lines = text.split('\n');
const startIndex = lines.findIndex(line => line.includes('你:') || line.includes('豆包:'));
const cleanLines = startIndex !== -1 ? lines.slice(startIndex) : lines;
// 2. 按角色分段
let markdown = [];
let currentRole = '';
let currentContent = [];
cleanLines.forEach(line => {
if (line.startsWith('你:')) {
if (currentRole) {
markdown.push(`## ${currentRole}:`);
markdown.push(currentContent.join('\n'));
markdown.push('---\n');
}
currentRole = '你';
currentContent = [line.replace('你:', '').trim()];
} else if (line.startsWith('豆包:')) {
if (currentRole) {
markdown.push(`## ${currentRole}:`);
markdown.push(currentContent.join('\n'));
markdown.push('---\n');
}
currentRole = '豆包';
currentContent = [line.replace('豆包:', '').trim()];
} else if (line.trim()) {
currentContent.push(line.trim());
}
});
// 处理最后一段
if (currentRole) {
markdown.push(`## ${currentRole}:`);
markdown.push(currentContent.join('\n'));
markdown.push('---\n');
}
return `# 豆包对话备份\n> 导出时间:${new Date().toLocaleString()}\n---\n\n${markdown.join('\n')}`;
}
// 生成并下载 Markdown 文件
const finalMarkdown = cleanToMarkdown(rawText);
const blob = new Blob([finalMarkdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_${new Date().toISOString().slice(0,10)}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
.txt 文件,复制里面的全部内容。const rawText = 部分。.md 文件,里面就是干净的 Markdown 格式对话了。.txt 文件,把无关内容删掉,再保存为 .md。.txt 文件吗?document.body.innerText 这个方法,本质上就是把网页上所有可见的文字“拍扁”成纯文本,所以它不可能保留任何 Markdown 格式(比如代码块、粗体、列表)。这就是为什么你导出的 .txt 文件里,所有格式都消失了,只剩下光秃秃的文字。<pre> 代表代码块,<strong> 代表粗体)。(function() {
// 1. 先滚动到底部,确保所有内容加载完成
window.scrollTo(0, document.body.scrollHeight);
await new Promise(r => setTimeout(r, 2000)); // 等待2秒,让懒加载完成
// 2. 找到所有对话消息(这个选择器更通用,适配新版页面)
const messages = document.querySelectorAll('div[class*="message"], div[role="article"]');
let markdown = [];
// 3. 生成文件头
markdown.push(`# 豆包对话备份`);
markdown.push(`> 导出时间:${new Date().toLocaleString()}`);
markdown.push('---\n');
// 4. 遍历每条消息,转换为 Markdown
messages.forEach(msg => {
// 判断角色:通过内容或位置来判断
const contentText = msg.textContent.trim();
if (!contentText) return;
// 简单判断:如果消息里有“你:”,就是用户消息;否则是豆包消息
// 你也可以根据自己的对话开头微调这个判断逻辑
const isUser = contentText.startsWith('你:') || contentText.includes('我现在');
const role = isUser ? '你' : '豆包';
// 提取并转换HTML内容
let finalText = msg.innerHTML
// 代码块:<pre> -> ```
.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/g, (match, code) => `\`\`\`\n${code.trim()}\n\`\`\``)
// 粗体:<strong> -> **
.replace(/<strong>(.*?)<\/strong>/g, '**$1**')
// 斜体:<em> -> *
.replace(/<em>(.*?)<\/em>/g, '*$1*')
// 列表:<ul>/<ol> -> -
.replace(/<li>(.*?)<\/li>/g, '- $1')
// 引用:<blockquote> -> >
.replace(/<blockquote>(.*?)<\/blockquote>/g, '> $1')
// 链接:<a> -> [text](url)
.replace(/<a href="(.*?)".*?>(.*?)<\/a>/g, '[$2]($1)')
// 移除所有其他HTML标签
.replace(/<[^>]+>/g, '')
// 清理多余的换行和空格
.replace(/\n\s*\n/g, '\n\n')
.trim();
// 只添加有内容的消息
if (finalText) {
markdown.push(`## ${role}:`);
markdown.push(finalText);
markdown.push('---\n');
}
});
// 5. 生成并下载文件
const finalMarkdown = markdown.join('\n');
const blob = new Blob([finalMarkdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_${new Date().toISOString().slice(0,10)}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 已导出带格式的 Markdown 备份');
})();
Ctrl + Shift + K 打开 Firefox 控制台。.md 文件。用 VS Code、Obsidian 等编辑器打开,你会看到代码块、粗体、列表等格式都被完美保留了。.chat-message),而是通过 div[class*="message"] 这种更模糊的方式查找,能更好地适应网页结构的变化。await,但外层函数没有声明为 async。我给你修正一下,这个版本可以直接运行:(async function() {
// 1. 先滚动到底部,确保所有内容加载完成
window.scrollTo(0, document.body.scrollHeight);
await new Promise(r => setTimeout(r, 2000)); // 等待2秒,让懒加载完成
// 2. 找到所有对话消息(这个选择器更通用,适配新版页面)
const messages = document.querySelectorAll('div[class*="message"], div[role="article"]');
let markdown = [];
// 3. 生成文件头
markdown.push(`# 豆包对话备份`);
markdown.push(`> 导出时间:${new Date().toLocaleString()}`);
markdown.push('---\n');
// 4. 遍历每条消息,转换为 Markdown
messages.forEach(msg => {
// 判断角色:通过内容或位置来判断
const contentText = msg.textContent.trim();
if (!contentText) return;
// 简单判断:如果消息里有“你:”,就是用户消息;否则是豆包消息
// 你也可以根据自己的对话开头微调这个判断逻辑
const isUser = contentText.startsWith('你:') || contentText.includes('我现在');
const role = isUser ? '你' : '豆包';
// 提取并转换HTML内容
let finalText = msg.innerHTML
// 代码块:<pre> -> ```
.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/g, (match, code) => `\`\`\`\n${code.trim()}\n\`\`\``)
// 粗体:<strong> -> **
.replace(/<strong>(.*?)<\/strong>/g, '**$1**')
// 斜体:<em> -> *
.replace(/<em>(.*?)<\/em>/g, '*$1*')
// 列表:<ul>/<ol> -> -
.replace(/<li>(.*?)<\/li>/g, '- $1')
// 引用:<blockquote> -> >
.replace(/<blockquote>(.*?)<\/blockquote>/g, '> $1')
// 链接:<a> -> [text](url)
.replace(/<a href="(.*?)".*?>(.*?)<\/a>/g, '[$2]($1)')
// 移除所有其他HTML标签
.replace(/<[^>]+>/g, '')
// 清理多余的换行和空格
.replace(/\n\s*\n/g, '\n\n')
.trim();
// 只添加有内容的消息
if (finalText) {
markdown.push(`## ${role}:`);
markdown.push(finalText);
markdown.push('---\n');
}
});
// 5. 生成并下载文件
const finalMarkdown = markdown.join('\n');
const blob = new Blob([finalMarkdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_${new Date().toISOString().slice(0,10)}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 已导出带格式的 Markdown 备份');
})();
Ctrl + Shift + K 打开 Firefox 控制台。.md 文件,里面就是带格式的对话备份了。wget -qO - https://typora.io/linux/public-key.asc | sudo apt-key add -
sudo add-apt-repository 'deb https://typora.io/linux ./'
sudo apt update && sudo apt install typora
.deb 包安装:wget https://github.com/marktext/marktext/releases/latest/download/marktext-amd64.deb
sudo dpkg -i marktext-amd64.deb
sudo apt -f install
Markdown All in One:提供快捷键、目录、实时预览等。Markdown Preview Enhanced:增强预览功能,支持导出 PDF、HTML。.md 文件后,右上角点击「打开预览」即可。sudo apt update && sudo apt install glow
glow 你的文件.md
.deb 包安装。.md 文件Ctrl + Shift + P,输入 Markdown: Export to HTMLsudo apt update && sudo apt install pandoc
# 基础转换
pandoc input.md -o output.html
# 带样式、代码高亮的专业转换
pandoc input.md -o output.html --standalone --css=github.css --highlight-style=monochrome
glow -s dark input.md > output.html
marked:# 安装
npm install -g marked
# 转换
marked input.md > output.html
Ctrl + Shift + B 打开书签管理器。豆包导出Markdown。书签工具栏(这样在地址栏下面就能看到)。javascript:(async function(){const e=document.querySelectorAll('div[class*="message"],div[role="article"]');let t=[];t.push(`# 豆包对话备份`),t.push(`> 导出时间:${new Date().toLocaleString()}`),t.push("---\n"),e.forEach((n=>{const o=n.textContent.trim();if(o){const e=o.startsWith("你:")||o.includes("我现在"),i=e?"你":"豆包",l=n.innerHTML.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/g,((e,t)=>`\`\`\`\n${t.trim()}\n\`\`\``)).replace(/<strong>(.*?)<\/strong>/g,"**$1**").replace(/<em>(.*?)<\/em>/g,"*$1*").replace(/<li>(.*?)<\/li>/g,"- $1").replace(/<blockquote>(.*?)<\/blockquote>/g,"> $1").replace(/<a href="(.*?)".*?>(.*?)<\/a>/g,"[$2]($1)").replace(/<[^>]+>/g,"").replace(/\n\s*\n/g,"\n\n").trim();l&&(t.push(`## ${i}:`),t.push(l),t.push("---\n"))}}));const o=t.join("\n"),i= new Blob([o],{type:"text/markdown;charset=utf-8"}),l=URL.createObjectURL(i),s=document.createElement("a");s.href=l,s.download=`豆包对话备份_${new Date().toISOString().slice(0,10)}.md`,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(l),console.log("✅ 已导出带格式的 Markdown 备份")})();
豆包导出Markdown 按钮。.md 文件,搞定!pandoc 和 curl 写一个脚本,从你导出的 .txt 文件自动清洗成 Markdown。不过这个方法不如书签一键式方便。.md 文件本身不包含图片二进制数据,它只通过  这样的语法引用图片。<img> 标签做特殊处理,所以图片会被当成普通 HTML 标签删掉了。(async function() {
// 1. 先滚动到底部,确保所有内容加载完成
window.scrollTo(0, document.body.scrollHeight);
await new Promise(r => setTimeout(r, 2000)); // 等待2秒,让懒加载完成
// 2. 找到所有对话消息
const messages = document.querySelectorAll('div[class*="message"], div[role="article"]');
let markdown = [];
// 3. 生成文件头
markdown.push(`# 豆包对话备份`);
markdown.push(`> 导出时间:${new Date().toLocaleString()}`);
markdown.push('---\n');
// 4. 遍历每条消息,转换为 Markdown
messages.forEach(msg => {
const contentText = msg.textContent.trim();
if (!contentText) return;
const isUser = contentText.startsWith('你:') || contentText.includes('我现在');
const role = isUser ? '你' : '豆包';
// 提取并转换HTML内容,重点处理图片
let finalText = msg.innerHTML
// 图片:<img> -> 
.replace(/<img[^>]*src="([^"]+)"[^>]*alt="([^"]*)"[^>]*>/g, '')
.replace(/<img[^>]*src="([^"]+)"[^>]*>/g, '') // 没有alt的情况
// 代码块:<pre> -> ```
.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/g, (match, code) => `\`\`\`\n${code.trim()}\n\`\`\``)
// 粗体:<strong> -> **
.replace(/<strong>(.*?)<\/strong>/g, '**$1**')
// 斜体:<em> -> *
.replace(/<em>(.*?)<\/em>/g, '*$1*')
// 列表:<ul>/<ol> -> -
.replace(/<li>(.*?)<\/li>/g, '- $1')
// 引用:<blockquote> -> >
.replace(/<blockquote>(.*?)<\/blockquote>/g, '> $1')
// 链接:<a> -> [text](url)
.replace(/<a href="(.*?)".*?>(.*?)<\/a>/g, '[$2]($1)')
// 移除所有其他HTML标签
.replace(/<[^>]+>/g, '')
// 清理多余的换行和空格
.replace(/\n\s*\n/g, '\n\n')
.trim();
if (finalText) {
markdown.push(`## ${role}:`);
markdown.push(finalText);
markdown.push('---\n');
}
});
// 5. 生成并下载文件
const finalMarkdown = markdown.join('\n');
const blob = new Blob([finalMarkdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_${new Date().toISOString().slice(0,10)}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 已导出带图片链接的 Markdown 备份');
})();
.md 文件,提取所有图片 URL。images/ 文件夹。./images/xxx.png。import re
import os
import requests
from urllib.parse import urlparse
def download_images_from_md(md_file_path):
# 读取Markdown文件
with open(md_file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 创建图片保存目录
img_dir = os.path.splitext(md_file_path)[0] + '_images'
os.makedirs(img_dir, exist_ok=True)
# 匹配所有图片链接
img_pattern = re.compile(r'!<span data-type="block-math" data-value="Lio/"></span><span data-type="inline-math" data-value="KC4qPyk="></span>')
img_urls = img_pattern.findall(content)
# 下载图片
for url in img_urls:
try:
# 从URL中提取文件名
parsed_url = urlparse(url)
filename = os.path.basename(parsed_url.path) or 'image.png'
img_path = os.path.join(img_dir, filename)
# 下载并保存
response = requests.get(url, stream=True)
response.raise_for_status()
with open(img_path, 'wb') as f:
for chunk in response.iter_content(1024):
f.write(chunk)
# 替换Markdown中的链接为本地路径
local_path = f'./{os.path.basename(img_dir)}/{filename}'
content = content.replace(url, local_path)
print(f'✅ 已下载: {url} -> {local_path}')
except Exception as e:
print(f'❌ 下载失败: {url}, 错误: {e}')
# 保存修改后的Markdown文件
new_md_path = os.path.splitext(md_file_path)[0] + '_with_local_images.md'
with open(new_md_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f'\n✅ 已生成离线版Markdown: {new_md_path}')
# 使用示例
if __name__ == '__main__':
md_file = '豆包对话备份_2026-02-24.md' # 替换成你的文件名
download_images_from_md(md_file)
.md 文件,在支持网络图片的编辑器(如 VS Code、Obsidian)里打开就能看到图片。.md 文件。。download_images.py。.md 文件放在同一个文件夹里。python3 download_images.py
.md 文件,里面的图片链接已经变成了本地路径。(async function() {
// 1. 先滚动到底部,确保所有内容加载完成
window.scrollTo(0, document.body.scrollHeight);
await new Promise(r => setTimeout(r, 2000)); // 等待2秒,让懒加载完成
// 2. 找到所有对话消息
const messages = document.querySelectorAll('div[class*="message"], div[role="article"]');
let markdown = [];
// 3. 生成文件头
markdown.push(`# 豆包对话备份`);
markdown.push(`> 导出时间:${new Date().toLocaleString()}`);
markdown.push('---\n');
// 4. 遍历每条消息,转换为 Markdown
messages.forEach(msg => {
const contentText = msg.textContent.trim();
if (!contentText) return;
const isUser = contentText.startsWith('你:') || contentText.includes('我现在');
const role = isUser ? '你' : '豆包';
// 提取并转换HTML内容,重点处理图片
let finalText = msg.innerHTML
// 图片:<img> -> 
.replace(/<img[^>]*src="([^"]+)"[^>]*alt="([^"]*)"[^>]*>/g, '')
.replace(/<img[^>]*src="([^"]+)"[^>]*>/g, '') // 没有alt的情况
// 代码块:<pre> -> ```
.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/g, (match, code) => `\`\`\`\n${code.trim()}\n\`\`\``)
// 粗体:<strong> -> **
.replace(/<strong>(.*?)<\/strong>/g, '**$1**')
// 斜体:<em> -> *
.replace(/<em>(.*?)<\/em>/g, '*$1*')
// 列表:<ul>/<ol> -> -
.replace(/<li>(.*?)<\/li>/g, '- $1')
// 引用:<blockquote> -> >
.replace(/<blockquote>(.*?)<\/blockquote>/g, '> $1')
// 链接:<a> -> [text](url)
.replace(/<a href="(.*?)".*?>(.*?)<\/a>/g, '[$2]($1)')
// 移除所有其他HTML标签
.replace(/<[^>]+>/g, '')
// 清理多余的换行和空格
.replace(/\n\s*\n/g, '\n\n')
.trim();
if (finalText) {
markdown.push(`## ${role}:`);
markdown.push(finalText);
markdown.push('---\n');
}
});
// 5. 生成并下载文件
const finalMarkdown = markdown.join('\n');
const blob = new Blob([finalMarkdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_${new Date().toISOString().slice(0,10)}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 已导出带图片链接的 Markdown 备份');
})();
.md 文件。.md。import re
import os
import sys
import requests
from urllib.parse import urlparse
def download_images_from_md(md_file_path):
# 读取Markdown文件
with open(md_file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 创建图片保存目录
img_dir = os.path.splitext(md_file_path)[0] + '_images'
os.makedirs(img_dir, exist_ok=True)
# 匹配所有图片链接
img_pattern = re.compile(r'!<span data-type="block-math" data-value="Lio/"></span><span data-type="inline-math" data-value="KC4qPyk="></span>')
img_urls = img_pattern.findall(content)
# 下载图片
for url in img_urls:
try:
# 从URL中提取文件名
parsed_url = urlparse(url)
filename = os.path.basename(parsed_url.path) or 'image.png'
img_path = os.path.join(img_dir, filename)
# 下载并保存
response = requests.get(url, stream=True)
response.raise_for_status()
with open(img_path, 'wb') as f:
for chunk in response.iter_content(1024):
f.write(chunk)
# 替换Markdown中的链接为本地路径
local_path = f'./{os.path.basename(img_dir)}/{filename}'
content = content.replace(url, local_path)
print(f'✅ 已下载: {url} -> {local_path}')
except Exception as e:
print(f'❌ 下载失败: {url}, 错误: {e}')
# 保存修改后的Markdown文件
new_md_path = os.path.splitext(md_file_path)[0] + '_with_local_images.md'
with open(new_md_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f'\n✅ 已生成离线版Markdown: {new_md_path}')
if __name__ == '__main__':
if len(sys.argv) != 2:
print(f'用法: python3 {os.path.basename(__file__)} <markdown_file_path>')
sys.exit(1)
md_file = sys.argv[1]
download_images_from_md(md_file)
download_images.py。python3 download_images.py 你的文件名.md
p26-flow-imgex-sign.byteimg.com)做了防盗链(Referer)和签名验证。Referer 头,只有来自 doubao.com 域名的请求才会被允许。sign、t),这些参数有过期时间,并且和你的会话绑定。.md 文件会非常大,但里面的图片是完全离线的,不需要再依赖任何外部链接。fetch API,在脚本运行时直接下载图片,因为此时 Referer 头是合法的。FileSaver.js 把图片保存到本地,再修改 Markdown 链接。.md 文件(async function() {
// 1. 自动滚动到底部,确保所有内容加载完成
window.scrollTo(0, document.body.scrollHeight);
await new Promise(r => setTimeout(r, 2000));
// 2. 找到所有对话消息
const messages = document.querySelectorAll('div[class*="message"], div[role="article"]');
let markdown = [];
// 3. 生成文件头
markdown.push(`# 豆包对话备份`);
markdown.push(`> 导出时间:${new Date().toLocaleString()}`);
markdown.push('---\n');
// 4. 遍历每条消息,转换为 Markdown,并将图片转为Base64
for (const msg of messages) {
const contentText = msg.textContent.trim();
if (!contentText) continue;
const isUser = contentText.startsWith('你:') || contentText.includes('我现在');
const role = isUser ? '你' : '豆包';
// 先克隆节点,避免修改原页面
const contentClone = msg.querySelector('.message-content, [data-testid="message-content"]')?.cloneNode(true);
if (!contentClone) continue;
// 处理图片:转为Base64
const imgs = contentClone.querySelectorAll('img');
for (const img of imgs) {
try {
const src = img.src;
if (!src) continue;
// 用fetch获取图片,因为在浏览器环境下,Referer是合法的
const response = await fetch(src, {
headers: {
'Accept': 'image/*'
}
});
const blob = await response.blob();
// 转为Base64
const base64 = await new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
// 替换为Base64图片
const alt = img.alt || '图片';
img.outerHTML = ``;
console.log(`✅ 已嵌入图片: ${src}`);
} catch (e) {
console.error(`❌ 图片处理失败:`, e);
img.outerHTML = `![图片丢失]()`;
}
}
// 处理其他格式
let finalText = contentClone.innerHTML
.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/g, (match, code) => `\`\`\`\n${code.trim()}\n\`\`\``)
.replace(/<strong>(.*?)<\/strong>/g, '**$1**')
.replace(/<em>(.*?)<\/em>/g, '*$1*')
.replace(/<li>(.*?)<\/li>/g, '- $1')
.replace(/<blockquote>(.*?)<\/blockquote>/g, '> $1')
.replace(/<a href="(.*?)".*?>(.*?)<\/a>/g, '[$2]($1)')
.replace(/<[^>]+>/g, '')
.replace(/\n\s*\n/g, '\n\n')
.trim();
if (finalText) {
markdown.push(`## ${role}:`);
markdown.push(finalText);
markdown.push('---\n');
}
}
// 5. 生成并下载文件
const finalMarkdown = markdown.join('\n');
const blob = new Blob([finalMarkdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_${new Date().toISOString().slice(0,10)}_with_base64_images.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 已导出完全离线的Markdown备份(含Base64图片)');
})();
Ctrl + Shift + K 打开 Firefox 控制台。.md 文件。Ctrl + Shift + K 打开 Firefox 控制台。(function() {
// 找到对话区域(这个选择器更通用)
const chatContainer = document.querySelector('main') || document.body;
if (!chatContainer) {
alert('找不到对话区域');
return;
}
// 导出HTML
const html = chatContainer.innerHTML;
const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = "豆包对话完整备份_" + new Date().toISOString().slice(0,10) + ".html";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 已导出完整HTML备份');
})();
pandoc 这个强大的工具把它转换成 Markdown。sudo apt update && sudo apt install pandoc
pandoc 豆包对话完整备份_2026-02-25.html -o 豆包对话备份_2026-02-25.md
.md 文件里,就会包含所有的文字内容,并且保留了基本的格式。pandoc 是文档转换的行业标准,能稳定地把 HTML 转换成 Markdown,不会因为网页结构变化而失效。wget --mirror 下载整个网站的思路是一样的,我们可以用 JavaScript 脚本在浏览器里完成这件事。images/ 文件夹.html 文件和一个图片文件夹,完全离线查看(async function() {
// 1. 自动滚动到底部,确保所有内容加载完成
window.scrollTo(0, document.body.scrollHeight);
await new Promise(r => setTimeout(r, 2000));
// 2. 找到对话区域(优先main标签,兜底用body)
const chatContainer = document.querySelector('main') || document.body;
if (!chatContainer) {
alert('找不到对话区域');
return;
}
// 3. 克隆节点,避免修改原页面
const contentClone = chatContainer.cloneNode(true);
const imgs = contentClone.querySelectorAll('img');
const imageMap = new Map(); // 存储图片URL和本地路径的映射
// 4. 处理所有图片
for (const img of imgs) {
const src = img.src;
if (!src) continue;
try {
// 用fetch获取图片(浏览器环境下Referer合法)
const response = await fetch(src, {
headers: { 'Accept': 'image/*' }
});
const blob = await response.blob();
// 生成文件名
const ext = blob.type.split('/')[1] || 'png';
const filename = `image_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.${ext}`;
// 保存图片到本地(通过FileSaver)
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话快照_images/${filename}`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// 记录映射关系
imageMap.set(src, `豆包对话快照_images/${filename}`);
console.log(`✅ 已下载图片: ${src} -> ${filename}`);
} catch (e) {
console.error(`❌ 图片下载失败: ${src}`, e);
}
}
// 5. 替换HTML中的图片链接
imageMap.forEach((localPath, src) => {
contentClone.innerHTML = contentClone.innerHTML.replace(new RegExp(src, 'g'), localPath);
});
// 6. 生成并下载HTML文件
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>豆包对话快照_${new Date().toISOString().slice(0,10)}</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.message { margin: 20px 0; padding: 15px; border-radius: 8px; }
.user { background-color: #f0f7ff; }
.assistant { background-color: #f5f5f5; }
img { max-width: 100%; height: auto; }
</style>
</head>
<body>
<h1>豆包对话快照</h1>
<p>导出时间:${new Date().toLocaleString()}</p>
<hr>
${contentClone.innerHTML}
</body>
</html>
`.trim();
const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话快照_${new Date().toISOString().slice(0,10)}.html`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 已导出带本地图片的HTML快照');
})();
Ctrl + Shift + K 打开控制台,粘贴脚本并回车。.html 文件和一堆图片。豆包对话快照_images 的文件夹里。.html 文件和这个文件夹在同一个目录下。.html 文件,都能看到完整的对话和图片,就像在本地保存了一个完整的网页一样。javascript:(async function(){window.scrollTo(0,document.body.scrollHeight);await new Promise(r=>setTimeout(r,2000));const e=document.querySelector('main')||document.body;if(!e)return void alert('找不到对话区域');const t=e.cloneNode(!0),n=new Map();for(const o of t.querySelectorAll('img')){const i=o.src;if(i){try{const e=await fetch(i,{headers:{'Accept':'image/*'}}),t=await e.blob(),a=`image_${Date.now()}_${Math.random().toString(36).substr(2,9)}.${t.type.split('/')[1]||'png'}`,s=URL.createObjectURL(t),l=document.createElement('a');l.href=s,l.download=`豆包对话快照_images/${a}`,document.body.appendChild(l),l.click(),document.body.removeChild(l),URL.revokeObjectURL(s),n.set(i,`豆包对话快照_images/${a}`),console.log(`✅ 已下载图片: ${i} -> ${a}`)}catch(e){console.error(`❌ 图片下载失败: ${i}`,e)}}}n.forEach(((e,t)=>{t.innerHTML=t.innerHTML.replace(new RegExp(e,'g'),n.get(e))}));const o=document.createElement('a'),i=`<!DOCTYPE html>\n<html>\n<head>\n <meta charset="UTF-8">\n <title>豆包对话快照_${new Date().toISOString().slice(0,10)}</title>\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }\n .message { margin: 20px 0; padding: 15px; border-radius: 8px; }\n .user { background-color: #f0f7ff; }\n .assistant { background-color: #f5f5f5; }\n img { max-width: 100%; height: auto; }\n </style>\n</head>\n<body>\n <h1>豆包对话快照</h1>\n <p>导出时间:${new Date().toLocaleString()}</p>\n <hr>\n ${t.innerHTML}\n</body>\n</html>`,s=URL.createObjectURL(new Blob([i],{type:'text/html;charset=utf-8'}));o.href=s,o.download=`豆包对话快照_${new Date().toISOString().slice(0,10)}.html`,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(s),console.log('✅ 已导出带本地图片的HTML快照')})();
Ctrl + Shift + B 打开 Firefox 书签管理器。豆包导出HTML快照。书签工具栏(这样在地址栏下面就能看到)。豆包导出HTML快照 按钮。豆包对话快照_images 文件夹,就得到了一个完全离线的网页快照。sign=t=expires=javascript:(async function(){const h=document.querySelector('main')||document.body;let c=h.cloneNode(!0);for(let i of c.querySelectorAll('img')){try{if(!i.src)continue;let r=await fetch(i.src,{headers:{Accept:'image/*'}});let b=await r.blob();let d=await new Promise(e=>{let f=new FileReader;f.onloadend=()=>e(f.result);f.readAsDataURL(b)});i.src=d;console.log('✅ 图片嵌入完成')}catch(e){i.src='';i.alt='图片无法备份'}}let t=`<!DOCTYPE html><html><head><meta charset="utf-8"><title>豆包离线备份</title><style>body{max-width:900px;margin:30px auto;font-size:15px;line-height:1.6}img{max-width:100%;height:auto}</style></head><body>${c.innerHTML}</body></html>`;let a=document.createElement('a');a.href=URL.createObjectURL(new Blob([t],{type:'text/html;charset=utf-8'}));a.download=`豆包完整离线备份_${new Date().toISOString().slice(0,10)}.html`;document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(a.href);alert('✅ 导出完成!这是永久离线HTML')})();
豆包离线备份(永久版)(function() {
// 1. 找到对话区域
const chatContainer = document.querySelector('main') || document.body;
if (!chatContainer) {
alert('找不到对话区域');
return;
}
// 2. 生成HTML
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>豆包对话备份_${new Date().toISOString().slice(0,10)}</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
img { max-width: 100%; height: auto; }
</style>
</head>
<body>
<h1>豆包对话备份</h1>
<p>导出时间:${new Date().toLocaleString()}</p>
<hr>
${chatContainer.innerHTML}
</body>
</html>
`.trim();
// 3. 下载文件
const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_${new Date().toISOString().slice(0,10)}.html`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
alert('✅ 已导出HTML备份文件');
})();
Ctrl + Shift + K 打开 Firefox 控制台。.html 文件。 字符串,它就是图片本身\sqrt{2} 这类 LaTeX 代码渲染成漂亮的数学公式。<!-- 在HTML的<head>标签中添加以下内容 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body);"></script>
√2 或 sqrt(2)。(async function() {
const chatContainer = document.querySelector('main') || document.body;
if (!chatContainer) {
alert('找不到对话区域');
return;
}
const contentClone = chatContainer.cloneNode(true);
// 处理图片为Base64
for (const img of contentClone.querySelectorAll('img')) {
try {
if (!img.src) continue;
const response = await fetch(img.src, { headers: { 'Accept': 'image/*' } });
const blob = await response.blob();
const base64 = await new Promise(resolve => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
img.src = base64;
} catch (e) {
console.error('图片处理失败', e);
}
}
// 生成HTML,头部嵌入KaTeX资源
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>豆包对话备份_${new Date().toISOString().slice(0,10)}</title>
<!-- 嵌入KaTeX资源,用于渲染数学公式 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body);"></script>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
img { max-width: 100%; height: auto; }
</style>
</head>
<body>
<h1>豆包对话备份</h1>
<p>导出时间:${new Date().toLocaleString()}</p>
<hr>
${contentClone.innerHTML}
</body>
</html>
`.trim();
const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_带公式渲染_${new Date().toISOString().slice(0,10)}.html`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 已导出带公式渲染的HTML备份');
})();
html2canvas 之类的库把它们“截图”成图片,再替换掉原来的代码。这不仅需要引入额外的库,还会让脚本变得非常复杂和不稳定,很容易因为网页结构变化而失效。javascript:(async function(){const e=document.querySelector('main')||document.body;if(!e)return void alert('找不到对话区域');const t=e.cloneNode(!0);for(const o of t.querySelectorAll('img')){try{if(!o.src)continue;const e=await fetch(o.src,{headers:{'Accept':'image/*'}}),t=await e.blob(),n=await new Promise(e=>{const t=new FileReader;t.onloadend=()=>e(t.result),t.readAsDataURL(t)});o.src=n}catch(e){console.error('图片处理失败',e)}}const n=`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>豆包对话备份_${new Date().toISOString().slice(0,10)}</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css"><script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script><script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body);"></script><style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:800px;margin:0 auto;padding:20px;}img{max-width:100%;height:auto;}</style></head><body><h1>豆包对话备份</h1><p>导出时间:${new Date().toLocaleString()}</p><hr>${t.innerHTML}</body></html>`.trim(),a=URL.createObjectURL(new Blob([n],{type:'text/html;charset=utf-8'})),d=document.createElement('a');d.href=a,d.download=`豆包对话备份_带公式渲染_${new Date().toISOString().slice(0,10)}.html`,document.body.appendChild(d),d.click(),document.body.removeChild(d),URL.revokeObjectURL(a),console.log('✅ 已导出带公式渲染的HTML备份')})();
Ctrl + Shift + B)。豆包离线备份(永久版) 书签,右键选择「属性」。豆包离线备份(永久版)。javascript:(function(){const e=document.querySelector('main')||document.body;if(!e)return void alert('找不到对话区域');const t=e.cloneNode(!0);for(const o of t.querySelectorAll('img'))o.remove();const n=`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css"><script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script><script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body);"></script><style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:800px;margin:0 auto;padding:20px;}</style><h1>豆包轻量备份</h1><p>导出时间:${new Date().toLocaleString()}</p><hr>${t.innerHTML}`.trim(),a=URL.createObjectURL(new Blob([n],{type:'text/html;charset=utf-8'})),d=document.createElement('a');d.href=a,d.download=`豆包轻量备份_${new Date().toISOString().slice(0,10)}.html`,document.body.appendChild(d),d.click(),document.body.removeChild(d),URL.revokeObjectURL(a),alert('✅ 轻量备份导出完成(无图)')})();
Ctrl + Shift + B 打开 Firefox 书签管理器。豆包轻量备份(无图)。书签工具栏。img.src 可能只是一个占位符,或者图片数据还不完整,导致嵌入到 HTML 里的 Base64 数据是残缺的。javascript:(async function(){
// 1. 滚动到底部,触发加载
window.scrollTo(0, document.body.scrollHeight);
await new Promise(r => setTimeout(r, 1000));
// 2. 等待所有图片加载完成
const imgs = document.querySelectorAll('img');
const loadPromises = Array.from(imgs).map(img => {
return new Promise((resolve, reject) => {
if (img.complete) {
resolve();
} else {
img.onload = resolve;
img.onerror = resolve; // 即使图片加载失败,也继续执行
}
});
});
await Promise.all(loadPromises);
console.log('✅ 所有图片已加载完成,开始备份...');
// 3. 开始备份流程
const chatContainer = document.querySelector('main') || document.body;
if (!chatContainer) {
alert('找不到对话区域');
return;
}
const contentClone = chatContainer.cloneNode(true);
// 处理图片为Base64
for (const img of contentClone.querySelectorAll('img')) {
try {
if (!img.src) continue;
const response = await fetch(img.src, { headers: { 'Accept': 'image/*' } });
const blob = await response.blob();
const base64 = await new Promise(resolve => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
img.src = base64;
} catch (e) {
console.error('图片处理失败', e);
}
}
// 生成HTML,头部嵌入KaTeX资源
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>豆包对话备份_${new Date().toISOString().slice(0,10)}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body);"></script>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
img { max-width: 100%; height: auto; }
</style>
</head>
<body>
<h1>豆包对话备份</h1>
<p>导出时间:${new Date().toLocaleString()}</p>
<hr>
${contentClone.innerHTML}
</body>
</html>
`.trim();
const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_带自动等待_${new Date().toISOString().slice(0,10)}.html`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 已导出带自动等待的HTML备份');
})();
<img> 标签,等待它们的 onload 事件触发,确保图片数据已经完全下载到浏览器里。javascript:(async function(){window.scrollTo(0,document.body.scrollHeight),await new Promise(r=>setTimeout(r,1e3));const e=document.querySelectorAll('img'),t=Array.from(e).map(e=>new Promise((t,a)=>e.complete?t():void(e.onload=t,e.onerror=t)));await Promise.all(t),console.log("✅ 所有图片已加载完成,开始备份...");const n=document.querySelector("main")||document.body;if(!n)return void alert("找不到对话区域");const o=n.cloneNode(!0);for(const l of o.querySelectorAll("img")){try{if(!l.src)continue;const e=await fetch(l.src,{headers:{"Accept":"image/*"}}),t=await e.blob(),n=await new Promise(e=>{const n=new FileReader;n.onloadend=()=>e(n.result),n.readAsDataURL(t)});l.src=n}catch(e){console.error("图片处理失败",e)}}const a=document.createElement("a"),d=URL.createObjectURL(new Blob([`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>豆包对话备份_${new Date().toISOString().slice(0,10)}</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css"><script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script><script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body);"></script><style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:800px;margin:0 auto;padding:20px;}img{max-width:100%;height:auto;}</style></head><body><h1>豆包对话备份</h1><p>导出时间:${new Date().toLocaleString()}</p><hr>${o.innerHTML}</body></html>`.trim()],{type:"text/html;charset=utf-8"}));a.href=d,a.download=`豆包对话备份_带自动等待_${new Date().toISOString().slice(0,10)}.html`,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(d),console.log("✅ 已导出带自动等待的HTML备份")})();
javascript:(async function(){
window.scrollTo(0, document.body.scrollHeight);
await new Promise(r => setTimeout(r, 1000));
const imgs = document.querySelectorAll('img');
let loaded = 0;
const total = imgs.length;
console.log(`📊 检测到 ${total} 张图片,开始等待加载...`);
const loadPromises = Array.from(imgs).map(img => {
return new Promise((resolve) => {
if (img.complete) {
loaded++;
console.log(`✅ 已加载 ${loaded}/${total}`);
resolve();
} else {
img.onload = () => {
loaded++;
console.log(`✅ 已加载 ${loaded}/${total}`);
resolve();
};
img.onerror = () => {
loaded++;
console.log(`⚠️ 图片加载失败 ${loaded}/${total}`);
resolve();
};
}
});
});
await Promise.all(loadPromises);
console.log('🎉 所有图片处理完毕,开始生成离线备份...');
const chatContainer = document.querySelector('main') || document.body;
if (!chatContainer) {
alert('找不到对话区域');
return;
}
const contentClone = chatContainer.cloneNode(true);
for (const img of contentClone.querySelectorAll('img')) {
try {
if (!img.src) continue;
const response = await fetch(img.src, { headers: { 'Accept': 'image/*' } });
const blob = await response.blob();
const base64 = await new Promise(resolve => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
img.src = base64;
} catch (e) {
console.error('❌ 图片嵌入失败', e);
}
}
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>豆包对话备份_${new Date().toISOString().slice(0,10)}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body);"></script>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
img { max-width: 100%; height: auto; }
</style>
</head>
<body>
<h1>豆包对话备份</h1>
<p>导出时间:${new Date().toLocaleString()}</p>
<hr>
${contentClone.innerHTML}
</body>
</html>
`.trim();
const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `豆包对话备份_带进度_${new Date().toISOString().slice(0,10)}.html`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('✅ 导出完成!这是一个永久离线的HTML文件。');
})();
javascript:(async function(){window.scrollTo(0,document.body.scrollHeight),await new Promise(r=>setTimeout(r,1e3));const e=document.querySelectorAll("img"),t=Array.from(e),n=e.length;let o=0;console.log(`📊 检测到 ${n} 张图片,开始等待加载...`);const a=Promise.all(t.map(e=>new Promise(l=>{if(e.complete){o++,console.log(`✅ 已加载 ${o}/${n}`),l()}else{e.onload=()=>{o++,console.log(`✅ 已加载 ${o}/${n}`),l()},e.onerror=()=>{o++,console.log(`⚠️ 图片加载失败 ${o}/${n}`),l()}}})));await a,console.log("🎉 所有图片处理完毕,开始生成离线备份...");let d=document.querySelector("main")||document.body;if(!d)return void alert("找不到对话区域");const i=d.cloneNode(!0);for(const r of i.querySelectorAll("img")){try{if(!r.src)continue;const e=await fetch(r.src,{headers:{"Accept":"image/*"}}),t=await e.blob(),n=await new Promise(e=>{const n=new FileReader;n.onloadend=()=>e(n.result),n.readAsDataURL(t)});r.src=n}catch(e){console.error("❌ 图片嵌入失败",e)}}const s=document.createElement("a"),c=URL.createObjectURL(new Blob([`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>豆包对话备份_${new Date().toISOString().slice(0,10)}</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css"><script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script><script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body);"></script><style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:800px;margin:0 auto;padding:20px;}img{max-width:100%;height:auto;}</style></head><body><h1>豆包对话备份</h1><p>导出时间:${new Date().toLocaleString()}</p><hr>${i.innerHTML}</body></html>`.trim()],{type:"text/html;charset=utf-8"}));s.href=c,s.download=`豆包对话备份_带进度_${new Date().toISOString().slice(0,10)}.html`,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(c),console.log("✅ 导出完成!这是一个永久离线的HTML文件。")})();
✅ 已加载 15/117 这样的提示,知道还有多少图片在处理。