NLP学习笔记[0] -- 语料数据收集和预处理

前言和背景介绍:

如果让你来管理一个社区(比如有很多成员分享个人作品的那种), 你会怎么做呢? 想象一下, 要是用户在通过网页或者客户端来访问社区之外还能通过最常用的交流方式(比如QQ/微信甚至是打电话)就能发表自己的动态、查看和评论好友的帖子, 这该多么炫酷. 另外还有内容审核和管理, 要是用户和访客少还可以人工地逐一审核. 可是这样一方面人工成本非常高而且很麻烦, 另一方面审核慢也会造成不好的使用体验. 再想象一下, 要是有一个人工智能助手来辅助审核, 把有疑问的内容通过QQ之类的方式发送, 管理员只需要回复一个"同意"或者"拒绝"就能完成审核该多方便.

于是, 有了这些个愿景, 想要实现就需要全(头)栈(发)开(掉)发(没)了…
一个人工智能管理机器人的愿景
首先, 做一个人工智能项目必须得有足够的数据. 数据源有很多, 比如网站、广播、电影等等, 这里我想"另辟蹊径" – 从QQ群获取数据和知识.(毕竟有的群里还是有不少大佬的, 多加几个群窥屏学习总还是能学到点有用的)

那么问题来了, 如何获取QQ里的消息数据呢? 方法不少, 比如用MS SQL对付QQ的Msg.db(就在QQ的用户数据目录里)

QQ的消息记录数据库(图片里的打开方式不对, 请忽略)(上面图片里的打开方式不对, 请忽略)

不过, 在个案例中还希望AI能通过QQ跟用户交流, 这就还需要一个QQ的接口, 以前还有QQbot这些机器人接口, 但后来似乎就只剩酷Q. (酷Q也是比较老牌的, 自从狗狗接触酷Q到现在也快6年了)关于酷Q咋使, 官方推荐用易语言SDK, 它也有VC++、Python、Go等SDK, 不过用啥不重要了, 在这片文章里主要是讨论如何处理它的数据库以及NLP的前置处理. (如果有对酷Q开发感兴趣的朋友, 那就评论个留言吧, 感兴趣的多的话我就做教程)
酷Q应用开发的说明页面
而且用MS的SQL Server对付PC QQ的数据库并不方便, 而酷Q本身也有sqlite的日志系统和事件记录, sqlite又是一个轻量数据库, 读写和操作非常方便且通用, 这就一举多得了.


下面开始本文的正式内容(jupyter笔记)

从酷Q的event.db读数据并做基础预处理


在做文本处理之前得看看文本到底是不是有意义的语言, 如果是, 不同语言也应该采用不同的方式对待.
目前主流的开源自然语言语种识别工具有langdetect和langid, 其中, langdetect处理速度较高但准确率不足, langid准确率要高一些但速度也慢.
langid目前(Jul 15, 2017)提供了97种语言的预训练, 分类出来的的语言标号依据ISO 639-1语言编码标准

import langid
项目中常见自然语言语种 = {'en':'英文','zh':'中文', 'de':'德语', 'el':'希腊语', 'ja':'日语', 'la':'拉丁语', 'ru':'俄语', 'th':'泰语'}

jieba是一个开源的汉语分词工具, 号称做最好的python中文分词, 简便易用, 支持三种分词模式, 支持繁体, 支持自定义词典, 可以标注词性, 可以提取文本关键词. MIT授权.
jieba只支持中文, 英语以及类英语的语言可以用NLTK(NLTK也支持中文处理, 但使用不太友好, 中文分词还需要安装斯坦福分词器)
jieba词性标注采用和ictclas兼容的标记法
其他常用的汉语分词工具还有SnowNLP, PkuSeg, THULAC, HanLP

