众所周知,QQ的PC端向我们提供了导出聊天记录的功能,并且允许我们导出为可读的文本文档(txt)格式,就像这样:
然后导出之后就是这样的(不要怪我啥都看不见,这已经是我能提供的最多的信息了doge):
结合上图以及生活常识我们可以发现,导出的聊天记录存在如下问题:
- 昵称、备注存在变动,而系统是不会自动更正这个变动的,如上图所示;
- 由于PC端聊天记录与移动端聊天记录是有限交换的,即只互相同步最近的聊天记录,这就导致PC端导出的记录存在大量的缺失;
对于上面所述问题2,想解决那是不可能的,但我们可以想办法缓解,比如将另一方关于此聊天的记录也导出来,合并为一个,于是这里又出现了新的问题:
- 两方昵称、备注不一样,得更新为统一的吧;
- 两边的聊天记录不能直接复制粘贴,得去重;
为了解决对应的问题,直接上代码(文中涉及的消息块样式在代码后面的图片):
import datetime
import re
with open('chat_history_1.txt', encoding='utf-8') as file_base:
lines_base = file_base.readlines()
with open('chat_history_2.txt', encoding='utf-8') as file_add:
lines_add = file_add.readlines()
pattern_ts = "%Y-%m-%d %H:%M:%S"
pattern_re = r"[0-9]{4}(-)[0-9]{2}(-)[0-9]{2}( )[0-9]{1,2}(:)[0-9]{2}(:)[0-9]{2}"
pattern_re_2 = \
r"[0-9]{4}(-)[0-9]{2}(-)[0-9]{2}( )[0-9]{1,2}(:)[0-9]{2}(:)[0-9]{2}( )(备注1|备注2|备注3)" # 这个地方备注或昵称就是聊天记录里面显示的其中一方的昵称(包括两个聊天记录里面所有涉及到的其中一方的昵称或备注)
pattern_re_3 = r"[0-9]{4}(-)[0-9]{2}(-)[0-9]{2}( )[0-9]{1,2}(:)[0-9]{2}(:)[0-9]{2}( )(备注1|备注2|备注3)" # 这个地方备注或昵称就是聊天记录里面显示的另一方的昵称(包括两个聊天记录里面所有涉及到的另一方的昵称或备注)
list_stamp = []
for line_base in lines_base:
result = re.match(pattern_re, line_base) # 这里是读取chat_history_1的消息发送的时间轴(为了下一步合并去重做准备)
if result:
result = result.group()
timestamp = datetime.datetime.strptime(result, pattern_ts).timestamp() # 转换为时间戳
list_stamp.append(timestamp) # 添加入时间轴列表
else:
list_stamp.append(0.0) # 这个我也不知道是啥了,反正有用,不知道有啥用,估计就是占位?我也不确定,自己看着办吧
for index_add in range(len(lines_add)): #外层逐行循环chat_history_2里面的信息(索引)
print(index_add) # 看看循环到哪一个了
result = re.match(pattern_re, lines_add[index_add]) # 还是格式化时间
sign = 0 # 标签,用于标记这个是否是一个重复的信息(从而确定是否要转移该条消息)
if result: # 如果该行有格式化时间,就肯定是(极大概率是,毕竟应该不会有人给别人发消息的时候发格式化时间吧)一个消息的开头
sign = 1
result = result.group()
timestamp = datetime.datetime.strptime(result, pattern_ts).timestamp() # 转时间戳
for index_base in range(len(list_stamp)): # 开始对比时间轴进行插值
if timestamp < list_stamp[index_base]:
sign = 2
counter = 0
index_current = index_add
while True: # 检查这部分消息块(信息块结构见下图)总共占用了几行
index_current += 1
try:
if not re.match(pattern_re, lines_add[index_current]):
counter += 1
else:
break
except IndexError:
break
list_stamp.insert(index_base, timestamp)
lines_base.insert(index_base, lines_add[index_add]) # 插入首行信息,即包含格式化时间等的那一行,同步更新时间戳和内容
index_base += 1
index_add += 1
for i in range(counter): # 插入本信息块(信息块结构见下图)中的其余信息
list_stamp.insert(index_base, 0)
lines_base.insert(index_base, lines_add[index_add])
index_base += 1
index_add += 1
break
elif timestamp == list_stamp[index_base]: # 如果出现时间戳对上了,那就说明消息重复了,不需要合并过来
sign = 2
break
else:
continue
if sign == 1: # 为1代表时间戳直接干到底了,也就是时间戳超出了时间轴的范围,到达了最末端,说明chat_history_2里有比chat_history_1里最后一条记录还要晚的记录,这时候就不用插值了,一个个往里写就行了,下面的追加操作就和之前的插值理论上没啥区别,只不过变成了append
counter = 0
index_current = index_add
while True:
index_current += 1
print(index_current)
try:
if not re.match(pattern_re, lines_add[index_current]):
counter += 1
else:
break
except IndexError:
break
lines_base.append(lines_add[index_add])
index_add += 1
for i in range(counter):
lines_base.append(lines_add[index_add])
index_add += 1
for index in range(len(lines_base)): # 这个是在所有信息都合并完之后,重新统一所有乱七八糟的昵称和内容
result1 = re.match(pattern_re_2, lines_base[index])
result2 = re.match(pattern_re_3, lines_base[index])
if result1:
result1 = result1.group()
result1 = re.match(pattern_re, result1).group()
result1 += " 统一后一方的昵称1\n" # 和前面的正则表达式对起来哈,对错了就悲剧了
lines_base[index] = result1
elif result2:
result2 = result2.group()
result2 = re.match(pattern_re, result2).group()
result2 += " 统一后另一方的昵称\n"
lines_base[index] = result2
else:
continue
with open('chat_history_merged', 'w+', encoding='utf-8') as final: # 写入最终文件
final.writelines(lines_base)
(上图为消息块格式,注意,最后的那个换行是不能删掉的)
除此之外,对于文件格式的要求:
文件开头类似:
文件结尾类似:
就总之,整个文件就只剩下消息块,全都是消息块,从头到尾全部是完整的消息块(务必都得是完整的,导出来的结果一般都是完整的,把头部去掉就行,头部类似下图)
哦等等,最后,关于我为什么要整这么个程序……
就是方便查看,然后又有点强迫症,然后正好考完试了……
四舍五入就是闲的。
不知给这个程序取个什么名字好呢?