一、为什么要做这个工具?
平时写技术博客,我比较习惯使用 Typora 编写 Markdown。
这种方式有一个很大的优点:可以在本地管理文章,图片也可以放在项目目录下,通过相对路径引用。
例如:
markdown
# Spring Boot 入门
这是我的第一篇文章。


但是,当我们准备把文章发布到 CSDN 时,就会遇到一个比较麻烦的问题:
Markdown 正文可以直接复制过去,但是本地图片路径并不能直接在 CSDN 上正常显示。
传统的处理方式通常是:
- 打开 CSDN 编辑器。
- 复制 Markdown 正文。
- 找到文章中的本地图片。
- 逐张上传图片。
- 获取图片地址。
- 手动替换 Markdown 中的图片路径。
如果一篇文章有几十张图片,这个过程就会非常繁琐。
所以我开发了一个小工具:CSDN Markdown 图片助手(CSDN-MD-Helper)。
它的目标很简单:
把本地 Markdown 文章导入 CSDN,并自动完成本地图片上传和链接替换。
二、工具实现了哪些功能?
目前主要实现了以下功能:
| 功能 | 说明 |
|---|---|
| Markdown 导入 | 将本地 Markdown 正文导入 CSDN |
| 图片自动上传 | 自动上传文章中的本地图片 |
| 图片链接替换 | 将本地路径替换为 CSDN 图片地址 |
| 图片去重 | 同一个本地文件被多次引用时只上传一次 |
| 断点续处理 | 上传失败后可以继续处理剩余图片 |
| 正文备份 | 处理前可以备份编辑器中的原始正文 |
| 正文安全保护 | 检测用户操作和正文变化,避免覆盖修改 |
| 处理日志 | 实时展示图片上传进度和处理结果 |
图片识别方面,支持:
- 普通 Markdown 图片:
 - HTML 图片:
