抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

一、为什么要迁移博客

我本来以为我已经过了折腾博客的年纪了,没想到最近还是把博客从WordPress迁移到了Hexo,主要有以下几个原因:

1、People Die, But Long Live GitHub

最近,我用了10多年的PHP主机商宣布停运了,突然感到有些失落,我已经经历过太多网站的关停了,包括在我博客留下足迹的众多个人博客,现在还能打开的博客寥寥无几。

我就在思考,如果我有一天去世了,我的空间到期后也会被关闭,域名到期也会被回收,所以我希望我的博客在我大限到来之后能够再运行久一点。

因此我打算将WordPress博客迁移到静态博客Hexo,因为静态博客可以托管在GitHub Pages里,如果GitHub不倒闭的话,我的博客也不会关闭。

人终有一死,但GitHub永存不朽。

如果正在看这篇文章的你所在的年份是2121年之后的话,说明100年后我的博客还存在,至少证明我现在的决定是正确的。

2、使用Markdown写作

我一直幻想WordPress的编辑器有一天能够支持Markdown,可惜每次更新后都没看到希望,而且编辑器也变得越来越非主流。

我使用了第三方的Markdown插件,但是插件有时会把Markdown格式的文章又转换成html编码,导致文章很难编辑。

Hexo是为程序员而生的,只需用Markdown格式编写文章,Hexo框架会自动渲染,非常丝滑优雅。

3、静态博客的评论系统

我很久以前就想使用静态博客了,但是考虑到静态博客无法进行评论,不能将WordPress的评论迁移过去,所以还是放弃了。

经过多年的发展,市面上已经涌现出很多优秀的开源评论系统,如beaudar, valine, twikoo, waline, minivaline, disqus, disqusjs, gitalk, vssue, livere, isso, hashover等。

经过对比,我选择了使用waline评论系统,官网为 https://waline.js.org

4、GitHub的访问速度

众所周知,国内访问GitHub Pages的速度非常慢,有时甚至无法访问。

不过目前已经有别的网站托管服务商可以使用,比如优秀的Vercel,可以看这篇介绍文章:
《Vercel是什么神仙网站?》

主站可以使用Vercel部署,而GitHub Pages用来作为博客的备份。

二、博客迁移过程踩的坑

博客迁移过程主要参考了这篇文章:《WordPress迁移到Hexo填坑记录》

该文章将博客的评论迁移到valine评论系统,我使用的是waline,因为waline是基于valine开发的,所以迁移的过程大致相同。

1、博客文章的迁移

登录WordPress后台,点击工具 - 导出 - 下载导出的文件,可以得到WordPress.2021-08-15.xml文件,存放到hexo/source/目录下。

使用命令安装文章转换工具:

1
npm install hexo-migrator-wordpress --save

使用以下命令将WordPress文章转成Hexo的文章:

1
hexo migrate wordpress WordPress.2021-08-15.xml --paragraph-fix --import-image --skipduplicate

转换后文章保存在hexo/source/_posts目录下,由于我之前有一半文章是使用富文本编辑器写的,所以转换后Markdown格式有点问题,因此我手动把所有文章都修整了一遍。

2、文章链接的修改

我的WordPress文章的固定链接以前一直使用/%category%/%postname%.html,感觉还是一开始考虑得不够周到,导致现在出现了一点麻烦。

当初使用WordPress建站的时候,我觉得文章链接加上分类目录的话会更加清晰,能够方便分类。但是我大部分文章都会存放在多个分类里,而分类目录是WordPress随机添加的。

Hexo的博客系统使用目录分类比较麻烦,考虑再三,长痛不如短痛,将Hexo的链接去掉了分类目录,只保留了文章名。

这样会导致旧的链接跳进来后找不到文章,所以我先在WordPress做了301重定向,等搜索引擎更新链接后,再开放Hexo博客。

3、评论的迁移

我的WordPress里有1400多条评论,这些评论必须要保留下来。

新的评论系统使用了waline+MySQL部署,所以迁移过程和上面的文章教程类似,用脚本将评论导入到MySQL数据库即可。

但是我发现了那篇教程的脚本代码有bug:有些评论的父级id赋值有误,会导致有些评论加载不出来。

比如A发表了一条评论,B评论了A,这时B的pidrid都是A。
如果C又评论了B,那么C的pid是B,但是rid是A。
rid是评论的根id,如果设置错误的话,C的评论就无法显示出来。

这个bug我已经修复了,文章后面会贴出代码。

4、文章浏览数的迁移

文章的浏览数也是必须保留的,所幸的是,waline也自带了统计文章浏览数的功能。

和评论的迁移一样,也是将浏览数批量保存到MySQL数据库。

5、数据库迁移代码

我使用了macOSMAMP集成环境运行MySQL,再使用Python执行脚本,参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#coding:utf-8

from dataclasses import dataclass
from datetime import datetime, timezone, timedelta
import pymysql

@dataclass
class WPComment:
post_id: int
post_name: str
post_type: str
comment_ID: int
comment_author: str
comment_author_email: str
comment_author_url: str
comment_author_IP: str
comment_date: str
comment_content: str
comment_agent: str
comment_parent: int

@dataclass
class WPCounter:
post_id: int
post_name: str
post_type: str
post_views: int


#要替换的链接
replace_post_name = {
'the-chatty-jokers' : 'math-and-magic-the-chatty-jokers',
'dicing-with-destiny' : 'math-and-magic-dicing-with-destiny',
}


