python实现诗词接龙
前言
在之前的《中国诗词大会》上面,曾经出现过诗词接龙的规则,当然诗词接龙肯定不是现在才有的内容,它应该是来源于飞花令
飞花令
飞花令是古人行酒令时的一个文字游戏,得名于唐代诗人韩翃(hóng)《寒食》中的春城无处不飞花”。行飞花令时可选用诗词曲中的句子,但选择的句子一般不超过7个字。
一般而言,对于成语接龙大家或多或少都还能接上一些,但是对于诗词接龙,如果不是特别研究诗词的人,那么一般最多接上那么一句两句而已。
在节目中看到一个个选手那么厉害的表现,其实也很羡慕,但是对于程序员来说,不管你多厉害,依然是敌不过程序的,毕竟在大脑中存贮的诗词再丰富,也没有计算机存的多,反应也不会有电脑那么快。
不然在比赛中如何判定胜负呢,这都是提前写好的程序,由电脑直接判定
思路
本文来简单的实现一下诗词接龙
首先是收集诗词,在比赛中可以限定规则,比如诗句只能是五言律诗,或者只能是唐诗宋词等,所以我们可以根据规则来收集不同的的诗词库
我们的目标是古诗文网 :https://www.gushiwen.org/shiwen/
这个网站将各种类型的诗歌都分好类了,很容易就能找到需要的内容了
收集诗歌
通过爬虫脚本收集制定的诗歌,使用urllib或者request获取网页内容,通过bs4、pyquery、lxml、xpath等解析工具获取诗歌内容
然后将诗歌存贮到本地即可
程序:
import re
import requests
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED
# 爬取的诗歌网址
urls = ['https://so.gushiwen.org/gushi/tangshi.aspx',
'https://so.gushiwen.org/gushi/sanbai.aspx',
'https://so.gushiwen.org/gushi/songsan.aspx',
'https://so.gushiwen.org/gushi/songci.aspx',
'https://so.gushiwen.org/gushi/shijiu.aspx'
]
poem_links = []
# 诗歌的网址
for url in urls:
# 请求头部
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
req = requests.get(url, headers=headers)
soup = BeautifulSoup(req.text, "lxml")
content = soup.find_all('div', class_="sons")[0]
links = content.find_all('a')
for link in links:
poem_links.append('https://so.gushiwen.org'+link['href'])
poem_list = []
print(poem_links)
print(len(poem_links))
#匹配中文
def remove_punctuation(line):
rule = re.compile(u'[^a-zA-Z0-9\u4e00-\u9fa5]')
line = rule.sub('',line)
return line
# 爬取诗歌页面
def get_poem(url):
print(url+ ' be crawled')
#url = 'https://so.gushiwen.org/shiwenv_45c396367f59.aspx'
# 请求头部
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
req = requests.get(url, headers=headers)
soup = BeautifulSoup(req.text, "lxml")
poem = soup.find('div', class_='contson').text.strip()
poem = poem.replace(' ', '')
poem = re.sub(re.compile(r"([sS]*?)"), '', poem)
poem = re.sub(re.compile(r"([sS]*?)"), '', poem)
#poem = re.sub(re.compile(r"。([sS]*?)"), '', poem)
poem = poem.replace('!', '!').replace('?', '?')
poem_list.append(poem)
# 利用并发爬取
executor = ThreadPoolExecutor(max_workers=10) # 可以自己调整max_workers,即线程的个数
# submit()的参数: 第一个为函数, 之后为该函数的传入参数,允许有多个
future_tasks = [executor.submit(get_poem, url) for url in poem_links]
# 等待所有的线程完成,才进入后续的执行
wait(future_tasks, return_when=ALL_COMPLETED)
# 将爬取的诗句写入txt文件
poems = list(set(poem_list))
poems = sorted(poems, key=lambda x:len(x))
for poem in poems:
print(poem)
poem = poem.replace('《','').replace('》','').replace(':', '').replace('“', '')
print(poem)
with open('poem.txt', 'a') as f:
f.write(poem)
f.write('\n')
这样我们就得到了诗词样本,但是得到的样本需要稍微的处理一下,因为里面有些标点什么的需要去掉,通过上面的程序大部分已经去除了,但是还有一点需要处理,在这个网站上,某些通假字也被标注了,然后信息被自动获取了比如下面的(泪湿 :一作:满)
针对括号以及里面的内容,需要用下面这四句来替换
poem=re.sub('\(.*?\)','',poem) #替换中文括号包含的内容为空
poem=re.sub('\(.*?\)','',poem)#替换左边为中文右边为英文括号包含的内容为空
poem=re.sub('\(.*?\)','',poem)#替换右边为中文左边为英文括号包含的内容为空
poem=re.sub('\(.*?\)','',poem)#替换英文括号包含的内容为空
诗歌处理
将诗歌转化为拼音,通过xpinyin这个库可以将中文转换为拼音的形式存在
pip3 install xpinyin
有了诗歌语料和这个处理拼音的库,我们可以对诗歌进行分句,分句的标准为:按照结尾为。?!进行分句,这可以用正则表达式实现。之后,将分句好的诗歌写成字典:键(key)为该句首字的拼音,值(value)为该拼音对应的诗句,并将字典保存为pickle文件
import re
import pickle
from xpinyin import Pinyin
from collections import defaultdict
def main():
with open('poem.txt', 'r') as f:
poems = f.readlines()
sents = []
for poem in poems:
#parts = re.findall(r'[sS]*?[。?!]',poem.strip())
parts= re.split('。|?|!',poem.strip())#根据句号问号感叹号分割诗句
for part in parts:
if len(part) >= 4:
sents.append(part.strip())
poem_dict = defaultdict(list)
a = list(set(sents))
for sent in a:
head = Pinyin().get_pinyin(sent, tone_marks='marks', splitter=' ').split()[0]#将诗句转换为拼音,区第一个元素即只取第一个拼音
poem_dict[head].append(sent)#将诗句的第一个拼音以及诗句以字典的形式存贮
with open('poemDict.pk', 'wb') as f:
pickle.dump(poem_dict, f)
main()
pickle文件里面的内容大致如下:
jú ['菊花开,菊花残']
xué ['学书初学卫夫人,但恨无过王右军', '学舞宫腰,二月青犹短']
cáo ['嘈嘈切切错杂弹,大珠小珠落玉盘']
cài ['蔡女昔造胡笳声,一弹一十有八拍']
读取这个这个文件的方法如下
import pickle
a=open('poemDict.pk','rb')
poem_dict = pickle.load(a)
for key, value in poem_dict.items():
print(key, value)
控制程序:
接下来,读取pickle文件,编写诗歌接龙的代码,完整代码如下:
import pickle
from xpinyin import Pinyin
import random
import ctypes
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12
FOREGROUND_DARKWHITE = 0x07 # 暗白色
FOREGROUND_BLUE = 0x09 # 蓝色
FOREGROUND_GREEN = 0x0a # 绿色
FOREGROUND_SKYBLUE = 0x0b # 天蓝色
FOREGROUND_RED = 0x0c # 红色
FOREGROUND_PINK = 0x0d # 粉红色
FOREGROUND_YELLOW = 0x0e # 黄色
FOREGROUND_WHITE = 0x0f # 白色
std_out_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
# 设置CMD文字颜色
def set_cmd_text_color(color, handle=std_out_handle):
Bool = ctypes.windll.kernel32.SetConsoleTextAttribute(handle, color)
return Bool
# 重置文字颜色为暗白色
def resetColor():
set_cmd_text_color(FOREGROUND_DARKWHITE)
# 在CMD中以指定颜色输出文字
def cprint(mess, color):
color_dict = {
'蓝色': FOREGROUND_BLUE,
'绿色': FOREGROUND_GREEN,
'天蓝色': FOREGROUND_SKYBLUE,
'红色': FOREGROUND_RED,
'粉红色': FOREGROUND_PINK,
'黄色': FOREGROUND_YELLOW,
'白色': FOREGROUND_WHITE
}
set_cmd_text_color(color_dict[color])
print(mess)
resetColor()
color_list = ['蓝色','绿色','天蓝色','红色','粉红色','黄色','白色']
# 获取字典
with open('poemDict.pk', 'rb') as f:
poem_dict = pickle.load(f)
#for key, value in poem_dict.items():
#print(key, value)
MODE = str(input('Choose MODE(1 for 人工接龙, 2 for 机器接龙): '))
while True:
try:
if MODE == '1':
enter = str(input('请输入一句诗或一个字开始:'))
while enter != 'exit':
test = Pinyin().get_pinyin(enter, tone_marks='marks', splitter=' ')
tail = test.split()[-1]
if tail not in poem_dict.keys():
cprint('无法接这句诗。', '红色')
MODE = 0
break
else:
cprint('机器回复:%s'%random.sample(poem_dict[tail], 1)[0], random.sample(color_list, 1)[0])
enter = str(input('你的回复:'))[:-1]
MODE = 0
if MODE == '2':
enter = input('请输入一句诗或一个字开始:')
for i in range(10):
test = Pinyin().get_pinyin(enter, tone_marks='marks', splitter=' ')
tail = test.split()[-1]
if tail not in poem_dict.keys():
cprint('------>无法接下去了啦...', '红色')
MODE = 0
break
else:
answer = random.sample(poem_dict[tail], 1)[0]
cprint('(%d)--> %s' % (i+1, answer), random.sample(color_list, 1)[0])
enter = answer[:-1]
print('(*****最多展示前10回接龙。*****)')
MODE = 0
except Exception as err:
print(err)
finally:
if MODE not in ['1','2']:
MODE = str(input('Choose MODE(1 for 人工接龙, 2 for 机器接龙): '))