import jieba as jb, jieba.analyse as jban, jieba.posseg as pseg
汉语文本常见词性标注表 = {'d':'副词', 'vn':'动名词', 'n':'名词', 'v':'动词', 'a':'形容词', 'y':'语气词', 'c':'连词', 'x':'非语素', 'p':'介词', 'm':'数词', 'q':'量词', 'ul':'助词', 's':'处所词', 'f':'方位词', 'i':'成语', 'ns':'地名', 'o':'拟声词', 'b':'区别词', 'p':'介词', 'r':'代词', 't':'时间词', 'nr':'人名', 'l':'习用语', 'eng':'外来词', 'nz':'其他专名', 'z':'状态词'}
# 有的词性的词对于NLP来说是不重要的, 需要忽略, 比如非语素(标点符号什么的)以及语气词
不关注的词性 = ['x', 'y']
# jieba预设的词典虽然在大多时候能满足需求, 但是对于实际应用来说不够, 很多术语、“黑话”、网络口头语、“梗”会被错判, 还需要根据项目实际情况自制词典.  
# 为了方便观察, 就把自定义词典内容写这里了...
文件_自定字典 = open('userDict.txt', 'w', -1)
文件_自定字典.write('''
剪脚封灌 30 vn
感应加热 600 nz
市电 200 d
那 10 c
上去 20 z
上去过 6000 vf
下去 20 z
靠 10 y
是不 5000 l
初级 20 nz
次级 20 nz
斩波 200 nz
TC 600 nz
发错 10 z
群 200 n
控制 300 vn
可行 20 z
没有卖的 z
啊 6 y
地 5000 nz
良好接地 4960 z
异常 20 nz
指定的 300 d
raise语句 nz
except语句 nz
新洁能 nz
可行 40 z
''')
文件_自定字典.flush(); 文件_自定字典.close()
jb.load_userdict('userDict.txt')
jb.initialize()
Building prefix dict from the default dictionary ...
Loading model from cache /tmp/jieba.cache
Loading model cost 0.816 seconds.
Prefix dict has been built successfully.
import time # 用于计时比较
测试文本1 = "[CQ:at,id=qq/user/15*****] 😂氖泡接在驱动电源负和地线之间[CQ:face,id=108][CQ:face,id=108][CQ:at,id=qq/user/15*****]"
测试文本2 = "牛啤"
测试文本3 = "ΕΞΖΝи△M"
测试文本4 = "This is a test text..."
测试文本5 = '<h2><a id="user-content-probability-normalization" class="anchor" aria-hidden="true" href="#probability-normalization"></a>Probability Normalization</h2>'
测试文本6 = "import win32event as event; event_tigger = event.CreateEvent(None, False, False, 'Global\\233__update'); event.SetEvent(event_tigger)"
langid.classify(""); time_start = time.time()
print( langid.classify(测试文本1), langid.classify(测试文本2), langid.classify(测试文本3), langid.classify(测试文本4), langid.classify(测试文本5), langid.classify(测试文本6) ); 
print("语种检测(常规模式)耗时→%.3f秒" % ( time.time() - time_start ))
# langid.classify默认输出的第二个数值是对数概率, 不计算全部语种的概率所以速度快. 但是有时候因为还需要知道语种分类的"置信度", 就需要启用langid.py的概率归一化: 
time_start = time.time()
from langid.langid import LanguageIdentifier, model
lider = LanguageIdentifier.from_modelstring(model, norm_probs=True) # 其实主要的耗时是出在这里
print("启用置信度归一化耗时→%.3f秒" % ( time.time() - time_start ))
time_start = time.time()
print( lider.classify(测试文本1), lider.classify(测试文本2), lider.classify(测试文本3), lider.classify(测试文本4), lider.classify(测试文本5), lider.classify(测试文本6) ); 
print("语种检测(归一化置信度模式)耗时→%.3f秒" % ( time.time() - time_start ))
('zh', -204.03105926513672) ('zh', -19.84054446220398) ('el', -68.03624391555786) ('en', -54.41310358047485) ('eu', -78.12822103500366) ('en', -47.138615131378174)
语种检测(常规模式)耗时→0.025秒
启用置信度归一化耗时→2.760秒
('zh', 1.0) ('zh', 0.5123629134775305) ('el', 0.9340167392870947) ('en', 0.999999990990354) ('eu', 0.5994969697756787) ('en', 0.998501267059482)
语种检测(归一化置信度模式)耗时→0.027秒

