合肥工业大学软件工程专业《云计算、大数据技术与应用》课程综合设计报告。
爬取京东或淘宝某一商品的评论1000条,统计词频(使用MapReduce或HBase或Hive),并以词云的方式可视化呈现,最后设计为一套可以操作的系统。
项目采用Electron+Hadoop技术栈实现,前后端使用Socket进行通讯。
随着电子商务的快速发展,京东和淘宝成为中国最大的在线购物平台之一。这两个平台上有数亿的商品和海量的用户评论,这些评论包含了大量有价值的信息,可以用于市场调研、用户行为分析和产品改进等方面。然而,对于人工来处理这么大规模的评论数据是非常困难和耗时的,因此需要借助计算机技术来加速和自动化这个过程。
在这个场景中,我们的目标是爬取京东或淘宝某一商品的评论,并对这些评论进行词频统计和可视化呈现。这样的分析可以帮助我们了解消费者对于特定商品的意见和评价,并从中提取出一些关键词或热门话题,以辅助决策和战略制定。
为了实现这个目标,我们可以采用以下技术和工具:
综上所述,通过爬取京东或淘宝商品的评论,并利用MapReduce或HBase或Hive进行词频统计,再使用词云图像进行可视化呈现,我们可以更好地理解消费者对于特定商品的看法和评价,并从中提取有价值的信息。这个过程可以为企业决策和市场分析提供重要的参考依据。
1. 数据采集-爬取电商评论数据
2. 数据清洗-清洗电商评论数据
3. 文本分词
4. 上传清洗后的数据到数据库
5. 制作词云
6. WebSocket服务器设计
7. 前端设计与词云渲染
处理器 AMD Ryzen 9 7900X 12-Core Processor 4.70 GHz
机带 RAM 32.0 GB (31.2 GB 可用)
系统类型 64 位操作系统, 基于 x64 的处理器
所用软件 Eclipse,VMware Workstation Pro17,CentOS 7.0
本次实验中,我们使用WebCollector作为爬虫框架。WebCollector 是一个无须配置、便于二次开发的 Java 爬虫框架(内核),它提供精简的的 API,只需少量代码即可实现一个功能强大的爬虫。WebCollector-Hadoop 是 WebCollector 的 Hadoop 版本,支持分布式爬取。WebCollector是合肥工业大学大数据知识工程教育部重点实验室发起的开源项目,项目开发负责人是胡骏博士。网址:GitHub - CrawlScript/WebCollector: WebCollector is an open source web crawler framework based on Java.It provides some simple interfaces for crawling the Web,you can setup a multi-threaded web crawler in less than 5 minutes.
对于京东评论数据的爬取,一般通过对应的云函数调用完成。云函数的地址中对应的query项productId是产品ID。修改该项即可实现对不同评论数据的爬取。修改pageSize和page即可实现对不同页面的爬取。
具体爬虫代码见附录。
在完成数据的爬取后,需要将爬虫数据上传到HDFS,以便进一步进行分析。
首先,安装和配置了Hadoop集群。这包括设置好HDFS的相关配置文件,如core-site.xml和hdfs-site.xml,并启动Hadoop集群。
hdfs dfs -mkdir MRDataClean/server_cache |
try {
FileSystem hdfs;
Configuration conf=new Configuration();
conf.set("fs.default.name", "hdfs://master:9000");
hdfs = FileSystem.get(conf);
Path inFile =new Path("/MRDataClean/server_cache/" + taskId);
FSDataOutputStream outputStream = hdfs.create(inFile);
for(int i = 0 ; i < comments.size(); i ++){
outputStream.writeUTF(comments.get(i).replaceAll("\n", "") + "\n");
}
outputStream.flush();
outputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
上传完毕后,才可以进行下一步的分词操作。
在上传完成后,首先要进行数据清洗。清洗电商评论数据的目的是消除数据中的噪声、无效信息和冗余内容,使得数据更加干净、准确、可靠,以便进行后续的分析和挖掘。电商评论数据通常包含大量的文本,其中可能存在以下问题:
在完成数据清洗后,需要进行分词、词频统计。这里使用MapReduce模型完成上述操作。
当使用MapReduce进行分词和词频统计时,可以使用Java编程语言来编写Map和Reduce函数。
Mapper代码:
import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import org.apdachengjieba.*; import java.io.IOException; public class WordCountMapper extends Mapper private final static IntWritable one = new IntWritable(1); private Text word = new Text(); public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString();
// 创建Jieba分词对象 JiebaSegmenter segmenter = new JiebaSegmenter();
// 对输入文本进行分词 for (SegToken token : segmenter.process(line, JiebaSegmenter.SegMode.INDEX)) { // 过滤掉标点符号等非有效词汇 if (token.word.length() > 1) { word.set(token.word); context.write(word, one); } } } } |
Reducer代码:
import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; public class WordCountReducer extends Reducer private IntWritable result = new IntWritable(); public void reduce(Text key, Iterable int sum = 0;
// 对相同键的值进行累加 for (IntWritable value : values) { sum += value.get(); }
result.set(sum); context.write(key, result); } } |
以上代码中,WordCountMapper类继承了Mapper类,实现了map方法,在该方法中使用了jieba分词库对输入文本进行分词,并将分词结果作为键,值设置为常数1,通过context.write将结果输出。
WordCountReducer类继承了Reducer类,实现了reduce方法,在该方法中对相同键的值进行累加,并将键和最终计数结果输出。
最后,需要编写驱动程序(Driver)来配置和运行MapReduce作业:
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class WordCount { public static void main(String[] args) throws Exception { // 创建配置对象和作业对象 Configuration conf = new Configuration(); Job job = Job.getInstance(conf, "word count"); // 设置作业的主类、Mapper类和Reducer类 job.setJarByClass(WordCount.class); job.setMapperClass(WordCountMapper.class); job.setCombinerClass(WordCountReducer.class); job.setReducerClass(WordCountReducer.class); // 设置Mapper和Reducer的输出类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // 设置输入路径和输出路径 FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); // 提交作业并等待完成 System.exit(job.waitForCompletion(true) ? 0 : 1); } } |
在上述代码中,WordCount类作为作业的驱动程序,设置了作业的主类、Mapper类、Combiner类和Reducer类,同时指定了输入路径和输出路径。
编译并打包以上代码后,可以使用以下命令将其提交到Hadoop集群上运行:
hadoop jar WordCount.jar hfut/data hfut/output_data |
分词的结果如图所示:
为了使项目服务端、客户端之间能够有一个通信方式,这里采用Socket进行通信。服务端与客户端之间可以通过Socket连接传输工作需求和工作状态。从而实现通信。
创建一个MyServerSocket类,继承Thread类,使得其可以以单独的线程工作,防止其阻塞hadoop工作的主线程。
在run函数中,进行服务器的启动与创建,并等待客户端的连接。
约定客户端连接后以“#StartProgram#{productId}#{pageCount}”传输工作需求,其中productId为商品ID,pageCount为需要爬取的商品评论页数量。
收到传输工作需求后,服务端即调用JdCommentCrawler主类的工作方法,主类会自动开始接下来的一整套工作。
try {
server = new ServerSocket(port);
System.out.println("- 服务已在端口 " + port + "上启动。\n");
//从ServerSocket等待新连接的Socket。
client = server.accept();
System.out.println("- " + client.getInetAddress().getLocalHost() + " 已连接到服务。\n");
is = client.getInputStream();
os = client.getOutputStream();
br = new BufferedReader(new InputStreamReader(is));
bw = new BufferedWriter(new OutputStreamWriter(os));
while(true) {
String newMsg = br.readLine();
if (newMsg != null) {
System.out.println(">> " + newMsg + "\n");
// 判断客户端发来需要爬取的信息来源
if (newMsg.startsWith("#StartProgram")) {
String[] params = newMsg.substring("#StartProgram".length()).split("#");
String productId = params[0];
int pageCount = Integer.parseInt(params[1]);
JdCommentCrawler jcc = new JdCommentCrawler(productId, pageCount);
}
}
}
} catch (IOException e) {
e.printStackTrace();
if (e instanceof java.net.ConnectException)
System.out.println("- 服务启动失败,请重试或更换端口。" + "\n");
else
System.out.println("- 与客户端的连接已断开,服务停止。\n");
} finally {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
由于后续过程中客户端不需要再给服务端发送请求,但服务端需要给客户端发送大量响应,因此这里对服务端的响应体进行定义,统一采用JSON格式进行发送,如下:
public void sendMsg(String type,String msg) {
System.out.println(msg);
try {
bw.write("{\"type\":\"" + type + "\",\"data\":\"" + msg + "\"}\n|");
bw.flush();
System.out.println("<< " + msg + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendMsg(String msg) {
System.out.println(msg);
try {
bw.write("{\"type\":\"" + "message" + "\",\"data\":\"" + msg + "\"}\n|");
bw.flush();
System.out.println("<< " + msg + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
这里定义了sendMsg的两种实现,默认情况下消息的type为message,但调用者也可以手动指定消息的type。
发送工作需求后,客户端应当在有限期内等待服务端的工作与响应。
客户端这里采用Electron技术实现,Electron是一套使用Web相关技术开发桌面端应用的技术,允许使用Node.js在页面中混入原生的代码,从而实现底层功能。
由于这是一个小项目,这里为了开发方便,我们开启原生代码混入。
接下来,进行主界面设计:
首页要求用户输入服务器地址、端口、商品ID和爬取的页数。
输入完成后,点击开始按钮,即连接到服务器,并向服务器提交工作需求:
var net = require('net');
let results = [];
var client = net.connect({ host: serverIP.value, port: Number(serverPort.value) }, function () {
console.log('连接到服务器!');
client.write("#StartProgram" + productId.value + "#" + pageCount.value + "\n")
});
工作需求提交后,应当建立一个接收缓冲区对数据进行接收、分块、处理:
// 使用缓冲区接收大量响应数据,并进行对应的处理。
let cache = "";
client.on('data', function (data) {
let result = data.toString();
let unhandledMessages = [];
cache = cache + result;
if (cache.indexOf("|") != -1) {
unhandledMessages = cache.split("|");
cache = unhandledMessages[unhandledMessages.length - 1];
}
let sentence;
for (let item of unhandledMessages) {
sentence = item;
if (item != "") {
try {
let parsedResult = JSON.parse(item);
// console.log(parsedResult);
if (parsedResult.type == "result") {
let tuple = parsedResult.data.split(" ");
tuple[1] = Number(tuple[1]);
results.push(tuple);
} else if (parsedResult.type == "word") {
popWords.push(parsedResult.data);
} else if (parsedResult.type == "end") {
console.log("end");
message.loading(
"任务已完成,正在渲染词云..."
);
results.sort((a, b) => {
return (a[1] < b[1]);
})
results = results.filter((item) => {
return (stopwords.indexOf(item[0]) == -1);
})
results = results.slice(0, 500);
console.log(results);
setTimeout(() => {
showCloud.value = true;
shownResults.value = results;
}, 1000);
} else {
terminalOutput.value += parsedResult.data + "\n";
}
} catch (e) {
// console.log(sentence);
console.log(e);
}
}
}
});
这段代码主要负责消息的接收、处理、渲染。消息接收过程中的UI如下图所示:
这里为了更好的使用体验和效果,还将Hadoop分词过程中产生的词传送到了前端,并进行了动画展示:
分词工作结束后,会进入到最后页面,进行词云的渲染与绘制。
首先需要将分词和词频统计结果从HDFS中读出:
server.sendMsg("STEP 4 分词完成");
String result = "";
Path path = new Path("hdfs://master:9000/MRDataClean/server_cache/out/" + taskId + "/part-r-00000");
Configuration configuration = new Configuration();
FSDataInputStream fsDataInputStream = null;
FileSystem fileSystem = null;
BufferedReader br = null;
// 定义一个字符串用来存储文件内容
try {
fileSystem = path.getFileSystem(configuration);
fsDataInputStream = fileSystem.open(path);
br = new BufferedReader(new InputStreamReader(fsDataInputStream));
String str2;
while ((str2 = br.readLine()) != null) {
// 遍历抓取到的每一行并将其存储到result里面
server.sendMsg("result", str2.replace(" ", " "));
}
} catch (IOException e) {
e.printStackTrace();
}
词云绘制采用Vue-WordCloud这个组件进行渲染。在渲染时,选取前500个词,并剔除stop_words。
message.loading(
"任务已完成,正在渲染词云..."
);
results.sort((a, b) => {
return (a[1] < b[1]);
})
results = results.filter((item) => {
return (stopwords.indexOf(item[0]) == -1);
})
results = results.slice(0, 500);
console.log(results);
setTimeout(() => {
showCloud.value = true;
shownResults.value = results;
}, 1000);
渲染结果如下图所示:
服务端在完成本次工作需求后,应当关闭连接,并转入下一流程状态,等待全新的连接进入。
server.sendMsg("STEP 5 结果传送完成");
server.sendMsg("end","");
server.close();
server = new MySocketServer(port);
通过这个实验,我学到了如何使用Java编程语言和Web控制器进行数据采集、数据清洗、文本分词、数据库操作和词云生成等方面的知识和技能。这些技能对于实际的数据处理和分析工作非常有帮助,也为我未来在软件开发和数据科学领域的工作提供了基础。
完成这个实验的过程中,我学到了很多关于网络爬虫和数据可视化的知识和技巧。通过编写爬虫程序,我深入了解了HTTP请求和网页解析的原理,提高了对网页结构和内容的理解能力。
此外,通过制作词云,我能够更加直观地观察和分析文本数据。词云可以将文本信息以图形化的方式展示出来,使我能够更好地捕捉到其中的关键信息和趋势。
在实验中,我还学会了如何处理和清洗文本数据,以及如何调整词云的外观。这些技能对于日后进行数据处理和可视化工作将非常有用。
总的来说,这个实验让我在网络爬虫和数据可视化方面有了实际的经验,并且提高了我对文本数据分析的能力。我相信这些技能和经验将对我的学习和职业发展产生积极的影响。
最后,感谢吴共庆老师在《云计算、大数据技术及应用》这门课程中传授的理论知识与工程技能,使得我能够顺利地完成本次综合设计。
该项目已在github上发布,项目地址为:GitHub - RicePasteM/JdCommentCrawler: JdCommentCrawler - 基于Hadoop与Electron的京东商品评论词云统计系统。
欢迎互相学习与交流!