跳到主要内容

Hexo博客同步至CSDN

· 阅读需 11 分钟

使用百度搜索,CSDN上的文章经常排在前面,可是,我并不喜欢在CSDN上写博客。而且,如果使用GitHub Pages搭建个人博客,由于GitHub屏蔽了百度的蜘蛛,百度会抓取失败,网站也就没有索引和流量了。虽然Google对于个人博客的收录比较快,也没那么麻烦,但国内使用百度搜索的还是比较多的。除了考虑不用GitHub Pages搭建,还可以考虑把文章同步到CSDN,使用CSDN来进行导流。

上网一查基本都是CSDN文章导出Markdown到Hexo,却没有Hexo博客同步到CSDN的,在GitHub上也没有找到相关的开源程序,无奈之下,只好自己搞一个了。本文就记录一下其中踩到的坑以及解决方案。

Markdown渲染

在CSDN上创建Markdown文章时,会发送一个POST请求,有markdowncontentcontent字段:前者是Markdown内容,在使用CSDN的Markdown编辑器时会使用这个字段;后者是渲染后的内容,在页面上显示博客会使用这个字段。这两个字段是独立的,也就是在页面上显示博客取决于content,和markdowncontent没有关系,反之亦然。

既然如此,在提交数据时,我们就需要自行渲染Markdown。Markdown的渲染器其实很丰富,所以Markdown渲染似乎是比较简单的。可是,代码块语法高亮呢?

经过观察,发现代码块的标签有prism类:

<pre><code class="prism language-python">
</code></pre>

prism是一个语法高亮的库,是基于JavaScript的。可是,我对JavaScript不是很熟,贸然使用JavaScript搞这个东西会很慢。所以,没办法,那就用Python调用nodejs吧,把要交换的数据写到文件里,这种方式虽然不够优雅,但可以简单地解决这个问题。这里我直接使用标准输入作为nodejs的输入,懒:

const md = require('markdown-it')({
html: true,
});
const prism = require('markdown-it-prism');
const mathjax = require('markdown-it-mathjax');

const input = require('fs').readFileSync('/dev/stdin', 'utf8').trim();
md.use(prism);
md.use(mathjax());
const ret = md.render(input);
console.log(ret);

其中markdown-it用来渲染Markdown,markdown-it-prism用来进行语法高亮,markdown-it-mathjax用来处理公式。

然后供Python调用。由于使用了/dev/stdin,所以与Windows不兼容。在Python中,先创建临时文件,然后把Markdown内容写入,再通过管道传给nodejs作为标准输入,读取结果,也就是Markdown渲染后的HTML。

_, tmp = mkstemp()
with open(tmp, "w") as f:
f.write(markdown)
cmd = "cat '{}' | node {}".format(tmp, self.CONVERT_JS_PATH)
logger.info(cmd)
html = subprocess.check_output(cmd, shell=True).decode("utf-8")
return html

替换图片

我的个人博客使用对象存储来保存图片,并且启用了防盗链,这样可以节约流量。但是,放在CSDN上的文章,我打算将图片传到CSDN,用CSDN站内的图片链接,这样我依然可以对对象存储启用防盗链,也避免CSDN上的流量消耗我对象存储的流量。如果只考虑新增文章,那么没什么问题,但假如我想更新文章,那岂不要重新传一遍图片?如果不重新传图片,那么假如我要更新图片呢?

为此,可以把图片在对象存储的链接存到alt属性里,这样就可以知道是否需要更新图片了。不过,由于对图片链接进行了替换,在后面检查是否需要更新文章时需要做一下处理。

下面是整个过程的流程:

新建文章和更新文章的过程是一样的,在CSDN里它们使用的是相同的API,区别就在于前者没有指定文章id,后者指定了。

在预处理和后处理阶段需要针对图片链接进行调整。在CSDN博客比较预处理时,需要去掉imgsrc属性,并使用alt属性作为图片链接:

<!-- before -->
<img src="https://img-blog.csdnimg.cn/img.jpg" alt="https://muranxuan.top/img.jpg">
<!-- after -->
![](https://muranxuan.top/img.jpg)

而在渲染后处理阶段,检查图片链接是否来自CSDN,如果不是,那就下载并上传图片,把图片的下载链接保存到alt里,以供比较预处理阶段使用。

URL编码

按照上述的过程保存文章到CSDN,会发现+号不见了。由于+号在代码里用的还是比较多的,这一点还是需要处理的。本来还以为是CSDN的BUG,查看提交的Form Data也没有发现什么问题。但是,当点击view source后,就发现了问题所在:

原来这些内容是经过URL编码的呀,遂加上URL编码:

url = "https://mp.csdn.net/mdeditor/saveArticle"
data = {
"title": quote(title),
"markdowncontent": quote(markdowncontent),
"content": quote(content),
"id": id,
"private": private,
"read_need_vip": read_need_vip,
"tags": quote(tags),
"status": status,
"categories": quote(categories),
"channel": channel,
"type": type,
"articleedittype": articleedittype,
"Description": description,
"csrf_token": csrf_token,
}
j = self.sess.post(url, data=data).json()

不过,Description别加URL编码,在查看Form Data时也可以看出Description没有URL编码。如果Description使用了URL编码,在看文章列表时会显示的是URL编码后的内容:

细节处理

比较预处理

由于可以在Markdown里写HTML,所以可以利用HTML注释将文章的一些信息存到Markdown内容里。由于对图片地址进行了替换,因此,在比较预处理阶段需进行处理,否则每次都会是不一致的。

代码块语言

代码块的语言需要规范,否则prism会不识别。首先,需要是小写,例如Python需写为python,然后,不能用符号,例如c#需写为csharp。但是,我的Hexo博客是能识别的,而我又不想改博客Markdown文件。为了CSDN而作调整,假如我以后又想同步到别的平台,又要做调整呢?所以,可以在预处理阶段将其规范化为prism支持的形式。

但是,在使用时发现,C++代码依然是不能语法高亮的,即使语言改为了cpp,prism依然无法解析。不过,改为C语言就可以。然而,C++和C语言的语法毕竟是不一样的,这样做并不完美。然后,通过看源码,发现markdown-it-prism调用了prismjs。在经过一番Debug之后,终于发现prismjs在加载node_modules/prismjs/components/prism-cpp.js时出错,修改一下就好了:

// before
Prism.languages.cpp = Prism.languages.extend('c', {
// after
Prism.languages.cpp = Prism.languages.extend('clike', {

公式渲染

由于习惯使用$来插入行内公式,用$$来插入行间公式,这并不是Mathjax默认的写法,这个可以通过markdown-it-mathjax这个模块来进行调整。

但是,我有的公式写的并不那么规范,因为不同的渲染器表现不一样,所以我只保证我个人博客上的渲染效果正常。例如,对于行内公式,如果公式和$之间有空格,markdown-it-mathjax将不会处理,不过行间公式可以,因此,需要在预处理阶段进行调整。这个可以用一个正则解决,然后配合Front-matter里的mathjax字段进行判断,虽然不100%准确,但基本上是可行的:

re.sub(r'(?<!\$)`?\$\s*(.*?)\s*\$`?(?!\$)', r'$\1$', markdown)

登录

使用用户名密码登录时,看请求的内容里有fkid、loginType、pwdOrVerifyCode、uaToken、userIdentification、webUmidToken,userIdentification和pwdOrVerifyCode分别是用户名和密码,loginType为1时表示使用用户名密码登录,其它几个字段不清楚怎么来的。不过,我发现webUmidToken随便写个什么就可以了,也就是只传userIdentification、pwdOrVerifyCode、loginType、webUmidToken就可以登录了。

但是,登录几次之后就出现了服务器错误的提示。好吧,还是直接用Cookies吧。


由于nodejs从/dev/stdin读取输入,使得程序不支持Windows(不过,这个好解决)。然后,程序里利用了我Hexo博客个性化的一些设置,例如,我的Hexo博客为每篇文章分配一个abbrlink,这样可以让文章链接很短且不重复。所以,程序并不通用。为了让程序变得通用,需要做较多修改,暂时并无打算,所以暂不开源了~


后记:由于博客迁移到 Typecho,这个程序就没啥用了。