背景

在将博客评论系统从 Valine 迁移到 Waline 的过程中,需要将历史评论数据从 LeanCloud 导出并转换为 Waline 支持的格式。

Waline 官方提供了数据迁移助手(https://waline.js.org/migration/tool.html),可以将 LeanCloud/Valine 的数据转换成 Waline 支持的格式。但在实际使用中,官方迁移助手点击转换按钮后无响应,因此我使用 AI 编写了一个 Python 脚本来实现数据转换。

该脚本主要完成以下工作:

  • 读取从 LeanCloud 导出的 Valine 评论数据(JSON 格式)
  • 将字段映射为 Waline 格式(添加 user_idstatusstickylike 等字段)
  • 将 Valine 的表情符号语法(如 :happy::tear:)转换为对应的 emoji
  • 生成符合 Waline 导入规范的 JSON 文件

提示词示例

我给 AI 的提示词如下:

1
2
3
4
我现在要将我博客的评论系统从 valine 切换到 waline 。但这两者的数据格式不一样,你帮我写个代码 test/valine2waline.py 实现这一转换
test/waline_example.json 是 waline 的数据格式模板
test/Comment_20260313_085101.json 是我导出的我当前 valine 评论系统的数据
帮我输出一个文件 test/waline_output.json

之后又追加了一个需求:

1
修改代码,把之前夹在两个 :: 之间的表情符号改成最接近的emoji

代码示例

仅供参考

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
#!/usr/bin/env python3
"""
Valine 到 Waline 评论数据转换脚本
将 Valine (LeanCloud) 导出的评论数据转换为 Waline 格式
"""

import json
import re
from datetime import datetime


# Valine 表情符号到 emoji 的映射表
EMOTION_MAP = {
'whee': '😁',
'no way': '😱',
'clap': '👏',
'amorousness': '😍',
'longing': '🥺',
'love you': '😘',
'happy': '😊',
'tear': '😢',
'punch': '👊',
}


def convert_emotions_to_emoji(text):
"""
将 Valine 的表情符号语法 :emotion: 转换为对应的 emoji
"""
def replace_emotion(match):
emotion_name = match.group(1).strip()
return EMOTION_MAP.get(emotion_name, match.group(0))

# 匹配 :xxx: 格式的表情符号,但排除 URL 中的冒号
# 使用负向后顾断言确保冒号前不是 http 或 https
return re.sub(r'(?<!https)(?<!http):([a-z\s]+):', replace_emotion, text)


def convert_valine_to_waline(valine_data):
"""
将 Valine 格式的评论数据转换为 Waline 格式

主要字段映射:
- objectId: 保持不变,作为评论的唯一标识
- comment: 评论内容,保持不变
- nick: 昵称,保持不变
- mail: 邮箱,保持不变
- link: 网站链接,保持不变
- ua: User Agent,保持不变
- ip: IP地址,保持不变
- url: 评论所在页面URL,保持不变
- pid: 父评论ID,保持不变
- rid: 根评论ID,保持不变
- createdAt/updatedAt/insertedAt: 时间字段,保持不变

新增字段(Waline特有):
- user_id: 用户ID,设为null(匿名用户)
- status: 评论状态,默认为"approved"(已批准)
- sticky: 置顶标记,设为null
- like: 点赞数,设为null
- QQAvatar: Valine特有字段,不转换到Waline
"""

waline_comments = []

for valine_comment in valine_data:
# 转换评论内容中的表情符号
original_comment = valine_comment.get("comment", "")
converted_comment = convert_emotions_to_emoji(original_comment)

waline_comment = {
"user_id": None,
"comment": converted_comment,
"ip": valine_comment.get("ip", ""),
"link": valine_comment.get("link", ""),
"mail": valine_comment.get("mail", ""),
"nick": valine_comment.get("nick", "Anonymous"),
"pid": valine_comment.get("pid"),
"rid": valine_comment.get("rid"),
"sticky": None,
"status": "approved",
"like": None,
"ua": valine_comment.get("ua", ""),
"url": valine_comment.get("url", "/"),
"objectId": valine_comment.get("objectId", ""),
"insertedAt": valine_comment.get("insertedAt", valine_comment.get("createdAt")),
"createdAt": valine_comment.get("createdAt"),
"updatedAt": valine_comment.get("updatedAt", valine_comment.get("createdAt"))
}

waline_comments.append(waline_comment)

return waline_comments


def create_waline_export(comments):
"""
创建完整的 Waline 导出格式
"""
return {
"__version": "1.39.3",
"type": "waline",
"version": 1,
"time": int(datetime.now().timestamp() * 1000),
"tables": ["Comment", "Counter", "Users"],
"data": {
"Comment": comments,
"Counter": [],
"Users": []
}
}


def main():
# 读取 Valine 数据
with open('/home/tianlejin/myblog/test/Comment_20260313_085101.json', 'r', encoding='utf-8') as f:
valine_data = json.load(f)

print(f"读取到 {len(valine_data)} 条 Valine 评论")

# 转换为 Waline 格式
waline_comments = convert_valine_to_waline(valine_data)

# 创建完整的导出数据
waline_export = create_waline_export(waline_comments)

# 保存为 JSON 文件
output_path = '/home/tianlejin/myblog/test/waline_output.json'
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(waline_export, f, ensure_ascii=False, indent=2)

print(f"转换完成!已生成 {len(waline_comments)} 条 Waline 评论")
print(f"输出文件:{output_path}")

# 打印一些统计信息
urls = set(comment['url'] for comment in waline_comments)
print(f"\n统计信息:")
print(f"- 总评论数:{len(waline_comments)}")
print(f"- 涉及页面数:{len(urls)}")

# 统计回复评论数量
replies = [c for c in waline_comments if c['pid'] is not None]
print(f"- 回复评论数:{len(replies)}")
print(f"- 顶级评论数:{len(waline_comments) - len(replies)}")

# 统计表情符号转换
emotion_count = sum(1 for c in valine_data if any(f':{e}:' in c.get('comment', '') for e in EMOTION_MAP.keys()))
print(f"- 包含表情符号的评论数:{emotion_count}")


if __name__ == "__main__":
main()

使用方法:

  1. 从 LeanCloud 导出 Valine 评论数据(JSON 格式)
  2. 修改脚本中的输入输出文件路径
  3. 运行脚本:python3 valine2waline.py
  4. 将生成的 waline_output.json 导入到 Waline 数据库

扩展阅读