本人菜雞一隻,今天來寫寫結巴分詞!
哇,距離上一次寫文章已經20天過去了,最近這些天還真是挺忙的,主要是上上週到了跑月數據的節點,然後上週原始數據出了問題,我調了一週多才把這個錯誤解決了,還修復了一個隱藏的小bug
在這裏提醒下自己,用任何表在做關聯的時候一定要好好檢查,關聯鍵是不是唯一的,否則會數據倍增!!
其實在這一段時間裏,還是有一點點自己學習的。
首先整理了ES的一些API,還沒整理完,後面我會出一篇文章來寫這個的!
本文,是最近使用結巴分詞做的一些事情的總結!
業務場景:
先說說業務場景,數據庫中有一些文本,我想從文本里面統計某些詞的count,這些詞是我自定義的詞(就比如說是洗髮水的品牌名稱吧),我想看看每一條文本中,是否有提到相關品牌,並且提到的次數!
實現工具:
python+結巴分詞+數據庫,應該是開發起來最快的了吧,雖然python可能性能不是那麼好,但是用起來方便!
結巴的介紹1:https://gitee.com/fxsjy/jieba
結巴的介紹2:https://github.com/fxsjy/jieba
實際遇到的問題:
1、英文詞組如何處理
有一些品牌是英文,有大小寫的,有空格,比如(SUPER MILD),可能結巴分詞直接把這兩個英文拆開了,那就匹配不上了!
因此我搜索到了解決方案:https://segmentfault.com/q/1010000016011808
1、在結巴根目錄下,打開__init__.py只需要往相應的正則表達式添加空格即可,如
-re_han_default = re.compile("([\u4E00-\u9FD5a-zA-Z0-9+#&\._%]+)", re.U) +re_han_default = re.compile("([\u4E00-\u9FD5a-zA-Z0-9+#&\._% ]+)", re.U)
2、或者在調用jieba.cut(text, cut_all=False)之前,先執行:
jieba.re_han_default = re.compile('(.+)', re.U)
這樣就可以識別詞組了,當然也可以考慮換一個分詞包來實現,比如:MWETokenizer
2、添加自定義詞(其實就是洗髮水品牌名稱)
https://github.com/fxsjy/jieba/issues/14
上面這個網址,大家有興趣可以往後翻翻,有一些開發者提出的一些使用上的問題,也有人在幫忙回答,說不定就能解決你的問題!
貼上代碼:
# coding=gbk
import psycopg2
import jieba.analyse
##讀取品牌名稱列表,通過這個list來過濾掉不屬於洗髮水品牌的詞彙
def readbrandTXT(rootdir):
brandList = []
with open(rootdir, 'r', encoding='UTF-8') as file_to_read:
while True:
line = file_to_read.readline()
if not line:
break
line = line.strip('\n')
brandList.append(line)
return brandList
# 獲取該文本的詞和詞的出現次數
def getNum(text, list):
word = []
counter = {}
jieba.re_han_default = re.compile('(.+)', re.U)
seg_list = jieba.cut(text)
listStr = "#".join(seg_list)
list = listStr.split('#')
for w in list:
if w in list:
if not w in word:
word.append(w)
if not w in counter:
counter[w] = 1
else:
counter[w] += 1
counter_list = sorted(counter.items(), key=lambda x: x[1], reverse=True)
return counter_list
# 插入數據庫的方法
def insertManyRow(strings):
try:
conn = psycopg2.connect(database="數據庫名", user="用戶名", password="密碼", host="ip地址",port="端口號")
cur2 = conn.cursor()
sql2 = "INSERT INTO schema名稱.表名(字段1,字段2,字段3,字段4...很多字段) " \
"VALUES(%(字段1)s,%(字段2)s,%(字段3)s,%(字段4)s...很多字段)"
cur2.executemany(sql2, strings)
conn.commit()
cur2.close()
conn.close()
print("成功插入數據,關閉連接")
except Exception as e:
print("執行sql時出錯:%s" % (e))
cur2.rollback()
conn.close()
##獲取還未處理的數據
def getUnprocessedData():
conn = psycopg2.connect(database="數據庫名", user="用戶名", password="密碼", host="ip地址",port="端口號")
cur3 = conn.cursor()
#這裏是一段leftjoin,查出有哪些數據還未處理,一次性查出2000條
cur3.execute("""SELECT a.* FROM 全量數據表 a LEFT JOIN 已處理的數據表 b \
on a.主鍵=b.主鍵 \
WHERE b.主鍵 is null limit 2000""")
rows = cur3.fetchall() # all rows in table
#print(rows)
#print("============")
return rows
if __name__ == "__main__":
brandList = readbrandTXT('D:\\洗髮水品牌列表.txt')
for i in range(100):
print('開始循環:',i+1)
strings = []
rows = getUnprocessedData()
#通過i來判斷批量插入的時間點
i = 0
try:
for index in range(len(rows)):
#將文本傳入getNum,和想要的品牌列表
counter_list = getNum(rows[index][4] + rows[index][5], brandList )
if (counter_list.__len__()!=0):
for j in counter_list:
text = {}
text = {'字段1': rows[index][0], '字段2': rows[index][1], '字段3': rows[index][2],
'字段4': rows[index][3],
"字段5": rows[index][4]....把字段都放進來}
strings.append(text)
i = i + 1
# print(text)
else:
#如果返回的結果爲空,證明該文本中沒有相關詞彙,就插入null
text = {'字段1': rows[index][0], '字段2': rows[index][1], '字段3': rows[index][2],
'字段4': rows[index][3],
"字段5": None....把字段都放進來,}
strings.append(text)
i = i + 1
#約500條數據插入一次
if (i >= 500):
print("i:",i)
insertManyRow(strings)
strings.clear()
i = 0
#如果到了最後,數據不到500條也需要強行執行插入
if (strings.__len__() != 0):
print("strings.__len__()",strings.__len__())
insertManyRow(strings)
strings.clear()
except Exception as e:
print('Error',e)
#就算當前循環插入失敗,也繼續下一步循環(因爲是增量處理,所以插入失敗的數據會被重新查出來)
continue
好的,本人比較不是專業的python工程師,所以代碼可能寫的稀爛,不好意思,請諒解!
我遇到的問題,與需要注意的點:
1、要使用英文詞彙,需要添加正則匹配規則的修改
2、添加自定義詞的時候
-1.加載文檔
jieba.load_userdict("D:\\load_taobrand.txt")
但是在使用加載文檔的時候,會遇到文檔裏面用空格來做不同列的分割符,但是我的詞彙中自帶空格,導致詞彙無法添加,解決方案是要麼用單獨添加詞組的方式,要麼修改load_userdict方法默認的分隔符,詳情見如下:
相關issue:https://github.com/fxsjy/jieba/issues/423
-2.單獨添加詞語
jieba.add_word("自定義詞語")
3、判別是否需要添加該自定義的詞
-1.詞組純英文單詞
如果有的品牌是純英文單詞,例如:AB,如果修改了正則匹配規則,並添加了該詞到自定義詞彙中,會導致“ABCDEFG HIJKLMN”這樣的一串英文,原本分爲ABCDEFG,HIJKLMN兩個詞,變成分爲AB,CDEFG,HIJKLMN三個詞,這樣其實違背了我們的意願,我們想要的是,單獨提到AB品牌的文章,因此純英文單詞,沒有任何符號的詞不需要加入到自定義詞彙中!
-2.純中文品牌
純中文品牌也需要注意,有些品牌名字比較長,比如:“悅詩風吟”,如果不添加該詞的話,結巴分詞默認會分爲“悅詩”,“風吟”兩個詞,這樣也就匹配不上該品牌,所以名字較長,或者不是常規詞彙的中文品牌,需要添加到自定義詞彙中!
-3.品牌名稱夾雜中英文或者特殊符號
這部分品牌建議還是要添加到詞彙中的,例如:DE&E,如果不添加,默認是“DE”,“&”,“E”,因此如果要識別,也需要添加
4、處理數據當中,我遇到了一些識別字符串中是否包含英文,是否包含中文,是否純英文等等需求,我稍微記錄下(不同數據庫用法不同,我是greenplum/postgresql)
--當包含中文的時候,如下兩個函數返回長度不一樣
select octet_length(字符串) <> char_length(字符串)
--是否純英文
select 字符串 ~ '^([a-zA-Z]+\s?[a-zA-Z]*\s?[a-zA-Z]*)$';
總結:
總結下有哪些需要改進的地方:
1、品牌名字過短,是一些常用詞彙或者有歧義,比如“一點點”,“時候”,“美的”等品牌,可能文章中是“天冷的時候”,“美的一天”,但是卻被識別成某個品牌,就是有些品牌名字會跟大衆使用的詞彙混合起來,就不好識別,暫時沒有想到好的方法來解決
2、性能比較差,我的每個文本其實很短,也就40個字左右吧(可能還沒有),有21W條數據,運行我的代碼,批量插入數據庫,最終運行了5個小時左右,也就是每秒能夠往數據庫插入十幾條數據,相對來說還是比較慢的
當然這個需求只是個先行版,後面肯定會有更多更復雜的東西在裏面,我也沒想好,後面再說吧!有興趣大家可以好好看看結巴分詞的GitHub的README,裏面還是寫的很清晰的,文本分詞還是很常見的需求~
好了本文就到這裏,菜雞一隻,如果有任何疑問,或者我說的不對的地方,歡迎大家留言!