<img src="图片路径"> - Markdown 引用式图片。
- 包含中文、空格和括号的路径。
- 相对路径、绝对路径以及本地
file:地址。
另外,代码块、行内代码中的图片示例不会被当作真实图片上传,远程图片和 data: 图片也会跳过。
三、整体架构设计
项目使用 Python 开发,是一个 Windows 桌面应用。
主要分为三个部分:
- app.py:负责桌面界面、用户交互、任务调度。
- csdn_browser.py:负责浏览器管理、CSDN 编辑器操作、图片上传和正文处理。
- md_images.py:负责 Markdown 图片解析、本地路径识别以及图片链接替换。
整体架构如下:
#mermaid-svg-gczxGSjz0eIcQYQD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gczxGSjz0eIcQYQD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gczxGSjz0eIcQYQD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gczxGSjz0eIcQYQD .error-icon{fill:#552222;}#mermaid-svg-gczxGSjz0eIcQYQD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gczxGSjz0eIcQYQD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gczxGSjz0eIcQYQD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gczxGSjz0eIcQYQD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gczxGSjz0eIcQYQD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gczxGSjz0eIcQYQD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gczxGSjz0eIcQYQD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gczxGSjz0eIcQYQD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gczxGSjz0eIcQYQD .marker.cross{stroke:#333333;}#mermaid-svg-gczxGSjz0eIcQYQD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gczxGSjz0eIcQYQD p{margin:0;}#mermaid-svg-gczxGSjz0eIcQYQD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gczxGSjz0eIcQYQD .cluster-label text{fill:#333;}#mermaid-svg-gczxGSjz0eIcQYQD .cluster-label span{color:#333;}#mermaid-svg-gczxGSjz0eIcQYQD .cluster-label span p{background-color:transparent;}#mermaid-svg-gczxGSjz0eIcQYQD .label text,#mermaid-svg-gczxGSjz0eIcQYQD span{fill:#333;color:#333;}#mermaid-svg-gczxGSjz0eIcQYQD .node rect,#mermaid-svg-gczxGSjz0eIcQYQD .node circle,#mermaid-svg-gczxGSjz0eIcQYQD .node ellipse,#mermaid-svg-gczxGSjz0eIcQYQD .node polygon,#mermaid-svg-gczxGSjz0eIcQYQD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gczxGSjz0eIcQYQD .rough-node .label text,#mermaid-svg-gczxGSjz0eIcQYQD .node .label text,#mermaid-svg-gczxGSjz0eIcQYQD .image-shape .label,#mermaid-svg-gczxGSjz0eIcQYQD .icon-shape .label{text-anchor:middle;}#mermaid-svg-gczxGSjz0eIcQYQD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gczxGSjz0eIcQYQD .rough-node .label,#mermaid-svg-gczxGSjz0eIcQYQD .node .label,#mermaid-svg-gczxGSjz0eIcQYQD .image-shape .label,#mermaid-svg-gczxGSjz0eIcQYQD .icon-shape .label{text-align:center;}#mermaid-svg-gczxGSjz0eIcQYQD .node.clickable{cursor:pointer;}#mermaid-svg-gczxGSjz0eIcQYQD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gczxGSjz0eIcQYQD .arrowheadPath{fill:#333333;}#mermaid-svg-gczxGSjz0eIcQYQD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gczxGSjz0eIcQYQD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gczxGSjz0eIcQYQD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gczxGSjz0eIcQYQD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gczxGSjz0eIcQYQD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gczxGSjz0eIcQYQD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gczxGSjz0eIcQYQD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gczxGSjz0eIcQYQD .cluster text{fill:#333;}#mermaid-svg-gczxGSjz0eIcQYQD .cluster span{color:#333;}#mermaid-svg-gczxGSjz0eIcQYQD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gczxGSjz0eIcQYQD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gczxGSjz0eIcQYQD rect.text{fill:none;stroke-width:0;}#mermaid-svg-gczxGSjz0eIcQYQD .icon-shape,#mermaid-svg-gczxGSjz0eIcQYQD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gczxGSjz0eIcQYQD .icon-shape p,#mermaid-svg-gczxGSjz0eIcQYQD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gczxGSjz0eIcQYQD .icon-shape .label rect,#mermaid-svg-gczxGSjz0eIcQYQD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gczxGSjz0eIcQYQD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gczxGSjz0eIcQYQD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gczxGSjz0eIcQYQD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 任务与事件
Tkinter 桌面界面
BrowserWorker 后台线程
Queue 消息队列
csdn_browser.py
md_images.py
Playwright
Edge / Chrome
CSDN Markdown 编辑器
这里有一个值得注意的设计:
浏览器操作没有直接放在 Tkinter 主线程中执行,而是交给 BrowserWorker 后台线程处理。
原因是浏览器操作涉及页面加载、网络请求、图片上传等耗时任务。如果直接在 GUI 主线程执行,界面很容易出现卡顿甚至无响应。
因此,我使用两个 Queue:
commands:GUI 向后台线程发送任务。events:后台线程向 GUI 发送进度、日志和处理结果。
同时,所有 Playwright 操作都由 BrowserWorker 所在线程执行,避免多个线程同时操作浏览器对象。
四、核心实现:如何自动上传本地图片?
这是整个项目最核心的部分。
我没有自己重新实现 CSDN 的图片上传协议,而是利用 Playwright 操作浏览器,让 CSDN 官方 Markdown 编辑器完成图片上传。
整体流程如下:
#mermaid-svg-POm93rmkaDGL6EHS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-POm93rmkaDGL6EHS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-POm93rmkaDGL6EHS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-POm93rmkaDGL6EHS .error-icon{fill:#552222;}#mermaid-svg-POm93rmkaDGL6EHS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-POm93rmkaDGL6EHS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-POm93rmkaDGL6EHS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-POm93rmkaDGL6EHS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-POm93rmkaDGL6EHS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-POm93rmkaDGL6EHS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-POm93rmkaDGL6EHS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-POm93rmkaDGL6EHS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-POm93rmkaDGL6EHS .marker.cross{stroke:#333333;}#mermaid-svg-POm93rmkaDGL6EHS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-POm93rmkaDGL6EHS p{margin:0;}#mermaid-svg-POm93rmkaDGL6EHS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-POm93rmkaDGL6EHS .cluster-label text{fill:#333;}#mermaid-svg-POm93rmkaDGL6EHS .cluster-label span{color:#333;}#mermaid-svg-POm93rmkaDGL6EHS .cluster-label span p{background-color:transparent;}#mermaid-svg-POm93rmkaDGL6EHS .label text,#mermaid-svg-POm93rmkaDGL6EHS span{fill:#333;color:#333;}#mermaid-svg-POm93rmkaDGL6EHS .node rect,#mermaid-svg-POm93rmkaDGL6EHS .node circle,#mermaid-svg-POm93rmkaDGL6EHS .node ellipse,#mermaid-svg-POm93rmkaDGL6EHS .node polygon,#mermaid-svg-POm93rmkaDGL6EHS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-POm93rmkaDGL6EHS .rough-node .label text,#mermaid-svg-POm93rmkaDGL6EHS .node .label text,#mermaid-svg-POm93rmkaDGL6EHS .image-shape .label,#mermaid-svg-POm93rmkaDGL6EHS .icon-shape .label{text-anchor:middle;}#mermaid-svg-POm93rmkaDGL6EHS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-POm93rmkaDGL6EHS .rough-node .label,#mermaid-svg-POm93rmkaDGL6EHS .node .label,#mermaid-svg-POm93rmkaDGL6EHS .image-shape .label,#mermaid-svg-POm93rmkaDGL6EHS .icon-shape .label{text-align:center;}#mermaid-svg-POm93rmkaDGL6EHS .node.clickable{cursor:pointer;}#mermaid-svg-POm93rmkaDGL6EHS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-POm93rmkaDGL6EHS .arrowheadPath{fill:#333333;}#mermaid-svg-POm93rmkaDGL6EHS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-POm93rmkaDGL6EHS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-POm93rmkaDGL6EHS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-POm93rmkaDGL6EHS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-POm93rmkaDGL6EHS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-POm93rmkaDGL6EHS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-POm93rmkaDGL6EHS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-POm93rmkaDGL6EHS .cluster text{fill:#333;}#mermaid-svg-POm93rmkaDGL6EHS .cluster span{color:#333;}#mermaid-svg-POm93rmkaDGL6EHS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-POm93rmkaDGL6EHS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-POm93rmkaDGL6EHS rect.text{fill:none;stroke-width:0;}#mermaid-svg-POm93rmkaDGL6EHS .icon-shape,#mermaid-svg-POm93rmkaDGL6EHS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-POm93rmkaDGL6EHS .icon-shape p,#mermaid-svg-POm93rmkaDGL6EHS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-POm93rmkaDGL6EHS .icon-shape .label rect,#mermaid-svg-POm93rmkaDGL6EHS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-POm93rmkaDGL6EHS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-POm93rmkaDGL6EHS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-POm93rmkaDGL6EHS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
读取 Markdown 正文
解析图片引用
解析本地路径并检查文件
临时保护未上传的图片标记
粘贴一张本地图片
CSDN 编辑器触发图片上传
监听网络响应并提取 imageUrl
校验图片地址
替换正文中的图片路径
还有未处理的图片?
完成,用户检查文章
1. 首先解析 Markdown 图片
项目通过 md_images.py 识别文章中的图片引用。
例如:
markdown