还可以考虑加个编程语言探测, 比如GitHub通过linguist对主流编程语言(300多种)达到84%正确识别
其实也可以自己用朴素贝叶斯实现一个, 但是暂时没时间和精力去做了…

def 语种测试(文本):
    语种 = ""
    语种分类信息 = lider.classify(文本)
    if 语种分类信息[1] > 0.98: # 实际观察发现, 置信度低于这个值的通常是无意义内容或者不是自然语言
        try:
            语种 = 项目中常见自然语言语种[语种分类信息[0]]
        except KeyError:
            语种 = "未知"
    else:
        语种 = None
    return 语种

def 分词并标注(文本, 调试模式 = False):
    结果 = []
    文本的语种 = 语种测试(文本)
    if 文本的语种 != None:
        if 调试模式 == True:
            print("###debug---检测到 %s 文本 \"%s\" 输入---" % (文本的语种, 文本) )
        if 文本的语种 == "中文":
            tmp_pseg结果 = pseg.lcut(文本)
            if 调试模式 == True:
                for, 词性 in tmp_pseg结果:
                    try:
                        结果.append( (, "%s(%s)" % ( 词性, 汉语文本常见词性标注表[词性]) ) )
                    except KeyError:
                        结果.append( (, 词性) )
            else:
                结果 = [ (, 词性) for, 词性 in tmp_pseg结果 if 词性 not in 不关注的词性 ] # 忽略多余的语素
        else:
            raise UserWarning("暂不处理其他语种")
    else:
         raise SyntaxWarning("无法确定输入文本的语种")
    return 结果
print(分词并标注("Python可以使用raise语句抛出一个指定的异常, 之后再使用except语句根据异常信息来处理.", 调试模式 = True))
###debug---检测到 中文 文本 "Python可以使用raise语句抛出一个指定的异常, 之后再使用except语句根据异常信息来处理." 输入---
[('Python', 'eng(外来词)'), ('可以', 'c(连词)'), ('使用', 'v(动词)'), ('raise语句', 'nz(其他专名)'), ('抛出', 'v(动词)'), ('一个', 'm(数词)'), ('指定的', 'd(副词)'), ('异常', 'nz(其他专名)'), (',', 'x(非语素)'), (' ', 'x(非语素)'), ('之后', 'f(方位词)'), ('再', 'd(副词)'), ('使用', 'v(动词)'), ('except语句', 'nz(其他专名)'), ('根据', 'p(介词)'), ('异常', 'nz(其他专名)'), ('信息', 'n(名词)'), ('来', 'v(动词)'), ('处理', 'v(动词)'), ('.', 'x(非语素)')]

MutBot这个项目以QQ作为主要数据来源, 所以预处理除了做自然语言语种判断、分词和词性标注之外, 还需要过滤CQ码以及多余的符号. 这里用正则表达式简单处理

import re
def CQ记录转换(原CQ记录, 调试模式 = False):
    if 调试模式 == True:
        print("***debug--传入文本 \"%s\"" % 原CQ记录)
    消息_处理 = 原CQ记录
    消息 = {"CQ码":"", "内容":""}
    tmp = re.search('(?P<CQ码>\[CQ:\w+,\S+\])', 消息_处理)
    tmpcounter = 0
    while tmp != None and tmpcounter < 5:    # 把CQ码全都单独收起来, 剩下的就是内容了
        消息["CQ码"] += tmp.group(1) + ", "
        消息_处理 = 消息_处理.replace(tmp.group(1), "")
        tmp = re.search('(?P<CQ码>\[CQ:\w+,\S+\])', 消息_处理)
        tmpcounter += 1
    消息["内容"] = 消息_处理
    return 消息
消息 = CQ记录转换(测试文本1, 调试模式 = True); print(消息)
***debug--传入文本 "[CQ:at,id=qq/user/15*****] 😂氖泡接在驱动电源负和地线之间[CQ:face,id=108][CQ:face,id=108][CQ:at,id=qq/user/15*****]"
{'CQ码': '[CQ:at,id=qq/user/15*****], [CQ:face,id=108][CQ:face,id=108], ', '内容': ' 😂氖泡接在驱动电源负和地线之间'}

