本人菜雞一隻!
本篇文章,主要是記錄《【python】爬蟲篇:通過文章內容使用TF-IDF算法對文章進行分類(五)》中所說的具體代碼,具體處理方向和思路見下文:
【python】爬蟲篇:通過文章內容使用TF-IDF算法對文章進行分類(五):https://blog.csdn.net/lsr40/article/details/87281966
代碼如下(但是由於代碼可能有些年代了,我稍微整理過,我增加了很多註釋,如果不能運行報錯也方便大家調錯):
第一段:生成字典和生成已有類別的文章的向量
#該方法是將從數據庫中查出來的所有的已有分類的數據生成該次數據字典,
#保存該字典,並將所有數據根據該字典做成向量插回數據庫
#這裏傳入的words是多篇文章的數據是一個list
def produceDictionary(words):
#創建數據庫連接
conn_word_1 = psycopg2.connect(database="數據庫名", user="用戶名", password="密碼", host="ip",port="端口")
cur1 = conn_word_1.cursor()
#用來存放所有詞的list
merged_tag = []
#t是把每一條數據的id和生成的分詞情況保存下來的list
t = []
#遍歷每一篇文章
for index in range(len(words)):
#爲每一篇文章分詞(名詞),爲每個詞打上權重,取權重最大的前5個詞語
tag = jieba.analyse.extract_tags(words[index][1], withWeight=True, allowPOS='n',topK=5)
#把詞和權重分開
tag_dict = {s[0]: s[1] for s in tag}
#講詞放入set中進行去重
merged_tag = set(tag_dict.keys()) | set(merged_tag)
#把該條數據加入到t中
t.append((words[index][0],tag_dict))
merged_tag_str=','.join(merged_tag)
#插入字典的詞彙,通過逗號分隔
sql1="INSERT INTO 庫.表 VALUES('字典編號id','{merged_tag}')".format(merged_tag=merged_tag_str)
cur1.execute(sql1)
conn_word_1.commit()
print('詞典寫入完畢')
conn_word_2 = psycopg2.connect(database="數據庫名", user="用戶名", password="密碼", host="ip",port="端口")
cur2 = conn_word_2.cursor()
print('根據生成的字典,生成已有分類的文章向量,開始向數據庫插入向量')
for row in t:
v = []
for i in merged_tag:
if i in row[1].keys():
#在有該詞的位置上放入對應的權重值
v.append(row[1][i])
else:
#否則爲0
v.append(0)
sql2="INSERT INTO 庫.表 VALUES({row},'{v}')".format(row=row[0],v=v)
cur2.execute(sql2)
conn_word_2.commit()
if __name__ == '__main__':
#上面的代碼都是在書寫方法,這裏纔是實際調用運行
conn = psycopg2.connect(database="數據庫名", user="用戶名", password="密碼", host="ip",port="端口")
cur3 = conn.cursor()
cur3.execute("SELECT 文章id,文章類型,正文內容 FROM 庫.表 " \
"WHERE 正文內容 is not null and 正文內容 <> '' and length(正文內容 ) > 10")
rows = cur3.fetchall() # all rows in table
words=[]
print('拉取到數據')
for index in range(len(rows)):
words.append((rows[index][0],rows[index][2]))
print("============")
print('words數據拉取並且遍歷完畢')
print("============")
#運行方法
produceDictionary(words)
第二段:生成已有類別文章的平均向量
#設置打印時顯示方式,threshold=np.nan意思是輸出數組的時候完全輸出,不需要省略號將中間數據省略
np.set_printoptions(threshold=np.nan)
#不使用科學計數法
np.set_printoptions(suppress=True)
def loadAvgVector(index,art_type):
conn = psycopg2.connect(database="數據庫名", user="用戶名", password="密碼", host="ip",port="端口")
cur = conn.cursor()
select_sql="SELECT 類型,向量 FROM 庫.表 where 類型='{art_type}'".format(art_type=art_type)
print('select_sql:',select_sql)
cur.execute(select_sql)
rows = cur.fetchall() # all rows in table
words=[]
#創造一個一維,長度爲95206的零向量(因爲我字典有95206個單詞,所以每個向量長度也那麼長)
b = np.zeros(95206)
#遍歷每一條數據
for i in range(len(rows)):
#取出對應的數據(去頭去尾的原因應該是前面有大括號)
arr=rows[i][1][1:-1].split(',')
#print('row',rows[index][1][1:-1].split(','))
#words.append(rows[index][1])
#把取出來的字符串,做成向量
arr_f = list(map(float, arr))
a = np.array(arr_f)
#與零向量相加
b=b+a
#str('{:.10f}'.format(item)
#這裏對於數值做了些簡單的處理,具體什麼我也記不清楚了,大家可以把註釋去掉,打印出來看看
#獲得平均向量
c=(b / len(rows)).tolist()
#str('{:.10f}'.format(item) if item != 0.0 else item
#這段好像是在處理數值爲0的數據
result = '['+','.join([str('{:.10f}'.format(item) if item!=0.0 else item) for item in c])+']'
# print(result)
sql = "INSERT INTO 庫.保存每個類型平均值的表 VALUES({index},'{result}','{art_type}')".format(index=index, result=result,art_type=art_type)
#print(sql)
cur.execute(sql)
conn.commit()
# print('['+result+']')
if __name__ == '__main__':
dictionary=['類型1','類型2','類型3',.....]
for index in range(len(dictionary)):
loadAvgVector(index,dictionary[index])
第三段:爲文章分類
#求兩向量之間的夾角的函數,傳入兩個向量
def cosine_similarity(vector1, vector2):
#print('進入計算')
dot_product = 0.0
normA = 0.0
normB = 0.0
for a, b in zip(vector1, vector2):
if(a!=0 or b!=0):
#計算分母
dot_product += a * b
#計算分子
normA += a ** 2
normB += b ** 2
#如果分子中其中有一個是0
if normA == 0.0 or normB == 0.0:
return 0
else:
#計算兩個向量的夾角
return round(dot_product / ((normA ** 0.5) * (normB ** 0.5))*100, 2)
#求兩個向量的cos值
#傳入的參數分別是未分類的文章,多個類型的平均向量,字典
def compareCos(tuple):
result=tuple[0]
vectors=tuple[1]
dictionary=tuple[2]
#獲取當前數據的id
number_id=result[0]
#獲取當前數據的文本內容
word = result[1]
tag = jieba.analyse.extract_tags(word, withWeight=True, allowPOS='n', topK=20)
tag_dict = {s[0]: s[1] for s in tag}
print('tag_dict : ', tag_dict)
print("============")
#通過字典獲得未分類文章的向量
set_row = ','.join(dictionary).split(',')
vector = []
for i in set_row:
if i in tag_dict:
vector.append(tag_dict[i])
else:
vector.append(0)
# 新輸入的文章向量
print(vector)
#將該文章的向量和所有類別的平均向量比較,獲取前三的類別
topK=[]
index=-1
cos=()
for v in vectors:
sub_v=v[1][1:-1].split(',')
sub_f_v=list(map(float, sub_v))
#print('v1',sub_f_v)
now=cosine_similarity(vector, sub_f_v)
cos=(now,v[0])
print('cos',cos)
if (len(topK) < 3):
topK.append(cos)
topK.sort(reverse=True)
else:
if (cos[0] > topK[0][0]):
tmp = topK[1]
topK[1] = topK[0]
topK[0] = cos
topK[2] = tmp
print('topK',number_id,':',topK)
conn = psycopg2.connect(database="數據庫", user="用戶名", password="密碼",host="ip",port="端口")
cur = conn.cursor()
print('創建連接')
cur.execute("INSERT INTO 庫.表(文章id,文章類別) VALUES(%s,%s)",(number_id,topK))
conn.commit()
print('提交')
conn.close()
#加載所有文章類型的平均向量
def loadVector():
conn = psycopg2.connect(database="數據庫", user="用戶名", password="密碼",host="ip",port="端口")
cur = conn.cursor()
cur.execute(
"SELECT 類型,平均向量 FROM 庫.表")
rows = cur.fetchall() # all rows in table
vectors = []
for index in range(len(rows)):
vectors.append((rows[index][0], rows[index][1]))
return vectors
if __name__ == '__main__':
#獲得所有類型的平均向量
vectors=loadVector()
conn = psycopg2.connect(database="數據庫", user="用戶名", password="密碼",host="ip",port="端口")
cur = conn.cursor()
#將字典查詢出來
cur.execute("SELECT 字典 FROM 庫.表")
dictionary = cur.fetchone()
# rows = cur3.fetchone() # all rows in table
#查詢需要做分類的文章的sql(可以使用left join來關聯查出還未跑過的數據)
cur.execute("select 文章id,文章內容 from 庫.表")
results = cur.fetchall()
print('拉取到數據')
#這裏是個多線程!大家還需要自己設置調整下
with ThreadPoolExecutor(1) as executor:
for result in results:
executor.submit(compareCos, (result,vectors,dictionary))
conn.close()
這些代碼寫的時候,考慮的東西太少了,是有許多基礎的地方可以優化的(我自己都覺得這些代碼寫的很雜很爛)
我暫時能想到的幾個優化的點,比如:
1、寫一個方法專門創建(獲得)數據庫連接
2、更好的調用多線程的方法,加快程序運行速度
3、方法,變量的命名還是要規範點
大概就是這樣的一個情況,大家如果有興趣可以爬取各種各樣的文章來嘗試下這樣的分類方式,個人覺得還蠻準,挺有意思的~