解析后会得到图片引用信息,包括:
- 图片在原始 Markdown 中的位置。
- 图片原始路径。
- 图片描述。
- 图片引用类型。
为什么要记录原文位置?
因为后面可能需要替换多张图片。如果每替换一次就直接修改字符串,前面内容长度发生变化后,后续图片的位置也会发生变化。
所以这里采用记录原始文本位置、最后根据原文和替换结果重新构建正文的方式,避免位置偏移问题。
2. 处理相对路径
假设文章目录结构如下:
text
MyBlog/
├── article.md
└── images/
├── test.png
└── result.png
Markdown 中引用:
markdown

工具会以 article.md 所在目录为基准解析图片路径,而不是以程序启动目录为基准。
这样无论从哪个目录启动程序,都可以正确找到图片。
3. 为什么要临时保护图片标记?
这里是一个比较有意思的细节。
如果直接把下面的正文写入 CSDN:
markdown

CSDN 编辑器可能会提前处理本地图片路径,将其转换为图片加载失败的占位内容。
这样后续就很难再从原始正文中找到正确的本地路径。
因此,我设计了一个临时保护机制:
在尚未上传的图片标记中插入不可见占位字符,让编辑器暂时不要识别这些图片。
等图片上传成功,拿到 CSDN 图片地址之后,再恢复图片标记并替换路径。
这样就可以避免编辑器提前破坏本地图片引用。
4. 利用 Playwright 模拟图片粘贴
这是整个上传流程的关键。
程序首先读取本地图片文件,将图片字节编码为 Base64,然后在浏览器页面中构造:
FileDataTransferClipboardEvent
最后向 CSDN Markdown 编辑区域派发 paste 事件。
核心逻辑可以简化为:
javascript
const file = new File(
[bytes],
data.name,
{ type: data.mime }
);
const transfer = new DataTransfer();
transfer.items.add(file);
editor.dispatchEvent(
new ClipboardEvent('paste', {
clipboardData: transfer,
bubbles: true,
cancelable: true
})
);
这样 CSDN 编辑器就会按照自身的图片粘贴流程处理文件。
5. 监听上传结果并替换链接
图片粘贴之后,程序通过 Playwright 监听页面的网络请求和响应。
获取响应后,需要进行几项检查:
- 请求是否属于本轮图片上传。
- 响应是否成功。
- 响应内容是否符合预期 JSON 结构。
- 是否存在
data.imageUrl。 - 图片地址是否使用 HTTPS,且属于允许的 CSDN 图片域名。
拿到有效地址之后,还会继续检查该地址是否真正出现在编辑器正文中。
只有确认上传完成并成功插入正文,才会执行替换。
例如:
上传前:
markdown

