本文导读:介绍一个开源的中文敏感数据智能脱敏系统,结合 正则匹配深度学习NER 双重识别策略,支持四种脱敏模式,提供 Web 和 Docker 一键部署。

前言

在数据驱动的时代,个人隐私保护已成为不可忽视的议题。无论是企业的日志系统、客服对话记录,还是数据分析报告,都可能包含大量敏感信息——手机号、身份证号、银行卡号、姓名等。

《个人信息保护法》和《数据安全法》的实施,对数据脱敏提出了更高要求。传统的正则匹配方案虽然能处理结构化数据(如手机号),但面对「张三给李四转了 5000 元」这类非结构化文本时往往力不从心。

本文将介绍一个我开发的开源项目——敏感数据智能脱敏系统,它结合了正则匹配和**深度学习 NER(命名实体识别)**的优势,实现了对中文文本的智能脱敏处理。

GitHub 项目地址

系统概述

核心特性

特性 说明
双重识别 正则匹配结构化数据 + NLP 识别非结构化实体
四种策略 部分脱敏、完全脱敏、占位符替换、哈希脱敏
Web 基于 Gradio 的现代化交互界面
容器化 Docker 多阶段构建,一键部署
高性能 支持 Intel MKLDNN 加速,fast/accurate 双模式

技术架构


架构设计

设计原则

在设计这个系统时,遵循了以下原则:

  1. 开闭原则: 对扩展开放,对修改关闭——新增脱敏类型只需扩展,无需修改现有代码
  2. 单一职责: 每个脱敏器只负责一种识别策略
  3. 依赖倒置: 高层模块依赖抽象,而非具体实现

类结构设计

系统采用 抽象工厂 + 策略模式 的组合设计:

1
2
3
4
5
6
7
8
9
10
class EntityType(Enum):
"""支持的敏感信息类型"""
PERSON = "人名" # 人物名称
LOCATION = "地名" # 地理位置
ORGANIZATION = "组织机构" # 公司/机构
TIME = "时间" # 时间表达式
PHONE = "电话" # 电话号码
EMAIL = "邮箱" # 电子邮件
ID_CARD = "身份证" # 身份证号码
BANK_CARD = "银行卡" # 银行卡号码
1
2
3
4
5
6
class MaskStrategy(Enum):
"""脱敏策略"""
FULL = "full" # 完全脱敏: ***
PARTIAL = "partial" # 部分脱敏: 张*三
HASH = "hash" # 哈希脱敏: [a1b2c3d]
PLACEHOLDER = "placeholder" # 占位符: [人名]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@dataclass
class Entity:
"""识别出的敏感实体"""
text: str # 实体文本内容
entity_type: EntityType # 实体类型
start: int # 开始位置
end: int # 结束位置
confidence: float = 1.0 # 置信度

@dataclass
class MaskResult:
"""脱敏处理结果"""
original_text: str # 原始文本
masked_text: str # 脱敏后文本
entities: list[Entity] # 识别的所有实体

脱敏器继承体系

这种设计的优势:

  • 易于扩展: 新增脱敏方式只需继承 BaseDesensitizer 并实现 recognize_entities()
  • 策略可组合: 四种脱敏策略可独立选择,与识别方式解耦
  • 接口统一: 所有脱敏器对外暴露相同的 desensitize() 接口

核心实现

正则脱敏器

对于 结构化敏感数据(手机号、身份证、银行卡、邮箱),正则匹配是最准确高效的方案:

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
class RegexDesensitizer(BaseDesensitizer):
"""基于正则表达式的脱敏器"""

# 预定义的正则模式
PATTERNS: dict[EntityType, str] = {
EntityType.PHONE: r"1[3-9]\d{9}", # 中国大陆手机号
EntityType.EMAIL: r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
EntityType.ID_CARD: r"\d{17}[\dXx]", # 18位身份证
EntityType.BANK_CARD: r"\d{16,19}", # 银行卡号
}

