简介:一款基于 Python 开发的敏感文件扫描工具,面向需要保护个人及企业敏感信息的普通用户、安全运维人员与 Python 学习者。软件可精准识别身份证号、银行卡号、手机号、邮箱、密码等敏感数据,支持 TXT、DOC、DOCX、PDF、XLS、XLSX、PPT、PPTX 等常见格式,采用正则匹配引擎实现毫秒级检测,全程本地扫描不上传,确保隐私不外泄。压缩包共 79 个文件,约 337MB,涵盖 19 个 Python 源码文件、可直接运行的 exe 程序、39 个 pyc 缓存文件、打包构建目录、配置文件与 6 份 Markdown 说明文档(含快速打包指南、加密解密工具使用说明等),目录结构清晰。已有 186 人学习下载,适合需要快速部署隐私扫描方案、学习本地正则匹配实现或二次开发自定义扫描规则的技术人员。
1. 扫描大师这类敏感文件扫描工具,究竟在解决什么痛点
想象一个场景:你是一家初创公司的技术负责人,某天合规部门突然要求检查全公司电脑里是否有违规存储的个人信息。你用脚本全盘搜索*.xlsx、*.docx,结果在同事的桌面文件夹里翻出了一堆带有身份证号、手机号的客户登记表,有些甚至直接放在名为"内部资料"的压缩包里。这时候你需要的不是网管式的漫无目的翻找,而是一个能自定义规则、精准捕捉特定敏感数据的扫描工具。扫描大师这类工具解决的正是这个需求:把散落在磁盘角落、压缩包内部、甚至备份镜像里的身份证号、手机号、邮箱地址自动揪出来,生成一份可以拿去汇报、整改的报告。
这类工具不是杀毒软件,它不判断文件是否有害,只判断文件是否携带你定义的敏感信息。它可以是一条命令行,也可以是一个带界面的卫士。手头这台机器、这台服务器、这批历史备份里到底躺着多少敏感数据,只有扫过才知道。适合谁用?一是要做合规自查的运维和开发,二是对个人隐私比较在意的普通用户——想看看自己网盘备份里是不是藏着几年前上传的身份证照片。这篇文章不打算讨论某个闭源软件的界面按钮,而是把"自定义敏感文件扫描"这件事的完整落地路径拆开:规则怎么设计、压缩包怎么处理、坑在哪里、结果怎么用。
2. 敏感数据检测的核心原理:正则规则、指纹匹配与自定义规则设计
2.1 为什么内置规则永远不够用
市面上很多敏感文件扫描工具提供"快速扫描"按钮,点一下就开始跑,跑完告诉你找到了多少手机号、多少身份证。这种内置规则最大的问题是你不知道它匹配了什么,也没法调整。比如内置规则把座机号码也当成手机号,或者把社保卡号当成身份证号,误报刷屏,真正的问题反而不容易暴露。
自定义规则的思路完全不同:规则由你定义,匹配逻辑对你的扫描范围和数据类型完全透明。底层技术不外乎两种。第一是正则表达式匹配,把目标数据的格式特征描述成模式串,比如连续18位数字、1开头11位数字这种;第二是内容指纹匹配,对已知敏感文件的哈希值建库,扫到相同哈希的文件直接命中,比如公司下发过一份含员工身份证的Excel模板,把它的MD5记下来,之后任何人改个文件名另存,也能被认出来。
实际做扫描工具时,两种技术通常会混合使用。正则负责抓"格式符合但内容未知"的新数据,指纹负责抓"内容完全没变但换了马甲"的老数据。正则的问题在于规则太宽会误报,太严会漏报;指纹的问题在于只对一模一样的文件有效,任何字节改动都会失效。这也是为什么自定义规则的熟练度,直接决定一个扫描工具是玩具还是生产力。
2.2 身份证、手机号、邮箱的规则怎么写:三个典型正则示例
最基础的是手机号匹配。中国大陆手机号是11位,以1开头,第二位通常是3、5、7、8、9。但直接写1[3-9]\d{9}会匹配出大量非手机号,比如一串以13开头的QQ号。更稳的做法是匹配后做一次前置字符检查,确认手机号不是某个更长数字串的一部分。
import re # 手机号:11位,1开头,第二位3-9,前后不能是数字或字母 phone_pattern = re.compile(r'(?<!\d)(?<![0-9a-zA-Z])1[3-9]\d{9}(?!\d)') # 邮箱:常见邮箱格式,限制域名长度避免把网址当邮箱 email_pattern = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b') # 身份证:18位,前17位数字,最后一位可能是数字或X idcard_pattern = re.compile(r'\b\d{17}[\dXx]\b')这段代码用(?<!\d)和(?!\d)做边界限定,避免12345678901385123456这种长串中间被截出手机号。邮箱正则里的\b用于单词边界。身份证的规则到这里只是第一关,18位数字串很常见——订单号、信贷编号都可能是18位数字,所以必须加上校验码验证。
身份证最后一位是校验码,由前17位按加权因子计算得出。正则只能筛出"长得像"的,校验码能把假货剔除大半。一个完整的自定义规则应该分成两步:先用正则粗筛,再用算法精验。这一步是扫描工具是否可信的分水岭。
2.3 规则引擎的匹配流程与命中判定
扫描工具的运行逻辑一般来说是这样一个流水线:遍历指定目录,按扩展名或文件签名判断文件类型,文本类文件直接按行读,二进制文件先经过编码探测再转成文本,Office文档则先解压提取内部XML,然后喂给规则引擎。规则引擎把每一条规则编译成独立的自动机,对文本做一次扫描,所有规则同时跑,而不用每来一条规则就重新读一遍文件。
命中之后,工具要记录的不仅是命中的字符串本身,还包括文件路径、行号、上下文内容。只记录一个匹配到的号码,后续人工复核时你根本不知道它在哪一行、前后是什么内容,根本无法判断是真实客户数据还是测试样本。我做扫描工具时,至少会保存命中的前后各50字作为上下文摘要。规则引擎还有一个关键参数叫"每条规则的命中上限",比如一个文件里匹配到1000个手机号,就没必要继续记录更多了,否则结果文件会爆炸。
3. 把扫描大师跑起来:自定义扫描任务的最小可复现方案
3.1 安装与初始化:需要准备哪些环境
扫描工具如果只依赖Python标准库,部署起来最省事。文本扫描、ZIP解压、目录遍历标准库都能覆盖,但RAR解压需要第三方库,另外检测Office文档里的内容结构最好有一个openpyxl或者至少能处理XML的库。我用的是Python 3.9以上版本,配合rarfile库处理RAR,openpyxl处理Excel。
初始化环境的时候有两条路。第一条是全量安装:pip install rarfile openpyxl。第二条是纯标准库方案,先不装任何东西,用zipfile处理ZIP,用email库解析.eml邮件备份,把RAR文件的扫描先挂起,等真正遇到RAR时再补装rarfile。我一般会选择全量安装,因为扫描过程中突然发现缺库,中断重启的成本远高于一开始就装好。
rarfile有一个前提:它本身不是解压器,只是一个封装,真正干活的是系统里的unar或者WinRAR命令行工具。Windows上装了WinRAR之后,rarfile会尝试自动调用UnRAR.exe。Linux上建议安装unar或者unrar-free。没装底层工具,rarfile会在解压时直接抛RarCannotExec异常,这个坑我踩过不止一次。
3.2 配置一个扫描任务:路径、规则、输出
把规则和扫描参数分开写,是让工具可复用的关键。我习惯把规则放在一个单独的Python模块里,扫描脚本只负责读文件,不关心规则细节;反过来,规则模块也只暴露一个match(text)方法,不关心文件从哪来。这样换一套规则就等于换一个插件,扫描任务本身不用改。下面的代码是一个最小配置框架:
# rules.py import re RULES = { "phone": { "description": "中国大陆手机号", "pattern": re.compile(r'(?<!\d)(?<![0-9a-zA-Z])1[3-9]\d{9}(?!\d)'), "verify": lambda x: True }, "idcard": { "description": "身份证号(带校验)", "pattern": re.compile(r'\b\d{17}[\dXx]\b'), "verify": check_idcard # 校验码验证函数 }, "email": { "description": "邮箱地址", "pattern": re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b'), "verify": lambda x: True } }这个配置结构里有pattern和verify两个字段,前者负责格式粗筛,后者负责内容精验。verify函数接收命中的字符串,返回True/False。为手机号写lambda x: True是因为格式校验对手机号已经够用;身份证必须走校验码逻辑。扫描脚本会先把pattern.finditer的所有命中收集起来,再逐条跑verify,两条都过才算最终命中。
3.3 命令行一次扫描的完整过程
启动扫描就是一个命令行参数的事,不需要图形界面。下面这个命令是典型用法:
python scan.py --path D:\客户资料 --rules phone,idcard,email --output result.json --hotfix--path指定扫描目录,--rules用逗号分隔要启用的规则,--output指定结果文件,--hotfix表示自动忽略系统目录和临时文件。第一次跑建议不加--hotfix,先看全量结果,因为系统目录里的异常发现有时候更值得注意。
扫描过程中要关注两个指标。第一个是命中率:如果扫了10万个文件只有3个命中,先别急着高兴,可能是规则写得太严,或者文件编码识别失败导致内容根本没被正确提取。第二个是扫描耗时:如果单文件平均耗时超过50毫秒,要检查是不是在做无意义的全文正则回溯。实际的扫描循环大概长这样:
for root, dirs, files in os.walk(path): for fname in files: fpath = os.path.join(root, fname) text = extract_text(fpath) if text: matches = scan_text(text, rules) if matches: record(fpath, matches)extract_text内部判断扩展名,.txt/.log/.csv直接用二进制模式读取并按utf-8、gbk顺序尝试解码,.docx/.xlsx先解压再提取XML文本,.rar/.zip先解压到临时目录再递归扫描。这个函数是性能瓶颈,文件大、编码杂的时候,90%的耗时都花在这里。
扫描结果输出成JSON,记录命中的文件路径、规则名、命中字符串和上下文。结果文件本身就是一份可审计的清单,直接从"扫描完成"跳到"整改清单",不用二次加工。输出JSON而不是CSV,是因为字段长度不固定,CSV到Excel里容易截断,JSON后续写脚本处理起来更顺手。
4. 加密压缩包里的敏感文件怎么扫:RAR 与 ZIP 的解压扫描策略
4.1 为什么压缩包是敏感文件的"重灾区"
敏感资料最容易藏身的地方不是明文文件夹,而是压缩包。原因很直接:压缩包是一种"归档"形态,文件归档进去之后容易被遗忘;同时压缩包自带体积小的优势,方便通过聊天工具、邮件转发,收件人解压后随手放在桌面,从来没想过里面包含身份证复印件。还有一个现实因素:扫描工具的默认配置往往只递归扫描目录,不进入压缩包内部,压缩包里的敏感文件就成了监管盲区。做扫描大师这类工具,压缩包支持一定是核心卖点之一,也是自定义扫描里最容易被低估的一环。
4.2 用 Python 自动解压并扫描 RAR 文件的实现
处理RAR文件首先要有底层支持。rarfile库是Python侧的操作接口,但真正解压依赖系统安装的UnRAR.exe或unar。代码层面可以这样写:
import rarfile import tempfile, os rarfile.UNRAR_TOOL = "UnRAR.exe" # Windows下指定路径 def scan_rar(rar_path, rules, temp_root): with rarfile.RarFile(rar_path) as rf: for info in rf.infolist(): if info.is_dir(): continue target = os.path.join(temp_root, info.filename) rf.extract(info, path=temp_root) text = extract_text(target) if text: for rule in rules: hits = rule.match(text) if hits: report({ "archive": rar_path, "inner_file": info.filename, "rule": rule.name, "hits": hits })这段代码要把temp_root放在有足够空间的磁盘上,因为解压会释放原始文件大小。参数说明:info.filename是压缩包内的相对路径,从临时目录拼接回去要防止路径穿越;如果是损坏的RAR包,extract会抛RarCRCError,需要单独捕获。生产环境里我会限制单文件解压大小,比如超过500MB的RAR条目就直接跳过,毕竟扫描目标是敏感信息,不是文件系统备份恢复。
ZIP文件的处理就更简单一些,Python自带的zipfile就能解压,但有一个坑:ZIP条目文件名可能使用cp437编码,中文字符会变成乱码。遇到ZipFile读取文件名出现?符号时,可以手动尝试encode('cp437').decode('gbk')来恢复中文文件名。
4.3 遇到有密码的 RAR 文件怎么办:合法场景下的处理思路
压缩包里带密码是非常常见的事。很多部门习惯把含个人信息的Excel压缩后加密码发到群里,密码写在同一个群里,等于没加密。扫描工具遇到加密RAR会直接报错,因为rarfile解压到加密条目时,如果没有密码就会抛RarWrongPassword异常。正确的做法是扫描时对加密RAR文件单独标记,先解压能解压的,对加密的条目输出一个"需要人工处理"列表,而不是让整个扫描任务中断。
有人会想到用"rar密码移除"、"advanced rar password recovery"这类恢复工具。先说清楚边界:如果你自己就是文件的所有者,知道密码的找回方式和工具操作都属于正常范畴,比如加密的课程资料忘记解压密码,通过恢复工具找回自己设定的密码,没有法律问题。但如果你手里的压缩包来源不明,试图用暴力破解打开别人的加密文件,这已经不是技术问题,而是合规问题。扫描工具的定位是发现风险,不是破解风险。
对于自己拥有但忘记密码的RAR文件,我建议优先走官方找回流程,而不是上来就跑暴力破解。WinRAR本身不提供密码找回功能,第三方工具恢复密码的成功率取决于密码长度和复杂度。一个8位纯数字密码用单机CPU跑,运气好几分钟,运气不好几天。但6位以内纯数字密码基本上算是秒破,这类密码在内部的敏感文件压缩包中反而最常见。我的习惯是:扫描过程中发现加密RAR时,直接列入"待补密码清单",后续通过内部密码台账查询,查不到再考虑恢复工具,并且只用在自己有权访问的归档上。
扫描深度的问题也在这里暴露出一个常见矛盾:既要扫描压缩包内部,又不能无限递归下去。RAR里面套RAR、ZIP里面套ZIP的嵌套结构,在真实环境中确实存在。我会设置递归深度为3层,超过之后停止解压并标记为"深度受限"。设定这个值前要明确一点:敏感文件通常不会嵌套超过3层,藏得太深的文件,反而不像是无心泄露,更像是故意隐藏。
5. 扫描大师避坑指南:误报、漏报和性能坑,一次说清
5.1 身份证校验码不校验,误报率翻倍
现象:规则只写了\d{17}[\dXx],扫描结果里命中数千条"身份证号",人工复核后发现一半是订单号、流水号、ISBN编号,真正是身份证的不到三成。原因:18位数字串的格式特征太宽泛,任何固定长度数字序列都符合。解决:必须在verify函数里实现身份证校验码算法。校验码是前17位乘加权因子求和后对11取模,映射表得出第18位。把这一步补上,误报率能降一个数量级。
这里有一个更隐蔽的坑:很多身份证号是从Excel里导出的,前导零被吃掉,18位变成17位;还有单元格被设成科学计数法,身份证显示成4.10227E+17。这种情况下正则完全失效。解决的办法是扫描Excel时不仅提取显示文本,还要提取单元格的原始数值,并做格式还原。openpyxl读取单元格时,拿到的是数值类型,需要按yyyy-mm-dd之类格式反推回原始字符串,这是个细活,但值得做。
5.2 大文件直接卡死:内存溢出与编码错误的处理
现象:扫描一个2GB的日志文件时,Python进程内存占用飙升到好几个GB,然后系统开始换页,整个机器卡成幻灯片。原因:一次性把整个文件读进内存做正则匹配,对文本数据量来说是最省事的写法,但也是最耗内存的写法。解决:按块读取,每块1MB,保留末尾几百字节的边界重叠,防止手机号跨块截断。代码写法是:
def scan_large_file(fpath, rules, chunk_size=1024*1024): last_tail = "" with open(fpath, 'rb') as f: while True: chunk = f.read(chunk_size) if not chunk: break text = try_decode(last_tail + chunk) for rule in rules: rule.feed(text) last_tail = chunk[-256:]理解这段代码的关键在于last_tail。假设手机号前半段在上一块末尾,后半段在当前块开头,不使用tail缓冲就会漏报。文本解码也容易翻车:文件是GBK编码,你用UTF-8解码,得到一堆乱码,正则自然匹配不到。处理方式是把try_decode做成依次尝试UTF-8、GBK、Latin-1,并用"解码后是否包含常见中文字符"作为判断依据。如果三种编码都解出乱码,就放弃扫描,而不是直接崩溃。
5.3 扫描到压缩包外层就停下:递归深度设置与性能的平衡
现象:目录里有一个名为"备份2024.rar"的压缩包,你明知道里面有敏感文件,但扫描结果里完全没有它的记录。原因:扫描脚本只对普通文件做了内容提取,没把.rar/.zip纳入递归解压流程,或者解压层数设为了1。解决:把压缩包处理逻辑和普通文件扫描统一起来。扫描到压缩包时,先把解压后的文件列表加入待扫描队列。要注意的是,这种统一处理很容易形成递归风暴,所以必须设置深度上限,并且统一走临时目录。一个典型的配置是:扫描深度3层,每层限制解压文件总大小500MB,超过就跳过。临时目录用事后自动清理,因为解压出来的文件本身就是敏感文件,留在磁盘上等于自己制造了一个新的泄露点。
5.4 规则写得太宽,日志刷屏
现象:邮箱规则\w+@\w+.\w+上线后,扫描结果炸了。日志里全是aaa@bbb.com这种测试字符串,实际有威胁的泄露反而淹没在海量结果里。原因:规则没有限制域名格式和字符类型,匹配到了大量代码中的占位符、配置文件里的示例地址。解决:把邮箱规则收窄为域名优先列表,比如qq.com、163.com、gmail.com这些常见邮箱域名,同时排除.png、.jpg等资源文件里的字符串。更进一步,可以建立一个"上下文黑名单":如果命中字符串的前后50字里包含example、test、placeholder等标记,则不记录。规则宁严勿松,漏报可以通过多写几条互补规则来弥补,误报太多会导致没人愿意看报告。
5.5 密码恢复工具救急,但不该成为依赖
现象:遇到加密RAR文件,网上搜"rar密码移除"、"advanced rar password recovery",下载工具开始跑破解,跑了一晚上没出结果。原因:密码不是纯短数字,字典和暴力双管齐下也撬不开。解决:先确认文件所有权,找密码台账;确实找不到再考虑恢复工具,而且要优先选择支持GPU加速的版本,CPU破解16位复杂度密码的时间是以年为单位计算的。更靠谱的方案是在扫描流程里加入"密码提醒"环节——扫描到加密压缩包,自动记录文件名和来源路径,发给归档负责人确认密码,而不是让操作员现场破解。扫描大师这类工具把"加密包扫描"做成显式功能,本意是让压缩包不再成为藏污纳垢的角落,而不是把密码破解也包进来。
6. 让扫描结果真正可用:三招减少误报、固化基线
6.1 命中结果加"上下文摘要",二次核验成本减半
扫描报告里如果只有路径和命中的号码,审计人员必须打开原始文件核对。给每条命中附带前后120字符的上下文,就能在结果里直接判断是真实数据还是误报。做法很简单:正则finditer命中的时候,取start-120到end+120的切片,替换掉不可打印字符,存储进结果JSON。实际用下来,这个功能让一份上千条命中的报告,人工复核时间从两天压缩到三小时。注意切片边界不要落在代理对字符中间,Python字符串切片按码点走,遇到Emoji或者生僻字,切片可能切断代理对导致后续处理报错。稳妥的办法是使用text[start:end]之后做一次errors='ignore'编码清理。
6.2 把白名单路径做成基线,增量扫描
扫描不能每次全量跑,数据量大了以后全量扫描耗时太长。把已知安全的位置和文件加入白名单,后续扫描只针对白名单之外的新增和变化文件。实现上可以用文件修改时间mtime做增量判断:上次扫描时间之后的文件才进入扫描队列。每次扫描结束后,把文件路径、大小、mtime存成一个基线文件。下一次扫描时对照基线,mtime没变的文件直接跳过。基线文件本身也是敏感信息——它记录了哪些路径存在数据,所以要对基线文件做权限控制,最好和扫描结果放一起加密保存。
增量扫描最大的陷阱是:只按mtime判断,会漏掉内容变了但修改时间没变的情况,比如用工具直接改文件内容再恢复时间戳。对安全敏感的扫描任务,定期做一次全量扫描兜底是必要的,比如每季度一次。增量扫描服务于日常高频监控,全量扫描服务于审计节点,两条腿走路。
6.3 定时任务与通知:扫描从"一次性"变成"常态化"
单次扫描只能证明"当前没有敏感文件",一旦有人往磁盘里拷入新文件,之前的扫描结果就作废了。把扫描挂进定时任务里,每天凌晨跑一次,问题才真正可控。Windows上用计划任务,Linux上用cron,扫描结果直接追加到日志文件。发现新的命中时,自动调用邮件接口或Webhook通知负责人。这里有个细节:通知内容不要包含命中的完整手机号和身份证,只报数量、路径、命中的规则名称,完整信息在结果文件里。结果文件本身带访问控制,因为通知链路一旦泄露,等于把敏感信息又送出去一份。
我自己的习惯是把上一次扫描结果称为"已知清单",新命中与已知清单做差集后再通知。这样就不会天天凌晨三点收到”发现手机号”的告警——基线里的数据是已知的,不值得打扰人。等到告警安静下来,常态化的扫描才有意义:它盯着的是变化,不是存量。扫描工具做得再好,如果报告没人看,等于白做。希望每个做敏感文件扫描的人都能把结果管起来,让扫描真正成为个人信息保护的一道防线,也帮到你所在的团队少踩几个坑。
本文还有配套的精品资源,点击获取