以上内容基本就能实现简单的文本预处理了, 预处理数据作为后级的输入
目前主要的目标还是从QQ群聊天记录里提取语料和数据集


目前(05 Jan. 2020)的想法: 维护一个数据库用以处理和记录对话上下文关系以及前级(文本预处理等)的结果和处理情况

import sqlite3 as sqlite
import numpy as np, pandas as pd # 主要是为了简化原始数据的读取和预处理
pd.set_option('max_colwidth',100); pd.set_option('display.max_rows', 20); pd.set_option('display.max_columns', 8) # 为了方便查看内容

pandas是一款数据处理工具,集成了numpy以及matplotlib,拥有便捷的数据处理以及文件读取能力

pandas主要有几大功能:

  1. Object Creation: pandas有三种对象, series、df和Panel Object. Series, 通过传入list对象来新建, 可以指定索引; DataFrame, 通过传入numpy数组/dictionary对象来创建.
  2. Viewing Data: 查看头部/查看索引和列名/查看统计结果什么的, 还可以转换成numpy格式/做矩阵转置以及排序.
  3. Selection: 类似于sql的select, 可以指定列/行/标签/值/位置/条件来筛选
  4. Missing Data: 默认的空值是np.nan, 可通过reindex函数来增删改查某坐标轴(行或列)的索引,并返回一个数据的拷贝, 还可以判断是否为空值(返回False或True)
  5. Operations: 一些常用计算, 包括平均值/ 数值移动等. (通过应用还可以做累计求和和其他自定义的方法)
  6. Merge: pandas提供了多个方法来合并不同的对象. 其中Merge方法类似SQL的合并方式.
  7. Grouping: 分组主要是为了对某些数据的计算, 例如df.groupby(‘A’).sum()
  8. Reshaping
  9. Time Series: pandas很适合用来处理时序, 可以调整时间间隔/时区转换/时间格式转换
  10. Categoricals
  11. Plotting: 用于数据绘图, 和Matplotlib基本一样
  12. Getting Data In/Out : 可以方便地在不同类型的文件(csv/text/json/html/excel/sql等)中导入或导出数据

完整的user guide可以在pydata上看到
这里主要是要用pandas的DataFrame简化sql操作
用 pandas DataFrame 读取数据结果的好处主要是不需要每次都调用 fetchall之类的函数, 还能能方便地通过表头的名字来阅读整个表

DB_CQ = sqlite.connect("eventv2.db")
DB_APP = sqlite.connect("app.db")
# 出于尊重隐私考虑, 不提供这两个数据库, 并且这两个数据库相关的部分内容打了码
query_selectLog = "SELECT `id`, `tag`, `GROUP`, `account`, `operator`, `content`, `TIME` FROM `event`  WHERE `id` < 6666  ORDER by `id` DESC  LIMIT 35;"
DF_CQdata = pd.read_sql_query(query_selectLog, DB_CQ)
DF_CQdata
id tag group account operator content time
0 6665 contact qq/group/48353*** qq/user/4297*** [CQ:image,file=04A2309D3F05B7949596D3C1484D1116.jpg] 1578211271
1 6664 contact qq/group/483537882 qq/user/4297*** 没用 1578211269
2 6663 contact qq/group/483537882 qq/user/18371*** 主动是把能量换到别的电池里去 1578211269
3 6662 contact qq/group/483537882 qq/user/429**** 原车有这个 1578211264
4 6661 contact qq/group/483537882 qq/user/616471607 了解一下近几年的电池保护方案吧… 1578211258
... ... ... ... ... ... ... ...
30 6635 contact qq/group/483537882 qq/user/1837****** 另外20a你还被动均衡咋想的? 1578211060
31 6634 contact qq/group/483537882 qq/user/1837107*** 电流用外边电阻啊? 1578211046
32 6633 contact qq/group/483537882 qq/user/42971**** MOS不能调节输出电流把 1578211021
33 6632 contact qq/group/483537882 qq/user/183710**** aos和英飞凌 to252,dfn5x6,dfn3x3的mos 2.7v导通的有的是 1578211018
34 6631 contact qq/group/483537882 qq/user/616471607 新洁能了解一下 1578211006

