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

需求

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

具体实现

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

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

通过代码发送请求

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

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.0.3</version>
</dependency>

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

public class App {
    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";

    public static void main(String[] args) {
        HttpRequest req = HttpUtil.createGet(GET_COMMENTS_URL);
        req.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0");
        req.header("Referer", "https://item.jd.com/45194954688.html");
        System.out.println(req.execute().body());
    }
}

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

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

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

  • fastjson依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>
public static void main(String[] args) {
	HttpRequest req = HttpUtil.createGet(GET_COMMENTS_URL);
	req.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0");
	req.header("Referer", "https://item.jd.com/45194954688.html");
	String respJson = StrUtil.sub(req.execute().body(), 25, -2);
	JSONObject jsonObject = JSON.parseObject(respJson);
	JSONArray comments = jsonObject.getJSONArray("comments");
	ArrayList<String> list = new ArrayList<>();
	comments.forEach(comment -> list.add(((JSONObject) comment).get("content").toString()));
	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

引入依赖:

<dependency>
    <groupId>com.hankcs</groupId>
    <artifactId>hanlp</artifactId>
    <version>portable-1.7.5</version>
</dependency>

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

public static void main(String[] args) throws InterruptedException {
    List<String> comments = FileUtil.readLines("E:\\User\\Desktop\\comments.txt", Charset.defaultCharset());
    Map<String, Long> map = participle(comments);
    System.out.println(map);
}

private static Map<String, Long> participle(List<String> list) {
    Map<String, Long> map = list
            .stream()
            .flatMap(string -> {
                Sentence analyze = NLPTokenizer.analyze(string);
                List<IWord> wordList = analyze.wordList;
                return wordList.stream()
                        .filter(word -> word.getLabel().contains("a"))
                        .map(word -> word.getValue());
            })
            .collect(Collectors.groupingBy(String::toString, Collectors.counting()));
    return map;
}

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

运行结果:
运行结果

生成词云

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

引入依赖

<dependency>
    <groupId>com.kennycason</groupId>
    <artifactId>kumo-core</artifactId>
    <version>1.17</version>
</dependency>
<!-- 汉语支持 -->
<dependency>
    <groupId>com.kennycason</groupId>
    <artifactId>kumo-tokenizers</artifactId>
    <version>1.17</version>
</dependency>

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

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

public static void toImage(Map<String, Long> words, String filePath) {
    final List<WordFrequency> wordFrequencies = new ArrayList<>();
    // 构造List<WordFrequency>
    words.forEach((k, v) -> wordFrequencies.add(new WordFrequency(k, Math.toIntExact(v))));

    // 设置图片大小
    final Dimension dimension = new Dimension(600, 600);
    // 生成词云对象,设置图片大小,图像形状
    final WordCloud wordCloud = new WordCloud(dimension, CollisionMode.RECTANGLE);
    // 每个词语的边界
    wordCloud.setPadding(0);
    // 设置图片背景色
    wordCloud.setBackgroundColor(Color.WHITE);
    // 设置背景形状为方形
    wordCloud.setBackground(new RectangleBackground(dimension));
    // 设置词云显示的三种颜色,越靠前设置表示词频越高的词语的颜色
    wordCloud.setColorPalette(new ColorPalette(Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE));
    // 字体标量:第一个参数为字体的最小值,第二个参数为字体的最大值
    wordCloud.setFontScalar(new LinearFontScalar(20, 100));
    // 设置一款中文字体
    wordCloud.setKumoFont(new KumoFont(new Font("华文新魏", 2, 20)));
    // 生成词云
    wordCloud.build(wordFrequencies);
    wordCloud.writeToFile(filePath);
}

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

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

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

完整代码
package com.qianyucc.WordCloud.test;

import cn.hutool.core.io.*;
import cn.hutool.core.util.*;
import cn.hutool.http.*;
import com.alibaba.fastjson.*;
import com.hankcs.hanlp.corpus.document.sentence.*;
import com.hankcs.hanlp.corpus.document.sentence.word.*;
import com.hankcs.hanlp.tokenizer.*;
import com.kennycason.kumo.*;
import com.kennycason.kumo.bg.*;
import com.kennycason.kumo.font.*;
import com.kennycason.kumo.font.scale.*;
import com.kennycason.kumo.palette.*;

import java.awt.*;
import java.nio.charset.*;
import java.util.*;
import java.util.List;
import java.util.stream.*;

/**
 * @author lijing
 * @date 2019-10-28 16:18
 * @description 爬取评论,分词生成词云
 */
public class App {
    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";

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 17; i++) {
            Thread.sleep(RandomUtil.randomInt(1000 * 2, 1000 * 5));
            List<String> comments = getComments(i);
            FileUtil.writeLines(comments, "E:\\User\\Desktop\\comments.txt", Charset.defaultCharset(), true);
        }
        List<String> comments = FileUtil.readLines("E:\\User\\Desktop\\comments.txt", Charset.defaultCharset());
        Map<String, Long> map = participle(comments);
        toImage(map, "E:\\User\\Desktop\\WordCloud.png");
    }

    public static void toImage(Map<String, Long> words, String filePath) {
        final List<WordFrequency> wordFrequencies = new ArrayList<>();
        // 构造List<WordFrequency>
        words.forEach((k, v) -> wordFrequencies.add(new WordFrequency(k, Math.toIntExact(v))));

        // 设置图片大小
        final Dimension dimension = new Dimension(600, 600);
        // 生成词云对象,设置图片大小,图像形状
        final WordCloud wordCloud = new WordCloud(dimension, CollisionMode.RECTANGLE);
        // 每个词语的边界
        wordCloud.setPadding(0);
        // 设置图片背景色
        wordCloud.setBackgroundColor(Color.WHITE);
        // 设置背景形状为方形
        wordCloud.setBackground(new RectangleBackground(dimension));
        // 设置词云显示的三种颜色,越靠前设置表示词频越高的词语的颜色
        wordCloud.setColorPalette(new ColorPalette(Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE));
        // 字体标量:第一个参数为字体的最小值,第二个参数为字体的最大值
        wordCloud.setFontScalar(new LinearFontScalar(20, 100));
        // 设置一款中文字体
        wordCloud.setKumoFont(new KumoFont(new Font("华文新魏", 2, 20)));
        // 生成词云
        wordCloud.build(wordFrequencies);
        wordCloud.writeToFile(filePath);
    }

    private static Map<String, Long> participle(List<String> list) {
        Map<String, Long> map = list
                .stream()
                .flatMap(string -> {
                    Sentence analyze = NLPTokenizer.analyze(string);
                    List<IWord> wordList = analyze.wordList;
                    return wordList.stream()
                            .filter(word -> word.getLabel().contains("a"))
                            .map(word -> word.getValue());
                })
                .collect(Collectors.groupingBy(String::toString, Collectors.counting()));
        return map;
    }

    private static List<String> getComments(int page) {
        HttpRequest req = HttpUtil.createGet(StrUtil.format(GET_COMMENTS_URL, page));
        req.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0");
        req.header("Referer", "https://item.jd.com/45194954688.html");
        String respJson = StrUtil.sub(req.execute().body(), 25, -2);
        JSONObject jsonObject = JSON.parseObject(respJson);
        JSONArray comments = jsonObject.getJSONArray("comments");
        ArrayList<String> list = new ArrayList<>();
        comments.forEach(comment -> list.add(((JSONObject) comment).get("content").toString()));
        return list;
    }
}
发布了126 篇原创文章 · 获赞 12 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/li3455277925/article/details/102782426