主要作用
地址信息解析工具是一款专为处理中文地址信息设计的智能数据处理软件。它能够自动从复杂的地址文本中提取姓名、电话号码、省份、城市、区县和街道信息,同时支持商家编码匹配和入库状态标记功能。
功能
1. 智能地址解析
使用方法

22222.png (49.35 KB, 下载次数: 0)
下载附件
2025-8-26 12:21 上传
目前这个工具还是有很多问题,姓名提取不是百分百准确,大家有更好的方法可以指导下
下载地址链接:
https://www.123865.com/s/rtfeVv-1qZBH提取码:sjDS
[Python] 纯文本查看 复制代码#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
一次性完成:
1. 智能解析姓名、电话、省市区街道
2. 根据【备注】匹配商家编码(无法确定留空)
3. 根据【寄回单号】标记入库情况
4. 自动学习并存储确认正确的姓名,提高后续识别准确率
[mw_shl_code=python,false]#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
一次性完成:
1. 智能解析姓名、电话、省市区街道
2. 根据【备注】匹配商家编码(无法确定留空)
3. 根据【寄回单号】标记入库情况
4. 自动学习并存储确认正确的姓名,提高后续识别准确率
"""
import re
import pandas as pd
import os
import sys
import jieba
import jieba.posseg as pseg
from typing import Dict, Any, Set, Optional, List, Tuple
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import threading
from datetime import datetime
import json
from collections import defaultdict
import configparser
# 初始化姓名学习系统
class NameLearningSystem:
def __init__(self, dict_file="chinese_names.dic", confirmed_file="confirmed_names.json"):
self.dict_file = dict_file
self.confirmed_file = confirmed_file
self.confirmed_names = defaultdict(int) # 存储确认的姓名及其出现次数
# 加载已确认的姓名
self.load_confirmed_names()
# 初始化jieba分词器
self.init_jieba()
def init_jieba(self):
"""初始化jieba分词器,加载自定义词典"""
# 加载基础词典
if os.path.exists(self.dict_file):
jieba.load_userdict(self.dict_file)
# 添加已确认的姓名到jieba词典
for name in self.confirmed_names:
jieba.add_word(name, freq=1000, tag='nr')
def load_confirmed_names(self):
"""加载已确认的姓名"""
if os.path.exists(self.confirmed_file):
try:
with open(self.confirmed_file, 'r', encoding='utf-8') as f:
self.confirmed_names = defaultdict(int, json.load(f))
print(f"已加载 {len(self.confirmed_names)} 个确认姓名")
except Exception as e:
print(f"加载确认姓名时出错: {e}")
def save_confirmed_names(self):
"""保存已确认的姓名"""
try:
with open(self.confirmed_file, 'w', encoding='utf-8') as f:
json.dump(dict(self.confirmed_names), f, ensure_ascii=False, indent=2)
print(f"已保存 {len(self.confirmed_names)} 个确认姓名")
except Exception as e:
print(f"保存确认姓名时出错: {e}")
def add_confirmed_name(self, name):
"""添加一个确认正确的姓名"""
if name and len(name) >= 1: # 允许单个字的姓名
self.confirmed_names[name] += 1
# 添加到jieba词典
jieba.add_word(name, freq=1000, tag='nr')
print(f"已添加确认姓名: {name} (出现次数: {self.confirmed_names[name]})")
def get_top_names(self, n=50):
"""获取出现次数最多的前n个姓名"""
return sorted(self.confirmed_names.items(), key=lambda x: x[1], reverse=True)[:n]
# 初始化姓名学习系统
name_learner = NameLearningSystem()
# -------------------- 1. 地址解析 --------------------
# 预编译正则表达式以提高性能
PHONE_PATTERN = re.compile(r'1[3-9]\d{9}|(?:\d{3,4}-)?\d{7,8}')
# 中国所有省份、直辖市、自治区
PROVINCES = [
'北京市', '天津市', '上海市', '重庆市', '河北省', '山西省', '辽宁省', '吉林省', '黑龙江省',
'江苏省', '浙江省', '安徽省', '福建省', '江西省', '山东省', '河南省', '湖北省', '湖南省',
'广东省', '海南省', '四川省', '贵州省', '云南省', '陕西省', '甘肃省', '青海省', '台湾省',
'内蒙古自治区', '广西壮族自治区', '西藏自治区', '宁夏回族自治区', '新疆维吾尔自治区',
'香港特别行政区', '澳门特别行政区'
]
# 常见城市
CITIES = [
'北京', '上海', '天津', '重庆', '石家庄', '太原', '沈阳', '长春', '哈尔滨', '南京', '杭州',
'合肥', '福州', '南昌', '济南', '郑州', '武汉', '长沙', '广州', '深圳', '南宁', '海口', '成都',
'贵阳', '昆明', '西安', '兰州', '西宁', '台北', '呼和浩特', '拉萨', '银川', '乌鲁木齐'
]
# 常见区县关键词
DISTRICT_KEYWORDS = ['区', '县', '市', '新区', '开发区', '园区']
# 常见街道关键词
STREET_KEYWORDS = ['街道', '路', '街', '大道', '巷', '弄', '胡同', '号', '栋', '单元', '楼', '室']
# 常见地址关键词(需要过滤掉的)
ADDRESS_KEYWORDS = [
'公司', '医院', '酒店', '商店', '饭店', '餐馆', '银行', '学校', '工厂', '部门', '小区', '大厦',
'广场', '中心', '花园', '店铺', '超市', '商场', '市场', '公园', '村', '镇', '乡', '地址'
]
# 常见姓氏
COMMON_SURNAMES = [
'王', '李', '张', '刘', '陈', '杨', '赵', '黄', '周', '吴', '徐', '孙', '胡', '朱', '高', '林',
'何', '郭', '马', '罗', '梁', '宋', '郑', '谢', '韩', '唐', '冯', '于', '董', '萧', '程', '曹',
'袁', '邓', '许', '傅', '沈', '曾', '彭', '吕', '苏', '卢', '蒋', '蔡', '贾', '丁', '魏', '薛',
'叶', '阎', '余', '潘', '杜', '戴', '夏', '钟', '汪', '贺', '伽', '栗', '龙', '姜', '雪', '冉'
]
# 姓名关键词
NAME_KEYWORDS = [
'收件人', '收货人', '姓名', '联系人', '收', '先生', '女士', '小姐', '老师', '大姐', '师傅'
]
def extract_phone(text: str) -> str:
"""提取电话号码"""
phone_match = PHONE_PATTERN.search(text)
return phone_match.group(0) if phone_match else ""
def extract_province(text: str) -> str:
"""提取省份"""
for province in PROVINCES:
if province in text:
return province
return ""
def extract_city(text: str, province: str) -> str:
"""提取城市"""
# 如果省份是直辖市,则城市就是省份
if province in ['北京市', '天津市', '上海市', '重庆市']:
return province.replace('市', '')
# 查找城市
for city in CITIES:
if city in text:
return city
return ""
def extract_district(text: str) -> str:
"""提取区县"""
# 使用正则表达式匹配区县名称
district_pattern = r'([\u4e00-\u9fa5]+?[区县市])'
matches = re.findall(district_pattern, text)
for match in matches:
# 排除省份和城市
if match not in PROVINCES and match not in CITIES:
return match
return ""
def extract_street(text: str) -> str:
"""提取街道地址"""
# 移除已经提取的省份、城市、区县
cleaned_text = text
# 查找街道关键词
street_parts = []
for keyword in STREET_KEYWORDS:
pattern = r'([\u4e00-\u9fa5\d]+?' + re.escape(keyword) + ')'
matches = re.findall(pattern, cleaned_text)
street_parts.extend(matches)
return ' '.join(street_parts)
def is_likely_name(text: str) -> bool:
"""判断文本是否可能是姓名"""
if not text:
return False
# 如果是已确认的姓名,直接返回True
if text in name_learner.confirmed_names:
return True
# 检查是否是单个中文字符或英文字母
if len(text) == 1:
# 单个中文字符必须是常见姓氏
if re.match(r'^[\u4e00-\u9fa5]$', text) and text in COMMON_SURNAMES:
return True
# 单个英文字母
if re.match(r'^[a-zA-Z]$', text):
return True
return False
# 检查是否是英文名
if re.match(r'^[a-zA-Z\s]+$', text):
return True
# 检查是否是中文姓名(2-4个汉字)
if not re.match(r'^[\u4e00-\u9fa5]{1,4}$', text):
return False
# 检查是否包含地址关键词
for keyword in ADDRESS_KEYWORDS:
if keyword in text:
return False
# 检查首字符是否是常见姓氏
if text[0] in COMMON_SURNAMES:
return True
return False
def extract_name_directly(text: str) -> str:
"""直接从文本中提取姓名"""
# 尝试使用jieba提取姓名
words = pseg.cut(text)
for word, flag in words:
if flag == 'nr' and is_likely_name(word):
return word
# 尝试使用正则表达式匹配姓名
name_patterns = [
r'(?:收件人|收货人|姓名|联系人)[::]\s*([\u4e00-\u9fa5a-zA-Z]{1,20})',
r'([\u4e00-\u9fa5a-zA-Z]{1,20})[\s]*(?:收|先生|女士|小姐|老师|大姐|师傅)',
r'(?:先生|女士|小姐|老师|大姐|师傅)[::]\s*([\u4e00-\u9fa5a-zA-Z]{1,20})',
r'([\u4e00-\u9fa5a-zA-Z]{1,20})[\s]*1[3-9]\d{9}',
r'1[3-9]\d{9}[\s]*([\u4e00-\u9fa5a-zA-Z]{1,20})',
r'^([\u4e00-\u9fa5a-zA-Z]{1,20})[\s]*(?:,|,|\.|。)',
r'(?:,|,|\.|。)[\s]*([\u4e00-\u9fa5a-zA-Z]{1,20})[\s]*$',
]
for pattern in name_patterns:
match = re.search(pattern, text, re.IGNORECASE)
if match:
candidate = match.group(1).strip()
if is_likely_name(candidate):
return candidate
return ""
def extract_name_by_removing_parts(text: str) -> str:
"""通过移除其他部分来提取姓名"""
# 先移除电话号码
phone = extract_phone(text)
if phone:
text = text.replace(phone, '')
# 移除省份
province = extract_province(text)
if province:
text = text.replace(province, '')
# 移除城市
city = extract_city(text, province)
if city:
text = text.replace(city, '')
# 移除区县
district = extract_district(text)
if district:
text = text.replace(district, '')
# 移除街道
street = extract_street(text)
if street:
text = text.replace(street, '')
# 移除地址关键词
for keyword in ADDRESS_KEYWORDS:
text = text.replace(keyword, '')
# 移除特殊字符和数字
text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z]', ' ', text)
text = re.sub(r'\s+', ' ', text).strip()
# 尝试提取可能的姓名
words = text.split()
for word in words:
if is_likely_name(word):
return word
return ""
def extract_name(text: str) -> str:
"""提取姓名"""
# 先尝试直接提取
name = extract_name_directly(text)
if name:
return name
# 如果直接提取失败,尝试通过移除其他部分来提取
return extract_name_by_removing_parts(text)
def parse_address(text: str) -> Dict[str, Any]:
"""解析地址文本,提取姓名、电话和地址信息"""
if not isinstance(text, str):
text = str(text) if text is not None else ''
original_text = text
# 1. 提取电话
phone = extract_phone(text)
# 2. 提取姓名
name = extract_name(text)
# 3. 提取省份
province = extract_province(text)
# 4. 提取城市
city = extract_city(text, province)
# 5. 提取区县
district = extract_district(text)
# 6. 提取街道地址
street = extract_street(text)
# 构建完整的地址
address_parts = []
if province:
address_parts.append(province)
if city:
address_parts.append(city)
if district:
address_parts.append(district)
if street:
address_parts.append(street)
full_address = ' '.join(address_parts)
return {
"姓名": name,
"电话": phone,
"省份": province,
"城市": city,
"区县": district,
"街道": street,
"完整地址": full_address,
"原始文本": original_text
}
# -------------------- 2. 商家编码匹配 --------------------
CODE_PATTERN = re.compile(r'[A-Z0-9]+', re.I)
def build_code_dict(code_df: pd.DataFrame) -> Dict[str, str]:
"""构建商家编码映射字典"""
mapping = {}
for _, row in code_df.iterrows():
code = str(row.get("商家编码", "")).strip()
if not code:
continue
for cell in row.astype(str):
for key in CODE_PATTERN.findall(str(cell).upper()):
if key and key not in mapping:
mapping[key] = code
return mapping
def match_code(df: pd.DataFrame, code_dict: Dict[str, str]) -> pd.DataFrame:
"""在备注列中匹配商家编码"""
def lookup(remark):
if pd.isna(remark):
return None
for token in CODE_PATTERN.findall(str(remark).upper()):
if token in code_dict:
return code_dict[token]
return None
if "备注" in df.columns:
df["商家编码"] = df["备注"].apply(lookup)
else:
print("⚠️ 警告:数据文件中没有找到'备注'列,跳过商家编码匹配")
df["商家编码"] = None
return df
# -------------------- 3. 入库情况标记 --------------------
def get_inbound_set(inbound_df: pd.DataFrame) -> Set[str]:
"""获取所有入库单号的集合"""
return set(inbound_df.astype(str).stack().str.strip().dropna())
def mark_inbound(df: pd.DataFrame, inbound_set: Set[str]) -> pd.DataFrame:
"""标记入库情况"""
if "寄回单号" in df.columns:
df["入库情况"] = df["寄回单号"].apply(
lambda x: "已入库" if (pd.notna(x) and str(x).strip() in inbound_set) else "未入库"
)
else:
print("⚠️ 警告:数据文件中没有找到'寄回单号'列,跳过入库标记")
df["入库情况"] = "未知"
return df
# -------------------- 4. 处理函数 --------------------
def process_files(input_file, code_file, inbound_file, address_col, progress_callback=None):
"""处理文件的主函数"""
try:
# 读取数据
df = pd.read_excel(input_file)
code_df = pd.read_excel(code_file)
inbound_df = pd.read_excel(inbound_file)
except Exception as e:
return False, f"读取文件时出错:{e}", None
if progress_callback:
progress_callback(10, "开始解析地址信息...")
# 解析地址列
target_col = address_col
if target_col >= len(df.columns):
return False, f"错误:指定的地址列索引 {target_col} 超出范围(0-{len(df.columns)-1})", None
# 添加原始文本列以便调试
df["原始地址文本"] = df.iloc[:, target_col]
# 解析地址
total_rows = len(df)
parsed_results = []
for i, text in enumerate(df.iloc[:, target_col]):
parsed_results.append(parse_address(text))
if progress_callback and i % 10 == 0:
progress = 10 + int(70 * i / total_rows)
progress_callback(progress, f"解析地址信息... ({i}/{total_rows})")
parsed_df = pd.DataFrame(parsed_results)
df["解析姓名"] = parsed_df["姓名"]
df["解析电话"] = parsed_df["电话"]
df["解析省份"] = parsed_df["省份"]
df["解析城市"] = parsed_df["城市"]
df["解析区县"] = parsed_df["区县"]
df["解析街道"] = parsed_df["街道"]
df["解析地址"] = parsed_df["完整地址"]
if progress_callback:
progress_callback(80, "匹配商家编码...")
# 匹配商家编码
code_dict = build_code_dict(code_df)
df = match_code(df, code_dict)
if progress_callback:
progress_callback(90, "标记入库情况...")
# 标记入库情况
inbound_set = get_inbound_set(inbound_df)
df = mark_inbound(df, inbound_set)
# 生成输出文件名
input_dir = os.path.dirname(input_file)
input_name = os.path.splitext(os.path.basename(input_file))[0]
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = os.path.join(input_dir, f"{input_name}_处理完成_{timestamp}.xlsx")
# 保存结果
try:
df.to_excel(output_file, index=False)
# 自动学习提取的姓名
learned_count = 0
for name in df["解析姓名"].dropna().unique():
if isinstance(name, str) and (re.match(r'^[\u4e00-\u9fa5]{1,4}$', name) or re.match(r'^[a-zA-Z\s]+$', name)):
name_learner.add_confirmed_name(name)
learned_count += 1
# 保存确认的姓名
name_learner.save_confirmed_names()
return True, f"处理完成,结果已保存为:{output_file}", output_file
except Exception as e:
return False, f"保存文件时出错:{e}", None
# -------------------- 5. 配置文件管理 --------------------
def load_config():
"""加载配置文件"""
config = configparser.ConfigParser()
if os.path.exists('config.ini'):
config.read('config.ini', encoding='utf-8')
return config
def save_config(config):
"""保存配置文件"""
with open('config.ini', 'w', encoding='utf-8') as configfile:
config.write(configfile)
# -------------------- 6. GUI界面 --------------------
class AddressParserApp:
def __init__(self, root):
self.root = root
self.root.title("地址信息解析工具")
self.root.geometry("600x450")
# 文件路径变量
self.input_file = tk.StringVar()
self.code_file = tk.StringVar()
self.inbound_file = tk.StringVar()
self.address_col = tk.IntVar(value=2) # 默认地址列索引为2
# 加载配置文件
self.config = load_config()
if 'FILES' in self.config:
if 'code_file' in self.config['FILES']:
self.code_file.set(self.config['FILES']['code_file'])
if 'inbound_file' in self.config['FILES']:
self.inbound_file.set(self.config['FILES']['inbound_file'])
if 'address_col' in self.config['FILES']:
self.address_col.set(int(self.config['FILES']['address_col']))
# 最近处理完成的文件
self.last_output_file = None
# 创建界面
self.create_widgets()
def create_widgets(self):
# 主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 输入文件选择
ttk.Label(main_frame, text="数据文件:").grid(row=0, column=0, sticky=tk.W, pady=5)
ttk.Entry(main_frame, textvariable=self.input_file, width=50).grid(row=0, column=1, padx=5)
ttk.Button(main_frame, text="浏览", command=self.browse_input_file).grid(row=0, column=2)
# 编码文件选择
ttk.Label(main_frame, text="编码文件:").grid(row=1, column=0, sticky=tk.W, pady=5)
ttk.Entry(main_frame, textvariable=self.code_file, width=50).grid(row=1, column=1, padx=5)
ttk.Button(main_frame, text="浏览", command=self.browse_code_file).grid(row=1, column=2)
# 入库文件选择
ttk.Label(main_frame, text="入库文件:").grid(row=2, column=0, sticky=tk.W, pady=5)
ttk.Entry(main_frame, textvariable=self.inbound_file, width=50).grid(row=2, column=1, padx=5)
ttk.Button(main_frame, text="浏览", command=self.browse_inbound_file).grid(row=2, column=2)
# 地址列设置
ttk.Label(main_frame, text="地址列索引:").grid(row=3, column=0, sticky=tk.W, pady=5)
ttk.Spinbox(main_frame, from_=0, to=20, textvariable=self.address_col, width=10).grid(row=3, column=1, sticky=tk.W, padx=5)
ttk.Label(main_frame, text="(从0开始)").grid(row=3, column=1, sticky=tk.E, padx=5)
# 进度条
self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=500, mode='determinate')
self.progress.grid(row=4, column=0, columnspan=3, pady=10, sticky=(tk.W, tk.E))
# 状态标签
self.status_label = ttk.Label(main_frame, text="准备就绪")
self.status_label.grid(row=5, column=0, columnspan=3, pady=5)
# 按钮框架
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=6, column=0, columnspan=3, pady=10)
ttk.Button(button_frame, text="开始处理", command=self.start_processing).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="打开已完成文件", command=self.open_output_file).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="退出", command=self.root.quit).pack(side=tk.LEFT, padx=5)
# 配置网格权重
main_frame.columnconfigure(1, weight=1)
def browse_input_file(self):
filename = filedialog.askopenfilename(title="选择数据文件", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")])
if filename:
self.input_file.set(filename)
def browse_code_file(self):
filename = filedialog.askopenfilename(title="选择编码文件", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")])
if filename:
self.code_file.set(filename)
# 保存到配置文件
if 'FILES' not in self.config:
self.config['FILES'] = {}
self.config['FILES']['code_file'] = filename
save_config(self.config)
def browse_inbound_file(self):
filename = filedialog.askopenfilename(title="选择入库文件", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")])
if filename:
self.inbound_file.set(filename)
# 保存到配置文件
if 'FILES' not in self.config:
self.config['FILES'] = {}
self.config['FILES']['inbound_file'] = filename
save_config(self.config)
def update_progress(self, value, message):
self.progress['value'] = value
self.status_label['text'] = message
self.root.update_idletasks()
def start_processing(self):
# 验证输入
if not all([self.input_file.get(), self.code_file.get(), self.inbound_file.get()]):
messagebox.showerror("错误", "请选择所有必要的文件")
return
# 保存地址列索引到配置文件
if 'FILES' not in self.config:
self.config['FILES'] = {}
self.config['FILES']['address_col'] = str(self.address_col.get())
save_config(self.config)
# 禁用按钮,防止重复点击
self.progress['value'] = 0
self.status_label['text'] = "开始处理..."
# 在新线程中处理文件,避免界面卡死
thread = threading.Thread(target=self.process_files_thread)
thread.daemon = True
thread.start()
def process_files_thread(self):
try:
success, message, output_file = process_files(
self.input_file.get(),
self.code_file.get(),
self.inbound_file.get(),
self.address_col.get(),
self.update_progress
)
if success:
self.last_output_file = output_file
self.update_progress(100, message)
messagebox.showinfo("成功", message)
else:
self.update_progress(0, "处理失败")
messagebox.showerror("错误", message)
except Exception as e:
self.update_progress(0, "处理失败")
messagebox.showerror("错误", f"处理过程中发生错误: {str(e)}")
def open_output_file(self):
"""打开已完成文件"""
if self.last_output_file and os.path.exists(self.last_output_file):
try:
os.startfile(self.last_output_file) # Windows
except:
try:
# macOS
os.system(f'open "{self.last_output_file}"')
except:
try:
# Linux
os.system(f'xdg-open "{self.last_output_file}"')
except:
messagebox.showerror("错误", "无法打开文件,请手动打开")
else:
# 如果没有最近处理的文件,让用户选择要打开的文件
filename = filedialog.askopenfilename(
title="选择要打开的文件",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")],
initialdir=os.path.dirname(self.input_file.get()) if self.input_file.get() else "."
)
if filename:
try:
os.startfile(filename) # Windows
except:
try:
# macOS
os.system(f'open "{filename}"')
except:
try:
# Linux
os.system(f'xdg-open "{filename}"')
except:
messagebox.showerror("错误", "无法打开文件,请手动打开")
# -------------------- 7. 主函数 --------------------
def main():
# 直接启动GUI模式
root = tk.Tk()
app = AddressParserApp(root)
root.mainloop()
if __name__ == "__main__":
main()