35 rows × 7 columns

经过预处理的消息 = {} 
tmp_CQ转换 = ""
tmp_分词标注 = ""
time_start = time.time()
for name, rows in DF_CQdata.iterrows():
    tmp_CQ转换 = CQ记录转换(rows['content'])
    if len(tmp_CQ转换["内容"]) > 0:
        try:
            tmp_分词标注 = 分词并标注(tmp_CQ转换["内容"]) 
        except UserWarning:
            tmp_分词标注 = "[*unkownLanguage*]" + tmp_CQ转换["内容"]
        except SyntaxWarning:
            tmp_分词标注 = "[*unclearContent*]" + tmp_CQ转换["内容"]
    else:
        tmp_分词标注 = []
    经过预处理的消息[rows['id']] = {'CQ码':tmp_CQ转换['CQ码'] if len(tmp_CQ转换['CQ码']) > 0 else "", '分词标注':tmp_分词标注 if len(tmp_分词标注) > 0 else ""}
    
print("消息文本预处理耗时→%.3f秒" % (time.time() - time_start) )

for 消息ID, 预处理结果 in 经过预处理的消息.items():
    print("%d: CQ码→%s | 词性标注→%s" %(消息ID, 预处理结果['CQ码'] if len(预处理结果['CQ码']) > 0 else "无", 预处理结果['分词标注'] if len(预处理结果['分词标注']) > 0 else "无" ) )