def recognize_entities(self, text: str) -> list[Entity]:
entities = []
for entity_type, pattern in self.PATTERNS.items():
if entity_type not in self.entity_types:
continue
for match in re.finditer(pattern, text):
entities.append(Entity(
text=match.group(),
entity_type=entity_type,
start=match.start(),
end=match.end(),
confidence=1.0 # 正则匹配置信度为 100%
))
return entities
正则模式详解
类型 正则表达式 说明
手机号 1[3-9]\d{9} 以 1 开头,第二位 3-9,共 11 位
身份证 \d{17}[\dXx] 17 位数字 + 1 位数字或 X
银行卡 \d{16,19} 16-19 位连续数字
邮箱 RFC 5322 简化版 标准邮箱格式

NLP 脱敏器

对于 非结构化敏感数据(人名、地名、组织机构等),借助 NLP 的命名实体识别(NER)能力:

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
class NLPDesensitizer(BaseDesensitizer):
"""基于 PaddleNLP 的脱敏器"""

TAG_MAPPING: dict[str, EntityType] = {
"PER": EntityType.PERSON,
"LOC": EntityType.LOCATION,
"ORG": EntityType.ORGANIZATION,
"TIME": EntityType.TIME,
}

def __init__(self, strategy, entity_types, ner_mode="fast"):
super().__init__(strategy, entity_types)
self._ner = Taskflow("ner", mode=ner_mode)

def recognize_entities(self, text: str) -> list[Entity]:
ner_results = self._ner(text)
entities = []
for item in ner_results:
tag = item.get("type")
if tag in self.TAG_MAPPING:
entity_type = self.TAG_MAPPING[tag]
if entity_type in self.entity_types:
entities.append(Entity(
text=item["text"],
entity_type=entity_type,
start=item["start"],
end=item["end"],
confidence=item.get("probability", 0.9)
))
return entities

NER 模式对比

指标 fast 模式 accurate 模式
模型 BiGRU-CRF (LAC) ERNIE + CRF
大小 ~50MB ~400MB
延迟 <100ms 200-500ms
准确率 ~90% ~95%
内存 ~500MB ~2GB
场景 实时服务 批量处理

组合脱敏器

1
2
3
4
5
6
7
8
9
10
11
12
13
class CompositeDesensitizer(BaseDesensitizer):
"""组合脱敏器: 正则 + NLP"""

def recognize_entities(self, text: str) -> list[Entity]:
# 1. 正则识别 (优先级高,准确率 100%)
regex_entities = self._regex_desensitizer.recognize_entities(text)

# 2. NLP 识别 (补充非结构化数据)
paddle_entities = self._paddle_desensitizer.recognize_entities(text)

# 3. 去重合并 (避免重复识别同一区域)
all_entities = regex_entities + paddle_entities
return self._remove_overlapping(all_entities)

脱敏策略详解

效果: 张三张*三13812345678138****5678

1
2
3
4
def _partial_mask(self, text: str) -> str:
if len(text) <= 2:
return text[0] + "*" * (len(text) - 1)
return text[0] + "*" * (len(text) - 2) + text[-1]

适用场景: 人工审核、演示展示、客户沟通记录

效果: 张三**13812345678***********

1
2
def _full_mask(self, text: str) -> str:
return "*" * len(text)

适用场景: 日志记录、公开数据、合规归档

效果: 张三[人名]13812345678[电话]

1
2
def _placeholder_mask(self, entity: Entity) -> str:
return f"[{entity.entity_type.value}]"

适用场景: 数据分析、NLP 训练数据、统计报表

效果: 张三[4f8b2c1a]

1
2
3
def _hash_mask(self, text: str) -> str:
hash_val = hashlib.md5(text.encode()).hexdigest()[:8]
return f"[{hash_val}]"

适用场景: 数据对账、可追溯场景、A/B 测试

策略选择指南

