java爬取商品评论,分词生成词云

小灰灰 2023-06-10 16:20 22阅读 0赞

需求

一般我们在购买一件商品的时候,都会习惯性的翻看评论,查看大家对这件商品的评价,但是有时候评论太多,而我们想快速了解一下消费者对这件商品的评价是什么样的。
评论数量
这里就使用java实现一个简单爬虫,爬取某款内存条的评论,根据评论中的关键词生成词云,让我们对这款内存条有一个大致的了解。结果如下:
词云
可见,大家对这款内存条的评价还是不错的。

具体实现

找到获取商品评论信息的api

打开一个购物网站的首页,搜索关键词“内存条”,我们以第一个为例,下拉找到“评价”。可见“商品介绍”、“商品评价”等是使用tab栏切换来实现的,于是我们猜测这里点击“商品评价”的时候前端会使用ajax向后端发送请求获取数据。
tab
于是我们按F12打开控制台(这里以火狐浏览器为例),找到“网络”,再点击:“商品评价”,这个时候可以看到浏览器发出的所有请求以及服务器做出的响应(如果没有请求显示的话可能是浏览器有缓存,我们可以选择禁用缓存,再次刷新)。
network
这个时候我们就开始寻找哪条请求是获取所有评论的请求了,在过滤条件我们选择HTML其他,可以过滤出后端返回的数据类型为json或者html的请求。我们发现有两个返回数据比较大,初步猜测获取评论的api是其中一个。
筛选
点开进行对比之后,我们可以找到获取评论对应的接口:
找到接口
这个时候我们就拿到了获取评论的api
获取评论的api

通过代码发送请求

新建一个maven项目,关于发送请求的工具类,这里使用的是Hutool,详细信息可以参考官方文档:https://www.hutool.cn/docs/\#/

  1. <dependency>
  2. <groupId>cn.hutool</groupId>
  3. <artifactId>hutool-all</artifactId>
  4. <version>5.0.3</version>
  5. </dependency>

在发送请求之前我们需要先设置请求头信息:
其中字段User-Agent用于传达浏览器的种类,这里我们就伪装成火狐浏览器。字段Referer会告知服务器请求的原始资源的 URI,也就是说这个字段表示请求是从哪个页面发起的。
请求头信息

  1. public class App {
  2. public static final String GET_COMMENTS_URL = "https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv347&productId=100007698730&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&fold=1";
  3. public static void main(String[] args) {
  4. HttpRequest req = HttpUtil.createGet(GET_COMMENTS_URL);
  5. req.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0");
  6. req.header("Referer", "https://item.jd.com/45194954688.html");
  7. System.out.println(req.execute().body());
  8. }
  9. }

运行结果如下,我们会发现这是一段jsonp格式的内容,去掉头尾多余部分就是我们需要的json字符串:
返回的数据
这个时候我们可以复制json信息,在百度搜索“json格式化”,会有许多在线格式化json的工具。

个人推荐这个网站:https://www.json.cn/

json格式化
这个时候我们就可以看到comments数组是评论信息,其中content字段就是评论的内容。知道了评论内容的具体位置之后下面就是解析json了,这里使用alibaba的fastjson,爬取到评论内容之后我们先将其存入List中。

  • fastjson依赖


    com.alibaba
    fastjson
    1.2.62

    public static void main(String[] args) {

    1. HttpRequest req = HttpUtil.createGet(GET_COMMENTS_URL);
    2. req.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0");
    3. req.header("Referer", "https://item.jd.com/45194954688.html");
    4. String respJson = StrUtil.sub(req.execute().body(), 25, -2);
    5. JSONObject jsonObject = JSON.parseObject(respJson);
    6. JSONArray comments = jsonObject.getJSONArray("comments");
    7. ArrayList<String> list = new ArrayList<>();
    8. comments.forEach(comment -> list.add(((JSONObject) comment).get("content").toString()));
    9. list.forEach(System.out::println);

    }

其中StrUtil.sub为Hutool工具类中的方法,用于分割字符串,这里用于提取出json字符串

运行结果如下:
运行结果

分页获取数据,以及代码的封装

观察url的参数,发现出现了名字为page的参数,猜测这里的page表示页码。
page参数
当我们点击第二页的时候,再次查看控制台,找到请求的url
点击第二页
对比两个url,发现page由0变为1
对比url
这里我们就可以封装一个方法用于获取所有评论,并将数据储存到文件中(这个过程可能会很慢):
爬取数据保存到文件

StrUtil.format用于格式化字符串
RandomUtil.randomInt用户产生2秒到5秒之间的随机时间,防止ip被封
FileUtil.write用于将字符串写入文件,并采用追加模式
详情可以参考Hutool的官网。

运行程序之后发现评论已写入文件:
写入文件的评论

分词

