QQ 聊天记录 MHT 文件转 HTML 写在前面 虽然最近博客没有更新,但博主一直在写一些回忆性的文章(都存放在某个赛博空间里)。最近突然回想起一些往事,想翻看以前的聊天记录。尽管那时明月在,曾照彩云归,好在博主没有删掉数据的习惯,消息记录几乎都还保留着。虽然,我们的对话仅仅持续了一年半。
正文 目前,QQ 客户端提供三种导出格式:.bak
、.mht
和 .txt
。凭借一些常识和对导出后文件大小的观察,很容易发现,bak
和 txt
应该只是单纯的纯文本消息,不含任何图文内容。因此,如果想保留消息中的图片或其他资源,毫无疑问,mht
格式便成了唯一的选择。
MHT 它是一种能够将图片、样式表、脚本等多种资源打包成一个独立文件的格式。通过这种方式,所有的信息都能被封装在一个文件内,方便进行管理和导出。
当博主尝试导出消息记录时,由于聊天内容过多,客户端一度陷入了假死状态,持续了相当长一段时间。博主此时隐隐地觉得不妙。
导出的文件在用 Edge
打开时,由于体积过大(高达 500MB),导致无法显示任何内容。转而使用 Chrome
时,则出现了如下错误提示(🤣👉🤡):
1 Malformed multipart archive
通过在 file.org 查阅资料,博主才意识到,原来 .mht
文件并不支持所有浏览器。它必须依赖于 Internet Explorer
或 Edge
才能正常打开。
MHT files are commonly associated with the Internet Explorer Web browser.
这也解释了为什么 Edge
会自动切换到 Internet Explorer
模式(但是,打开一直无响应和转圈圈…)。
找了一些转换工具也没转换成功,没办法,只能自己造轮子了。其实也并不复杂,只需要简单了解一下文件结构~
导出的 .mht
文件结构大致如下:
Header 部分 :包含文件类型、编码方式、内容类型等元数据。Body 部分 :存放页面的 HTML 内容,以及相关的图片、样式表、脚本等资源。分隔符 :各个部分通过边界(boundary)分隔,确保资源能够正确解析。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 From: <Save by Tencent MsgMgr > Subject: Tencent IM Message MIME-Version: 1.0 Content-Type: multipart/related; charset="utf-8" type="text/html"; boundary="----=_NextPart_4938232B_D5C6_4d39_AC69.64B3BD4C94EF" ------=_NextPart_4938232B_D5C6_4d39_AC69.64B3BD4C94EF Content-Type: text/html Content-Transfer-Encoding:7bit <html xmlns ="http://www.w3.org/1999/xhtml" > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" /> <title > QQ Message</title > <style type ="text/css" > body { font-size : 12px ; line-height : 22px ; margin : 2px ; } td { font-size : 12px ; line-height : 22px ; } </style > </head > <body > <table width ="100%" cellspacing ="0" > <tr > <td > <div style ="padding-left: 10px" > <br /> <b > 消息记录</b > </div > </td > </tr > <tr > <td > <div style ="padding-left: 10px" > 消息分组:已删除联系人</div > </td > </tr > <tr > <td > <div style ="padding-left: 10px" > 消息对象:iami233</div > </td > </tr > <tr > <td > <div style ="padding-left: 10px" > </div > </td > </tr > <tr > <td style =" border-bottom-width: 1px; border-bottom-color: #8ec3eb; border-bottom-style: solid; color: #3568bb; font-weight: 700; height: 24px; line-height: 24px; padding-left: 10px; margin-bottom: 5px;" > 日期:2024-02-24 </td > </tr > <tr > <td > <div style ="color: #006efe; padding-left: 10px" > <div style ="float: left; margin-right: 6px" > iami233</div > 21:09:45 </div > <div style ="padding-left: 20px" > <font style ="font-size: 10pt; font-family: "" color ="000000" > 我是一条示例消息捏~</font > </div > </td > </tr > <tr > <td > <div style ="color: #42b475; padding-left: 10px" > <div style ="float: left; margin-right: 6px" > 小 iami233</div > 13:44:52 </div > <div style ="padding-left: 20px" > <img src ="{E3D98612-5231-47ad-B7E4-1160C3A24C55}.dat" /> </div > </td > </tr > </table > </body > </html > ------=_NextPart_4938232B_D5C6_4d39_AC69.64B3BD4C94EF Content-Type: image/gif Content-Transfer-Encoding: base64 Content-Location:{E3D98612-5231-47ad-B7E4-1160C3A24C55}.dat ... Base64 encoded data ... ------=_NextPart_4938232B_D5C6_4d39_AC69.64B3BD4C94EF--
简单了解完文件结构后,我们只需提取出 <html>...</html>
部分,同时将图片资源转存,便能实现所需的核心功能🥰
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 import reimport base64with open ( 'example.mht' , "r" , encoding="utf-8" ) as f: content = f.read ( ) boundary_match = re.search ( r'boundary=" ( [^"]+ ) "' , content ) boundary = boundary_match.group ( 1 ) parts = re.split ( rf"--{boundary} ( ?:-- ) ?\n" , content ) html_part = next (( p for p in parts if "Content-Type: text/html" in p ) , None ) html_match = re.search ( r" ( ?: \n\n|\r\n\r\n ) ( .*? ) ( ?=\n--|$ ) " , html_part, re.DOTALL )print ( html_match.group ( 1 ) .strip ( ) , "\n" )for part in parts: if "Content-Location:" not in part: continue headers_str, _, body = part.partition ( "\n\n" ) headers = {} for line in headers_str.split ( "\n" ) : if ":" in line: key, value = line.split ( ":" , 1 ) headers [key.strip ( ) .lower ( )] = value.strip ( ) content_location = headers.get ( "content-location" ) content = base64.b64decode ( body.strip ( )) with open ( content_location, "wb" ) as f: f.write ( content ) print ( f"{content_location} 已保存" )
剩下的只需要亿点点优化就可以了,比如:
将 HTML 中的内联样式全部提取并转换为 CSS 类,尽可能的减小文件体积和冗余样式 支持将资源文件输出到指定文件夹,并自动更新 HTML 中的资源引用路径。 消息记录无法导出语音,视频等内容,所以需要自动处理空白记录并插入提示文本。 好啦,剩下的直接前往 5ime/mht2html 查看即可。
不过,需特别提醒,此项目针对性非常强,基本只为博主查看聊天记录所用。如果你有其他需求,比如大文件切割什么的,就得自己动手研究啦~
写在后面 在写代码的某一刻,我也曾幻想过,想着将聊天记录中的所有文字提取出来,分词生成词云,甚至训练个模型什么的。可是,随着时光流转,那些想法渐渐褪去光彩,变得毫无意义。
毕竟,那些文字,终究只是过去的碎片罢了。
难道不是吗?
最终,博主依旧没去翻看那些文字。脑海中的记忆早已模糊不清,留下的,或许只剩下空白与迷惘,etc.