上传后:
markdown

如果同一个本地文件在文章中被引用了多次,程序只上传一次,然后将得到的地址应用到所有对应引用。
五、如何避免处理过程中覆盖用户的文章?
自动化工具不仅要能完成任务,还需要考虑误操作问题。
例如,程序正在上传图片,用户突然手动修改了文章。如果程序继续写入,就有可能覆盖用户刚才的修改。
因此,我专门增加了一套正文安全保护机制。
主要包括:
1. 原正文备份
处理前可以将编辑器原始正文备份到本地:
text
%LOCALAPPDATA%\CSDN-MD-Helper\recovery
2. 用户交互检测
通过监听页面的可信键盘、鼠标、粘贴、剪切和拖放事件,判断用户是否在处理期间操作了编辑器。
一旦检测到用户操作,就停止后续处理。
3. 正文快照校验
每次重要写入前后都会读取编辑器正文,与预期内容进行比较。
发现正文发生变化时,不继续覆盖。
4. 写入结果验证
正文写入后,不会直接认为操作成功,而是重新读取正文,等待内容稳定并验证是否与目标一致。
如果写入失败,会尝试恢复上一步正文。
5. 失败后继续处理
假设一篇文章有 10 张图片,前 6 张已经上传成功,第 7 张上传失败。
工具会保留已经完成的图片链接,并尽可能恢复到上一轮正文。
解决图片问题后,可以选择「仅修复图片」,继续处理剩余图片,而不必重新导入整篇文章。
六、桌面应用与工程化处理
除了核心的浏览器自动化,我还处理了一些桌面应用开发中的细节。
- 使用 Tkinter 构建图形界面。
- 使用后台线程和 Queue 实现任务调度。
- 支持 Microsoft Edge 和 Google Chrome。
- 使用独立的持久化浏览器 Profile 保存登录状态。
- 处理 Windows DPI 缩放,避免高分辨率屏幕下界面显示异常。
- 使用 PyInstaller 将程序打包为独立 exe。
项目提供两种运行方式:
源码运行:
powershell
py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\pythonw.exe app.py
打包运行:
双击:
text
dist/CSDN-Markdown-Helper-v6.exe
打包程序包含 Python 运行环境和 Playwright 驱动,但电脑仍需安装 Edge 或 Chrome。
七、如何使用?

