以诗之名

(2018年3月20日更新)“以诗之名”全面改版,本文内容过期。详情见“以诗之名-关于”。

这是什么?

以诗之名”是我和 Yixiao_Li 同学共同完成的一个搜索引擎,用于搜索你的名字(或者其他的几个汉字)包含于哪首古诗词中。
如搜索马化腾的“化腾”二字,出现的第一首诗是:

    造化精神无尽期,
    跳腾踔厉即时追。
    目前言句知多少,
    罕有先生活法诗。

这首诗的第一句中含有“化”、“腾”二字,而且是对齐的。说这是马化腾名字的出处也未尝不可 🙂

搜索结果中包含有你输入的全部关键字,所以我想,在大多数时候,你应该输入“化腾”而不是“马化腾”。
搜索结果是按照一定规则排序的:关键字对齐、关键字在一句之内及总长度较短的诗会排得比较靠前,因为我们认为,这样的诗,正是你想看到的。

此搜索引擎支持查询简体字和繁体字,但程序并不能自动识别你输入的是简体字还是繁体字,需要你显式地指明——若你输入的是繁体字,就打开繁体字对应的开关,否则程序就默认你输入的是简体字。除了“繁体”外,还有“仅唐”和“仅宋”两个开关,这两个开关是互斥的,除非你的浏览器没有执行我写的脚本程序,或是你有意破坏。打开这两个开关中的一个,会只在唐人的作品或宋人的作品中搜索诗词。注意,唐人也会写词,宋人也会写诗。

目前只收录了部分唐诗宋词,数据正在进一步整理中。我希望能再添加“仅经”、“仅楚”和“仅曲’等开关。

开发过程

为何要公开开发过程呢?好吧,这只是我的备忘录而已。

第一步:寻找诗词数据库

在github上搜索“唐诗”就搜到了一个很全的唐诗数据库:chinese-poetry,感谢@jackeyGao,感谢开源!

这个“数据库”是JSON格式的,而且是繁体字版的。

先将其导入数据库。数据库的创建语句是:

    CREATE DATABASE shiming DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

数据表的创建语句是:

    create table SHI_f (id int AUTO_INCREMENT primary key NOT NULL,title char(100), author char(50), paragraphs text, ft_index text, type char(5), src char(25));
    create table SHI_j (id int AUTO_INCREMENT primary key NOT NULL,title char(100), author char(50), paragraphs text, ft_index text, type char(5), src char(25));

其中“_j”表示简体,“_f”表示繁体,用Python解析JSON代码如下:

    # -*-coding:utf8-*-
    import os
    import json
    import traceback
    from sql_head import *
    from langconv import *

    global db
    db = None                   #全局变量,数据库连接

    def insert(title, author, paragraphs, type, src):
        u'''将数据插入数据库,会进行繁体转换,保存简、繁体两版'''
        global db
        sql_j = '''insert into SHI_j (`title`, `author`, `paragraphs`, `ft_index`, `type`, `src`) values ("{0}", "{1}", "{2}", "{3}", "{4}", "{5}")'''
        sql_f = '''insert into SHI_f (`title`, `author`, `paragraphs`, `ft_index`, `type`, `src`) values ("{0}", "{1}", "{2}", "{3}", "{4}", "{5}")'''
        #进行繁体到简体的转换
        title_j = Converter('zh-hans').convert(title.decode('utf-8'))
        title_j = title_j.encode('utf-8')
        author_j = Converter('zh-hans').convert(author.decode('utf-8'))
        author_j = author_j.encode('utf-8')
        paragraphs_j = Converter('zh-hans').convert(paragraphs.decode('utf-8'))
        index_j = ''
        for i in paragraphs_j:
            index_j += i+" "
        index = ''
        for i in paragraphs:
            index += i+" "
        db=linkmysql(db)        #连接数据库
        cursor = db.cursor()    #获得游标
        try:
            cursor.execute(sql_j.format(title_j, author_j, paragraphs_j, index_j, type, src))    #插入记录
            cursor.execute(sql_f.format(title, author, paragraphs, index, type, src))              #插入记录
            db.commit()
        except:
            # 发生错误时回滚
            traceback.print_exc()
            db.rollback()
            return -1
        else:
            return 0

    def jiexi_json(src):
        '''从给定源读html文件解析出标题、作者、内容等'''
        if "song" in src:
            type = "song"
        elif "tang" in src:
            type = "tang"
        else:
            type = "None"
        f = open(src, "r")
        try:
            s = json.load(f, encoding='utf-8')
        except:
            traceback.print_exc()
            f.close()
            return
        f.close()
        for i in s:
            try:
                title = i['title']
                author = i['author']
                paragraphs=''
                for item in i['paragraphs']:
                    if item.find(u'《')!=-1 or item.find(u'〖')!=-1:
                        break
                    paragraphs += item
                insert(title, author, paragraphs, type, src)
            except:
                traceback.print_exc()
                continue

    def bianli(rootdir):
        '''遍历rootdir目录中的文件'''
        num = 0
        for parent,dirnames,filenames in os.walk(rootdir):
            for filename in filenames:                        #输出文件信息
                if filename.endswith('.json'):                #该文件是json文件
                    print "["+str(num)+"]",filename
                    src = filename
                    jiexi_json(src)

    if __name__ == '__main__':
        db=linkmysql(db)        #连接数据库
        bianli(".")
        db.close()              #关闭和数据库的连接

