将WordPress导出的xml转换为Markdown
话说一下狠心将自己的博客,从WordPress转到了Hugo,在此之前,只备份了网站的数据库文件和从后台导出的xml文件,也没想着要先在WordPress装插件把文章转为Markdown。重装VPS后才发现后悔已经晚了……主要是不想那么麻烦,重新部署一套环境导入数据,再通过插件导出。
背景
平常为了方便多端记录,一直都是在OneNote里写,等整理好了发布到博客上,没有Markdown文件。那就得想办法把导出的xml文件转换成markdown。
网上搜了许多,这里列两个搜到的文章中提及较多的:
- 找到一个python写的
ExitWP
工具,下载到本地一看还是python2的代码,据说是有人改成了python3的,但是没找到,还是不用了。 - 找到一个是nodejs下的
wordpress-to-markdown
工具,但是转换出来的不仅仅是文章,还有其他的,感觉好乱,而且转换后的markdown文件,我还得手工去修改Front Matter,还是算了吧。
没办法,那就自己整!
需求
- 批量转换,只转换
post_type
为post
,忽略attachment
、page
、nav_menu_item
等 - 不仅仅要文章标题和内容,还需要文章的发布日期、修改日期、别名(post_name)、分类、标签等
- 文章markdown文件中的Front Matter部分需要按照自己的模板自动生成,避免手工修改
- 文件名命名为
发布日期
+文章标题
的格式
开整
解析xml文件首先想到的是自己常用语言python下有XML标准库。到python文档中大致了解了一下,然后开整,遇到问题搜索吧。
解析xml文件
WordPress导出的xml文件,主体内容是在<item><\item>
标签中,首先将这部分内容提取出来。
from xml.dom.minidom import parse
with parse('wordpress.xml') as dom:
elements = dom.documentElement
items = elements.getElementsByTagName('item')
判断wp:post_type
标签的内容是不是post
,筛选出文章。对文章提取所需要的信息。这里为了方便后续数据的管理,我将单篇文章的信息构造成字典。
if item.getElementsByTagName('wp:post_type')[0].childNodes[0].data == 'post':
post = {
"title": item.getElementsByTagName('title')[0].childNodes[0].data,
"slug": item.getElementsByTagName('wp:post_name')[0].childNodes[0].data,
"date": item.getElementsByTagName('wp:post_date')[0].childNodes[0].data,
"lastmod": item.getElementsByTagName('wp:post_modified')[0].childNodes[0].data,
"content": item.getElementsByTagName('content:encoded')[0].childNodes[0].data,
"categories": [],
"tags": []
}
这里将默认的categories
和tags
键赋值为空列表,主要是因为WordPress导出的xml中分类和标签信息用的都是<category>
来存储,通过domain
属性来区分,而且分类和标签可能会有多个值,所以这里需要单独处理一下。
if item.getElementsByTagName('category'):
for cat in item.getElementsByTagName('category'):
if cat.getAttribute("domain") == 'category':
post['categories'].append(cat.childNodes[0].data)
elif cat.getAttribute("domain") == 'post_tag':
post['tags'].append(cat.childNodes[0].data)
以上代码就把单篇文章的信息解析出来存储到了字典中,也可以将所有文章的字典写入到列表中,方便后续的批量操作。
构造内容模板
根据自己的Front Matter构造写入内容,可以根据自己的需要修改。
text = f"""---
title: "{post['title']}"
slug: "{post['slug']}"
date: {post['date'].replace(' ', 'T') + '+08:00'}
lastmod: {post['lastmod'].replace(' ', 'T') + '+08:00'}
keywords: ""
description: ""\n
categories: {str(post['categories']).replace("'", '"')}
tags: {str(post['tags']).replace("'", '"')}
featuredImage: ""
toc: false
---\n
{post['content']}"""
这里对于分类和标签的再次加工是强迫症的需求,python中字符串列表中的字符串是单引号,比如['AAA', 'BBB', 'CCC']
,写入文件后也是这种样子,与Front Matter中其他字段都是""
有些格格不入,所以这里将单引号替换成双引号,最终输出为["AAA", "BBB", "CCC"]
。
写入文件
调整好格式后,接下来就是写入markdown文件,先按照需求生成文件名。
filename = f"{post['date'][:10].replace('-', '')}-{post['title']}.md"
再结合前面的文章信息列表,来个循环,搞定!
for post in posts:
with open(os.path.join(path, filename), encoding='utf-8', mode='a') as f:
f.write(text)
收工
本次折腾基本满足了我的需求,目前还有些不够完美的地方,就是文章中的链接、图片地址没有做处理,考虑到我的博客的文章中配图较少,发布前稍作手工调整就好。
不知道朋友们有没有碰到跟我类似的情况,这里将代码重构了一下,完善了代码的注释和函数的参数说明,给有需要的朋友,不满足需求的,也可以自行修改。
完整代码如下:
from xml.dom.minidom import parse
import os
class XmlToMarkdown:
"""一个xml转markdown的小工具
解析wordpress导出的xml文件, 并将其中的文章批量转换成markdown文件
Args
----
file : str
指定从wordpress导出的xml文件
"""
def __init__(self, file: str) -> None:
self.file = file
def _read_wordpress_xml(self) -> list:
"""解析wordpress导出的xml文件, 返回解析出的数据
"""
posts = []
# 解析 wordpress 导出的 xml 文件,并将内容写入字典
with parse(self.file) as dom:
elements = dom.documentElement
items = elements.getElementsByTagName('item')
for item in items:
# 只解析 post_type 为 post 节点,即只解析文章,过滤掉附件、菜单等
if item.getElementsByTagName('wp:post_type')[0].childNodes[0].data == 'post':
try:
# 单篇文章的内容
post = {
"title": item.getElementsByTagName('title')[0].childNodes[0].data,
"slug": item.getElementsByTagName('wp:post_name')[0].childNodes[0].data,
"date": item.getElementsByTagName('wp:post_date')[0].childNodes[0].data,
"lastmod": item.getElementsByTagName('wp:post_modified')[0].childNodes[0].data,
"content": item.getElementsByTagName('content:encoded')[0].childNodes[0].data,
"categories": [],
"tags": []
}
# 分类和标签的 Tag 都是 category, 而且可能有多条,此处做一个循环和判断
if item.getElementsByTagName('category'):
for cat in item.getElementsByTagName('category'):
if cat.getAttribute("domain") == 'category':
post['categories'].append(cat.childNodes[0].data)
elif cat.getAttribute("domain") == 'post_tag':
post['tags'].append(cat.childNodes[0].data)
# 单篇文章字典追加到文章列表中
posts.append(post)
except:
pass
print(f"一共解析了{len(posts)}篇文章")
return posts
def to_md(self, path: str = None) -> None:
"""将解析出的数据写入markdown文件, 命名为`文章日期+文章名称.md`
输出到指定位置或当前目录下的`output`文件夹中
Args
----
path : str
指定输出文件的位置, 不指定默认为当前所在的目录
"""
data = self._read_wordpress_xml()
# 不指定 path, 则默认为当前文件所在的目录
if path:
path = path + "\\output"
else:
path = os.getcwd() + "\\output"
# 创建 output 文件夹
if not os.path.exists(path):
os.mkdir(path)
for post in data:
# 定义 markdown 文件名
filename = f"{post['date'][:10].replace('-', '')}-{post['title']}.md"
with open(os.path.join(path, filename), encoding='utf-8', mode='a') as f:
# 根据自己的文章模板构造写入内容
text = f"""---
title: "{post['title']}"
slug: "{post['slug']}"
date: {post['date'].replace(' ', 'T') + '+08:00'}
lastmod: {post['lastmod'].replace(' ', 'T') + '+08:00'}
keywords: ""
description: ""\n
categories: {str(post['categories']).replace("'", '"')}
tags: {str(post['tags']).replace("'", '"')}
featuredImage: ""
toc: false
---\n
{post['content']}"""
f.write(text)
print('转换完成!')
if __name__ == '__main__':
file = 'WordPress.xml'
tool = XmlToMarkdown(file)
tool.to_md()
查看:GitHub
- 本文链接:http://notesth.com/wordpress-xml-to-markdown.html
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
我还是喜欢wordpress
wordpress是主流,但搬瓦工的小主机实在是伤不起,主要是平时折腾也比较少,先这样吧,说不定哪一天又搬回去了😂