本人长期出售超大量微博数据、旅游网站评论数据,并提供各种指定数据爬取服务,Message to YuboonaZhang@Yahoo.com。同时欢迎加入社交媒体数据交流群:99918768
背景
参加泰迪杯数据挖掘竞赛,这次真的学习到了不少东西,最后差不多可以完成要求的内容,准确率也还行。总共的代码,算上中间的过程处理也不超过500行,代码思想也还比较简单,主要是根据论坛的短文本特性和楼层之间内容的相似来完成的。(通俗点说就是去噪去噪去噪,然后只留下相对有规律的日期,内容)
前期准备
软件和开发环境: Pycharm,Python2.7,Linux系统
用的主要Python包: jieba, requests, BeautifulSoup, goose, selenium, PhantomJS, pymongo等(部分软件的安装我前面的博客有介绍)
网页预处理
首先因为网站很多是动态的,直接用bs4是获取不到有些信息的,所以我们使用selenium和phantomjs将文件保存在本地,然后再处理。
相关的代码是
1 | def save(baseUrl): |
由于网页中存在着大量的噪音(广告,图片等),首先我们需要将与我们所提取内容不一致的所有噪声尽可能去除。我们首先选择将一些带有典型噪声意义的噪声标签去除,比如script等,方法我们选择BeautifulSoup来完成。
代码大概是这样
1 | for element in soup(text=lambda text: isinstance(text, Comment)): |
处理之后的网页对比
可以看出网页噪声少了很多,但是还是不足以从这么多噪声中提取出我们所要的内容
由于我们不需要标签只需要标签里面的文字,所以我们可以利用BeautifulSoup提取出文字内容再进行分析
1 | for string in soup.stripped_strings: |
可以看出来还是非常杂乱,但是又是十分有规律的。我们可以发现每个楼层中的文本内容实质上都差不多,可以说重复的很多,而且都是一些特定的词,比如: 直达楼层, 板凳,沙发,等这类的词,所以我们需要将这些词删掉然后再进行分析
我所用的方法是利用jieba分词来对获取的网页文本进行分词,统计出出现词频最高的词,同时也是容易出现在噪声文章中的词语,代码如下
1 | import jieba.analyse |
统计出来然后经过我们测试和筛选得出的停用词有这些
回帖
积分
帖子
登录
论坛
注册
离线
时间
作者
签到
主题
精华
客户端
手机
下载
分享
目前统计的词大约200左右。
然后还有去除重复文本的工作
1 | # 去重函数 |
在经过观察处理后的网页文本,我们发现还有一项噪声无法忽略,那就是纯数字。因为网页文本中有很多纯数字但是又不重复,比如点赞数等,所以我准备用正则匹配出纯数字然后删除。但是这样就会出现问题…因为有些用户名是纯数字的,这样我们会把用户名删掉的。为了解决这个问题我们使用保留字符数大于7的纯数字,这样既删除了大部分的没用信息又尽可能的保留了用户名
相关的代码如下
1 | st = [] |
处理之后的文本如下,规律十分明显了
接下来就是我们进行内容提取的时候了
内容提取
内容提取无非是找到评论块,而评论块在上面我们的图中已经十分清晰了,我们自然而然的想到根据日期来区分评论块。经过观察,所有的论坛中日期的形式只有5种(目前只看到5种,当然后期可以加上)。我们可以用正则匹配出日期所在的行,根据两个日期所在行数的中间所夹的就是评论内容和用户名来完成我们的评论内容提取。
传入我们处理后的文本然后就匹配出日期所在行数
1 | # 匹配日期返回get_list |
因为有回帖和没有回帖处理方式也不一样所以我们需要分类进行讨论。因为我们知道评论的内容是在两个匹配日期的中间,这样就有一个问题就是最后一个评论的内容区域不好分。但是考虑到大部分的最后一个回帖都是一行我们可以暂取值为3(sub==3,考虑一行评论和一行用户名),后来想到一种更为科学的方法,比如判断后面几行的文本密度,如果很小说明只有一行评论的可能性更大。
下面的代码是获取日期所在行数和两个日期之间的行数差
1 | # 返回my_count |
接下来就要分类讨论了
如果只有楼主没有评论(即my——count==1),这个时候我们可以使用开源的正文提取软件goose来提取正文。
如果有评论我们就需要根据sub的值来进行分类如果sub==2占多数(或者说比sub==3)占的多,那么我们就认为可能是用户名被删掉,删掉的原因有很多,比如去重的时候有人在楼中楼回复了导致用户名重复被删除,有可能该网站的标签比较特殊用户名在去标签的时候删除等,情况比较复杂且出现的频率不太高,暂未考虑。何况不影响我们提取评论内容,只需分类出来考虑就行
注意:下面余弦相似度这个是我开始的时候想多了!大部分情况就是:日期-评论-用户名,后来我没有考虑余弦相似度分类,代码少了,精度也没有下降。这里不删是想留下一个思考的过程。代码看看就好,最后有修改后的源码。
- 还有就是最常见的内容,就是sub==3占多数的情况。因为大部分的评论都是一行文本,所以我们需要考虑的的是sub==3的时候获取的评论文本在哪一行。通俗来说就是这三行的内容是日期-评论-用户名,还是日期-用户名-评论呢?虽然大部分是第一种情况,但是第二种情况我们也不能忽略。怎么判断这两种情况呢?这确实让我思考了很长一段时间,后来想到可以用余弦相似度来解决这个问题.科普余弦相似度可以看这里。简单来说就是用户名的长度都是相似的,但是评论的内容长度差异就非常大了。比如用户名长度都是7个字符左右,但是评论的长度可以数百,也可以只有一个。所以我们可以两两比较余弦相似度,然后取平均,相似度大的就是用户名了。这样我们就可以区分出评论内容进行提取了!这就是主要的思想。剩下的就是代码的实现了。
简单贴一下相关的代码
1 | # 利用goose获取正文内容 |
展望
提取的准确率应该要分析更多的bbs网站,优化删除重复词(太粗暴),优化停用词,针对短文本没回复情况的优化,准确提取楼主的用户名等,无奈时间太紧无法进一步优化。才疏学浅,刚学了几个月python,代码难免有不合理的地方,望各位提出宝贵意见。