分词这里使用hanLP,官方网站:http://hanlp.com/。官方文档:https://github.com/hankcs/HanLP/blob/master/README.md

引入依赖:

  1. <dependency>
  2. <groupId>com.hankcs</groupId>
  3. <artifactId>hanlp</artifactId>
  4. <version>portable-1.7.5</version>
  5. </dependency>

这里我们不仅需要分词,还要筛选出所有的形容词和副词,因为一般根据形容词和副词可以看出消费者对这件商品的看法。查阅hanLP官方文档发现,我们可以使用NLP分词:
NLP分词
使用NLP分词之前我们需要先下载data,如下:
data下载
并且在resources下面新建hanlp.properties配置data所在目录,新建log4j.properties用于配置日志(可自行百度)
配置文件
这个时候我们就可以分词并且进行统计了:

  1. public static void main(String[] args) throws InterruptedException {
  2. List<String> comments = FileUtil.readLines("E:\\User\\Desktop\\comments.txt", Charset.defaultCharset());
  3. Map<String, Long> map = participle(comments);
  4. System.out.println(map);
  5. }
  6. private static Map<String, Long> participle(List<String> list) {
  7. Map<String, Long> map = list
  8. .stream()
  9. .flatMap(string -> {
  10. Sentence analyze = NLPTokenizer.analyze(string);
  11. List<IWord> wordList = analyze.wordList;
  12. return wordList.stream()
  13. .filter(word -> word.getLabel().contains("a"))
  14. .map(word -> word.getValue());
  15. })
  16. .collect(Collectors.groupingBy(String::toString, Collectors.counting()));
  17. return map;
  18. }

代码中的word.getLabel().contains("a")是找出所有词性中包含a的,分词之后,形容词的标签为a,副词为ad,这里实际上是找出所有的形容词和副词。
最后在collect操作中按照分词进行分组,并统计出分词出现的频率

运行结果:
运行结果

生成词云

生成词云使用的是kumo,项目地址:https://github.com/kennycason/kumo

引入依赖

  1. <dependency>
  2. <groupId>com.kennycason</groupId>
  3. <artifactId>kumo-core</artifactId>
  4. <version>1.17</version>
  5. </dependency>
  6. <!-- 汉语支持 -->
  7. <dependency>
  8. <groupId>com.kennycason</groupId>
  9. <artifactId>kumo-tokenizers</artifactId>
  10. <version>1.17</version>
  11. </dependency>

参考官方文档,如果分词和词频都是已知的,我们就可以自己构造List<WordFrequency>,上面我们已经进行了分词和词频的计算,所以这里采用这种方法。
官网描述

kumo自带有中文分词分析器,如果使用kumo自带的分词的话可以不必使用hanLP,具体可以参考kumo的文档。这里使用hanLP主要有以下好处:1. 可以根据词性筛选出词语。2. 可以自定义筛选条件。3. 分词更加准确

  1. public static void toImage(Map<String, Long> words, String filePath) {
  2. final List<WordFrequency> wordFrequencies = new ArrayList<>();
  3. // 构造List<WordFrequency>
  4. words.forEach((k, v) -> wordFrequencies.add(new WordFrequency(k, Math.toIntExact(v))));
  5. // 设置图片大小
  6. final Dimension dimension = new Dimension(600, 600);
  7. // 生成词云对象,设置图片大小,图像形状
  8. final WordCloud wordCloud = new WordCloud(dimension, CollisionMode.RECTANGLE);
  9. // 每个词语的边界
  10. wordCloud.setPadding(0);
  11. // 设置图片背景色
  12. wordCloud.setBackgroundColor(Color.WHITE);
  13. // 设置背景形状为方形
  14. wordCloud.setBackground(new RectangleBackground(dimension));
  15. // 设置词云显示的三种颜色,越靠前设置表示词频越高的词语的颜色
  16. wordCloud.setColorPalette(new ColorPalette(Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE));
  17. // 字体标量:第一个参数为字体的最小值,第二个参数为字体的最大值
  18. wordCloud.setFontScalar(new LinearFontScalar(20, 100));
  19. // 设置一款中文字体
  20. wordCloud.setKumoFont(new KumoFont(new Font("华文新魏", 2, 20)));
  21. // 生成词云
  22. wordCloud.build(wordFrequencies);
  23. wordCloud.writeToFile(filePath);
  24. }

在main函数里面调用,即可生成词云:

  1. public static void main(String[] args) throws InterruptedException {
  2. List<String> comments = FileUtil.readLines("E:\\User\\Desktop\\comments.txt", Charset.defaultCharset());
  3. Map<String, Long> map = participle(comments);
  4. toImage(map,"E:\\User\\Desktop\\WordCloud.png");
  5. }

生成的图片如下,我们可以看出大部分用户对其的评价都很不错:
生成的图片

