1.项目目的
编写一段能够根据输入的上联对出下联的Python对对联程序。输入上联后,能随机生成下联,最终达到:
(1) 程序的智能性高,功能完善。希望生成的对联满足平仄、韵脚、词性等要求,并具有一定语义。
(2) 在实践项目的过程中提高同学的代码编写能力。具体能力包括:程序设计知识的综合运用能力;自主学习、错误调试的能力;规范书写代码及报告的意识和能力;阅读代码、改写代码的能力等。
2.项目内容
这是一个运用从网上爬取的对联、通过随机的算法生成一段符合押韵、平仄规则的对联的程序。大体过程为:
(1) 运用xPath、Request、Re模块等从网站上爬取对联,并对对联进行文本格式处理;
(2) 运用jieba模块对对联进行词性分析,运用pinyin模块对对联进行平仄校准;
(3) 根据词频随机做出符合押韵、平仄规则的下联。
3.输入输出
程序流程图
爬虫分析&数据分析
1.爬虫整体思路
(1)爬取网页
选择想要爬取的网址,并模拟头部浏览器访问该网站。创建一个dict,将头部信息以键值对的形式存入到dict对象中。本次项目爬取的网址为: http://duilian.haoshiwen.org
然后调用urllib.request.Request()函数创建一个request对象,该函数第一个参数传入url,第二个参数可以传入数据,默认是传入0数据,第三个参数是传入头部,该参数也是有默认值的,默认是不传任何头部。将dict对象传入urllib.request.Request()函数第三个参数。
此时,已经成功设置好报头,然后使用urlopen()打开该Request对象即可打开对应的网址。然后使用response.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万条对联),下载地址如下:
中国对联数据集 - Heywhale.com
train_in.txt(上联)和train_out.txt(下联)两个文件
2. 算法分析
- Lear_data模块是通过学习,记录词频
这部分是逐个分析上联中的词语,如获得一个词语后,在下联中找到这个词语可以匹配的词,并根据该词语匹配的次数用公式计算词频概率(对应概率越大说明该词最有可能与上联中的对应词匹配)。计算公式是多次试验后发现的比较能反映词频的算法,不一定是最优。
代码如下:

讯享网# 对联学习 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, 进行对联自动生成的话,打开学习生成的文件, 用couplet和ran函数实现匹配并生成下联:在couplet函数中,用random随机为上联分配一个权重/概率(达到随机匹配目的),对上联分词后的词语一一使用ran函数,然后再组合成上联。在ran函数中,将上联分配到的概率r与所有能配对的词语一一比较,如果比较到某个词的概率大于r,就返回该词i。否则使r减少后再重复以上步骤。然后进行输出,输出时进行平仄校验,平仄正确率大于80%后直接输出,否则再次随机生成下联,直到平仄符合。
以下介绍verify模块,主要是自定义的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.实验结果
运行结果:


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/76010.html