文本分类流程(一)文本分类的大致步骤+数据爬取+数据预处理------毕业论文的纪念

文本分类大体被我分为4类,链接如下:
文本分类流程(一)文本分类的大致步骤+数据预处理------毕业论文的纪念
文本分类(二)文本数据数值化,向量化,降维
文本分类(三)–对已经处理好的数据使用KNN、Naive Bayes、SVM分类方法
文本分类(四)–分类好坏的评价

代码部分–Github:

Github

本文主要介绍文本分类的前几个步骤,数据获取+数据清洗
下一个模块文本分类(二)文本数据数值化,向量化会开始讲解文本文本数据到数值数据的过渡,将文本数据转化为数值数据,并且提取转化后的特征,降维,输入到文本分类器中进行训练、测试、评估。

文本分类的基本步骤:

1.数据获取

方法:网页爬虫,使用别人的代码或者自己写代码(GitHub上找有没有你需要的)

2.数据清洗

方法:

  1. 去重,去除重复的评论。如果数据抓取的好,则可以不用执行这一步,不过一般得搞一搞,免得有评论刷评的。
  2. 去除上面得到的空字符串
  3. 手工标记数据的类别,正类为1,负类为-1
  4. 去除其他的表情符号(包括表情符号,一些常见的标点符号,特殊的字符),使用正则表达式匹配\W就可以做到(亏我当初还去网上一个个的查找,删除。
  5. 如果一个评论中只有上一步所表述的符号,则将其全部去除之后会得到一些空的字符串,再像第二步一样,去除所得到的空字符串。
  6. 删除停用词(自己在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
过滤器1
列表2
过滤器2
...

以上的过滤器就可以封装成一个链({过滤器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)

好啦,前两步的所有东西就记录在这里,因为还有很多步骤的细节可以展现,在这里写出来就太多啦,放在下一章文本分类(二)文本数据数值化,向量化写。

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值