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