消息文本预处理耗时→0.187秒
6665: CQ码→[CQ:image,file=04A2309D3F05B7949596D3C1484D1116.jpg],  | 词性标注→无
6664: CQ码→无 | 词性标注→[('没用', 'v')]
6663: CQ码→无 | 词性标注→[('主动', 'b'), ('是', 'v'), ('把', 'p'), ('能量', 'n'), ('换', 'nz'), ('到', 'v'), ('别的', 'r'), ('电池', 'n'), ('里', 'f'), ('去', 'v')]
6662: CQ码→无 | 词性标注→[('原车', 'n'), ('有', 'v'), ('这个', 'r')]
6661: CQ码→无 | 词性标注→[('了解', 'v'), ('一下', 'm'), ('近几年', 'l'), ('的', 'uj'), ('电池', 'n'), ('保护', 'v'), ('方案', 'n')]
6660: CQ码→无 | 词性标注→[('被动', 'vn'), ('是', 'v'), ('用', 'p'), ('电阻', 'n'), ('热', 'n'), ('耗散', 'v'), ('掉', 'zg')]
6659: CQ码→无 | 词性标注→[('最后', 'f'), ('还是', 'c'), ('热敏电阻', 'n'), ('好', 'a'), ('用', 'p')]
6658: CQ码→[CQ:face,id=182],  | 词性标注→[*unclearContent*]我真不知道
6657: CQ码→[CQ:image,file=47CFFBFE586387C795EF5281A2C325AE.jpg],  | 词性标注→无
6656: CQ码→无 | 词性标注→[('....', 'm'), ('你', 'r'), ('看来', 'v'), ('真不知道', 'i'), ('啥', 'r'), ('叫', 'v'), ('被动', 'vn'), ('均', 'd'), ('流', 'v'), ('主动', 'b'), ('均', 'd'), ('流', 'v')]
6655: CQ码→无 | 词性标注→[('不然', 'c'), ('就', 'd'), ('保护', 'v'), ('了', 'ul')]
6654: CQ码→[CQ:face,id=178][CQ:face,id=178][CQ:face,id=178],  | 词性标注→无
6653: CQ码→无 | 词性标注→[('充满', 'a'), ('单体', 'n'), ('高', 'a'), ('的', 'uj'), ('放电', 'v'), ('这样', 'r'), ('低', 'a'), ('的', 'uj'), ('才能', 'v'), ('充上', 'v')]
6652: CQ码→无 | 词性标注→[('被动', 'vn'), ('均', 'd'), ('流', 'v'), ('别说', 'c'), ('你', 'r'), ('不', 'd'), ('知道', 'v'), ('啥', 'r'), ('叫', 'v'), ('被动', 'vn'), ('均', 'd'), ('流', 'v'), ('主动', 'b'), ('均', 'd'), ('流', 'v')]
6651: CQ码→无 | 词性标注→[('充电', 'v')]
6650: CQ码→无 | 词性标注→[('都', 'd'), ('说', 'v'), ('了', 'ul'), ('当', 't'), ('修理', 'v'), ('设备', 'vn'), ('用', 'p')]
6649: CQ码→无 | 词性标注→[('被动', 'vn'), ('均', 'd'), ('流', 'v'), ('你', 'r'), ('咋', 'r'), ('想', 'v'), ('的', 'uj')]
6648: CQ码→无 | 词性标注→[('我', 'r'), ('是', 'v'), ('说', 'v')]
6647: CQ码→[CQ:image,file=D07AC615574F12C4434BE3A25D34102F.jpg],  | 词性标注→无
6646: CQ码→[CQ:face,id=178],  | 词性标注→无
6645: CQ码→无 | 词性标注→[('这么', 'r'), ('多', 'm'), ('你', 'r'), ('说', 'v'), ('咋搞', 'v')]
6644: CQ码→无 | 词性标注→[*unclearContent*]回来了
6643: CQ码→[CQ:image,file=5F173229FF29B6B0A281C8ADF64FB699.jpg],  | 词性标注→无
6642: CQ码→无 | 词性标注→[('被动', 'vn'), ('均', 'd'), ('流', 'v'), ('咋', 'r'), ('想', 'v'), ('的', 'uj')]
6641: CQ码→无 | 词性标注→[*unclearContent*]10。5。都行
6640: CQ码→无 | 词性标注→[('为啥', 'r'), ('不', 'd'), ('主动', 'b'), ('均', 'd'), ('流', 'v')]
6639: CQ码→无 | 词性标注→[('电流', 'n'), ('可以', 'c'), ('调', 'v')]
6638: CQ码→无 | 词性标注→[*unclearContent*]最大20安
6637: CQ码→无 | 词性标注→[('汽车', 'n'), ('电池', 'n'), ('包', 'v')]
6636: CQ码→无 | 词性标注→[('修车', 'v'), ('用', 'p')]
6635: CQ码→无 | 词性标注→[('另外', 'c'), ('20', 'm'), ('a', 'eng'), ('你', 'r'), ('还', 'd'), ('被动', 'vn'), ('均衡', 'a'), ('咋', 'r'), ('想', 'v'), ('的', 'uj')]
6634: CQ码→无 | 词性标注→[('电流', 'n'), ('用', 'p'), ('外边', 'f'), ('电阻', 'n')]
6633: CQ码→无 | 词性标注→[('MOS', 'eng'), ('不能', 'v'), ('调节', 'vn'), ('输出', 'v'), ('电流', 'n'), ('把', 'p')]
6632: CQ码→无 | 词性标注→[('aos', 'eng'), ('和', 'c'), ('英飞凌', 'nr'), ('to252', 'eng'), ('dfn5x6', 'eng'), ('dfn3x3', 'eng'), ('的', 'uj'), ('mos', 'eng'), ('2.7', 'm'), ('v', 'eng'), ('导通', 'n'), ('的', 'uj'), ('有的是', 'l')]
6631: CQ码→无 | 词性标注→[('新洁能', 'nz'), ('了解', 'v'), ('一下', 'm')]
# 再维护一个数据库用以记录预处理过的内容以及上下文关系
DB_ppced = r"CQevePPC.db"
Conn_ppced = sqlite.connect(DB_ppced)

目前(10 Jan. 2020)的想法是使用两个表: ppcLogcontextIndex, 分别用于记录预处理后的数据和上下文的关系(按群和时间分组, 以便后续提取对话)

