商品评论获取分析和可视化词云图制作
本文主要介绍了如何手写爬虫爬行电子商务平台论数据,分析爬行内容,导入mysql数据库并进行词频统计,可视化制作词云图。 涉及的技术点如下:
- 电子商务网站页面分析
- python简单爬虫
- java语言的webCollector使用爬虫框架
- python与java分别进行json其中,文件分析java解析结合mapreduce
- pymysql操作mysql数据库实现数据导入
- 清洁爬行数据
- mapreduce统计词频
- hive统计词频
- wordcloud词云图制作
- echarts词云图制作 在这里,我列出了两个一般的想法,只介绍具体的实现想法和技术细节,由于时间原因不能非常全面。读者可以根据自己的需要选择每个过程的实现方法。 整体流程图如下
因为博主喜欢五颜六色,所以画也五颜六色。 下面开始具体介绍!
电子商务网站页面分析
这里需要注意的是,我们分析了网站,以确定如何涉及爬虫。之前也做过一些这方面的尝试,当时对可视化比较感兴趣,当时做的是新冠肺炎疫情可视化项目,所以学到了一些。我看到爬数据的第一个想法是按相应的网页F打开开发工具,查看其中的一些内容。这个网上有很多介绍,我就不细说了。如果我不能从这里得到有用的信息,比如一个宝藏,我就找不到有用的信息此时,我将考虑直接爬上网页源代码,然后分析以获取有用的信息。 下面上图!
值得注意的是,这是最简单的情况,即评论信息json存储很容易获得,但在很多情况下,它不是那么顺利,所以你需要分析原始页面并获得评论,这将是相对复杂和正确的使用html通过xpath来获取解析html获得评论要复杂得多。你可以参考其他优秀博客。博主的水平有限,但解释太多
Python简单爬虫加json文件解析与mysql数据库存储
这是因为我集成在一起,内容不多。直接贴源码,我在代码中加注释,直接看注释:
import json import time import pymysql import requests class MySpider: def __init__(self): self.urls1 = ["但是,具体地址请私信" .format(i) for i in range(50)] self.urls2 = ["但是,具体地址请私信".format(i) for i in range(50)] # url self.headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"} # 最好加上请求头,否则很容易爬不到内容。如果有,请求头不妨慢慢或更换 self.fileName = [] self.conn = pymysql.Connect( host='127.0.0.1', port=3360, #这是博主本地的mysql数据库也可以不在这里连接,根据自己的需要随意更改 user='root', # 这里的具体配置取决于您的数据库 password=, database='my_db', ) self.cursor = self.conn.cursor() self.fileListName = "dataNameList.txt" # 将爬取的文件名存在一个文件里这样在解析的时候不再需要重新爬取并不必要 def parse_url(self): # 爬取网站数据 i = 0 urls = self.urls1+self.urls2 for url in urls: response = requests.get(url,headers=self.headers) time.sleep(2) filename = "E:\SpiderData\commentWithoutProcessing"+str(i)+".json" i += 1 with open(self.fileListName,'a') as f: f.write(filename+";") with open(filename,'w') as f: text = response.text.split(')')[0] # 将非json部分过滤掉 text = text.split('(')[1] f.write(text) print("########数据获取结束######") def parse_data(self): # 对爬取的数据进行解析 with open(self.fileListName,'r') as f: nameAll = f.read() self.fileName = nameAll.split(';') i = 0 # 存储地址可指定 filenames = ["E:\SpiderData\commentWithoutProcessing{}.json".format(i) for i in range(100)] sql = """create table comments( ID bigint, Contents Text, Score int , Type int)ENGINE=innodb DEFAULT CHARSET=utf8""" self.cursor.execute(sql) for filename in filenames: if i<50: type = 1 else: type = 2 i += 1 with open(filename,'r') as f: try: # 这里加入try-except来处理文件解析失败的异常亲测会出现异常 # 大家可以试试这与我对爬取到的文件的处理方式有关,我的处理方式会出现一些文件只剩一般导致json解析失败因此加入这样的异常处理,如果大家有更好的想法我将不胜感激!!! data = json.loads(f.read()) print("开始解析" + filename + "文件,存入数据库...........") # sql = 'insert into "comments"("ID","Contents","Score") values (,%(Contents)s,%(Score)s)' for info in data['comments']: id = int(info['id']) content = info['content'] score = int(info['score']) information = (id, content, score,type) sql = "INSERT INTO `comments`(`ID`,`Contents`,`Score`,`Type`) VALUES (%s,%s,%s,%s)" rows = self.cursor.execute(sql, information) self.conn.commit() print(filename + "解析结束......") except: print("解析失败!"+filename) self.conn.close() if __name__ == "__main__": spider = MySpider()
这里展示一下结果: 这里还涉及利用pymysql操作mysql的知识,这里的坑也很多,mysql经常崩溃,博主也修改了很久,要注意mysql的引号是反过来的·!!!大家可以查找相关资料 这里如果是windows的话关机后mysql会关闭,要重新开启有个笨方法:
博主水平不高,这个代码应该不难理解,大家可以以这个为基础进行修改完善。还有就是博主亲测是能爬取数据的如果您尝试不行可以试着修改请求头,同时博主水平有限如果有任何错误还望批评指正!!不胜感激。
java语言的webCollector爬虫框架使用
这个框架我还是第一次使用之前从来没有尝试过! 如果大家使用maven的话可以通过导入依赖:
<dependency> <groupId>cn.edu.hfut.dmic.webcollector</groupId> <artifactId>WebCollector</artifactId> <version>2.73-alpha</version> </dependency>
引用一些关于这个爬虫框架组件的简单介绍:
- Crawler 对应一个完整的爬虫,封装了整个采集流程及所有的插件槽位。Crawler提供了一些基类,通过继承这些基类可实现爬虫的定制。
- DBManager 数据管理器,提供了爬虫数据管理的基础接口。DBManager可自动实现高并发环境下URL去重,而不需要用户考虑去重业务。可通过插件形式实现基于不同数据库的数据管理器,如伯克利DB或RocksDB。
- Visitor 用于定制用户对每个页面需要执行的操作,包括页面抽取和新链接发现。
- Fetcher 抓取调度器。负责爬虫并发采集任务的调度,可利用有限的内存实现对上亿页面的高并发采集。
- Requester 负责发送HTTP请求并接收响应。用户可定制各种Requester插件以实现不同的Http请求,包括Http头定制、代理定制等。
- Plugin 上述大部分组件都提供了插件化功能。即用户可以定制自己的DBManager、Requester等,以实现高定制化的爬虫。
具体内容请查看GitHub 的 webCollector 主页 我拜读了他的一部分源码发现自己还有许多许多知识要学。下面贴出该部分的代码!这里的代码不是我自己写的,是拿老师给的代码进行简单修改得到的。不过这个爬虫使用的大致框架为如此,十分崇拜这个爬虫框架的编写者!!!
package my.webcollector;
import java.io.*;
import okhttp3.Request;
import cn.edu.hfut.dmic.webcollector.model.CrawlDatum;
import cn.edu.hfut.dmic.webcollector.model.CrawlDatums;
import cn.edu.hfut.dmic.webcollector.model.Page;
import cn.edu.hfut.dmic.webcollector.plugin.berkeley.BreadthCrawler;
import cn.edu.hfut.dmic.webcollector.plugin.net.OkHttpRequester;
public class JDCommentCrawler extends BreadthCrawler {
public JDCommentCrawler(String crawlPath) {
// 第二个参数表示不需要自动探测URL
super(crawlPath, false);
// 设置线程数为1
setThreads(1);
// 添加种子(评论API对应的URL,这里翻页10次)
for (int pageIndex = 0; pageIndex < 100; pageIndex++) {
String seedUrl = String
.format("具体地址请私信",
pageIndex);
// 在添加种子的同时,记录对应的页号
addSeedAndReturn(seedUrl).meta("pageIndex", pageIndex);
}
}
@Override
public void visit(Page page, CrawlDatums crawlDatums) {
// 模拟人访问网页的速度
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取之前保存的页号信息
int pageIndex = page.metaAsInt("pageIndex");
String body = page.html();
// 保存当前访问的productPageComments页面信息
JDCommentCrawler.createFile(body, "D:\\BigDataWordCloud\\src\\htmlTexts\\10038246700670-page"
+ pageIndex + ".html");
}
/** * 将字符串保存到文件 */
public static boolean createFile(String content, String filePath) {
// 标记文件生成是否成功
boolean flag = true;
try {
// 保证创建一个新文件
File file = new File(filePath);
if (!file.getParentFile().exists()) {
// 如果父目录不存在,创建父目录
file.getParentFile().mkdirs();
}
if (file.exists()) {
// 如果已存在,删除旧文件
file.delete();
}
file.createNewFile();
// 将格式化后的字符串写入文件
Writer write = new OutputStreamWriter(new FileOutputStream(file),
"UTF-8");
write.write(content);
write.flush();
write.close();
} catch (Exception e) {
flag = false;
e.printStackTrace();
}
return flag;
}
/** * 模拟普通用户使用浏览器访问 */
public static class MyRequester extends OkHttpRequester {
String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36 Edg/102.0.1245.33";
// 每次发送请求前都会执行这个方法来构建请求
@Override
public Request.Builder createRequestBuilder(CrawlDatum crawlDatum) {
// 这里使用的是OkHttp中的Request.Builder
// 可以参考OkHttp的文档来修改请求头
return super.createRequestBuilder(crawlDatum)
.removeHeader("User-Agent") //移除默认的UserAgent
.addHeader("Referer", "https://item.jd.com/")
.addHeader("User-Agent", userAgent);
}
}
public static void main(String[] args) throws Exception {
// 实例化一个评论爬虫,并设置临时文件夹为crawl
JDCommentCrawler crawler = new JDCommentCrawler("crawl");
// 抓取1层
crawler.setRequester(new MyRequester()); // 设置请求头
crawler.start(1);
}
}
这样就可以爬到结果如果内容为空可以修改请求头。
利用mapreduce解析大量文件
这里是指对刚刚利用webcollector爬取到的html文件进行json解析,得到有用数据,python爬取的刚刚已经处理过。 这里的具体流程见下图:
这里给出代码mapreduce的框架固定也比较容易理解但它真正运行起来还是要看您的环境搭建,所以谨慎尝试。
import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import java.io.IOException; public class MRDataClean4JDComments { public static void main(String[] args) { try { Job job = Job.getInstance(); job.setJobName("MRDataClean4JDComments"); job.setJarByClass(MRDataClean4JDComments.class); job.setMapperClass(doMapper.class); // job.setReducerClass(doReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); Path in = new Path("hdfs://:8020/input/newHTML"); Path out = new Path("hdfs://hadoop102:8020/out/newOutput"); FileInputFormat.addInputPath(job, in); FileOutputFormat.setOutputPath(job, out); try { System.exit(job.waitForCompletion(true) ? 0 : 1); } catch (InterruptedException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } } public static class doMapper extends Mapper<Object, Text, Text, Text> { @Override protected void map(Object key, Text value, Mapper<Object, Text, Text, Text>.Context context) throws IOException, InterruptedException { String initJsonString = value.toString(); JSONObject initJson = JSONObject.parseObject(initJsonString); if (!initJsonString.contains("productCommentSummary") && !initJsonString.contains("comments")) { // 过滤掉不符合要求的,这里其实打开文件就会发现文件只有一行 return; } JSONObject myjson = initJson.getJSONObject("ten"); JSONObject productCommentSummary = myjson.getJSONObject("productCommentSummary"); String productId = productCommentSummary.get("productId").toString(); String commentCount = productCommentSummary.get("commentCount").toString(); String goodCount = productCommentSummary.get("goodCount").toString(); String generalCount = productCommentSummary.get("generalCount").toString(); String poorCount = productCommentSummary.get("poorCount").toString(); String goodRateShow = productCommentSummary.get("goodRateShow").toString(); String generalRateShow = productCommentSummary.get("generalRateShow").toString(); String poorRateShow = productCommentSummary.get("poorRateShow").toString(); /* comments 包括十条评论 */ JSONArray comments = myjson.getJSONArray("comments"); for (int i = 0; i < comments.size(); i++) { JSONObject comment = comments.getJSONObject(i); String guid = comment.getString("guid"); String content = comment.getString("content").replace('\n', ' '); String creationTime = comment.getString("creationTime"); String score = comment.getString("score"); String nickname = comment.getString("nickname"); String userLevelName = comment.getString("userLevelName"); String userClientShow = comment.getString("userClientShow"); String isMobile = comment.getString("isMobile"); String days = comment.getString("days"); StringBuilder sb = new StringBuilder(); sb.append(productId); sb.append("\t"); sb.append(commentCount); sb.append("\t"); sb.append(goodCount); sb.append("\t"); sb.append(generalCount); sb.append("\t"); sb.append(poorCount); sb.append("\t"); sb.append(goodRateShow); sb.append("\t"); sb.append(generalRateShow); sb.append("\t");