完整代码
  1. package com.qianyucc.WordCloud.test;
  2. import cn.hutool.core.io.*;
  3. import cn.hutool.core.util.*;
  4. import cn.hutool.http.*;
  5. import com.alibaba.fastjson.*;
  6. import com.hankcs.hanlp.corpus.document.sentence.*;
  7. import com.hankcs.hanlp.corpus.document.sentence.word.*;
  8. import com.hankcs.hanlp.tokenizer.*;
  9. import com.kennycason.kumo.*;
  10. import com.kennycason.kumo.bg.*;
  11. import com.kennycason.kumo.font.*;
  12. import com.kennycason.kumo.font.scale.*;
  13. import com.kennycason.kumo.palette.*;
  14. import java.awt.*;
  15. import java.nio.charset.*;
  16. import java.util.*;
  17. import java.util.List;
  18. import java.util.stream.*;
  19. /** * @author lijing * @date 2019-10-28 16:18 * @description 爬取评论,分词生成词云 */
  20. public class App {
  21. public static final String GET_COMMENTS_URL = "https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv347&productId=100007698730&score=0&sortType=5&page={}&pageSize=10&isShadowSku=0&fold=1";
  22. public static void main(String[] args) throws InterruptedException {
  23. for (int i = 0; i < 17; i++) {
  24. Thread.sleep(RandomUtil.randomInt(1000 * 2, 1000 * 5));
  25. List<String> comments = getComments(i);
  26. FileUtil.writeLines(comments, "E:\\User\\Desktop\\comments.txt", Charset.defaultCharset(), true);
  27. }
  28. List<String> comments = FileUtil.readLines("E:\\User\\Desktop\\comments.txt", Charset.defaultCharset());
  29. Map<String, Long> map = participle(comments);
  30. toImage(map, "E:\\User\\Desktop\\WordCloud.png");
  31. }
  32. public static void toImage(Map<String, Long> words, String filePath) {
  33. final List<WordFrequency> wordFrequencies = new ArrayList<>();
  34. // 构造List<WordFrequency>
  35. words.forEach((k, v) -> wordFrequencies.add(new WordFrequency(k, Math.toIntExact(v))));
  36. // 设置图片大小
  37. final Dimension dimension = new Dimension(600, 600);
  38. // 生成词云对象,设置图片大小,图像形状
  39. final WordCloud wordCloud = new WordCloud(dimension, CollisionMode.RECTANGLE);
  40. // 每个词语的边界
  41. wordCloud.setPadding(0);
  42. // 设置图片背景色
  43. wordCloud.setBackgroundColor(Color.WHITE);
  44. // 设置背景形状为方形
  45. wordCloud.setBackground(new RectangleBackground(dimension));
  46. // 设置词云显示的三种颜色,越靠前设置表示词频越高的词语的颜色
  47. wordCloud.setColorPalette(new ColorPalette(Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE));
  48. // 字体标量:第一个参数为字体的最小值,第二个参数为字体的最大值
  49. wordCloud.setFontScalar(new LinearFontScalar(20, 100));
  50. // 设置一款中文字体
  51. wordCloud.setKumoFont(new KumoFont(new Font("华文新魏", 2, 20)));
  52. // 生成词云
  53. wordCloud.build(wordFrequencies);
  54. wordCloud.writeToFile(filePath);
  55. }
  56. private static Map<String, Long> participle(List<String> list) {
  57. Map<String, Long> map = list
  58. .stream()
  59. .flatMap(string -> {
  60. Sentence analyze = NLPTokenizer.analyze(string);
  61. List<IWord> wordList = analyze.wordList;
  62. return wordList.stream()
  63. .filter(word -> word.getLabel().contains("a"))
  64. .map(word -> word.getValue());
  65. })
  66. .collect(Collectors.groupingBy(String::toString, Collectors.counting()));
  67. return map;
  68. }
  69. private static List<String> getComments(int page) {
  70. HttpRequest req = HttpUtil.createGet(StrUtil.format(GET_COMMENTS_URL, page));
  71. req.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0");
  72. req.header("Referer", "https://item.jd.com/45194954688.html");
  73. String respJson = StrUtil.sub(req.execute().body(), 25, -2);
  74. JSONObject jsonObject = JSON.parseObject(respJson);
  75. JSONArray comments = jsonObject.getJSONArray("comments");
  76. ArrayList<String> list = new ArrayList<>();
  77. comments.forEach(comment -> list.add(((JSONObject) comment).get("content").toString()));
  78. return list;
  79. }
  80. }

发表评论

表情:
评论列表 (有 0 条评论,22人围观)

还没有评论,来说两句吧...

相关阅读

    相关 网易音乐评论

    前言 我是个很喜欢听歌的人,手机里面下了几百首歌,而且还会每个月还会增加几首,因为我觉得听歌能让我活得更有趣。平常的我,喜欢听一些流行歌曲或者被翻唱突然又火起来的老歌、无