python對(duì)對(duì)聯(lián)
1.項(xiàng)目目的
編寫(xiě)一段能夠根據(jù)輸入的上聯(lián)對(duì)出下聯(lián)的對(duì)對(duì)聯(lián)程序。輸入上聯(lián)后,能隨機(jī)生成下聯(lián),最終達(dá)到:
(1) 程序的智能性高,功能完善。希望生成的對(duì)聯(lián)滿足平仄、韻腳、詞性等要求,并具有一定語(yǔ)義。
(2) 在實(shí)踐項(xiàng)目的過(guò)程中提高同學(xué)的代碼編寫(xiě)能力。具體能力包括:程序設(shè)計(jì)知識(shí)的綜合運(yùn)用能力;自主學(xué)習(xí)、錯(cuò)誤調(diào)試的能力;規(guī)范書(shū)寫(xiě)代碼及報(bào)告的意識(shí)和能力;閱讀代碼、改寫(xiě)代碼的能力等。
2.項(xiàng)目?jī)?nèi)容
這是一個(gè)運(yùn)用從網(wǎng)上爬取的對(duì)聯(lián)、通過(guò)隨機(jī)的算法生成一段符合押韻、平仄規(guī)則的對(duì)聯(lián)的程序。大體過(guò)程為:
(1) 運(yùn)用、、Re模塊等從網(wǎng)站上爬取對(duì)聯(lián),并對(duì)對(duì)聯(lián)進(jìn)行文本格式處理;
(2) 運(yùn)用模塊對(duì)對(duì)聯(lián)進(jìn)行詞性分析,運(yùn)用模塊對(duì)對(duì)聯(lián)進(jìn)行平仄校準(zhǔn);
(3) 根據(jù)詞頻隨機(jī)做出符合押韻、平仄規(guī)則的下聯(lián)。
3.輸入輸出
輸入:任意一副對(duì)聯(lián)的某一聯(lián)
輸出:判斷該聯(lián)為上聯(lián)或下聯(lián)并隨機(jī)生成滿足平仄、韻腳的另一聯(lián)
程序流程圖
爬蟲(chóng)分析&數(shù)據(jù)分析 1.爬蟲(chóng)整體思路
(1)爬取網(wǎng)頁(yè)
選擇想要爬取的網(wǎng)址,并模擬頭部瀏覽器訪問(wèn)該網(wǎng)站。創(chuàng)建一個(gè)dict,將頭部信息以鍵值對(duì)的形式存入到dict對(duì)象中。本次項(xiàng)目爬取的網(wǎng)址為:
然后調(diào)用..()函數(shù)創(chuàng)建一個(gè)對(duì)象,該函數(shù)第一個(gè)參數(shù)傳入url,第二個(gè)參數(shù)可以傳入數(shù)據(jù),默認(rèn)是傳入0數(shù)據(jù),第三個(gè)參數(shù)是傳入頭部,該參數(shù)也是有默認(rèn)值的,默認(rèn)是不傳任何頭部。將dict對(duì)象傳入..()函數(shù)第三個(gè)參數(shù)。
此時(shí),已經(jīng)成功設(shè)置好報(bào)頭,然后使用()打開(kāi)該對(duì)象即可打開(kāi)對(duì)應(yīng)的網(wǎng)址。然后使用.read() 接收 json 數(shù)據(jù), 數(shù)據(jù)格式為UTF-8
(2) 逐一獲取需要的標(biāo)簽并解析數(shù)據(jù)
可以看到在網(wǎng)頁(yè)中我們需要的對(duì)聯(lián)內(nèi)容在p標(biāo)簽內(nèi),因此從html中獲取p標(biāo)簽內(nèi)容, 再利用正則表達(dá)式匹配符合要求的內(nèi)容, 并將其寫(xiě)入數(shù)據(jù)庫(kù)
(3)保存內(nèi)容
主要涉及文件的讀寫(xiě)。
代碼如下:
# 取消服務(wù)器證書(shū)驗(yàn)證功能
ssl._create_default_https_context = ssl._create_unverified_context
database = [] # 列表記錄數(shù)據(jù)
def askUrl(i): # 得到指定一個(gè)URL的網(wǎng)頁(yè)內(nèi)容
global database
# 爬取對(duì)聯(lián)大全網(wǎng)站
url = "http://duilian.haoshiwen.org/view.php?aid=" + str(i)
head = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54"
} # 模擬頭部瀏覽器
try: # 用try except進(jìn)行異常處理
request = urllib.request.Request(url, headers=head)
response = urllib.request.urlopen(request)
html = response.read().decode("UTF-8")
selector = etree.HTML(html) # 將源碼轉(zhuǎn)化為能被XPath匹配的格式
result = selector.xpath('//p/text()') # 返回所有p標(biāo)簽
for html in result:
findCouplet = re.compile(r'[\u4e00-\u9fff]{7}') # 使用正則表達(dá)式查找符合格式的內(nèi)容
couplet = re.findall(findCouplet, html)
if couplet:
database.append(couplet) # 寫(xiě)入數(shù)據(jù)庫(kù)
except urllib.error.URLError as e:
if hasattr(e, "code"):
print(e.code)
if hasattr(e, "reason"):
print(e.reason)
因?yàn)樵摼W(wǎng)站的數(shù)據(jù)格式多樣, 我自己爬到的數(shù)據(jù)比較少,所以我下載了網(wǎng)上的數(shù)據(jù)庫(kù)來(lái)作為擴(kuò)充(包含約70萬(wàn)條對(duì)聯(lián)),下載地址如下:
中國(guó)對(duì)聯(lián)數(shù)據(jù)集 -
.txt(上聯(lián))和.txt(下聯(lián))兩個(gè)文件
2. 算法分析 模塊是通過(guò)學(xué)習(xí),記錄詞頻
這部分是逐個(gè)分析上聯(lián)中的詞語(yǔ),如獲得一個(gè)詞語(yǔ)后,在下聯(lián)中找到這個(gè)詞語(yǔ)可以匹配的詞,并根據(jù)該詞語(yǔ)匹配的次數(shù)用公式計(jì)算詞頻概率(對(duì)應(yīng)概率越大說(shuō)明該詞最有可能與上聯(lián)中的對(duì)應(yīng)詞匹配)。計(jì)算公式是多次試驗(yàn)后發(fā)現(xiàn)的比較能反映詞頻的算法,不一定是最優(yōu)。
代碼如下:
# 對(duì)聯(lián)學(xué)習(xí)
def learns():
global infile, outfile, zishi
size = len(infile) # infile為上聯(lián)庫(kù)中的內(nèi)容
for i in range(size):
cut = jieba.lcut(infile[i]) # 對(duì)上聯(lián)庫(kù)中對(duì)聯(lián)逐一分詞
for j in range(len(cut)):
position = infile[i].find(cut[j]) # 找到該詞位置
learn(cut[j], outfile[i][position: position + len(cut[j])]) # 學(xué)習(xí)該詞
for j in range(len(infile[i])):
learn(infile[i][j], outfile[i][j]) # 學(xué)習(xí)對(duì)聯(lián)中每個(gè)字
# 單個(gè)的詞語(yǔ)學(xué)習(xí)
def learn(word, nword):
global zishi
zikey = zishi.keys()
if word in zikey: # 如果該詞語(yǔ)已被學(xué)習(xí)過(guò)
zishikey = zishi[word].keys() # 獲取所有可以匹配該詞的詞語(yǔ)
if nword in zishikey: # 如果下聯(lián)對(duì)應(yīng)詞在可匹配詞語(yǔ)中
for j in zishikey:
if j != '$':
if j != nword: # 可匹配詞語(yǔ)中的其他詞對(duì)應(yīng)概率減小
zishi[word][j] = zishi[word][j] / (1 + 1 / zishi[word]['$'])
zishi[word][nword] = (zishi[word][nword] + 1 / zishi[word]['$']) / (1 + 1 / zishi[word]['$'])
# 該對(duì)應(yīng)詞的概率增大
zishi[word]['$'] = zishi[word]['$'] + 1 # 可匹配詞語(yǔ)總數(shù)+1
else: # 如果該詞語(yǔ)未被學(xué)習(xí)過(guò)(以下步驟同上)
for j in zishikey:
if j != '$':
zishi[word][j] = zishi[word][j] / (1 + 1 / zishi[word]['$'])
zishi[word][nword] = (1 / zishi[word]['$']) / (1 + 1 / zishi[word]['$'])
zishi[word]['$'] = zishi[word]['$'] + 1
else:
zishi[word] = {'$': 1, nword: 1}
這是學(xué)習(xí)后生成的文件:
意思為:“上聯(lián)”:{“$”+匹配的總字?jǐn)?shù),“下聯(lián)1”:對(duì)應(yīng)概率,“下聯(lián)2”:對(duì)應(yīng)概率……}
平仄方法區(qū)分
看對(duì)聯(lián)的最后一個(gè)字,上聯(lián)最后一個(gè)字是三聲和四聲(仄聲),下聯(lián)的最后一個(gè)字是一聲和二聲(平聲),如下聯(lián):興xīng 一聲 是上聯(lián),旺wàng四聲是下聯(lián)。
def is_down(content): # 判斷是否是下聯(lián), 如果是返回true
s = list(content)
a = pinyin.get(s[-1], format="numerical")[-1]
return a == '1' or a == '2'
main模塊講解:
首先如果用戶輸入1, 進(jìn)行對(duì)聯(lián)自動(dòng)生成的話,打開(kāi)學(xué)習(xí)生成的文件, 用和ran函數(shù)實(shí)現(xiàn)匹配并生成下聯(lián):在函數(shù)中,用隨機(jī)為上聯(lián)分配一個(gè)權(quán)重/概率(達(dá)到隨機(jī)匹配目的),對(duì)上聯(lián)分詞后的詞語(yǔ)一一使用ran函數(shù),然后再組合成上聯(lián)。在ran函數(shù)中,將上聯(lián)分配到的概率r與所有能配對(duì)的詞語(yǔ)一一比較,如果比較到某個(gè)詞的概率大于r,就返回該詞i。否則使r減少后再重復(fù)以上步驟。然后進(jìn)行輸出,輸出時(shí)進(jìn)行平仄校驗(yàn),平仄正確率大于80%后直接輸出,否則再次隨機(jī)生成下聯(lián),直到平仄符合。
以下介紹模塊,主要是自定義的jy函數(shù):
方法比較簡(jiǎn)單,就是獲取每個(gè)字的音調(diào)后一一比對(duì), 如果平仄不對(duì)則記錄下來(lái), 最后拿正確數(shù)除以總的字?jǐn)?shù)獲得正確率。
代碼如下:
import pinyin
def is_down(content): # 判斷是否是下聯(lián), 如果是返回true
s = list(content)
a = pinyin.get(s[-1], format="numerical")[-1]
return a == '1' or a == '2'
def jy(s, x):
s = list(s)
x = list(x)
yin1 = []
yin2 = []
for i in s: # 獲取對(duì)聯(lián)中每個(gè)字的拼音聲調(diào)
a = pinyin.get(i, format="numerical")
yin1.append(a[-1])
for i in x:
b = pinyin.get(i, format="numerical")
yin2.append(b[-1])
error = 0
length = len(yin1)
for i in range(length): # 比對(duì)平仄
if (yin1[i] == '1' or yin1[i] == '2') and (yin2[i] == '1' or yin2[i] == '2'):
error += 1
if (yin1[i] == '3' or yin1[i] == '4') and (yin2[i] == '3' or yin2[i] == '4'):
error += 1
# 平仄正確率輸出
return (length - error * 1.0) / length
如果用戶輸入2, 則調(diào)用jy函數(shù)輸出平仄的評(píng)分。
代碼如下:
import jieba
import random
import json
from verify import jy, is_down
def couplet(s): # 生成另一聯(lián)
R = 0
x = []
for i in range(len(s)):
x.append(ran(s[i], random.random()))
# 用random隨機(jī)生成一個(gè)0到1之間的數(shù)后,用ran函數(shù)進(jìn)行權(quán)重比較、隨機(jī)輸出
return "".join(x)
def ran(w, r):
# 根據(jù)隨機(jī)賦予字詞的權(quán)重進(jìn)行比較
global zishi, writemode, R
R = 0
zikey = zishi.keys() ##返回字典中所有鍵
if w != '' and w in zikey: # 當(dāng)所輸上聯(lián)在對(duì)聯(lián)庫(kù)中,根據(jù)庫(kù)得出下聯(lián)
zishikey = zishi[w].keys()
max = ["", 0.0]
for i in zishikey:
if i != '$':
if r < float(zishi[w][i]): # 當(dāng)該權(quán)重小于匹配到的字詞對(duì)應(yīng)概率,返回該字詞
R += r
return i
else: # 減小權(quán)重,再次比較
r = r - float(zishi[w][i])
return max[0]
else: # 如果該上聯(lián)不在數(shù)據(jù)庫(kù)中,劃分字詞后逐詞匹配
return "".join([ran(i, random.random()) for i in w])
if __name__ == '__main__':
# 引入校驗(yàn)函數(shù)
global infile, outfile, zishi, writemode, R
try:
with open("zknow.txt", "r", encoding='utf-8') as zishi_file:
# 打開(kāi)學(xué)習(xí)后生成的文件
zishi = json.loads(zishi_file.read().replace("'", '"'))
# 將json格式數(shù)據(jù)解碼為python對(duì)象,構(gòu)建字典
except IOError:
zishi = {}
while True:
choice = input("1. 自動(dòng)對(duì)對(duì)聯(lián) 2. 對(duì)聯(lián)評(píng)分")
if choice == "1":
s = input("輸入對(duì)聯(lián):")
s = jieba.lcut(s) # 對(duì)聯(lián)切詞
x = couplet(s) # 生成另一聯(lián)
s = "".join(s)
for i in range(1000):
# 檢驗(yàn)平仄,平仄正確率大于百分之80后直接輸出,否則再次隨機(jī)生成下聯(lián),直到平仄符合
pz = jy(s, x)
if pz >= 0.8:
print("-----------------------------------")
if is_down(s):
print("上聯(lián):" + x)
print("下聯(lián):" + s)
else:
print("上聯(lián):" + s)
print("下聯(lián):" + x)
print("-----------------------------------")
print("評(píng)分:")
print("平仄: " + str(round(pz * 100, 2)) + "分")
print("對(duì)仗: " + str(round(R * 10000, 2)) + "分")
break
else:
x = couplet(s)
else:
s = input("輸入上聯(lián):")
x = input("輸入下聯(lián):")
pz = jy(s, x)
print("評(píng)分:")
print("平仄: " + str(round(pz * 100, 2)) + "分")
實(shí)驗(yàn)結(jié)果&對(duì)比分析
1.實(shí)驗(yàn)結(jié)果
運(yùn)行結(jié)果: