这里写目录标题
大量的文本中识别数据,要充分考虑效率和准确率。本文的方案是通过正则和NLP门址模型联合识别的方案。
首先利用现有粗略地址将包含有地址和事由的长文本缩短到短文本,在用正则匹配出地址,然后通过匹配出地址的长度和其他规则发现没有正确识别的地址。对于这部分地址通过MGeo模型高精度实现文本中地址识别地址,然后对识别后的地址做各行间的相似度计算,对于相似的字符串,把出现次数少的替换成出现次数多的。
通过MGeo模型高精度实现文本中地址识别
一、提取地址列后12个字
数据有个粗略地址和详细描述两列,通过提取地址列后12个字,一方面可以去除地址内容之前的数据,减少正则匹配,另一方面为Mgeo nlp模型处理做了截断提高精度和效率。
import pandas as pd
# 文件路径
path = r"D:\data\rs\结果v6.xlsx"
path1 = r"D:\data\rs\结果v620.xlsx"
# 要提取的字符数
N = 20
# 读取数据并处理空值
df = pd.read_excel(path)
df['工单内容'] = df['工单内容'].fillna('').astype(str)
# 定义提取函数
def extract_content(row, n):
try:
address = row['案发地址']
content = row['工单内容']
if not isinstance(address, str) or not isinstance(content, str):
return ''
start = content.find(address)
if start == -1:
return ''
start += len(address)
return content[start:start+n]
except Exception as e:
print(f"Error processing row {row.name}: {e}")
return ''
# 使用 lambda 把参数 n 传进去
df['提取内容'] = df.apply(lambda x: extract_content(x, N), axis=1)
# 保存结果
df.to_excel(path1, index=False)
二、正则表达式删除不需要的文本
删除 匹配目标词 + 后续内容
1、数字 及之后的去掉(目的是去掉xx楼)
2、"*" 及之后的去掉(目的是去掉xx楼)
3、把A-Z字母及之后的去掉(目的是去掉xx小区A区)
4、"村的住户"、"的村民"字符串及之后去掉(后面肯定不是地址)
5、"(无门牌号"及之后的去掉
6、","及后面的去掉
7、"的住户"和"住户"和"的业主"及之后面去掉
8、"回迁楼"、"民房"及后面的去掉
9、"最"及后面的去掉(应为后面是最东边之类的)
10、"号楼"及后面的去掉
11、删除 "("及其后的内容
12、 删除 "的"及其后的内容
13、 删除 "附近"及其后的内容
14、删除 三期 关键字及之后的 其中三 可以替换为任意的汉字数字(注意:由于这条规则是要匹配 期 前面的一个变量,非常容易错,使用贪婪匹配会将前面联系的汉字都匹配进去,使用非贪婪只能匹配到 期 一个字)
text = re.sub(r'([一二三四五六七八九十十一十二十三十四十五十六十七十八十九二十])期', '小区', text)
15 . 删除 "村民、居民、人"及其后的内容
三、保留关键字并删除之后的字
保留关键字"小区"并删除之后的内容
四、正则逻辑优化
-
优先保留"小区"并删除之后的,(防止被后续规则误删)
-
处理特定结构(村的村民、住户,回迁楼等)
-
处理期数(三期),将 三期替换为小区(不能删除之后的,因为口语中可能将小区用xx期表示)
-
符号处理(*,逗号,括号)
-
处理电话、安装、供暖等关键词
-
处理住户、业主、村民、民房等
-
处理最、的、附近等
-
处理数字、字母
调整原则:
减少误删除,先处理最确定的原则,缩小文本长度,减少后面规则匹配上的概率,避免误删除
- 优先保留小区信息 (防止被后续规则误删)
具体模式优先:越具体的匹配规则越先执行
格式清理前置:符号类清理早于文字内容清理
保留操作最后:关键保留操作放在处理链末端
通用模式置后:宽泛的.*模式放在处理链后部
#
import pandas as pd
import re
# 文件路径
path = r"D:\data\rs\结果v620.xlsx"
output_path = r"D:\data\rs\结果v620_地址清洗_v3.xlsx"
# 读取数据
df = pd.read_excel(path)
# 复制原始列用于处理
df['提取地址'] = df['提取内容'].astype(str).copy()
def clean_address(text):
text = str(text)
# 0.清理期数描述(三期、四期等),这个容易错,因为是向前匹配容易把之前联系的小区名匹配进去并替换掉
text = re.sub(r'([一二三四五六七八九十十一十二十三十四十五十六十七十八十九二十])期', '小区', text)
# 0 。保留小区 (防止被后续规则误删)
text = re.sub(r'小区.*', '小区', text)
# 1. 特定结构清理(村、回迁楼等)
text = re.sub(r'的村民.*', '', text)
text = re.sub(r'(无门牌号.*', '', text)
text = re.sub(r'回迁楼.*', '', text)
# 3. 特殊符号清理(*,逗号,括号)
text = re.sub(r'\*.*', '', text)
text = re.sub(r',.*', '', text)
text = re.sub(r'(.*', '', text)
# 4. 业务相关关键词清理(安装、供暖、电话等)
text = re.sub(r'安装.*', '', text)
text = re.sub(r'供暖.*', '', text)
text = re.sub(r'做.*', '', text)
text = re.sub(r'电话.*', '', text)
text = re.sub(r'租.*', '', text)
text = re.sub(r'名称.*', '', text)
# 5. 清理住户/业主/居民描述
text = re.sub(r'(的住户|住户|的业主).*', '', text)
text = re.sub(r'村民.*', '', text)
text = re.sub(r'居民.*', '', text)
text = re.sub(r'人.*', '', text)
text = re.sub(r'附近.*', '', text)
text = re.sub(r'民房.*', '', text)
# 6. 通用描述清理(最、的)
text = re.sub(r'最.*', '', text)
text = re.sub(r'的.*', '', text) # 注意这个模式较宽泛,放在后面
# 7. 基础元素清理(数字、字母)
text = re.sub(r'\d+.*', '', text)
text = re.sub(r'[A-Za-z].*', '', text)
return text.strip()
# 应用清理函数
df['提取地址'] = df['提取地址'].apply(clean_address)
# 保存结果
df.to_excel(output_path, index=False)
print("地址清洗完成,已保存至:", output_path)
优化补充:
1、对村村 替换为村 将 小区小区(xx小区三期替换来的) 替换为 小区
四、相似度计算,查重
不适用编辑距离,太慢。利用行政区分组减少对比空间,然后用近似算法降低计算量。
通过MinHash+LSH技术高效检测地址相似性。首先清洗地址文本并提取字符级N-Gram特征,每个地址转换为MinHash签名。按行政区划建立局部敏感哈希(LSH)索引,将相似度超过阈值的地址映射到相同哈希桶。通过多进程并行处理各行政区,对每个地址查询LSH获得候选集,排除自身后生成相似地址组。最终输出结构化的"行政区-原地址-相似地址列表"结果,实现大规模地址数据的快速相似聚类。
#
import pandas as pd
import re
from datasketch import MinHash, MinHashLSH
import multiprocessing as mp
# --------------------------------------
# 配置参数
# --------------------------------------
INPUT_PATH = r"D:\data\rs\合并结果12.xlsx"
ADDR_COLUMN = '具体地址' # 要处理的详细地址列
REGION_COLUMN = '案发地址' # 行政区划列
OUTPUT_PATH = r"D:\data\rs\d.xlsx"
N_GRAM = 3 # N-Gram长度
THRESHOLD = 0.7 # 相似度阈值
NUM_PERM = 128 # MinHash精度参数
# --------------------------------------
# 预处理函数
# --------------------------------------
def preprocess(text):
"""地址标准化"""
text = re.sub(r'[^\w\u4e00-\u9fff]', '', text) # 去除非中文字符
return text.strip()
def generate_ngrams(text, n=3):
"""生成字符级N-Gram"""
return [text[i:i+n] for i in range(len(text)-n+1)]
# --------------------------------------
# 核心处理函数(每个行政区独立处理)
# --------------------------------------
def process_region(region_data):
"""处理单个行政区的地址相似性"""
region_name, addresses = region_data
lsh = MinHashLSH(threshold=THRESHOLD, num_perm=NUM_PERM)
address_dict = {}
minhash_dict = {} # 存储MinHash对象
results = []
# 构建当前行政区的LSH索引
for idx, addr in enumerate(addresses):
processed = preprocess(addr)
ngrams = generate_ngrams(processed, N_GRAM)
mh = MinHash(num_perm=NUM_PERM)
for gram in ngrams:
mh.update(gram.encode('utf-8'))
lsh.insert(idx, mh)
address_dict[idx] = addr
minhash_dict[idx] = mh # 保存MinHash对象
# 查询相似对(使用正确的MinHash对象)
for idx in address_dict:
mh = minhash_dict[idx]
candidates = lsh.query(mh)
candidates = [c for c in candidates if c != idx]
if candidates:
original = address_dict[idx]
similars = list(set(address_dict[c] for c in candidates))
results.append((region_name, original, ', '.join(similars)))
return results
# --------------------------------------
# 主流程
# --------------------------------------
if __name__ == '__main__':
# 读取数据并按行政区划分组
df = pd.read_excel(INPUT_PATH)
df[ADDR_COLUMN] = df[ADDR_COLUMN].astype(str)
grouped = df.groupby(REGION_COLUMN)[ADDR_COLUMN].unique()
# 多进程并行处理每个行政区
with mp.Pool(mp.cpu_count()) as pool:
all_results = pool.map(process_region, grouped.items())
# 整合结果
final_data = []
for region_results in all_results:
final_data.extend(region_results)
# 保存结果
result_df = pd.DataFrame(final_data,
columns=['行政区', '原地址', '相似地址列表'])
result_df.to_excel(OUTPUT_PATH, index=False)
print(f"处理完成!发现相似组: {len(final_data):,}")
五、去重
要求生成一张新表,
d表是上一部生产的相似地址清单表。合并结果12表是地址源表。
1、读取"D:\data\rs\后处理\合并结果12.xlsx" ,统计具体地址列每个内容重复的个数
2、读取"D:\data\rs\后处理\d.xlsx" 里面有原地址和相似地址列表
3、对"D:\data\rs\后处理\d.xlsx" 每一行,读取"相似地址列表"列形成成一个列表,对列表中的每个元素 与"D:\data\rs\后处理\d.xlsx" 原地址列的内容进行比较,比较的原则为 "D:\data\rs\后处理\合并结果12.xlsx"的这两个元素的重复个数
4、在"D:\data\rs\后处理\合并结果12.xlsx"中将重复个数少的替换成多的。
5、遍历"D:\data\rs\后处理\d.xlsx" 每一行,并在"D:\data\rs\后处理\合并结果12.xlsx"完成替换后形成新表
数据量大使用polars库来进行
import polars as pl
def process_data():
# 读取原始数据并统计频次
original_df = pl.read_excel(r"D:\data\rs\后处理\合并结果12.xlsx")
count_df = original_df.group_by("具体地址").agg(pl.len().alias("出现次数"))
# 读取相似地址表
similar_df = pl.read_excel(r"D:\data\rs\后处理\d.xlsx").with_columns(
pl.col("相似地址列表").str.split(", ")
)
# 创建地址频次字典
count_dict = dict(zip(count_df["具体地址"], count_df["出现次数"]))
# 生成替换规则
replacement_rules = {}
for row in similar_df.iter_rows(named=True):
# 获取当前组所有地址
group_addresses = [row["原地址"]] + row["相似地址列表"]
# 排除不存在的地址
valid_addresses = [addr for addr in group_addresses if addr in count_dict]
if not valid_addresses:
continue
# 找出频次最高的地址
max_count = max(count_dict[addr] for addr in valid_addresses)
candidates = [addr for addr in valid_addresses if count_dict[addr] == max_count]
target = candidates[0] # 频次相同取第一个
# 生成替换规则
for addr in group_addresses:
if addr == target or addr not in count_dict:
continue
# 保留最大频次规则
current_rule = replacement_rules.get(addr, addr)
current_count = count_dict.get(current_rule, 0)
if count_dict[target] > current_count:
replacement_rules[addr] = target
# 应用替换规则
new_df = original_df.with_columns(
pl.col("具体地址").replace(replacement_rules, default=pl.col("具体地址"))
)
# 保存结果
new_df.write_excel(r"D:\data\rs\后处理\合并结果_标准化.xlsx")
print("处理完成,生成标准化地址表")
if __name__ == "__main__":
process_data()