class WPDatabase:
def __init__(self):
self.db = pymysql.connect(
host='localhost',
user='root',
password='',
database='wordpress',
# MaxOS使用MAMP时需要加上unix_socket
# unix_socket='/Applications/MAMP/tmp/mysql/mysql.sock',
)

# 获取评论
def get_comments(self):
sql_comment = '''
SELECT
p.ID,
p.post_name,
p.post_type,
c.comment_ID,
c.comment_author,
c.comment_author_email,
c.comment_author_url,
c.comment_author_IP,
c.comment_date,
c.comment_content,
c.comment_agent,
c.comment_parent
FROM
`wp_comments` AS c,
`wp_posts` AS p
WHERE
c.comment_post_ID = p.ID AND
c.comment_approved != "spam"
ORDER BY
c.comment_ID ASC;
'''
cursor = self.db.cursor()
cursor.execute(sql_comment)
results = cursor.fetchall()

comments = []
for row in results:
wp_comment = WPComment(*row)
wp_comment.comment_content = wp_comment.comment_content.replace("'", "\\'")
wp_comment.comment_author = wp_comment.comment_author.replace("'", "\\'")
if wp_comment.post_name in replace_post_name:
wp_comment.post_name = replace_post_name[wp_comment.post_name]
comments.append(wp_comment)
return comments

# 获取文章浏览量
def get_counters(self):
sql_counter = '''
SELECT
p.ID,
p.post_name,
p.post_type,
c.meta_value
FROM
`wp_postmeta` AS c,
`wp_posts` AS p
WHERE
c.meta_key = "post_views_count" AND
p.post_status = "publish" AND
c.post_id = p.ID
ORDER BY
c.post_id ASC;
'''
cursor = self.db.cursor()
cursor.execute(sql_counter)
results = cursor.fetchall()

counters = []
for row in results:
wp_counter = WPCounter(*row)
if wp_counter.post_name in replace_post_name:
wp_counter.post_name = replace_post_name[wp_counter.post_name]
wp_counter.post_views = int(wp_counter.post_views)
counters.append(wp_counter)
return counters


class WalineDatabase:
def __init__(self):
self.db = pymysql.connect(
host='localhost',
user='root',
password='',
database='waline',
# MaxOS使用MAMP时需要加上unix_socket
# unix_socket='/Applications/MAMP/tmp/mysql/mysql.sock',
)
self.wp_to_waline_id = {}
self.wp_reply_id = {}
self.wp_reply_nickname = {}

# 递归查找评论的根id
def find_root_id(self, child_id):
parent_id = self.wp_reply_id[child_id]
if parent_id == child_id:
return parent_id
else:
return self.find_root_id(parent_id)

# 添加评论
def add_comments(self, wp_comments):
cursor = self.db.cursor()
for wp_comment in wp_comments:
pid = 'NULL'
rid = 'NULL'
comment = ''
if wp_comment.comment_parent == 0:
# 新评论
comment = '<p>%s</p>\n'%(wp_comment.comment_content)
self.wp_reply_id[wp_comment.comment_ID] = wp_comment.comment_ID
else:
# 回复别人的评论
pid = self.wp_to_waline_id[wp_comment.comment_parent]
rid = self.wp_to_waline_id[self.find_root_id(wp_comment.comment_parent)]
reply_name = self.wp_reply_nickname[wp_comment.comment_parent]
comment = '<p><a class="at" href="#%s">@%s</a> , %s</p>\n'%(pid, reply_name, wp_comment.comment_content)
self.wp_reply_id[wp_comment.comment_ID] = wp_comment.comment_parent

url = ''
if wp_comment.post_type == 'page':
url = '/%s/'%(wp_comment.post_name)
else:
url = '/%s.html'%(wp_comment.post_name)

sql_comment = f'''
INSERT INTO `wl_Comment` (`comment`, `insertedAt`, `ip`, `link`, `mail`, `nick`, `pid`, `rid`, `status`, `ua`, `url`)
VALUES ('{comment}', '{wp_comment.comment_date}', '{wp_comment.comment_author_IP}', '{wp_comment.comment_author_url}', '{wp_comment.comment_author_email}', '{wp_comment.comment_author}', {pid}, {rid}, 'approved', '{wp_comment.comment_agent}', '{url}');
'''
cursor.execute(sql_comment)
self.db.commit()

self.wp_to_waline_id[wp_comment.comment_ID] = cursor.lastrowid
self.wp_reply_nickname[wp_comment.comment_ID] = wp_comment.comment_author
print(wp_comment.comment_date, wp_comment.comment_author)

# 添加文章浏览量
def add_counters(self, wp_counters):
cursor = self.db.cursor()
for wp_counter in wp_counters:
if wp_counter.post_views == 0:
continue

url = ''
if wp_counter.post_type == 'page':
url = '/%s/'%(wp_counter.post_name)
else:
url = '/%s.html'%(wp_counter.post_name)

sql_comment = f'''
INSERT INTO `wl_Counter` (`time`, `url`)
VALUES ({wp_counter.post_views}, '{url}');
'''
cursor.execute(sql_comment)
self.db.commit()
print(wp_counter.post_id, wp_counter.post_views)


if __name__ == '__main__':
wp_database = WPDatabase()
waline_database = WalineDatabase()

wp_comments = wp_database.get_comments()
waline_database.add_comments(wp_comments)

wp_counters = wp_database.get_counters()
waline_database.add_counters(wp_counters)

评论