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 机器接龙): '))

 

posted @ 2019-05-16 17:41  枫奇丶宛南  阅读(315)  评论(0编辑  收藏  举报