最近,算是做了个小东西吧,就是统计群里面的人说了什么,谁最爱说什么话,口头禅什么的。最终结果还是比较差,不过仍然把全过程记录下,说下遇到的困难与解决方式。
起因
前一阵子在群里面水,大家在打赌,谁肯定说过最多的xx,谁说过最多的xx,后来我想了想就说用程序跑一边看看。
准备要素
- 聊天记录
- 分词工具
- 有趣的统计
聊天记录
腾讯QQ群聊天记录导出只有3种格式,bak加密(破解不了),mht 相当于html,打都打不开,也懒得转,最后一个txt,我选择了txt。
其实现在写文档的时候想了想,如果我要是用mht方式处理的话,好像就不用写这么多正则了。。。不过处理html基本是要上js的,然而js怎么与java互动呢?或者说直接用js来实现呢?反正麻烦无穷无尽吧。
分词工具
分词工具什么语言的都有,最后还是优先java了,初选 NLPChina。因为对这个不懂,不清楚,不了解,查了查。
NLP 是 自然语言处理的简称。(可以说一个装逼了
我根据网上资料,产生了以下理解,一种NLP是使用字典的方式,通过统计上的方法,把句子拆分成词汇。
另一种是,近年来比较火的,机器学习?这种方式是不断的给机器喂数据,然后让程序自动去处理分词。
有趣的统计数据
这部分内容,我在前期和后期都做了记录,在实现项目前,我找了一个朋友,随便想了想。后来等到有了导出的txt数据,又增加了一点东西。主要是以下几点
- 统计群里面的朋友常说哪些词,不常说哪些词。
- 统计群里面的成员谁最爱说话,
- 统计哪个时间最爱说话
- 统计谁最受欢迎(被at次数最多
- 统计那些话题讨论最热
- 做个情感分析?
- 改过多少昵称?
需要的东西都准备好了,现在准备开始做了。
数据处理
我目前只想统计过去2017年的聊天数据,看了看导出的txt文件,大概只能选择正则的方式了。消息的格式基本上就是很标准,如下图所示,正则其实就是按照顺序写,匹配的东西大概是什么样子,或者是什么格式,找到绝对正确的思路实现一下就行。
其实正则写了一段时间,一方面是java自带的方法不会用,另一方面是正则不会写。
自带方法不会用的原因是,不理解这个是干嘛的,后来了解正则里面group的概念之后,才知道怎么回事。这里简单说说java正则相关的内容。
Java正则相关的内容一共有两个Class,一个是Pattern、一个是Matcher,前面的意思是初始化正则表达式相关的内容,后面的相当于一个匹配器。
Pattern初始化的方式就是传入正则和正则初始化参数。Pattern提供的几个Method 不太合适,最后只能选择Matcher进行处理。
Matcher就和正则的关系比较大了,懂正则的话,看一眼基本上就都懂,Matcher里面的方法 简单说就是,从哪开始,到哪结束,开始找,找到了啥,有没有这个,等。会的话本身就简单,不会也看不懂,还要去补正则。。。
String txt2strArr = "\\r\\n\\r\\n2017-\\d\\d-\\d\\d [\\d]+:[\\d]+:[\\d]+ [^<(\\r\\n]+[<(][^>)\\r\\n]+[>)]\\r\\n[\\r\\n\\S\\s]*?(?=\\r\\n\\r\\n)";
Matcher m = Pattern.compile(txt2strArr).matcher(str);
List<String> s = new ArrayList<String>();
while (m.find()) {
s.add(m.group(0));
}
自己手里面大约是4w条对话,后来找朋友要了一份,大约是10w,其实数据都不全,应该捏一起看看有多少。
然后就是对数据进行细分,把数据都整理成Bean对象,具体的做法依旧是正则,一个循环,把所有的内容生成为对象,同时根据txt的实际情况处理了部分内容,如[图片][表情]计数啊等等。
String regexDate = "(2017-\\d\\d-\\d\\d [\\d]+:[\\d]+:\\d\\d)";
String regexNickname = "(?<=:\\d\\d )([^<(\\r\\n]+)(?=[<(])";//使用group的方式,获得括号
String regexUid = "(?<=[<(])([\\S]+)(?=[>)])";
String regexContext = "(?<=[>)]\\r\\n)([\\s\\S]*)";
String regexStrAt = "(?<=@)([\\S]+(?=\\s)|[\\S]+)";
String regexStrEmoji = "\\[表情\\]";
String regexStrPic = "\\[图片\\]";
String regexStrHttp = "(http[\\S]+(?=[\\s])|http[\\S]+)";//http with blank end or http simple end
数据整理完了后,需要把数据按照人头区分开来,手写了个map,把实际的人和相关的id关联起来了。当然这段代码公开并不好,所以就从github上面删了。
数据初步统计
以上数据有了之后,开始进行统计,
首先是一共说过多少话,谁说的最多,前三名占了多少什么的,这些都没什么可说的,随便一写就有了。这里就不贴代码了。
接下来是对时间纬度进行统计,使用了Data 对象,个人认为比较有价值的信息是,周几说话人多,周几说话人少,24h内,什么时候说话人最多,什么时候说话人最少。
在这里,处理了一下Date相关的内容,仔细看了下API 文档,总结下:Date 相关的方法基本都被遗弃掉了,不推荐使用,如果想获得日期相关的具体细节,要么使用format的方式格式化出来,要么使用Calender这个类来进行处理。由于一直使用的Date对象,就继续使用format的方式来处理了。
当然时间的统计不仅仅能在整体的数据上面使用,还可以在每个人身上使用,所以单独抽出来写成一个方法。
分词统计
接下来就是分词的处理了,其实分词这个东西看了好久,在issue里面才找到wiki这回事。
最开始的时候看首页的README.md
其中说很多东西没有写上来,需要自己看,然后我就开始看test。
后来看 test包 越看越不对,这tm都是单元测试的内容啊,用ide重新建了个项目才知道怎么回事,因为刚用idea,不太清楚怎么回事。
再后来感觉范例不是那么回事,就开始只看demo了,但是看demo并不能让我做出选择用哪部分内容。然后就开始乱翻,,从issue中找到一个wiki的事情,然后去看了wiki,才把这东西梳理清楚。
弄清楚怎么回事之后(差了一部分资料),选择使用最基础的部分也就是ToAnalysis 精准分词 店长推荐。改下配置不输出词性
for (Meta me : list) {
if (me.getStr() != null && !me.getStr().equals("")) {
Result parse = ToAnalysis.parse(me.getStr());
for (Term t : parse) {
if (t.getRealName().length() == 1) {
continue;
}
if (sortedWords.containsKey(t.getRealName())) {
sortedWords.put(t.getRealName(), sortedWords.get(t.getRealName()) + 1);
} else {
sortedWords.put(t.getRealName(), 1);
}
}
}
}
等我迫不及待的看下分词统计结果的时候,当然是非常让人失望的,而且也非常现实。使用最多的当然是标点符号啊,还有被分成单字的。直接把长度是1的字符过滤掉了,然后又跑了一边,结果emmmm…
大概就是这个效果。很无奈,我又没有更高级的算法来进行处理,也没有更牛逼的字典。
其他的杂项
情感分析的内容在NLPChina的 issue中看到了,仔细看了下demo,需要一个情感字典。没有,放弃。
分析热点话题,基本上相当于理解语意了,不知道怎么处理,放弃。
谁被at次数最多?会实现,懒,而且at的内容并不一定有规律,做法就是把所有人的id、所有用过的昵称、都扔到一个map中,key是昵称,value是真实的id,然后再新建一个map,key 是真实的id,value计数,比较一下基本就能得出结果。但是这个并不好玩,而且还面对着这样的一个问题,at后面显示的是用户真实的昵称,而聊天的昵称显示的是群的昵称,两个昵称对不上是很容易出现的。截图就不放了,还需要打码。
总结
功能要么做不出来,做出来又无法优化,又无聊,又无用。心灰意冷,大概就是这样了。
学会使用了正则。
学会了查找开源内容
因为这个东西完全是在IDEA上面实践的,学会了部分IDEA的用法
了解了部分NLP的东西(知道了几个牛逼的名词)
知道了自己几斤几两
比较失败的地方:
- 使用远高于自身水品的东西,不自量力
- 同时学习好几件事情,吃力不讨好。
- 期望过高
- 没有尽快把事情做完,拖了好多天–这个简单的东西拖了近20天
- 没有对内容进行重构,基本上是按照思路顺序写的,好多重复冗余的东西,简单改改就能大幅提升效率
- 命名还是不太行
东西提交到github了,愿意用的自取:
github
最后放个比较有意思的
真tmd,上班摸鱼群
转载自原文链接, 如需删除请联系管理员。
原文链接:qq群聊记录统计,转载请注明来源!