# 数据库设计
query_createTable_ppcLog = '''
CREATE TABLE "ppcLog" (
"lid"       INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"Link"      INTEGER,
"cqCode"    TEXT,
"jiebaRES"  TEXT,
"lastModif" NUMERIC NOT NULL DEFAULT CURRENT_TIMESTAMP,
"note"      TEXT
);'''
query_createTable_contextIndex = '''
CREATE TABLE "contextIndex" (
"id_conversation"   INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"GroupFrom"         INTEGER,
"UserFrom"          INTEGER,
"dialogsContent"    TEXT,
"DialogsLink"       INTEGER
"time_sort"         NUMERIC NOT NULL DEFAULT CURRENT_TIMESTAMP,
"note"              TEXT
);'''
# 先检查数据库状态, 如果没有预设的表那就初始化
def DBtest(dbConnHandle, tableNames, checkQuery = []):
    querys_tableCheck = []
    for i in range( len(tableNames) ):
        querys_tableCheck.append("SELECT * FROM `sqlite_master` where type = 'table' and name = '%s';" % tableNames[i])
    DBisOK = False
    OrigSct = ""
    tmp_cont = 0
    cont_table = 0
    cont_unexc = 0
    for query in querys_tableCheck:
        curs = dbConnHandle.execute(query)
        gets = curs.fetchall()
        if len(gets) > 0:
            OrigSct = gets[-1][-1]
            print("数据表`%s`存在. ~ 读到记录:  \n%s " % (tableNames[tmp_cont], OrigSct) )
            cont_table += 1
            if len(checkQuery) == len(tableNames):
                if OrigSct.replace("\n", "").replace(" ","").replace(";", "").replace("\t", "") != checkQuery[tmp_cont].replace("\n", "").replace(" ","").replace(";", "").replace("\t", ""):
                    cont_unexc -= 4
                else:
                    DBisOK += 1
        tmp_cont += 1

    if checkQuery != []:
        if cont_table < 1 :
            print("指定的表不存在")
            DBisOK = False
        if cont_unexc < 0: 
            if cont_table > 0:
                raise SyntaxWarning("数据库检查未通过: 已经存在不同结构的同名表. (有 %d 个表记录与提供的记录不一致)" % (-cont_unexc / 4))
                return cont_unexc
        else:
            if DBisOK == len(tableNames) :
                print("数据库检查通过. %d 个表(共%d个表)校验一致" % (DBisOK, len(tableNames)) )
                DBisOK = True    
    else:
        if DBisOK == len(tableNames) :
            print("指定表存在")
            DBisOK = True
        else:
            print("指定表不存在")
            DBisOK = False
    return DBisOK        
def DB_init(dbConnHandle, tableNames = [], checkQuery = []):
    realyNeedInit = False
    try:
        dbStatus = DBtest(dbConnHandle, tableNames, checkQuery)
    except SyntaxWarning as info:
        print("初始化失败, 建议检查运行环境(文件冲突). 错误信息:  \n    ", info)
        return -233
    if dbStatus == False:
        realyNeedInit = True
    elif dbStatus < 0:
        r
    if realyNeedInit == True:
        print("准备开始初始化...")
        try:
            for query in checkQuery:
                dbConnHandle.execute(query)
            print("提交变动...")
            dbConnHandle.commit()
            print("数据表初始化完成")
        except Exception as errinfo: 
            print("---SQL执行失败---: ", errinfo)
    else:
        print("已存在同名表, 跳过初始化")
DB_init(Conn_ppced, ["ppcLog", "contextIndex"], [query_createTable_ppcLog, query_createTable_contextIndex])
指定的表不存在
准备开始初始化...
提交变动...
数据表初始化完成
DBtest(Conn_ppced, ["ppcLog", "contextIndex"], [query_createTable_ppcLog, query_createTable_contextIndex])
数据表`ppcLog`存在. ~ 读到记录:  
CREATE TABLE "ppcLog" (
"lid"       INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"Link"      INTEGER,
"cqCode"    TEXT,
"jiebaRES"  TEXT,
"lastModif" NUMERIC NOT NULL DEFAULT CURRENT_TIMESTAMP,
"note"      TEXT
) 
数据表`contextIndex`存在. ~ 读到记录:  
CREATE TABLE "contextIndex" (
"id_conversation"   INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"GroupFrom"         INTEGER,
"UserFrom"          INTEGER,
"dialogsContent"    TEXT,
"DialogsLink"       INTEGER
"time_sort"         NUMERIC NOT NULL DEFAULT CURRENT_TIMESTAMP,
"note"              TEXT
) 
数据库检查通过. 2 个表(共2个表)校验一致





