文本分类大体被我分为4类,链接如下:
文本分类流程(一)文本分类的大致步骤+数据预处理------毕业论文的纪念
文本分类(二)文本数据数值化,向量化,降维
文本分类(三)–对已经处理好的数据使用KNN、Naive Bayes、SVM分类方法
文本分类(四)–分类好坏的评价
文章目录
代码部分–Github:
本文主要介绍文本分类的前几个步骤,数据获取+数据清洗
下一个模块文本分类(二)文本数据数值化,向量化会开始讲解文本文本数据到数值数据的过渡,将文本数据转化为数值数据,并且提取转化后的特征,降维,输入到文本分类器中进行训练、测试、评估。
文本分类的基本步骤:
1.数据获取
方法:网页爬虫,使用别人的代码或者自己写代码(GitHub上找有没有你需要的)
2.数据清洗
方法:
- 去重,去除重复的评论。如果数据抓取的好,则可以不用执行这一步,不过一般得搞一搞,免得有评论刷评的。
- 去除上面得到的空字符串
- 手工标记数据的类别,正类为1,负类为-1
- 去除其他的表情符号(包括表情符号,一些常见的标点符号,特殊的字符),使用正则表达式匹配\W就可以做到(亏我当初还去网上一个个的查找,删除。
- 如果一个评论中只有上一步所表述的符号,则将其全部去除之后会得到一些空的字符串,再像第二步一样,去除所得到的空字符串。
- 删除停用词(自己在CSDN上搜索停用词,复制粘贴到文本txt文档中)。同上,如果去除了只有停用词的字符串,则去除空字符串。去除停用词的字符串之间使用空格来连接,以便以后使用counterVectorizer和TfidfVectorizer将文本数据转化为数值数据。
注意:我将初始的文本数据全部都放进一个列表中,将标记文本的正负类也按照相应的索引放在一个列表中对应存储。每次去除特殊字符或者是停用词得到空字符串时,将原来存有文本的列表comments中的相关元素删除,也要与之对应的class删除。这里一定要记住是哪个index下面的数据被删除了,再将这个Index对应的class也删除。
3.文本数据---->数值向量
4.使用分类分方法。
机器学习相关的:KNN, Naive Bayes, SVM, AdaBoost, 决策树,逻辑回归,神经网络,深度学习。我主要使用的是前三种,各种方法之间进行对比。
简单贴下比较好的算法介绍的相关文章:
机器学习(二)分类算法详解
分类算法总结
聚类算法与分类算法的总结
5.进行评估
方法:主要使用accuracy,precision,recall,f1-score来评价。
每步的具体做法 + 代码:
1.数据获取
开始是自己跟着静觅-Python3网络爬虫开发实战教程这个网站来学习网页爬虫,因为刚刚接触网页原理,很多参数都看不懂。有一种强迫症的存在,就想着要将所有的东西全部学会,跟着他的教程一步步地敲击代码,记笔记,这样做让我感受不到进步,并且需要学习的东西太多,时间紧迫,没有一种抓住重点的感觉。遂将此教程暂时搁置在一边,了解了一下大致讲的什么,之后有需要再过来针对性的学习。最后学习了requests, beautifulsoup, xpath, css, pymysql,json等相关的知识,就没有再继续看了。
前一步的中间接受网友的建议去学习scrapy框架还有xpath,css,将scrapy中文网实验网站的所有能够爬取的都爬取了,还有根据上面所学的知识利用scrapy去爬取豆瓣影评。搞了也差不多有一个星期,当时就觉得自己似乎可以去爬取京东的店铺评论了(现在想想当时咋就那么不自量力呢)。好,有想法我就去做,去使用xpath中学习到的知识去爬取京东的评论,发现在利用F12所看到的内容居然与查看源代码所看见的内容是不一样的。我们能够从前者看见网页中所呈现的所有元素,然而对于后者,我们就只能够从其中看见一些简单的javascript函数,还有一些json字符串。当时我很纳闷啊,怎么是这样的。试了很多次了,还是这样,问别人也不太明白,就简单的将其当作是京东采取的一些反爬虫机制了。
因为时间真的太紧张了,数据获取的工作始终没有进展,于是就开始接受网友的建议直接去GIthub上搜索相关的代码,找到了美团的评论爬取代码,修改了一下,只获取店铺的评论,其他的完全不管,这样将数据以及相应的Index存储到csv文件中。数据获取的工作当时我以为的就是暂时到这个地步就结束了。
借鉴修改所得代码如下:
parse_restaurant_id.py
# -*- coding: utf-8 -*-
# __author__ = "zok" 362416272@qq.com
# Date: 2019-04-17 Python: 3.7
"""
这是上面一个大神的代码,我拿来自己使用的
他的GitHub地址为:https://github.com/wkunzhi/SpiderCrackDemo/tree/master/MeiTuan
-------
创建一个类,获取店铺id使用
在之后餐馆解析时,继承此类,使用getId获取店铺Id
"""
import requests
import json
import re
import csv
# 目标:创建一个类,其中有一个属性,是一个函数,可以提取餐馆的id
class ParseId(object):
# 设置请求头
def __init__(self):
headers = {
'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
}
self.urls = self.getUrl()
self.headers = headers
# 获取这些网页
def getUrl(self):
urls = []
for i in range(1,60):
urls.append(('https://yt.meituan.com/meishi/c17/pn'+'%s'+"/")%(i))
return urls
def getId(self):
id_list = []
k = 1
for v in self.urls:
html = requests.get(v,headers = self.headers)
# 进行正则匹配,寻求店铺id信息
re_info = re.compile(r'\"poiId\":(\d{4,})')
id = re_info.findall(html.text)# 获得的是列表
# 想利用writerows将列表添加到csv文件中
for v in id:
id_list.append((k,v))
k+=1
# 将店铺ID保存到CSV文件中
with open(r'D:\lagua\study\coding\pythonPractice\meituanComment\poId.csv','w+',newline="") as f:
writer = csv.writer(f)
writer.writerows(id_list)
temp_id = []
for il in id_list:
temp_id.append(il[1])
return temp_id
parse_id_comments.py
# -*- coding: utf-8 -*-
# __author__ = "zok" 362416272@qq.com
# Date: 2019-04-17 Python: 3.7
"""
这是上面一个大神的代码,我拿来自己使用的
他的GitHub地址为:https://github.com/wkunzhi/SpiderCrackDemo/tree/master/MeiTuan
-------
目标:
根据店铺获取评论
局限性:一个店铺只能获取10条评论
"""
import requests
import json
import time
import csv
import sys
# 当引用模块和当前模块不再同一个目录下时,要将引入的模块添加进来
from urllib import parse
sys.path.append(r'D:\lagua\study\coding\pythonPractice\meituanComment\meituanComment\spiders\parse_restaurant_id.py')
from parse_restaurant_id import ParseId
# 创造解析类,用来提取用户评论
class ParseComments(object):
def __init__(self, shop_id):
self.shop_id = shop_id# 从具有这个属性,实例需要有具体的shop_id
self.get_data()# 规定类自带的属性
self.coms_ = self.get_data()
# 获取数据---从网页上下载数据
def get_data(self):
url_code = self.get_originUrl() # 获取原始的url
# 我的ssl证书有问题,访问https会出现问题,美团支持http和https,所以最后改为http没有问题
url = 'http://www.meituan.com/meishi/api/poi/getMerchantComment?'
params = { # 利用字典传入参数
'platform': '1',
'partner': '126',
'originUrl': url_code, # 是字典中的一个必须的关键url
'riskLevel': '1',
'optimusCode': '1',
'id': self.shop_id, # 从终端传入的商品id
'offset': '0',
'pageSize': '10',
'sortType': '1',
}
# 加入请求头,模拟浏览器
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36',
}
response = requests.get(url=url, params=params, headers=headers) # 获得requests对象,发送网页请求,并且获得响应
data = response.text # 我们所需要分析的数据
self.parse(data) # 交给下面的parse函数解析
return self.parse(data)
## 获得不同商品对应的url
def get_originUrl(self): # 获取某个餐馆的具体url----与某一家餐馆一一对应
"""编码解码
"""
needParse = ('http://www.meituan.com/meishi/' + '%s'+ '/')%self.shop_id
return parse.quote_plus(needParse)
# quote_plus 函数用加号来代替URL中的空格-----构造某个商品的url
# 对某个网页下的数据进行解析,提取出我们所想要的数据
def parse(self, data):
"""解析数据
"""
data_dict = json.loads(data) #是Json格式的数据 将json字符串--->json对象
print('parse is running....')
try:
coms = [] # 某个店铺的评论,一个店铺会有 10 条
for item in data_dict.get('data').get('comments'): # 获取数据字典中的data数据,data中也含有comments的数据
coms.append(item.get('comment'))
except:
print('Error!!!!!!!!!!!!!')
print('%s'%self.shop_id)
return coms # 将从每个店铺中获取到的评论返回,作为类的一个元素可以传到相应实例中
@staticmethod # python返回函数的静态方法,不强制传递参数
# 函数的执行,可以不用创造实例
def parse_time(timeStamp): # 解析时间戳,备用---
"""13位 解码时间
"""
time_stamp = float(int(timeStamp) / 1000)
time_array = time.localtime(time_stamp)
return time.strftime("%Y-%m-%d %H:%M:%S", time_array)
# 数据已经在另一个文件中被解析,只需要拿出来就可以,不必自己执行函数获取
# 要在ParseId后面加上(),因为这是调用的一个类,需要继承这个类,,之后再继承这个类的属性,执行函数
ids = ParseId().getId()
# 创建实例---将数据写进csv文件中
special_shops = []
comments_ = []# 存放所有店铺的coms
for shop_id in ids:
pc = ParseComments(shop_id)
special_shops.append(pc)
for v in pc.coms_:# coms是类中的一个变量,在执行parse函数时我返回了,并且将其作为这个类的一个属性,在创建实例时继承了下来
comments_.append(v)
"""
这是改变之后的:
在写进文件夹之后直接根据相应的评论长度建立一个index,将它与相应的comments写入到csv文件中
"""
# 底下是将数据写入到csv文件中,要指明为utf-8,否则会出现乱码
with open(r'D:\lagua\study\coding\pythonPractice\MT_test.csv', 'w+', encoding='utf-8',newline='') as f:
writer = csv.writer(f)
to_be_write = []
index_= list(range(len(comments_))) # 序列号
for v1,v2 in zip(index_,comments_):# 将两个列表打包
to_be_write.append([v1,v2]) # 最后得到的是[[1,'a'],[2,'b'],[3,'c']]这样的形式,可以用writerows写进csv文件中
writer.writerows(to_be_write)##### 注意此处写入是用的元组---用元组形式写入,事实上得到的是列表
2.数据清洗
跟我在最开始介绍的差不多。说说我做了哪些尝试吧。我开始的时候是去网上找如何进行数据的预处理,对比自己获取的文本,看看有哪些东西需要删除的,机器不能够识别的全部都删除。这样下来文本基本上就干净了。
在将字符串与其相应的文本评论相对应时,我尝试了一些方法。开始是想要用json来存储数据,将评论的文本作为键,1,-1当作是键值value。去学习了一堆的json字符串转化为字典的方法,如何将字典转化为json格式的数据。最后搞来,学习了一些字典以及json相关的知识,在这个过程中我突然想到其实这些不必这么复杂,直接使用列表将元素相对应也就可以,我对列表掌握的还比较好。
开始的数据我还傻乎乎的每次处理数据先从相应的csv文件中读取,处理完之后再将数据放回csv文件中。不谈及效率,因为我也无法评判效率的高低,就是觉得代码特别臃肿,我将此做法跟一个网友交流一番之后发现这样不好,csv只是一个存储数据的一个文件,数据最开始可以从csv中获取,最后将我们处理好的最终数据存储在csv文件中,这样比较好,不然的话,每次存取很麻烦。
嘿嘿,我就按照他说的来做,在数据处理的过程中将所有的数据都存放在列表中。在这里要注意的是,开始我以为我的数据量过大,有几百k,列表承受不住,其实完全不用担心,列表好着呢,他肚量可大了,尽情地放。
在这里我还学习了一些文本处理的一些编程技巧:将所有数据处理的函数都包装成一个过滤器,将待处理的数据输入处理器中,输出处理完的数据,再接着将这个数据输入到下一个过滤器中。有一种石英砂过滤器的感觉。
列表1---->过滤器1------>列表2------>过滤器2-------…-------->列表n
以上的过滤器就可以封装成一个链({过滤器1,过滤器2,…过滤器n}),我们将初始数据输入,将最后要得到的输出返回就可以啦。这就是所谓的包装,哈哈,外面不知道我里面干了啥,特别的神秘,就这样暗悄悄地将数据处理完毕啦,看我多么的神秘,多么的厉害哈哈!
之后查看资料发现在sklearn中就有这样一个模块,对开始的文本数据预处理,转化为数值向量,放进分类器中训练,得到评价指标。这就是sklearn下的pipeline模块。
具体参考资料如下:
sklearn 中的 Pipeline 机制
sklearn学习笔记3——pipeline
我也来简单的写一下:
举个简单的例子,使用KNN来进行分类
from sklearn.preprocessing import StandardScaler# 标准化处理
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline# 管道包
from sklearn.neighbors import KNeighborsClassifier
from last_process_2 import noStopWord_comments,noStopWord_class# 我处理得到的评论列表极其对应的类别
import re
from sklearn.feature_extraction.text import TfidfVectorizer,CountVectorizer# 向量化的类
from sklearn.model_selection import train_test_split# 将数据分割为训练集与测试集合
from sklearn import preprocessing# 数据处理使用
# 将开始的字符串用空格隔开---最后得到的是一个列表,里面中的每个元素都是“你 今天 很 好看” 这样的形式,方便使用tfidfVectorizer讲文本数据转化为向量
def get_split(x):
temp = [v for v in re.split(' ',x) if v is not '']
return temp
def joinData(data_to_join):# 空字符串将文本隔开
temp = [' '.join(x) for x in data_to_join]
return temp
corpus = joinData([get_split(v) for v in noStopWord_comments]) # 得到的是一个列表
tfidf = TfidfVectorizer()# 创建实例,继承方法
retfidf = tfidf.fit_transform(corpus) # 拟合+转换
input_data_matrix = retfidf.toarray()# 数值矩阵
x_train,x_test,y_train,y_test=train_test_split(input_data_matrix,noStopWord_class,test_size=0.2,random_state=400)# 切割训练数据
pipe_knn = Pipeline([('sc',StandardScaler()),('PCA',PCA(n_components=200)),('clf',KNeighborsClassifier())])
pipe_knn.fit(x_train,y_train)# 拟合+transform数据
print(pipe_knn.score(x_test,y_test))# 得到分类的准确率,即得分
# 0.8073770491803278
第一步处理代码:
final_process_1.py
"""
1、去重,作为初始数据处理
2、去空tag【】【】
3、去除空字符串''
得到我想要的数据,进行人工标记
————————————————————
"""
import csv
import re
# 将数据全部都存放再一个列表中
class InitialProcess(object):
path_initial = r'D:\lagua\study\coding\pythonPractice\DataProcess\MT.csv' # 从美团上获取的初始文本
path_meituan = r'D:\lagua\study\coding\pythonPractice\DataProcess\MeiTuan.csv'# 数据处理第一道得到的index文本
# 去重
def noRepeat(self,x):
uni = []
for v in x:
if v not in uni:
uni.append(v)
return uni
# 去除只带有标签的评论
def noTags(self,x):
def f_replace(x):
temp = x.replace('【口味】','').replace('【环境】','').replace('【服务】','').replace('【等位】','').replace('\n','').strip()
return temp
return [f_replace(v) for v in x]
# 将空字符串去掉
def noVocant(self,x):
temp = [v for v in x if v is not '']
return temp
# 读取之前获取的数据
def getData(self,path):
seg_all = []
with open(path,'r',encoding='utf-8') as f:
reader = csv.reader(f)
for line in reader:
seg_all.append(line[1])
print("列表说:我将csv中的数据搞到手啦")
return self.noRepeat(seg_all)
def get_index(self,x):
temp = [i for i,v in enumerate(x) if v=='']
return temp
# 将文件写进csv文件中
def writeCSV(self,path2,data):
with open(path2,'w',encoding='utf-8',newline='') as f:
writer = csv.writer(f)
for k,v in enumerate(data):
writer.writerow([k,v])
def do_thisMethod(self):
data = self.getData(self.path_initial)# 获取数据,顺便去重
data_noTag = self.noTags(data)# 去除标签
data_noVocant = self.noVocant(data_noTag) # 去除上一步可能得到的空字符串
self.writeCSV(self.path_meituan,data_noVocant) # 写进文档存储,以后就在这里面提取数据,进一步处理
IniPro = InitialProcess()# 继承类
IniPro.do_thisMethod()# 实例化之后使用类中的方法
去除停用词。 开始我们讲过,我们是直接将从网上获取的一些停用词复制粘贴到txt文档中的。我们去除停用词首先要读取文档中的停用词,将这些汉字词语符号删除。这里这里会遇到一个问题,是有关编码的,我们一般的汉字存储是ASNI格式,我们要用python读取,需要将数据转化为unicode格式。
具体这样做:将我们开始保存的txt文本另存为一个txt文件,在这时,你可以看见相关页面中有选择编码的格式,自己选择为unicode格式即可,之后就读取这个文件中的停用词。
在读取文本的时候还有一点需要注意:
def stopwordslist(): # 返回列表
with open(path_stop,'r',encoding='utf-8', errors='ignore') as f:
stopwords = [line.strip() for line in f.readlines()]
return stopwords
加上errors='ignore’参数以便忽略错误,继续执行读取函数。
这里遇到了一个编码的问题:Python中读取txt文本出现“ ‘gbk’ codec can’t decode byte 0xbf in position 2: illegal multibyte sequence”的解决办法
第二步:去除无效字符 + 停用词代码:
final_process_2.py
"""
目的:数据第二步清洗
1、读取文本数据+相应类别,各自分别放进comments和class_的列表中
2、使用正则表达式去除其他符号
3、从停用词列表中读取数据,去除停用词,每个文本得到的剩下单词用' '空格连接起来
4、将分类所得到的正负文本放进不同的文档中,自己检查看大体上是否分类正确
"""
import csv
# import sys
import jieba
import re
# import importlib
path_meituan = r'D:\lagua\study\coding\pythonPractice\DataProcess\MeiTuan.csv'
path_class = r'D:\lagua\study\coding\pythonPractice\DataProcess\class_.csv' # 类别
path_stop = r'D:\lagua\study\coding\pythonPractice\DataProcess\stopWords_3.txt' # 停用词
path_neg = r'D:\lagua\study\coding\pythonPractice\DataProcess\neg_newMeiTuan.txt'
path_posi = r'D:\lagua\study\coding\pythonPractice\DataProcess\posi_newMeiTuan.txt'
# 读取类别
def get_class():
class_ = []
with open(path_class,'r',encoding='utf-8',errors='ignore',newline='') as f:
reader = csv.reader(f)
for line in reader:
temp = line[0].replace('\n','').strip()
class_.append(int(temp))
return class_
# 获取数据--文本评论
def getData(path): # 获取数据
seg_all = []
with open(path,'r',encoding='utf-8') as f:
reader = csv.reader(f)
for line in reader:
seg_all.append(line[1]) # csv第一列是index,第二列是文本评论
return seg_all
def get_index(x): # 获得待删除的Index
temp = [i for i,v in enumerate(x) if v=='']
return temp
# 根据index删除元素---主要针对comments来讲
def delete_comments_accord_index(x,index): # 针对comments删除元素
to_be_delete = [v for i,v in enumerate(x) if i in index]
for i in to_be_delete:
x.remove(i)
return x
# 根据index删除元素,对class来讲
# class中的元素只有1,-1处理方式和comments不一样
def delete_class_accord_index(class_,index): # 针对class删除元素
temp = []
for i,v in enumerate(class_):
if i not in index:
temp.append(v)
return temp
# 去除其他符号
def splitOther(data_split):
"""
params:
data_split:待切分的数据
return:
返回:是空元素的index + 待被去除index的文本---以元组的形式返回
"""
def re_split(desstr):
temp = ''.join(re.split(r'\W',desstr))
return temp
temp = [re_split(desstr) for desstr in data_split]
# 获得待删除的index
index = get_index(temp)
return (index,temp)
# 由index删除列表元素
def indexProcess(func,last_comments,last_class):
"""
params:
func:清洗数据的某个函数
last_comments:待处理的文本评论列表
last_class:待处理的文本类别
return:
返回已经去除了相应元素的文本列表 + 对应的class
"""
noFunc_index = func(last_comments)[0]
noFunc_comments = func(last_comments)[1]
final_noFunc = delete_comments_accord_index(noFunc_comments,noFunc_index)
class_noFunc = delete_class_accord_index(last_class,noFunc_index)
return (final_noFunc,class_noFunc)
def splitStop(data_split): # 创建停用词列表
# 从txt文档中读取停用词,放进列表中
def stopwordslist(): # 返回列表
with open(path_stop,'r',encoding='utf-8', errors='ignore') as f:
stopwords = [line.strip() for line in f.readlines()]
return stopwords
stop_words_list = stopwordslist()
stopwords = {}.fromkeys(stop_words_list)
# 建立一个函数去掉字符串中的停用词
def cutStopWords(word):
segs = jieba.cut(word, cut_all=False)
final = ''
for seg in segs:
if seg not in stopwords:
final += seg
final +=' '
return final
splitStopData = [cutStopWords(v) for v in data_split]
index = [i for i,v in enumerate(splitStopData) if v=='']
return (index,splitStopData)
# 自己检查使用,看是否将文本与标签相对应
def wirteClass(path_neg,path_posi,comments,class_): # 根据类别写文档
file_neg = open(path_neg,'w',encoding = 'utf-8',errors='ignore')
file_posi = open(path_posi,'w',encoding = 'utf-8',errors = 'ignore')
for i,v in enumerate(noStopWord_comments):
# 使用try...except 来检测读写过程中的错误
try:
if noStopWord_class[i]==-1:
file_neg.write(v+'\n')
else:
file_posi.write(v+'\n')
except:
print('Error')
file_neg.close()
file_posi.close()
class_ = get_class() # 获取类别
comments = getData(path_meituan) # 获取评论
# 充当过滤链
#-----------------------------------------------------------
noOtherWord_comments,noOtherWord_class= indexProcess(splitOther,comments,class_) # 根据index处理comments和class
noStopWord_comments,noStopWord_class=indexProcess(splitStop,noOtherWord_comments,noOtherWord_class)
#-----------------------------------------------------------
wirteClass(path_neg,path_posi,noStopWord_comments,noStopWord_class)
好啦,前两步的所有东西就记录在这里,因为还有很多步骤的细节可以展现,在这里写出来就太多啦,放在下一章文本分类(二)文本数据数值化,向量化写。