文本挖掘(四万字总结篇:爬虫 - 文本预处理 - 高频词统计 - 聚类 - 情感分析)

1 爬虫

1.1 爬虫原理

这部分内容可以跳过,掌握与否对后面内容的阅读影响并不大,但有兴趣的话可以看看呐~

  实现一个爬虫,一般需要经过两个步骤:处理请求解析源码/数据

  处理请求方面,我们可以使用Python程序自动发送请求,然后根据返回的网页脚本,判断该页面是服务器端渲染还是浏览器端渲染。服务器端渲染可以直接获取到源码并进行解析,如果是浏览器端渲染则需要获取浏览器向服务器发送的二次请求得到的数据。其中,服务器端渲染的网页需要我们解析源码,而浏览器端渲染的网页一般可以直接获得数据。

  • 服务器端渲染:右键 - 查看页面源代码,如果在源码中能看到页面中字条的内容,则认为该字条是服务端渲染的。

  • 浏览器端渲染:右键 - 检查,分别点击网络、Fetch/XHR,当搜索框获得焦点的时候,我们可以看到浏览器会向服务器发送一个请求,然后将服务器返回的数据渲染后到页面上,这种方式就是浏览器端渲染,也被称为AJAX技术。
    在这里插入图片描述

  对于浏览器端渲染的页面,我们直接获取二次请求得到的数据即可,而对于服务器端渲染的页面,我们需要从源码解析出有价值的内容。我们可以使用Python的第三方模块re、bs4、xpath等。re是使用正则表达式匹配网页源码,从而得到我们想要的内容;bs4是通过标签和属性定位网页源码中我们需要的内容的位置,其更符合前端的编程习惯;xpath同样是通过标签和属性定位,但它看起来更加直观。

  • re
import re

list = re.findall(r"\d+", "我的电话号码是:10086, 我女朋友的电话号码是:10010")  # ['10086', '10010']
  • bs4
from bs4 import BeautifulSoup

page = BeautifulSoup(res, "html.parser")  # 把页面源代码(res)交给BeautifulSoup进行处理, 生成BeautifulSoup对象
table = page.find("table", attrs={
   "class": "hq_table"})  # 找到table
  • xpath
# xpath是在XML文档中搜索内容的一门语言,html是xml的一个子集
from lxml import etree

xml = 
"""
<book>
    <id>1</id>
    <name>野花遍地香</name>
    <price>1.23</price>
    <author>
        <nick>周大强</nick>
        <nick>周芷若</nick>
    </author>
</book>
"""
tree = etree.XML(xml)
result = tree.xpath("/book/name/text()")  # ['野花遍地香']。/表示层级关系,第一个/是根节点,text() 拿文本

  另外,在处理请求的过程中,可能需要解决一系列的反爬措施(1)防止网站识别Python程序需要加上User-Agent请求头(2)对于使用cookie验证登录的网站需要带上登录后服务器返回的cookie作为请求头(3)防止因频繁的请求导致ip地址被封需要使用代理(4)以及针对浏览器端渲染的情况,直接请求数据时可能遇到的一系列加密手段,这时候获取数据需要模拟加密过程进行解密……

  个人理解的爬虫原理~

1.2 实现一个爬虫

  一般来说,平台知名度越大,其反爬措施就越多,这时候获取数据也会变得更加困难,而下文将会介绍一种技巧性的方法。

1.2.1 Selenium

  Selenium是一个用于Web应用程序测试的工具,它可以直接运行在浏览器中,模拟用户的操作,例如点击、输入、关闭、拖动滑块等,就像真正的用户在操作一样。通过Selenium我们可以直接定位到页面中某段文字的位置,在已经经过浏览器渲染的网页中获取需要的内容,而不需要关心网页是服务器端渲染还是浏览器端渲染,所见即所得。

1.2.2 超级鹰

  另一方面,某些数据可能需要登录网站后才能获取,而在登录选项中选择账号密码登录一般会被要求输入验证码,比如常见的数字、汉字验证码等,某东平台使用的是滑块。我们可以使用超级鹰处理滑块,它是一款成熟的验证码处理工具,其使用原理是通过截取浏览器中验证码的图片传到超级鹰工具接口,然后接口会返回识别出来的数据(数字,汉字,坐标等),我们通过Selenium可以直接操作浏览器从而通过验证。