其中langconv是用于进行繁体字和简体字转换的Python库,作者是@Skydark Chen,sql_head是我写的一个连接数据库用到很简单的库,代码如下:

    # -*-coding:utf8-*-
    import sys
    import MySQLdb

    #指定编码为utf8
    reload(sys)  
    sys.setdefaultencoding('utf8')

    db_config = {
        "hostname": "localhost",#主机名
        "username": "XXXX",#数据库用户名
        "password": "XXXX",#数据库密码
        "databasename": "XXXX",#要存入数据的数据库名
        }

    def linkmysql(db):
        try:#MySQLdb不支持长时间连接,在操作数据库前检查连接是否过期,过期则重连
            db.ping(True)
        except:
            #再次连接数据库
            db = MySQLdb.connect(db_config["hostname"],
                         db_config["username"],
                         db_config["password"],
                         db_config["databasename"],
                         charset='utf8')
        return db

运行该脚本,约半个小时后,数据导入完成,经查询,共有57591首唐诗,254237首宋词,这个数据量还是相当可观的。但比起数千年来,中华民族创造的灿烂诗海,只是沧海一粟而已。

第二步:查询语句

创建全文索引:

    ALTER TABLE SHI_f ADD FULLTEXT(`ft_index`);
    ALTER TABLE SHI_j ADD FULLTEXT(`ft_index`);
    repair table SHI_f;
    repair table SHI_j;

在简体库中搜索同时含有“张飞”二字的诗词,按字数多少递增排序,限定显示前500条。

    SELECT id, title, author, paragraphs FROM SHI_j WHERE MATCH (`ft_index`) AGAINST ("张") and MATCH (`ft_index`) AGAINST ("飞") order by LENGTH(paragraphs) limit 0,500;

第三步:编写网站

前端是用“轻量,小巧且精美的UI库”SUI Mobile 写的。
后台是用php写的。

第四步:部署上线

由于是两个人合作完成的,故有两个实例,一个是http://s.werner.wiki,还有一个是http://poetry.liyixiao.site/,这两个网站运行在不同的地方,只有很小的差别。

附:开始时的困境

一开始做这个项目时,我没有想到神奇的github,而是百度“唐诗大全”之类的字眼,妄图找到较全的唐诗数据库,当然是失败的。热情的popcorn同学得知我这一困境后为我友情提供多本唐诗宋词相关电子书,是azw3和mobi格式的。感谢popcorn同学的帮助!

当时我是这样处理电子书的:先通过一家网站将azw3格式的电子书转换成html格式,然后用Python解析html,从中提取出标题、作者、内容等我们关心的数据,并将其保存到数据库中。下面的代码是用于解析《宋词三百首全解》这本电子书的,虽然最终没有使用,但却具有纪念意义,故也将其摘录于此。

创建数据库和数据表

CREATE DATABASE SHIMING DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
create table SONGCI (id int AUTO_INCREMENT primary key NOT NULL,title char(20),author char(15),content text, src char(50));

解析网页

用Python解析网页,需安装BeautifulSoup4

    sudo pip install BeautifulSoup4

代码

    # -*-coding:utf8-*-
    import os
    import traceback
    from sql_head import *
    from bs4 import BeautifulSoup
    def insert(title, author, content, src):
        '''将数据插入数据库'''
        sql = '''insert into SONGCI (`title`, `author`, `content`, `src`) values ("{0}", "{1}", "{2}", "{3}")'''
        db = None
        db=linkmysql(db)        #连接数据库
        cursor = db.cursor()    #获得游标
        try:
            cursor.execute(sql.format(title, author, content, src))              #插入记录
            db.commit()
        except:
            # 发生错误时回滚
            db.rollback()
            db.close()                                      #关闭和数据库的连接
            return -1;
        else:
            db.close()                                      #关闭和数据库的连接
            return 0;
    def jiexi_html(src):
        '''从给定源读html文件解析出标题、作者、内容等'''
        f = open(src, "r")
        html_doc = f.read()
        f.close()
        soup = BeautifulSoup(html_doc, "lxml")
        [s.extract() for s in soup('sup')]  #去除所有sup标签
        title = soup.h1.string
        author = soup.find_all("p", class_="normaltext2")[0].get_text()
        content = soup.find_all("p", class_="normaltext4")[0].get_text()
        return title, author, content
    def bianli(rootdir):
        '''遍历rootdir目录中的文件'''
        for parent,dirnames,filenames in os.walk(rootdir):
            for filename in filenames:                        #输出文件信息
                if filename.endswith('.html'):                #该文件是html文件
                    print filename
                    src = filename
                    try:
                        title, author, content = jiexi_html(src)
                    except:
                        traceback.print_exc()
                        continue
                    try:
                        ret = insert(title, author, content, src)
                    except:
                        traceback.print_exc()
                        continue
                    if ret==0:
                        print 'success'
                    else:
                        print 'fail'

    if __name__ == '__main__':
        bianli(".")

参考

2 Replies to “以诗之名”

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

18 + 12 =