多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

RAR文件乱码与编码修复:Python解析RAR结构还原正确文件名

RAR文件乱码与编码修复:Python解析RAR结构还原正确文件名 简介面向无线通信与阵列天线方向的学习者压缩包内含一套基于MATLAB的波束赋形实现代码并结合HFSS仿真数据辅助验证。内容聚焦波束形成中的权值计算、数字信号处理与差分进化算法优化可用于研究不同场景下的波束方向图与信号增强效果。包内共19个文件包括16个CSV数据文件和3个M脚本文件CSV用于存放HFSS仿真或中间结果数据M文件则为算法主程序与辅助读取函数结构清晰便于按需调用和修改参数。压缩包整体约5MB轻量易用。目前已有1025人学习下载适合具备一定阵列天线基础、希望快速上手波束赋形仿真或研究差分进化算法应用的读者。通过运行代码并调整输入参数可以直观观察波束形状变化理解相位加权与优化算法的配合过程是一份兼顾理论与实操的参考资料。1. decode.rar 不是破解密码是把文件里的字节读回人话大多数人在搜索引擎敲下 decode.rar 时真正遇到的场景是从某个老项目里翻出一个 .rar 压缩包解压工具打开一看里面全是乱码文件名或者代码里 open 一个解压出来的文本文件直接摔给你 UnicodeDecodeError: utf-8 codec cant decode byte 0xeb in position 0。这时候你想做的不是密码破解而是搞清楚 RAR 文件里那些字节到底用什么编码写的、怎么把它 decode 成正确字符串。decode.rar 本质是两件事把 RAR 的二进制结构解析出来再把其中文本类字段用正确的字符集还原。这篇文章不碰任何暴力破解或移除密码的歪门邪道只讲我用 Python 从零解析 RAR 文件头、处理文件名乱码、修复 utf-8 解码报错的完整套路中途会涉及 VSCode 调试、16 进制编辑器的使用以及编码探测的参数选择适合被压缩包编码折磨过的后端和运维工程师。2. 从二进制层面看 RAR 文件结构别急着 decode先分块解码乱码之前必须弄清楚一件事RAR 文件不是纯文本文件它内部是一个接一个的块block文件名、注释、时间戳都以二进制字段存在这些块里。直接拿文本模式去 open 一个 .rar读到第一个非 UTF-8 字节就炸这就是大部分人在 VSCode 里看到 unicodedecodeerror 的根源。2.1 RAR 签名与块类型的判定RAR 格式分成 RAR4 和 RAR5 两代签名不同块格式也不同。常见做法是先用 16 进制编辑器比如 HxD 或 VSCode 的 Hex Editor 插件打开文件看最前面的字节RAR4 的签名是52 61 72 21 1A 07 00对应 ASCII 是Rar!\x1a\x07\x00。RAR5 的签名是52 61 72 21 1A 07 01 00比 RAR4 多了个版本字节。下面这段 Python 代码可以在不引入任何第三方库的情况下判断 RAR 版本并读取主块import struct def peek_rar_version(path): with open(path, rb) as f: head f.read(8) if head[:6] bRar!\x1a\x07\x00: return RAR4 elif head[:7] bRar!\x1a\x07\x01\x00: return RAR5 return NOT_RAR这段代码先读前 8 字节然后用切片比较签名。注意 RAR4 签名占 7 字节RAR5 占 8 字节所以顺序判断时把短签名放在前头不会出错。struct模块在这里只做了字节切片真正解析块头在后面会用到。如果返回NOT_RAR说明文件扩展名是 .rar 但内部格式不对常见于从网上下载的伪加密文件——文件头被篡改但块结构仍然是 zip 或 7z 的。2.2 解析 RAR4 块头CRC、类型、大小RAR4 的每个块头固定 7 字节末尾跟 2 字节的 CRC。块头字段拆开看是这个布局偏移长度字段说明02HEAD_CRC块头 CRC16校验 HEAD_TYPE 到 HEAD_SIZE 的字节21HEAD_TYPE块类型0x72 是标记块0x73 是压缩文件头0x74 是文件头32HEAD_FLAGS位标志第 4 位表示文件名用 Unicode 编码52HEAD_SIZE块头加数据区的总大小写一个函数把整个块头读出来def parse_rar4_block_header(f): head_crc, head_type, head_flags, head_size struct.unpack(HBHH, f.read(7)) return { crc: head_crc, type: hex(head_type), flags: head_flags, size: head_size, }struct.unpack的格式串HBHH含义是小端序、2 字节无符号整数、1 字节无符号整数、2 字节无符号整数、2 字节无符号整数正好对应 7 字节块头。拿到类型和大小以后就能决定到底要不要把这块数据完整读进内存。注意 RAR4 的文件头块0x74有额外 25 字节的固定数据区包含压缩方式、修改时间、CRC32 等文件名在数据区末尾所以光有块头不够还得继续读。2.3 一个最小可用的 RAR4 遍历器把上面两块拼起来写个只读不写的遍历器适合用来确认某个 .rar 文件里到底有几个文件、文件名原始字节是什么def walk_rar4(path): with open(path, rb) as f: # 跳过签名 f.seek(7) while True: block_pos f.tell() try: block parse_rar4_block_header(f) except struct.error: break if block[type] 0x74: data f.read(block[size] - 7) if data: # 文件名字段在数据区最后前面是固定 25 字节信息 name_bytes data[25:].split(b\x00)[0] yield block_pos, name_bytes else: f.seek(block[size] - 7, 1) if block[size] 0: break这里有个关键细节f.seek(block[size] - 7, 1)表示相对当前位置向后跳跳过的长度是块总大小减去已经读掉的 7 字节块头。如果block[size]是 0说明到了文件尾直接跳出循环。这个遍历器输出的name_bytes可能包含中文的 GBK 编码字节也可能包含 UTF-8 字节16 进制里看到\xbb\xd6\xce\xc4之类的连续字节基本是 GBK 中文下一步就是用正确的解码姿势处理它。3. 在 VSCode 里遇到 UnicodeDecodeError 的完整复盘从报错到定位在 VSCode 里打开一个解压出来的 txt 文件中文内容直接显示乱码或者跑 Python 脚本读文件时爆出UnicodeDecodeError: utf-8 codec cant decode byte 0xeb in position 0: invalid continuation byte这是 decode.rar 主题下最普遍的问题。0xeb 这个字节在 UTF-8 里是非法起始字节它可能是 GBK 汉字的一部分也可能是 CP437 扩展字符得先确认源编码再动手。3.1 复现报错的最小代码以下代码复现的场景是解压出来的文件本身是 GBK 编码的文本但读文件时没指定编码Python 3 默认按 UTF-8 解码with open(README.txt, r, encodingutf-8) as f: content f.read() print(content)运行后报错信息指向第 0 个位置因为文件第一个字节就是 0xebUTF-8 解码器发现它不符合任何多字节序列的规则直接抛异常。这时候把encoding参数换成gbk就能读通但问题在于你怎么知道该用 GBK盲猜编码是解码失败的第一根源。3.2 用字节探针确认真实编码最稳妥的办法是读文件前先看原始字节。在代码里加上一个 16 字节的采样函数def detect_bytes(path): with open(path, rb) as f: sample f.read(16) return sample.hex( )对同一个文件执行后会看到类似eb b9 7a b4 ab c4 e3 ba c3这样的结果。0xeb 0xb9在 GBK 里对应“你”0xc4 0xe3对应“好”两个双字节合起来就是“你好”。判断口诀是如果字节序列里大量出现0x81到0xFE作为前导字节、后面跟着0x40到0xFE的字节大概率是 GBK 或 Big5。如果大量出现0xC0到0xDF开头、后续字节都在0x80到0xBF则是 UTF-8 中文。我一般直接借助 chardet 库做正式探测但注意它小文件上容易误判所以采样字节数最好在 1000 字节以上import chardet def guess_encoding(path): with open(path, rb) as f: raw f.read(4096) result chardet.detect(raw) return result[encoding], result[confidence]detect返回的confidence是 0 到 1 之间的可信度低于 0.8 时不要直接采信可以再切一段文件试试。实际场景里 RAR 解压出来的文件编码大概率是老系统的本地代码页中文环境是 GBK日文环境是 Shift_JIS英文环境是 CP437。下面这个表格是常见编码的判定依据遇到乱码时直接对照编码特征字节典型乱码样子解码参数UTF-8起始字节 0xC0-0xFD续字节 0x80-0xBF锟斤拷encodingutf-8GBK双字节前导 0x81-0xFE尾随 0x40-0xFE鍚夋灄encodinggbkCP437单字节 0x80-0xFF 对应线条和符号无规律符号encodingcp437Shift_JIS双字节0x81-0x9F 或 0xE0-0xEF 起始̃encodingshift_jis3.3 用 errors 参数保住不抛异常很多跑批任务里一行乱码不该让整个程序崩溃。Python 的open支持errors参数可以填replace、ignore或者backslashreplace。区别是replace把非法字节替换成ignore直接丢弃backslashreplace会转成\xeb这种可见形式。调试阶段我建议用backslashreplace它不会丢信息with open(message.txt, r, encodingutf-8, errorsbackslashreplace) as f: content f.read()这样程序不会中断而且你还能从\xeb这样的转义序列里反推出原文件的字节流再用正确的编码二次解码。这套思路同样适用于从 RAR 内直接抽取内容的场景后面章节的解析代码里会用到。4. 写一个能正确处理编码的 RAR 文件解码器从块遍历到文件名还原光判断文件编码还不够回到 RAR 文件本身。RAR 内的文件名是以字节形式存储的RAR4 格式里文件名的编码取决于压缩时所在的系统代码页RAR5 内部统一使用 UTF-8 存储文件名。这意味着RAR4 文件在中文 Windows 上压缩名字是 GBK 字节在老外机器上压缩可能是 Latin-1。所以一个合格的 decode.rar 工具必须能同时处理这两种情况。4.1 读取文件名时如何做编码回退上一章的遍历器拿到name_bytes后解码不能死磕一种编码。常见做法是先尝试 UTF-8失败后按系统代码页或用户指定编码解码def decode_name(name_bytes, fallbackgbk): try: return name_bytes.decode(utf-8) except UnicodeDecodeError: return name_bytes.decode(fallback, errorsreplace)这个逻辑的顺序很重要。RAR5 文件名一定是 UTF-8所以先试 UTF-8 不会误判。RAR4 文件名如果是纯 ASCII用 UTF-8 解也能通过因为 ASCII 是 UTF-8 的子集。只有真正的双字节编码才会落到fallback。errorsreplace是兜底策略防止个别字节不在 GBK 映射表里导致再次抛出异常。4.2 支持 RAR5解析块头与额外字段RAR5 的块头结构和 RAR4 差别很大它不是固定长度而是变长整数。每个块头前两个字节是HEAD_CRC接下来是变长的HEAD_SIZE。RAR5 块类型定义在公开的格式文档里常见的是0x01主头、0x02文件头、0x03服务头。文件名存在文件头数据区的末尾但它的长度需要从额外字段里解析。实际写代码时我不建议从零实现 RAR5 解析轮子已经够好了直接用rarfile库更靠谱。4.3 兼容 RAR4 与 RAR5 的解码器实现下面代码可以同时处理 RAR4 和 RAR5 的文件名提取依赖库只有一个rarfile它内部已经把块结构解析好了我们只需要关注解码问题import rarfile def decode_rar_entries(path, fallback_encodinggbk): rarfile.UNRAR_TOOL /usr/bin/unrar # Windows 下改成 unrar.exe 路径 with rarfile.RarFile(path) as rf: for info in rf.infolist(): raw info.filename.encode(info.encoding or utf-8, errorsreplace) try: name raw.decode(utf-8) except UnicodeDecodeError: name raw.decode(fallback_encoding, errorsreplace) yield info.filename, name, info.file_size这段代码的逻辑是先从info.filename拿到 RAR 内部存储的原始文件名再用info.encoding回推它的字节。rarfile库在解析时已经记录了文件名字段所在的编码信息但 RAR4 文件经常没有这个字段所以info.encoding可能为None这时默认按 UTF-8 先去还原字节失败再按 GBK。注意info.filename.encode()这一步如果原始文件名已经带了 Unicode 替换符你就无法还原真实字节这就是为什么前一步必须用errorsreplace而不是直接encode()抛异常。4.4 实测参数与常见坑对照跑上述代码时最常遇到的坑有三个。第一个是rarfile依赖外部unrar工具没装的话会抛NeedFirstVolume或RarCannotExec解决办法是在代码里显式指定UNRAR_TOOL路径。第二个是 RAR 内文件名如果用了info.filename.encode()之后多字节序列被 Python 的 Unicode 替换符污染导致后续解码永远报错这种情况请保留原始字节——建议从 RAR 头解析阶段就保存name_bytes不要从高层的info.filename反推。第三个是伪加密 RAR文件头里加密标志位置 1 但实际没加密rarfile默认可能报BadRarFile。这时候可以用 16 进制编辑器直接看文件头偏移手动把加密标志位清零后再解正规加密的 RAR 则不要尝试绕过密码那是另一码事。5. 进阶用编码探测和字节回放还原乱码文件名再验证解码结果最后一章给出一个真正能落地的技巧组合专门对付那些解压后目录里一堆??????.txt或者文件名彻底乱掉的情况。核心思路是别试图从乱码字符串反推直接从原始字节重建。5.1 乱码文件名修复先 encode 回字节再 decode 到正确编码最常见的一种乱码是 UTF-8 字节被 GBK 解码后的产物比如“林”的 UTF-8 字节是E6 9E 97被 GBK 拆成两个双字节字符后显示为“鏋椂”。修复办法是把乱码字符串重新用 GBK 编码还原成原字节再用 UTF-8 解码def fix_garbled_name(garbled, source_encodinggbk, target_encodingutf-8): try: raw_bytes garbled.encode(source_encoding) return raw_bytes.decode(target_encoding) except (UnicodeEncodeError, UnicodeDecodeError): return garbled这个函数对“UTF-8 内容被 GBK 解释”这种场景百试百灵。反过来GBK 字节被 UTF-8 解释出来的乱码只需要把source_encoding和target_encoding对调。判断是哪种情况时看乱码里是否有鍖椾含这种全角符号夹杂西文字母的特征有就是 GBK 误读 UTF-8如果乱码全是不认识的汉字先试试上面的方向。参数传错了也不会崩溃会原封不动返还输入因为外层有异常兜底。5.2 合法场景下校验 RAR 密码是否正确不碰破解工具但“验证自己设定的密码是否正确”是完全正当的需求。rarfile库提供了setpassword接口可以通过尝试打开一个已知文件来确认密码with rarfile.RarFile(archive.rar) as rf: rf.setpassword(your-password) try: names rf.namelist() print(Password OK, files:, len(names)) except rarfile.WrongPassword: print(Password incorrect)这里的原理是namelist()在读取目录时就会触发加密块的密钥校验密码不对会直接抛WrongPassword不需要真的解压整个文件。如果你面对的是 RAR4 的伪加密头标志被改setpassword传空字符串也能通过这是校验逻辑里的一个盲区需要靠 16 进制编辑器人工确认加密标志位。用于历史文件找回密码的场景这套做法够用暴力枚举不在本文范围内也不推荐。5.3 最后收尾一条命令完成整个 RAR 目录的解码重命名把前面的函数拼成一个 CLI 入口可以顺手处理一批文件python decode_rar.py --input old.tar --output-dir ./decoded --encoding gbk对应的主逻辑如下支持把 RAR 内所有乱码文件名重写后再解压import argparse import os import shutil import rarfile def main(): parser argparse.ArgumentParser(descriptionDecode RAR entries and extract with fixed filenames) parser.add_argument(--input, requiredTrue) parser.add_argument(--output-dir, default./decoded) parser.add_argument(--encoding, defaultgbk) args parser.parse_args() os.makedirs(args.output_dir, exist_okTrue) with rarfile.RarFile(args.input) as rf: for info in rf.infolist(): fixed_name fix_garbled_name( info.filename, source_encodingargs.encoding, target_encodingutf-8 ) target_path os.path.join(args.output_dir, fixed_name) if info.is_dir(): os.makedirs(target_path, exist_okTrue) else: os.makedirs(os.path.dirname(target_path), exist_okTrue) with rf.open(info) as src, open(target_path, wb) as dst: shutil.copyfileobj(src, dst) if __name__ __main__: main()这个脚本没有把密码验证加进去如果需要就把rf.setpassword(args.password)放在with块开头。参数--encoding控制的是 RAR4 文件名按什么编码回退默认gbk适合中文 Windows 压出来的包如果是英文或日文环境改成cp437或shift_jis重新跑一遍就能得到完全不同的文件名。判断该用什么编码的最快方式是解压一个文件出来用chardet探测它的字节然后对号入座。本文还有配套的精品资源点击获取
返回列表