场景 推荐策略 原因
客服系统日志 部分脱敏 保留可读性,便于问题排查
公开数据报告 完全脱敏 最大化隐私保护
数据分析/BI 占位符 保留语义结构,便于统计
数据血缘追踪 哈希脱敏 可关联原始数据,不可逆推

Docker 部署

多阶段构建

查看完整 Dockerfile
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
# Stage 1: Builder (依赖安装)
FROM --platform=linux/amd64 ghcr.io/astral-sh/uv:python3.10-bookworm-slim AS builder

WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy

RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev --group paddle

# Stage 2: Runtime (运行时)
FROM --platform=linux/amd64 python:3.10-slim AS runtime

RUN useradd -m -u 1000 appuser
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv

ENV FLAGS_use_mkldnn=1
ENV OMP_NUM_THREADS=4

HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:7860/ || exit 1

USER appuser
CMD ["python", "app.py"]

一键部署

1
2
3
git clone https://github.com/daojiAnime/sensitive-data-masking.git
cd sensitive-data-masking
./scripts/start.sh
1
docker compose up -d
1
2
3
4
docker run -d -p 7860:7860 \
-e NER_MODE=fast \
-v ./models:/app/models \
ghcr.io/daojianime/sensitive-data-masking:latest

内存配置建议

模式 最小内存 推荐内存
fast 1GB 2GB
accurate 2GB 4GB

使用示例

Python API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from demo import CompositeDesensitizer, MaskStrategy, EntityType

# 创建组合脱敏器
desensitizer = CompositeDesensitizer(
strategy=MaskStrategy.PARTIAL,
entity_types=[EntityType.PERSON, EntityType.PHONE, EntityType.ID_CARD],
ner_mode="fast"
)

# 处理文本
text = """
客户张三(身份证号:110101199001011234)于2024年1月15日
来电咨询,联系电话:13812345678,邮箱:[email protected]
"""

result = desensitizer.desensitize(text)
print("脱敏后:", result.masked_text)
查看输出结果
1
2
3
4
5
6
7
8
脱敏后: 客户张*三(身份证号:1101011990****1234)于2024年1月15日
来电咨询,联系电话:138****5678,邮箱:[email protected]

识别实体: [
Entity(text='张三', type=PERSON, confidence=0.95),
Entity(text='110101199001011234', type=ID_CARD, confidence=1.0),
Entity(text='13812345678', type=PHONE, confidence=1.0),
]

扩展开发

1
2
3
4
5
6
7
8
9
10
11
# 1. 在 EntityType 枚举中添加新类型
class EntityType(Enum):
...
IP_ADDRESS = "IP地址" # 新增

# 2. 在 RegexDesensitizer 中添加正则模式
class RegexDesensitizer(BaseDesensitizer):
PATTERNS = {
...
EntityType.IP_ADDRESS: r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}",
}
1
2
3
4
5
6
7
8
9
10
# 1. 在 MaskStrategy 枚举中添加新策略
class MaskStrategy(Enum):
...
TRUNCATE = "truncate" # 新增:截断策略

# 2. 在 mask_text() 方法中实现
def mask_text(self, text: str, entity: Entity) -> str:
...
if self.strategy == MaskStrategy.TRUNCATE:
return text[:3] + "..."

总结

核心优势

  1. 识别全面: 覆盖结构化(手机/身份证/银行卡)和非结构化(人名/地名/组织)敏感信息
  2. 策略灵活: 四种脱敏策略适应不同业务场景
  3. 开箱即用: Gradio Web + Docker 一键部署
  4. 易于扩展: 基于设计模式的架构,方便添加新类型和策略

适用场景

  • 企业日志脱敏
  • 客服对话记录处理
  • 数据分析报告生成
  • 合规数据归档
  • NLP 训练数据预处理

未来规划

规划中

  • 支持更多实体类型(车牌号、社保号等)
  • 支持自定义正则规则配置

开发中

  • 支持批量文件处理
  • 支持 API 服务模式

优化中

  • 性能优化(GPU 加速)
Star 项目