1. 程式人生 > >使用Python完成公司名稱和地址模糊匹配

使用Python完成公司名稱和地址模糊匹配

  • 摘要:正如題目中說的一樣,這個程式的目的是實現公司名及公司地址的模糊匹配,也可以遷移到房產資訊、電話號碼之類的欄位上。本來的應用場景是反團伙欺詐以及失聯客戶的修復,大概的意思就是說多個相同公司的同事都在我公司借貸的欺詐可能性要高於其他客戶,以及造假的房產資訊和電話號碼可能不完全相同,但有一定的相似性,我們需要把這些客戶找出來,但是又不能用精確匹配。因為存在問題的房產資訊和電話可能只是相似,而不是完全相...
  • 正如題目中說的一樣,這個程式的目的是實現公司名及公司地址的模糊匹配,也可以遷移到房產資訊、電話號碼之類的欄位上。本來的應用場景是反團伙欺詐以及失聯客戶的修復,大概的意思就是說多個相同公司的同事都在我公司借貸的欺詐可能性要高於其他客戶,以及造假的房產資訊和電話號碼可能不完全相同,但有一定的相似性,我們需要把這些客戶找出來,但是又不能用精確匹配。因為存在問題的房產資訊和電話可能只是相似,而不是完全相同;對於公司名和地址來說,就更糟糕一些,即使是真實的資訊,但同一個公司的叫法可能會多種多樣,如果麻煩一些,還要建一個同義詞庫。而我們做的工作還沒有那麼全面,只是提取出了公司名中的關鍵詞。

    而做評分卡模型也需要用到這個思想。因為評分卡的其中一個欄位就是所在城市等級,這就需要從原始資料的地址中提取出城市資訊,但地址的格式又不夠標準,因為資訊是客戶人工填寫的,舉個例子,“山東省濟南市”也有可能被寫成“山東濟南”,所以一個正則表示式就不足以解決這個欄位提取的問題了。

    總結一下,模糊匹配的兩個應用場景: 
    1)構建反欺詐知識圖譜 
    2)評分卡提取所需欄位

    地址處理的基本思路就是建立一個標準庫,對地址逐個進行對比,再返回結果和置信度,所以詞庫的建立是地址處理的關鍵。

    下面是解決問題的過程:

    第一步,希望可以提取出公司名中的關鍵字。首先,要分析一下資料庫中公司名的形式:“浙江杭州立多林貿易有限公司”,“匯川區萬全兄弟購物中心”,“溫州雪龍集團有限公司北京銷售分公司”…… 
    經過分析,可以看出,公司名大致分為3個部分:1、省市區名稱;2、關鍵詞+公司屬性;3、分公司或分局等資訊。

    首先,需要使用python連線到資料庫,取出公司名欄位: 
    1、下載並安裝psycopg2模組,下載連結如下: 
    http://www.stickpeople.com/projects/python/win-psycopg/ 
    安裝完畢後,輸入下面語句,連線資料庫:

    import psycopg2
    #錄入資料
    conn=psycopg2.connect(database="testdb", user="postgres", password="postgres", host="10.180.157.168", port="1975") 
    cur=conn.cursor()
    needinfo=['brhs_unitname','dict_unit_province','dict_unit_city','dict_unit_arer','brhs_unit_address']
    cur.execute("SELECT brhs_unitname,dict_unit_province,dict_unit_city,dict_unit_arer,brhs_unit_address FROM aaa_t_jk_dhzh_brhs limit 1000;")
    selects=cur.fetchall() 
    enterprise_datas=pd.DataFrame(selects,columns=needinfo)

    2、根據上面的分析,下一步就要先將省市區提取出來。提取省市區的常規做法是建立行政區劃庫,類似於jieba分詞中的txt詞典。在解決這個問題時,我和同事兩個人的思路不盡相同。在使用jieba對公司名進行分詞後,一種思路是建立一個最簡詞庫,詞庫中的詞都是行政區劃的最簡稱,例如“寧夏”、“內蒙古”等,如果字典中的詞在分詞中,則取出該分詞;另一種思路是,使用全稱詞庫,例如“寧夏回族自治區”、“內蒙古自治區”等,如果分詞後的詞可以和其完全匹配,則取出該分詞,否則,按照一定的規則,將字典中的詞去掉“省”、“市”、“自治區”後,再和分詞進行匹配。經過討論,最後,行政區劃庫的形式如下圖所示:

    region_nosuffix region_suffix city_nosuffix city_suffix province_nosuffix province_suffix 東城 東城區 北京直轄區 北京直轄區 北京 北京市 西城 西城區 北京直轄區 北京直轄區 北京 北京市 朝陽 朝陽區 北京直轄區 北京直轄區 北京 北京市 : : : : : :

    3、使用python建立詞典,分別建立以下幾個詞典:

    #建立字典
    district=pd.read_excel('~\district_new_nosuffix.xlsx',sheetname='slice').fillna('')
    ##建立pcr字典
    district_dict_pcr=defaultdict(lambda:defaultdict(lambda:defaultdict(int)))
    for num in range(len(district)):
        district_dict_pcr[district.ix[num,'province_nosuffix']][district.ix[num,'city_nosuffix']][district.ix[num,'region_nosuffix']]=district.ix[num,'region_code']
    district_dict_pcr=dict(district_dict_pcr)
    ##建立rcp字典
    district_dict_rcp=defaultdict(list)
    for num in range(len(district)):
        district_dict_rcp[district.ix[num,'region_nosuffix']].extend([district.ix[num,'province_suffix'],district.ix[num,'city_suffix'],district.ix[num,'region_suffix']])
    district_dict_rcp=dict(district_dict_rcp)
    ##建立cp字典
    district_dict_cp=defaultdict(list)
    for num in range(len(district)):
        if district_dict_cp[district.ix[num,'city_nosuffix']]==[]:
            district_dict_cp[district.ix[num,'city_nosuffix']].extend([district.ix[num,'province_suffix'],district.ix[num,'city_suffix']])
    district_dict_cp=dict(district_dict_cp)
    ##建立pp字典
    district_dict_pp=defaultdict(str)
    for num in range(len(district)):
        district_dict_pp[district.ix[num,'province_nosuffix']]=district.ix[num,'province_suffix']
    district_dict_pp=dict(district_dict_pp)

    4、使用分詞包對公司名進行分詞,並標出詞性 
    顯然,需要提取出行政區,我們需要詞性為ns的詞,但分詞後發現,Jieba自帶的詞典中,我們需要的省市區的詞性並非全部為ns,而其他詞,如“大望路”等我們不需要的詞卻可能為”ns”,於是,需要建立專用分詞詞典,並標註其詞性為ns,替換預設的詞典。

    def word_flag(words):
        ''' 提取flag是u'ns'的word '''
        result=defaultdict(list)
        output=pseg.cut(words)
        for word,flag in output:
            result[flag].append(word)
        return result[u'ns']

    5、提取出三級行政區劃 
    前面已經提到過,由於書寫的不規範,導致同一個行政區劃,寫法可能不同。例如,“山西省太原市小店區”,既有可能被寫成“山西太原小店區”,也有可能被寫成“太原市小店區”,還可能直接被寫成“小店區”,所以,提取時,要分多種情況:

    def geo_checker(word):
        ''' 驗證word '''
        if len(word)>2:
            if (u'省' in word or u'自治區' in word):
                word=word.replace(u'省','').replace(u'自治區','')
                try:
                    district_dict_pcr[word]
                    return district_dict_pp[word]
                except KeyError:
                    return u'未知省份'
            elif (u'地區' in word or u'自治州' in word or u'盟' in word):
                word=word.replace(u'地區','').replace(u'自治州','').replace(u'盟','')
                try:
                    district_dict_cp[word]
                    return district_dict_cp[word]
                except KeyError:
                    return u'未知城市'                      
            elif (u'自治縣' in word or u'礦區' in word or u'自治旗' in word):
                word=word.replace(u'自治縣','').replace(u'礦區','').replace(u'自治旗','')
                try:
                    district_dict_rcp[word]
                    return district_dict_rcp[word]
                except KeyError:
                    return u'未知區縣' 
            elif u'縣' in word :
                word=word.replace(u'縣','')
                try:
                    district_dict_rcp[word]
                    return district_dict_rcp[word]
                except KeyError:
                    return u'未知區縣'             
            elif u'市' in word:
                  if (u'天津' in word or u'北京' in word or u'重慶' in word or u'上海' in word):
                      word=word.replace(u'市','')
                      try:
                          district_dict_pcr[word]
                          return district_dict_pp[word]
                      except KeyError:
                          return u'未知省份'
                  if u'區' in word:
                      word=word.replace(u'區','')
                      try:
                          district_dict_rcp[word]
                          return district_dict_rcp[word]
                      except KeyError:
                          return u'未知區縣'
                  word=word.replace(u'市','')
                  try:
                      district_dict_cp[word]
                      return district_dict_cp[word]
                  except KeyError:
                      try:
                          district_dict_rcp[word]
                          return district_dict_rcp[word]
                      except KeyError:
                          return u'未知城市或區縣'
            elif u'區' in word:
                word=word.replace(u'區','')
                try:
                    district_dict_cp[word]
                    return district_dict_cp[word]
                except KeyError:
                    try:
                        district_dict_rcp[word]
                        return district_dict_rcp[word]
                    except KeyError:
                        return u'未知城市或區縣'
            else:
                try:
                    district_dict_pcr[word]
                    return district_dict_pp[word]
                except KeyError:
                    try:
                        district_dict_cp[word]
                        return district_dict_cp[word]
                    except KeyError:
                        try:
                            district_dict_rcp[word]
                            return district_dict_rcp[word]
                        except KeyError:
                            return u'未知地理資訊'
        else:
            if (u'市' in word or u'區' in word):
                word=word.replace(u'市','').replace(u'區','')
                try:
                    district_dict_rcp[word]
                    return district_dict_rcp[word]
                except KeyError:
                    try:
                        district_dict_cp[word]
                        return district_dict_cp[word]
                    except KeyError:
                        return u'未知城市或區縣'
            else:
                try:
                    district_dict_pcr[word]
                    return district_dict_pp[word]
                except KeyError:
                    try:
                        district_dict_cp[word]
                        return district_dict_cp[word]
                    except KeyError:
                        try:
                            district_dict_rcp[word]
                            return district_dict_rcp[word]
                        except KeyError:
                            return u'未知地理資訊'
    def type_checker(x):
        if type(x)==list:return 1
        else:return 0
    
    def geo_standard(geo_list):
        for i in range(len(geo_list)):
            geo_list[i]=geo_checker(geo_list[i])
        geo_result=[x for x in geo_list if u'未知' not in x]
        geo_result_type=[type_checker(x) for x in geo_result]
        if sum(geo_result_type)>0:
            geo_result=geo_result[:geo_result_type.index(1)+1]
        return geo_result
    
    def geo_match(geo_result):
        for info in range(1,len(geo_result)):
            if not re.match(geo_result[info-1],geo_result[info]):
                return ''
        return 'Match'
    
    def geo_validate(geo_result):
        if geo_result==[]:
            return u'缺失地理資訊'
        elif type(geo_result[-1])!=list:
            if len(geo_result)>1:
                for info in range(1,len(geo_result)):
                    if not geo_match(geo_result):
                        return u'無效地理資訊'
            return geo_result[-1]
        else:
            if len(geo_result)==1:     
                return ','.join(geo_result[-1])
            else:
                for choice in range(len(geo_result[-1])):
                    possible=geo_result[:-1]
                    possible.append(geo_result[-1][choice])
                    if not geo_match(possible):continue
                    return possible[-1]
                return u'無效地理資訊'
    
    def geo_guarder(geo_result):
        geo_result=geo_result.replace(u'北京直轄區','').replace(u'天津直轄區','').replace(u'上海直轄區','').replace(u'重慶直轄區','').replace(u'海南直轄區','').replace(u'湖北直轄區','')
        return geo_result
    
    #名稱中的地理資訊處理完畢
    un_geo=unit_name.map(geo_substitute).map(word_flag).map(geo_standard).map(geo_validate).map(geo_guarder)

    6、接下來,提取分公司資訊

    #劃分名稱中的組織架構
    def branch(unit_name):
        ''' 檢測分支機構資訊 '''
        pattern=[u'公司(.+?)(分公司)',u'局(.+?)(分局)',u'站(.+?)(分站)',u'社(.+?)(分社)',u'公司(.+?)(分行)',u'小學(.+?)(分校)',u'大學(.+?)(分校)',u'高中(.+?)(分校)']
        #unit_name=unit_name.decode('utf8')
        formation=defaultdict(str)
        for choice in range(len(pattern)):
            reg=re.search(pattern[choice],unit_name)
            try:
                formation['parent']=unit_name[:reg.start(1)]
                formation['branch']=unit_name[reg.start(1):reg.end(2)]
                formation['section']=unit_name[reg.end(2):]
                return formation
            except:
                continue
        formation['parent']=unit_name
        return formation

    7、接下來,就是提取關鍵字資訊 
    這也是所有步驟中比較困難的一點,主要的問題在於,需要新建行業詞典。 
    下面是去掉行政區劃以及分公司後,幾個公司名的例子: 
    後英經貿有限公司 
    衛強餐飲有限公司 
    品展裝潢有限公司 
    泰廣興空分裝置配件有限公司 
    宇楠貿易有限公司 
    金路新力商貿有限公司 
    旭攀貿易有限公司 
    元東刀具廠 
    湛江霞山海明爐料商行 
    北銘鋼鐵有限公司 
    三達化學有限公司 
    友祚木業有限公司 
    雨竹廣告有限公司 
    蒂暖實業有限公司 
    傳銀五金廠 
    盛師傅藤器廠 
    經過分析,公司名稱大致可分為2種情況,1、字尾為“有限公司”、“責任有限個公司”、“有限責任公司”、“公司”;2、字尾名為“玩具廠”、“小賣部”、“化工廠”等。 
    所以,需要新建兩個行業詞典,其資料來源為公司購買的千萬級法人庫公司名稱與地址,由於資料量較大,需要使用kattle將其匯入資料庫中進行操作。從所有公司名稱中選出字尾為“有限公司”的名稱,去掉這四個字的字尾後,分別擷取倒數2個,3個,4個……字元,並按照其出現頻率排序,然後,通過人工,按照字串由多到少的順序選取行業屬性,其原因為,例如,假設“文化傳播”在四個字元中出現的頻率較高,有可能“化傳播”在三個字元的時候,頻率也較高,所以要先選出較長的字串,然後手動刪除掉較短字串中,雖然頻率較高,但並非一個詞的字串。得到的行業屬性如下: 
    安全防範技術 
    安全防範技術服務 
    安裝 
    安裝工程 
    百貨 
    辦公裝置 
    包裝 
    包裝製品 
    保潔 
    保潔服務 
    泵業 
    賓館 
    玻璃 
    材料 
    材料銷售 
    財務顧問 
    財務諮詢 
    財務諮詢服務 
    餐飲 
    餐飲服務 
    餐飲管理 
    倉儲 
    測繪 
    策劃 
    茶葉 
    產品 
    產品銷售 
    同理,構造企業屬性詞庫: 
    技術服務部 
    汽車修理站 
    配件經營部 
    營銷服務部 
    機械配件廠 
    汽車運輸隊 
    建築工程隊 
    證券營業部 
    汽車維護廠 
    工會委員會 
    金屬結構廠 
    金屬加工廠 
    物資經銷處 
    安裝工程處 
    汽車維修站 
    技術開發部 
    這樣我們就從企業名中剔除了屬性,留下關鍵詞,可以進行公司名稱的模糊匹配了。 
    當然,對於部分公司,可能還存在別稱,這就需要建立同義詞庫,這裡暫時不討論。

    第二部分,是公司地址的模糊匹配。相對於公司名,地址的模糊匹配較簡單。 
    斗篷山路317號靈智廣場 
    馬家工業園C幢4號 
    馬家工業園C幢4號 
    鴛鴦路8號海闊天空二期1-5-3 
    高坪鎮巨集發路30號 
    之江路934號 
    濱湖路明湖花園1-4號 
    花荄五路口文苑路東段203號 
    渝南大道19號鎧恩國際A館1022B 
    小呂鄉劉坡村 
    馬坊鄉欽橋村 
    珍溪鎮滴水村六社 
    永珍城一期A座底商A-1002 
    朱槿路11號柬埔寨園區5號樓2單元602號 
    之江路934號 
    生產物資市場11棟33號 
    東肖鎮東肖南路15號 
    經過分析,得出公司地址的全稱通常使用四級、五級、六級行政區劃、某某路+數字+號+具體門牌號,故可以使用正則表示式進行提取:

    unit_name=enterprise_datas['brhs_unit_address']
    df = unit_name.str.extract('(.*[鎮|鄉|街道|街|道])?(.*[村|委員會|委會])?(.*[組])?(.*路)?(\d+號)?([\u4e00-\u9fa5]*)?(.*)?',expand=True)
    df.columns = ['鄉鎮街道', '村','組', '道路', '門牌號', '地標名稱', '其他']
    writer = pd.ExcelWriter('pandas_simple.xlsx', engine='xlsxwriter')
    
    # Convert the dataframe to an XlsxWriter Excel object.
    df.to_excel(writer, sheet_name='Sheet1')
    writer.save()