怎么用 AI 编程把 248 篇 CSDN 博客搬回本地?
作者:程序员罗尼 | 发布:2026-03-16 | 原文:https://mp.weixin.qq.com/s/AZQnSLCyt721cXzPqI2EXw
本篇是一个「折腾记」:怎么用 Playwright + Node.js + Cursor,把我在 CSDN 上的 248 篇文章,完整、干净地备份成本地 Markdown。
一、为什么要自己「搬家」
简单说三个动机:
- 把内容掌握在自己手里:平台风控、改版、甚至关闭服务都不受我控制,本地 Markdown 永远在。
- 方便二次创作:Markdown 靠
grep和脚本就能各种分析、改写、汇总,很适合做自己的「知识库」。 - 可迁移性:以后想搬到博客园、Notion、Obsidian,甚至自建博客,只要数据结构干净,迁移成本就很低。
目标就是一句话:
把我 CSDN 账号下的所有文章,一篇不少地变成本地
.md文件,文件名有序、可恢复、可重复运行。
二、第一步:最朴素的 Playwright 爬虫
技术栈:
- Node.js + npm
- Playwright:模拟浏览器,打开页面、跑 JS
- Turndown:把 HTML 正文转成 Markdown
第一版脚本只做了几件非常直接的事:
- 自动打开自己的博客主页;
- 找出页面上所有指向文章详情的链接;
- 对每个链接,自动打开详情页,提取正文内容,转成 Markdown,存成一个独立文件。
这一版在「少量测试文章」上跑得很顺,但一上量,问题就来了。
三、第一次撞墙:403 + 安全验证
当我开始批量抓时,发现:
- 主页和文章页时不时返回错误提示;
- 页面被「安全验证」界面替代,让你点验证码、做人机验证;
- 脚本抓到的页面里根本没有正文,只有各种提示信息。
对应的改进有几层:
1. 伪装成「正常浏览器」
- 使用和日常浏览更加接近的浏览器标识和语言设置;
- 改用有界面模式,让页面真实地在屏幕上打开。
2. 把「登录 + 安全验证」交给人来做
脚本逻辑里专门加了一步:
- 打开个人主页
- 终端提示:「请在弹出的浏览器中登录 CSDN,登录完成后按回车继续…」
- 后续所有抓取都在这同一个登录会话下进行
这样,最敏感的那一步交给人去搞,后面脚本只重复「打开页面 → 取内容」。
3. 加「限速」防封
- 每篇之间随机
sleep 1–3 秒 - 后面又扩展成分批抓:每批 N 篇,中间大休息 2–3 分钟
这一轮之后,稳定性提升很多,试验了 10 篇,没有再被拦截,但还是觉得可以再安全一点,于是开始考虑用「官方 API + 浏览器」的组合拳。
四、发现列表接口:先拿清单,再抓正文
在翻各种前端请求时,我发现了一个给页面用的文章列表接口,可以按页返回账号下的文章信息(包括标题、简介、文章地址等)。
于是我把主逻辑改成了两段式:
- 列表段:通过这个接口分页拉,拿到账号下的全部文章清单(我这边大概是两百多篇);
- 正文段:对每篇文章的详情地址,用浏览器打开,按前一节的方式提正文,再转成 Markdown。
同时,我加了一些「工程化」的保护措施:
- 可以只抓前 N 篇,便于测试和回归;
- 可以从某一篇之后开始抓,避免重复;
- 可以按「每批多少篇、批间休息多久」来控节奏;
- 接口调用加了简单的重试逻辑,网络一抖动就退避几秒再来。
这一版在一开始跑得很顺,我也确实抓下来了七十多篇。
但很快又踩到一条更隐蔽的风险策略:从某个阈值之后,后面的文章开始陆续访问不到,接口依然能给出列表,但详情页会被挡住,等于「列表有了,正文拿不到」,整套方案又不够稳了。
五、更加「人类」的版本:列表页 + 新 tab 抓取
为了进一步贴近人工行为,我又写了一个脚本 scrape-csdn-by-list.js,流程是这样的:
- 打开列表页,等我手动登录
- 打开个人主页
- 等我在浏览器手动登录、完成可能的人机验证,按回车继续
- 慢慢往下滚,直到页面把能加载出来的文章都展现出来
- 不再翻传统意义上的「下一页」,而是像平时刷信息流那样,一直往下滑;
- 每次滚动后稍微停一停,让页面有时间把更多文章加载出来;
- 当连续几次滚动后文章数量都不再变化时,就认为这一屏已经刷到底了。
- 从页面里收集所有文章链接
- 把当前页面里属于自己账号的文章链接全部提取出来;
- 做一次简单的去重、过滤,只保留真正需要备份的那一批;
- 记录「上一次抓到的最后一个 ID」,本次只从它之后开始。
- 逐条「点开」文章:新标签页 → 抓 → 等 → 关掉
-
对每一篇文章:
-
先随机等 1–3 秒,再打开一个新标签页;
-
在新标签页里等正文加载出来,提取内容;
-
再随机等 1–3 秒,把这个标签页关掉;
-
然后继续下一篇。
整个行为模式非常接近一个真实的人在浏览自己的文章:下拉、点开一篇、看一会儿、关掉、再点下一篇。
不过实际跑下来,这一步也遇到一个问题:
- 人用浏览器访问时,看上去是可以无限下拉的列表;
- 但脚本实际执行的时候,看到的还是按页切分的视图,行为不一样。
所以光靠「滚动 + 列表页」依然拿不到真正的全量文章清单,于是我才有了下一步的兜底方案。
六、兜底:保存后台页面,离线解析出 248 篇
为了彻底搞清楚自己到底有多少篇文章,我选择了一个更「笨但靠谱」的做法:
- 用浏览器打开后台的文章管理页,把整页完整保存成一个本地 HTML 文件;
- 写一段小程序,专门去读这份 HTML:
- 把里面所有指向文章详情的链接和文章 ID 找出来;
- 做一遍去重、排序,最终生成一份纯文本的「文章 ID 清单」。
这一步的结果很惊喜:从离线 HTML 里一共解析出 248 篇文章,甚至比线上接口拿到的数量还多一些。
这也意味着:就算将来线上接口完全不可用,只要还有这份 HTML,我依然知道自己所有文章的 ID 列表。
七、最终形态:单篇小程序 + 逐条「点名」抓取
拿到完整的 ID 列表之后,我做了最后一次拆解,把任务分成两个非常简单的小模块。
1. 「只抓一篇」的小程序
这个小程序只做一件事:根据文章 ID 抓一篇文章,并保证文件命名有序。
它会:
- 从一个状态文件里读出「下一个编号」(没有就从 1 开始);
- 拼出这篇文章的详情地址;
- 用浏览器自动打开页面,提取正文内容;
- 保存成
编号-标题-文章ID.md这样的 Markdown 文件(比如12-某篇文章标题-12345678.md); - 在文件头写入标题、原文地址、ID、编号等元信息;
- 把「下一个编号」加 1,方便下一篇接着用。
如果发现本地已经有这个 ID 对应的文件,就直接跳过,支持中断后继续跑。
2. 「负责读一行、调一次」的小助手
第二个小工具就像一个勤勤恳恳的助手:
- 从那份 ID 清单里,逐行读出每个文章 ID;
- 每读到一行,就执行一次「只抓一篇」的小程序;
- 打印当前进度、简单日志;
- 如果某一篇失败,就间隔一段时间后自动重试几次;
- 成功抓完一篇后,再随机停几秒,防止触发新的风控。
这样一来,我既实现了「脚本一次只抓一篇、但可以连续跑完两百多篇」,又不用真的人肉敲两百多行命令。
到这里,整个「从平台到本地 Markdown」的链路就闭合了:
- 可以通过登录 + 轻微的人工参与,绕过各种安全验证;
- 可以用接口和页面拿到「尽可能完整」的文章清单;
- 可以用离线 HTML 兜底,确保文章 ID 不会遗漏;
- 最终用「单篇小程序 + 行为小助手」,稳稳地把两百多篇文章一篇篇落到本地。
八、最后的成果与反思
成果:
- 全部 248 篇 CSDN 文章,都有对应的
.md文件: - 文件名:
编号-标题-文章ID.md - 统一的 YAML front matter
- 所有脚本都是可重复运行的:中间崩了、网络断了、验证不过,都可以重启继续,已有的文件不会被重复抓。
几个小反思:
- 对内容拥有掌控权很重要: 平台随时可能变,但 Markdown 文件和脚本在手,迁移自由度就大得多。
- 自动化不等于「全自动」: 在有 WAF(Web 应用防火墙)的网站上,适度引入人工步骤(登录 + 一次性验证)反而能让自动化更稳定。 登录这一步其实本来也不太适合追求「全自动」。
- 把任务拆成很多小脚本,是一种工程思维: 从「一键抓所有」到「API 列表 + DOM + 本地 HTML + 单篇脚本」,每一块都职责单一、可测试、可替换,这样系统的韧性更强。
- 重复使用的工具,才是最值钱的代码: 现在这些脚本已经变成一套「通用的 CSDN 文章导出工具」,以后如果我想再备份别的账号、或者做一些统计分析,只要稍微调整参数就行了。
如果你也在 CSDN、知乎、简书写了很多年,也许可以考虑给自己的内容做一份这样的「底层备份」——不是为了反抗平台,而是为了给自己多一层自由。
最后补一句,这次折腾里,绝大部分体力活其实都是交给 Cursor 来做的:
我负责提想法、定大方向,遇到风控、脚本挂掉的时候,主要是跟它一起和错误日志「对线」、反复改 bug。
这次的体验也让我觉得,Playwright 这类自动化工具配合 AI,其实打开了很多新思路——以后不只是备份博客,很多「需要人肉刷页面」的事情,都可以考虑用「浏览器自动化 + AI」来尝试更多新玩法。
Let's play with AI
- 本文采用「人言兑.md」自动排版,主题: 山吹 -