True
# 接下来是把预处理好的内容存入数据库, 并标注哪些内容已经处理以及修改时间

# 为了提高插入数据的效率, 使用"executemany". 当然, 还可以用pandas.DataFrame.to_sql, 不过这里就怎么省事怎么来了.  
# 需要注意的是, 如果想要用sqlite3的python接口"executemany", 传入数据需要是`[(xxx,xx,...,x),...(x,xxx,...,xx)]`的结构, 其中每个元组的元素数要对应占位符. 另外, 一次处理的量不能太大.
  
Conn_ppced.executemany(
    "insert into ppcLog (`Link`, `cqCode`, `jiebaRES`, `note`) values (?, ?, ?, 'initialize')",
    [ ( link, res['CQ码'], str(res["分词标注"]) )  for link, res in 经过预处理的消息.items() ]
)
Conn_ppced.commit() # 操作后应该尽快提交, 以免数据库锁带来的麻烦(尤其是多线程)
# 提交完了, 瞅瞅情况
query_selectLog = "SELECT * FROM `ppcLog`;"
数据库中的预处理结果 = pd.read_sql_query(query_selectLog, Conn_ppced)
数据库中的预处理结果
lid Link cqCode jiebaRES lastModif note
0 1 6665 [CQ:image,file=04... 2020-01-15 09:37:53 initialize
1 2 6664 [('没用', 'v')] 2020-01-15 09:37:53 initialize
2 3 6663 [('主动', 'b'), ('是', 'v'), ('把', 'p'), ('能量', 'n'), ('换', 'nz'), ('到', 'v'), ('别的', 'r'), ('电池', ... 2020-01-15 09:37:53 initialize
3 4 6662 [('原车', 'n'), ('有', 'v'), ('这个', 'r')] 2020-01-15 09:37:53 initialize
4 5 6661 [('了解', 'v'), ('一下', 'm'), ('近几年', 'l'), ('的', 'uj'), ('电池', 'n'), ('保护', 'v'), ('方案', 'n')] 2020-01-15 09:37:53 initialize
... ... ... ... ... ... ...
30 31 6635 [('另外', 'c'), ('20', 'm'), ('a', 'eng'), ('你', 'r'), ('还', 'd'), ('被动', 'vn'), ('均衡', 'a'), ('咋'... 2020-01-15 09:37:53 initialize
31 32 6634 [('电流', 'n'), ('用', 'p'), ('外边', 'f'), ('电阻', 'n')] 2020-01-15 09:37:53 initialize
32 33 6633 [('MOS', 'eng'), ('不能', 'v'), ('调节', 'vn'), ('输出', 'v'), ('电流', 'n'), ('把', 'p')] 2020-01-15 09:37:53 initialize
33 34 6632 [('aos', 'eng'), ('和', 'c'), ('英飞凌', 'nr'), ('to252', 'eng'), ('dfn5x6', 'eng'), ('dfn3x3', 'eng... 2020-01-15 09:37:53 initialize
34 35 6631 [('新洁能', 'nz'), ('了解', 'v'), ('一下', 'm')] 2020-01-15 09:37:53 initialize

35 rows × 6 columns


以上内容便实现了数据接口以及数据预处理的基础功能(所以作为第0部分), 下一篇开始真正的NLP内容

本文内容仅为个人见解和心得, 希望能带来帮助. 如果有不正确/不准确之处还请多多指教, 一起学习一起进步~

本文内容由佚之狗原创, 可以随意使用但请注明出处.

发布了2 篇原创文章 · 获赞 0 · 访问量 44
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 深蓝海洋 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览