Elasticsearch7.6 之地址解析案例
一、背景
公司内部网络, 内部物流系统提出需求,通过用户任意输入的地址,自动解析出"省份"-“地市”-“区县”,以及对应的区划代码。
问题分析:
- 用户输入地址信息并不标准,无法直接通过数据库检索
- 处于内网,无法直接通过互联网获取解析结果
- 需要最新的区划数据
针对以上场景,解决办法就是搭建一个内部的地址解析环境
方案:
1.爬取最新的区划数据
2.使用Elasticsearch7.6,+IK分词器 ,对区划数据做索引文档。并封装调用接口,开放使用。
二、区划数据获取
参考
链接: Python 爬虫 中国行政区划信息爬取.
三、数据准备和写入
平时寄送快递的习惯都会要输入:省市区县+地址信息,因此创建的地址索引内容 也应该包含基础的省市区县甚至街道信息。事例中的地址名称都是标准的行政区划中的名称,也是我们从数据库中获取的数据。
eg:
山东省(省)淄博市(市)沂源县(区县)南鲁山镇(乡镇)平地村委会(村)
最终封装后的数据模型如下:
{ "address": "山东省淄博市沂源县南鲁山镇上土门村委会",
"country": "China",
"countrycode": "0",
"province": "山东省",
"provincecode": "370000",
"city": "淄博市",
"citycode": "370300",
"district": "沂源县",
"districtcode": "370323",
"xiang": "南鲁山镇",
"xiangcode": "370323111000",
"cun": "上土门村委会",
"cuncode": "370323111213"
}
向ES 写入的数据时,就是对address字段做分词索引,实现模糊查询。
3.1 创建索引库
根据数据模型,创建索引库如下: http://127.0.0.1:9200/area_index
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"address":{"type":"text", "analyzer":"ik_smart"},
"country":{"type":"text"},
"countrycode": {"type":"text"},
"province":{"type":"text"},
"provincecode": {"type":"text"},
"city":{"type":"text"},
"citycode": {"type":"text"},
"district":{"type":"text"},
"districtcode": {"type":"text"},
"xiang":{"type":"text"},
"xiangcode": {"type":"text"},
"cun":{"type":"text"},
"cuncode": {"type":"text"}
}
}
}
分词模式使用 ik_smart ,不要使用ik_max_word(拆分的太细了),也只用对address字段做分词。
3.2 数据写入
地址信息数据提前已经导入到了数据库,根据区划的父子关系封装为 (省)-(市)-(区县)南鲁-(乡镇)-(村) 类型的基础视图,这里使用Python脚本将数据从数据库写入到ES中
相关依赖:elasticsearch,sqlalchemy,cx_Oracle
3.2.1 数据库连接脚本
文件名:OracleDb.py
from sqlalchemy import create_engine
from sqlalchemy import MetaData,Table
from sqlalchemy.orm import sessionmaker
class OracleDb:
def __init__(self, url):
self.__url = url
self.__engine = create_engine("oracle://" + url + "")
self.__session = sessionmaker(self.__engine)
def findAll(self,mapper):
db_session = self.__session()
list= db_session.query(mapper).all()
db_session.close()
return list
3.2.2 ES连接
文件名: EsConnect.py
from elasticsearch import Elasticsearch
class EsConn:
def __init__(self,ip,port):
self.__ip=ip
self.__port=port
self.__db=Elasticsearch([{'host':ip,'port':port}])
@property
def db(self):
return self.__db
3.2.3 ORM映射
t_mv_areainfo_china表,我本地转化好待插入的数据, mapper.py 中的一个对象TMvAreainfoChina,
注:t_mv_areainfo_china 必须得有主键,该文件 通过sqlacodegen.main 生成,其中的getDic()方法自己封装,导入ES时有用。
文件名:mapper.py
# coding: utf-8
from sqlalchemy import Column, DateTime, VARCHAR,CHAR
from sqlalchemy.dialects.oracle import NUMBER
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects.oracle import RAW
Base = declarative_base()
metadata = Base.metadata
class TMvAreainfoChina(Base):
__tablename__ = 't_mv_areainfo_china'
rid = Column(VARCHAR(64), primary_key=True)
id = Column(VARCHAR(320))
address = Column(VARCHAR(1000))
country = Column(CHAR(5))
countrycode = Column(CHAR(1))
province = Column(VARCHAR(200))
provincecode = Column(VARCHAR(64))
city = Column(VARCHAR(200))
citycode = Column(VARCHAR(64))
district = Column(VARCHAR(200))
districtcode = Column(VARCHAR(64))
xiang = Column(VARCHAR(200))
xiangcode = Column(VARCHAR(64))
cun = Column(VARCHAR(200))
cuncode = Column(VARCHAR(64))
#插入ES时需要转为为标准字典格式,自己封装
def getDict(self):
return {
"id":self.id,
"address" : self.address,
"country" : self. country,
"countrycode" : self.countrycode,
"province" : self.province,
"provincecode" : self.provincecode,
"city" : self.city,
"citycode" : self.citycode,
"district" : self.district,
"districtcode" : self.districtcode,
"xiang" : self.xiang,
"xiangcode" : self.xiangcode,
"cun" : self. cun,
"cuncode" : self.cuncode
}
3.2.4 导入数据
from OracleDb import OracleDb
from EsConnect import EsConn
from elasticsearch import helpers
from mapper import TMvAreainfoChina
cnn=OracleDb('用户名:密码@ip:1521/实例')
AreainfoChina =TMvAreainfoChina
areainfos = cnn.findAll(AreainfoChina )
es = EsConn('127.0.0.1', 9200).db
#批量导入方法
def bulk(es,datalist,index):
action = ({
"_index": index,
"_id": item.id,
"_source": item.getDict()
} for item in datalist)
#print(action.__next__())
helpers.bulk(es, action)
bulk(es,areainfos,'area_index')
print("******************* end")
导入成功后:测试
四,这还没完
不要以为这就万事大吉了。实际使用中,解析常常南辕北辙要提高解析准确度,还要对索引做进一步优化
4.1 ik分词库扩展
将所有的地址数据生成dic文件,比如湖北省、湖北、襄阳市、襄阳、东港区、东港 等等,竟可能全面,避免出现错误解析,ik分词的配置略,配置完后需要重新导入数据。
eg:
4.2 接口算法优化
ik分词优化后可以提高至少50%解析正确率,要进一步提高准确率,可以提前分词中省、市关键字,对解析出来的结果做进一步过滤。
五 遗留
目前地址解析成功率在80%,主要是用户输入的地址信息并不标准,想要更准确要需要思考更多的办法,笔者对Elasticsearch7.6 使用很少,还望指点。