1.2.3 实现一个爬虫(源码在这里~)
  • 在这之前,大家可以先注册一个超级鹰账号哈,1元=1000题分,识别一次只需不到50题分,还是相当良心的。
  • 其次,在1.1 爬虫原理部分有简单介绍过xpath,这里有一种更便捷的方法获取元素的xpath,就像这样:右键-检查
  • 另外,部分商品可以使用id搜索
  • 再有就是,Selenium是需要配合浏览器驱动使用的,Chrome的驱动:chromedriver,对应浏览器版本的驱动下载完成后,将驱动放置在Python的安装目录,像下面这样:(也许还需要配置环境变量??如果遇到报错说没有找到浏览器驱动的话,可以自行搜一下具体是怎么配置的哈)

chaojiying.py(验证码处理)

#!/usr/bin/env python
# coding:utf-8

import requests
from hashlib import md5

class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
        self.username = username
        password =  password.encode('utf8')
        self.password = md5(password).hexdigest()
        self.soft_id = soft_id
        self.base_params = {
   
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        self.headers = {
   
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }

    def PostPic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
   
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {
   'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        params = {
   
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
        return r.json()


# if __name__ == '__main__':
    # 用户中心>>软件ID
    # chaojiying = Chaojiying_Client('超级鹰账号', '密码', '软件ID')
    # 本地图片文件路径替换code.png,有时WIN系统须要//
    # im = open('code.png', 'rb').read()  # im就是图片的所有字节
    # 官方网站>>价格体系
    # print(chaojiying.PostPic(im, 9101))  # 9101验证码类型

jd.py - 爬虫主程序

目标链接:(第24行代码)

在这里插入图片描述

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from chaojiying import Chaojiying_Client
import time

# 初始化超级鹰
chaojiying = Chaojiying_Client('******', '********', '******')  # 替换自己的账号、密码和软件ID

# 无头浏览器
# opt = Options()
# opt.add_argument("--headless")
# opt.add_argument("--disbale-gpu")
# options = opt

# 设置不关闭浏览器
option = webdriver.ChromeOptions()
option.add_experimental_option("detach", True)

web = webdriver.Chrome(options = option)

# 打开登录页面
web.get("")  # 填入某东登录页面链接,由于不能出现具体的目标链接,故以截图显示
# 最大化窗口,防止误触
web.maximize_window()

# time.sleep()是为了等待资源加载完
time.sleep(3)
# 使用账号登录
web.find_element(By.XPATH, '//*[@id="content"]/div[2]/div[1]/div/div[3]/a').click()

time.sleep(3)
# 输入用户名和密码
web.find_element(By.XPATH, '//*[@id="loginname"]').send_keys("***********")  # 某东账号
web.find_element(By.XPATH, '//*[@id="nloginpwd"]').send_keys("********")  # 某东密码
# 点击登录
web.find_element(By.XPATH, '//*[@id="loginsubmit"]').click()

time.sleep(3)
# 处理验证码,识别图像
verify_img = web.find_element(By.XPATH, '//*[@id="JDJRV-wrap-loginsubmit"]/div/div/div/div[1]/div[2]/div[1]/img')
dic = chaojiying.PostPic(verify_img.screenshot_as_png, 9101)
result = dic['pic_str']  # x1,y1
p_temp = result.split(",")
x = int(p_temp[0])

# 滑动滑块
btn = web.find_element(By.XPATH, '//*[@id="JDJRV-wrap-loginsubmit"]/div/div/div/div[2]/div[3]')
ActionChains(web).drag_and_drop_by_offset(btn, x, 0).perform()

time.sleep(8)
# 登陆成功,搜索界面(使用id搜索)
web.find_element(By.XPATH, '//*[@id="key"]').send_keys("100010935292", Keys.ENTER)

# time.sleep(5)
# # 点击商品
# web.find_element(By.XPATH, '//*[@id="J_goodsList"]/ul/li[1]/div/div[1]/a/img').click()

# time.sleep(5)
# # 移动到新窗口
# web.switch_to.window(web.window_handles[-1])

time.sleep(5)
# 商品属性
web.find_element(By.XPATH, '//*[@id="choose-attr-1"]/div[2]/div[1]/a').click()

time.sleep(5)
# 点击(商品评论)
comment_el = web.find_element(By.XPATH,'//*[@id="detail"]/div[1]/ul/li[5]')
comment_el.click()

time.sleep(5)
# 点击(只看当前商品评价)
only_el = web.find_element(By.XPATH, '//*[@id="comment"]/div[2]/div[2]/div[1]/ul/li[9]/label')
webdriver.ActionChains(web).move_to_element(only_el ).click(only_el ).perform()

f = open("comments.txt", mode="w", encoding='utf-8')

time.sleep(5)
# 评论列表
for i in range(100):
  # 每一页的评论
  div_list = web.find_elements(By.XPATH,'//*[@id="comment-0"]/div[@class="comment-item"]')  

  for div in div_list:
    comment = div.find_element(By.TAG_NAME, 'p').text
    f.write(comment + '\n\n')
    # 打印页数
    print(i)

  if i < 99:
    # 下一页 
    next_el = web.find_element(By.XPATH, '//*[@id="comment-0"]/div[12]/div/div/a[@class="ui-pager-next"]')
    # 防止元素遮挡 
    webdriver.ActionChains(web).move_to_element(next_el ).click(next_el ).perform()

  time.sleep(3)

f.close()
print('over!')

  • chaojiying.pyjd.py放在同一个目录下,然后下载相关依赖包,运行jd.py就ok了,再放个视频趴(懒得剪了😁):


    其中,Selenium执行的动作有:(1)打开登录页面并最大化窗口 - (2)使用账号登录并输入用户名和密码 - (3)处理验证码,滑动滑块(使用超级鹰滑动没通过的时候可以手动滑一次,时间足够的🌝) - (4)使用id搜索商品 - (5)点击商品属性 - (6)点击评论(加载页面的时候可以事先滑到评论位置,因为不这样做的话有时候评论数据不会显示出来⚠️)和只看当前商品评价 - (7)获取评论数据,再看下打印结果:

    可以看到,由于Selenium需要等待浏览器将页面渲染完成,最终程序运行了7分钟多,个人认为这算得上是使用Selenium实现爬虫的最大的缺点了。另外,其优点也是显而易见的:(1)不容易遇到反爬(但好像爬某宝还是不行🐵)(2)代码稳定性高,根据指定的页面修改xpath,代码放个半年再拿出来同样可以运行(3)通俗易懂,代码容易上手;

    以上是搜索商品的时候输入不同的商品id的获取到的评论,内容是这样的:
  • 其他需要注意的点:
    • 我曾经遇到过的一种情况就是,某一页的评论结构突然不一致了,导致爬到那一页之后就报错,这时候加一个条件判断区别处理就可以了。

2 预处理

  获取到文本之后,我们对文本进行预处理,包括去重拆分短句

2.1 去重

  无论文本挖掘的最终目的是什么,去重几乎是必需的,目的可能是排除一个消费者复制另一个消费者的评论,“挺好的”、“满意”、“不错”等重复率较高的评论,或者爬取重复等情况的出现,本文使用JS实现去重。(基于Node.js环境)(后面步骤不是必须使用Python的都会用JS来完成)

  先把所有评论合并为一个文件:

  代码可以这样写(JS):

let fs = require('fs')

// 读取comments目录下的所有文件
fs.readdir('../comments', (err, data) => {
   
  if (err) {
   
    throw err
    return;
  }
  
  let res

  // data是一个包含文件名的数组
  for (let item of data) {
   
    res = fs.readFileSync(`../comments/${
     item}`)

    fs.writeFileSync('./comments.txt', res.toString(), {
   flag:'a'})
  }
})

  然后对评论进行去重处理:

  打印结果(合并后一共有30799条评论,去重后剩余30352条评论,重复评论447条):

  程序可以这样写(JS):👇

let fs = require('fs')

// 读取评论
fs.readFile('comments.txt', function (err, data) {
   
  if (err) {
   
    console.log(err)
    return;
  }

  // 字符串转数组:(回车换行)
  let arr = data.toString().split('\r\n\r\n')

  // 数组去重
  let arr2 = []  // 保存重复的评论
  let arr1 = arr.filter((value, index, arr) => {
     // 保存去重后的评论
    if (arr.indexOf(value) !== index) {
   
      arr2.push(value)
    }

    return arr.indexOf(value) === index
  })

  // 写入数据
  // 去重后的评论
  fs.writeFile('./comments1.txt', arr1.join('\r\n\r\n'), function (err) {
   
    if (err) {
   
      console.log(arr)
      return;
    }

    console.log('comments1.txt写入成功。')
  })
  
  // 重复的评论
  fs.writeFile('./comments2.txt', arr2.join('\r\n\r\n'), function (err) {
   
    if (err) {
   
      console.log(arr)
      return;
    }

    console.log('comments2.txt写入成功。')
  })

  console.log(arr.length)
  console.log(arr1.length)
  console.log(arr2.length)
})

2.2 拆分短句

  如标题所示,后面我们将会计算特征词的情感得分,而研究上一般将短句的情感得分作为特征词的情感得分,因此我们可以将去重后的每一个评论拆分为多个短句,从而提高情感分析的准确度。

  先通过换行符划分短句,就像这样:

  🔽

  程序可以写成这样(JS):只是将两个换行符替换为一个换行符😎

let fs = require('fs')

fs.readFile('./comments1.txt', (err, data) => {
   
  if (err) {
   
    console.log(err)
    return;
  }
  
  fs.writeFile('./comments - 拆分短句1.txt', data.toString().replace(/\r\n\r\n/g, '\r\n'), (err) => {
   
    if (err) {
   
      console.log(err)
      return;
    }

    console.log('成功。')
  })
})

  另外,根据中文的书写习惯,一般以句号、感叹号、问号、省略号等作为一个句子结束的标志,所以我们可以依据这些标点符号继续拆分句子。PythonLTP模块实现了分句函数,在这里我们直接调用LTP模块的分句函数进行分句,以达到这样的效果:

  代码可以写成这样(Python):🐜

from ltp import LTP

f = open("comments - 拆分短句1.txt", encoding='utf-8')
list = []
line = f.readline()

while line:
  # 读取到数组
  list.append(line.replace("\n", ""))
  line = f.readline()

f.close()

ltp = LTP()
# 分句,sents是一个数组
sents = ltp.sent_split(list)

# 打开一个文件
fo = open("comments - 拆分短句2.txt", "w", encoding='utf-8')

for i in range(len(sents)):
  fo.write( f"{sents[i]}\n")

fo.close()

3 高频词统计

3.1 分词

  分词即将句子切分为多个词语,它是统计高频词的关键,我们可以使用Pythonjieba库实现分词,它有四种分词模式:💨

分词模式 特点
精确模式 试图将句子最精确地切开,适合文本分析;
全模式 把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
搜索引擎模式 在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词;
paddle模式 利用PaddlePaddle深度学习框架,训练序列标注(双向GRU)网络模型实现分词;

  jieba也支持自定义词典(dict.txt),可以添加未包含在jieba词库里的词语,jieba在处理自定义词典的词语时不会继续拆分,下面是一个自定义词典:💨

  分词后的结果:

  代码这样写(Python):

import jieba

# 添加自定义词典
jieba.load_userdict("E:\\封存\论文\处理过程\project\prehandler_py\dict.txt")

# 对文本进行操作
with open('comments - 拆分短句2.txt', 'r', encoding = 'utf-8') as sourceFile, open('comments-分词.txt', 'a+', encoding = 'utf-8') as targetFile:
    for line in sourceFile:
        seg = jieba.cut(line.strip(), cut_all = False)  # 精确模式
        # 分好词之后之间用空格隔断
        targetFile.write(' '.join(seg))
        targetFile.write('\n')
    print('写入成功!')

3.2 去停用词

  从分词结果可以看到,词语基本上可以被单独的划分出来,例如“外观”、“音质”、“像素”等,但分词后的句子夹杂着许多类似“已经”、“了”、“很”等没有实际意义的文本,这些词语我们称之为停用词,为使高频词统计的结果更加准确,我们对分词后的短句进行去除停用词处理。

  本文使用的停用词表结合了四川大学机器智能实验室停用词库、百度停用词和哈工大停用词表,一共包含了2131个停用词。

  停用词表 👊

  去停用词处理后:(此步骤同时进行分词去停用词

  代码可以这样写(Python):

import jieba

# 加载字典
jieba.load_userdict("E:\\封存\论文\处理过程\project\prehandler_py\dict.txt")

# 读取停用词
def stopwordslist(filepath):
  stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]
  return stopwords

def seg_sentence(sentence):
  sentence_seged = jieba.cut(sentence.strip(), cut_all = False)  # 分词
  stopwords = stopwordslist('E:\\封存\论文\处理过程\stopwords\stopwords.txt')  # 加载停用词的路径
  outstr = ''
  for word in sentence_seged:
    if word not in stopwords:
      outstr += word
      outstr += " "
  return outstr

inputs = open('comments - 拆分短句2.txt', 'r', encoding='utf-8')
outputs = open('comments - 分词、去停用词.txt', 'w', encoding='utf-8')

for line in inputs:
    line_seg = seg_sentence(line)  # 返回每行
    outputs.write(line_seg + '\n')
    
outputs.close()
inputs.close()

3.3 词频统计

  分词后得到的词语数量庞大,分别统计每一个词语的数量可以直观地看出消费者更关注哪些产品特征,本文只取数量在前300名之内的词语亦即消费者更关注的前300个特征进行分析。

  以下是在分词和去停用词处理后进行词频统计的结果:🐥

  统计词频的代码可以这样写(Python):(分词去停用词词频统计同时进行)

import jieba

# 加载字典
jieba.load_userdict("E:\\封存\论文\处理过程\project\prehandler_py\dict.txt")

# 读取停用词
def stopwordslist(filepath):
  stopwords = 
  • 46
    点赞
  • 352
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值