界面左侧用于选择文档、打开浏览器和选择处理方式;右侧显示进度和处理日志。窗口较窄时会自动改为上下布局,内容超出窗口时可用滚动条或鼠标滚轮查看。
程序启动时按 Windows 的系统 DPI 绘制,并按缩放比例调整默认窗口和图标大小。使用缩放比例不同的多个显示器时,窗口移到另一块屏幕后仍可能被 Windows 缩放;目前不支持跨屏动态调整 DPI。
- 选择一篇本地
.md或.markdown文件。保留它与图片之间原有的相对目录关系。 - 选择 Edge 或 Chrome,点击"打开 CSDN"。浏览器使用助手自己的独立配置目录,首次请自行登录;不会读取现有浏览器的登录资料。
浏览器关闭后可以重新打开或选择另一种浏览器。若当前助手浏览器仍打开,切换时请先保存文章并确认关闭。 - 在助手打开的浏览器中打开或新建一篇 Markdown 编辑器文章,并关闭助手浏览器中其他 Markdown 编辑标签页。
- 回到助手,选择处理方式:
- 导入所选 Markdown:用所选本地文档替换正文。如果当前已有正文,工具会先询问。
- 仅修复编辑器当前正文中的本地图片:保留你在 CSDN 正文中已有的文字修改,只替换可识别的本地图片引用。
- 处理前备份已有正文:默认勾选,可自行取消;例如编辑器中只有 CSDN 示例文字时可以不备份。
- 点击"开始处理正文与图片"。界面会显示已处理图片的进度;处理期间不要编辑 CSDN 正文。
- 完成后在 CSDN 中检查文章预览,自行保存草稿或发布。
需要注意:工具不会自动点击 CSDN 的保存草稿或发布按钮,最终仍然由用户检查和确认。
八、项目的一些不足
- 目前这个工具主要面向 Windows 环境。
- 另外,图片上传依赖 CSDN 官方网页编辑器的行为和页面结构。如果 CSDN 更新编辑器DOM、粘贴逻辑或者上传响应格式,可能需要调整对应的自动化代码。
- 浏览器登录信息保存在助手自己的独立配置目录中,不会读取用户平时使用的浏览器登录资料。这个目录也不要随意分享。
- 用了PyInstaller 的单文件模式。双击时,exe会把运行所需的一些文件解压到系统临时目录,再启动程序;正常退出后,临时文件会被清理。可能会导致启动略慢。
九、项目地址
GitHub: https://github.com/zjyOverQuartzer/CSDN-MD-Helper
核心代码:
app.py:桌面界面和后台任务调度。csdn_browser.py:浏览器自动化和文章处理流程。md_images.py:Markdown 图片解析和路径处理。tests/:图片解析、浏览器流程和界面测试。
其中,浏览器流程测试使用本地模拟编辑器和模拟上传服务,不需要连接真实 CSDN 账号。
最后
如果这个工具有什么bug或者好的建议,欢迎在评论区指出或者在github上提出一个issue,或者你也可以提交一个pr。
我还打算适配更多的博客平台,比如公众号,稀土掘金等,已经一键发布到多个平台中的功能,像CSDN 的一键发布还要会员。