本篇文章是python爬虫系列的第三篇,介绍如何抓取京东商城商品评论信息,并对这些评论信息进行分析和可视化。下面是要抓取的商品信息,一款女士文胸。这个商品共有红色,黑色和肤色三种颜色, 70B到90D共18个尺寸,以及超过700条的购买评论。
京东商品评论信息是由JS动态加载的,所以直接抓取商品详情页的URL并不能获得商品评论的信息。因此我们需要先找到存放商品评论信息的文件。这里我们使用Chrome浏览器里的开发者工具进行查找。
具体方法是在商品详情页点击鼠标右键,选择检查,在弹出的开发者工具界面中选择Network,设置为禁用缓存(Disable cache)和只查看JS文件。然后刷新页面。页面加载完成后向下滚动鼠标找到商品评价部分,等商品评价信息显示出来后,在下面Network界面的左侧筛选框中输入productPageComments,这时下面的加载记录中只有一条信息,这里包含的就是商品详情页的商品评论信息。点击这条信息,在右侧的Preview界面中可以看到其中包含了当前页面中的评论信息。(抓取价格信息输入prices)。
复制这条信息,并把URL地址放在浏览器中打开,里面包含了当前页的商品评论信息。这就是我们要抓取的URL地址。https://club.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv100&productId=10001234327&score=0&sortType=5&page=0&pageSize=10
仔细观察这条URL地址可以发现,其中productId=10001234327是当前商品的商品ID。与商品详情页URL中的ID一致。而page=0是页码。如果我们要获取这个商品的所有评论,只需要更改page后面的数字即可。
在获得了商品评论的真实地址以及URL地址的规律后,我们开始使用python抓取这件商品的700+条评论信息。并对这些信息进行处理和分析。
开始前的准备工作
在开始抓取之前先要导入各种库文件,这里我们分别介绍下需要导入的每个库文件的名称以及在数据抓取和分析中的作用。requests用于进行页面抓取,time用于设置抓取过程中的Sleep时间,random用于生产随机数,这里的作用是将抓取页面的顺序打乱,re用于在抓取后的页面代码中提取需要的信息,numpy用于常规的指标计算,pandas用于进行数据汇总和透视分析,matplotlib用于绘制各站图表,jieba用于对评论内容进行分词和关键词提取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#导入requests库(请求和页面抓取) import requests #导入time库(设置抓取Sleep时间) import time #导入random库(生成乱序随机数) import random #导入正则库(从页面代码中提取信息) import re #导入数值计算库(常规计算) import numpy as np #导入科学计算库(拼表及各种分析汇总) import pandas as pd #导入绘制图表库(数据可视化) import matplotlib.pyplot as plt #导入结巴分词库(分词) import jieba as jb #导入结巴分词(关键词提取) import jieba.analyse |
将爬虫伪装成浏览器
导入完库文件后,还不能直接进行抓取,因为这样很容易被封。我们还需要对爬虫进行伪装,是爬虫看起来更像是来自浏览器的访问。这里主要的两个工作是设置请求中的头文件信息以及设置Cookie的内容。
头文件信息很容易找到,在Chrome的开发者工具中选择Network,刷新页面后选择Headers就可以看到本次访问的头文件信息,里面包含了一些浏览器的技术参数和引荐来源信息。将这些信息直接添加到代码中就可以,这里我们将头部信息保存在headers中。
1
2
3
4
5
6
7
|
#设置请求中头文件的信息 headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11' , 'Accept' : 'text/html;q=0.9,*/*;q=0.8' , 'Accept-Charset' : 'ISO-8859-1,utf-8;q=0.7,*;q=0.3' , 'Connection' : 'close' , 'Referer' : 'https://www.jd.com/' } |
在查看头文件信息的旁边还有一个Cookies标签,点击进去就是本次访问的Cookies信息。这里的Cookies信息与前面头文件中的Cookie信息一致,不过这里更加清晰。把Request Cookies信息复制到代码中即可,这里我们将Request Cookies信息保存在Cookie中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#设置Cookie的内容 cookie = { 'TrackID' : '1_VWwvLYiy1FUr7wSr6HHmHhadG8d1-Qv-TVaw8JwcFG4EksqyLyx1SO7O06_Y_XUCyQMksp3RVb2ezA' , '__jda' : '122270672.1507607632.1423495705.1479785414.1479794553.92' , '__jdb' : '122270672.1.1507607632|92.1479794553' , '__jdc' : '122270672' , '__jdu' : '1507607632' , '__jdv' : '122270672|direct|-|none|-|1478747025001' , 'areaId' : '1' , 'cn' : '0' , 'ipLoc-djd' : '1-72-2799-0' , 'ipLocation' : '%u5317%u4EAC' , 'mx' : '0_X' , 'rkv' : 'V0800' , 'user-key' : '216123d5-4ed3-47b0-9289-12345' , 'xtest' : '4657.553.d9798cdf31c02d86b8b81cc119d94836.b7a782741f667201b54880c925faec4b' } |
抓取商品评论信息
设置完请求的头文件和Cookie信息后,我们开始抓取京东商品评论的信息。前面分析URL的时候说过,URL中包含两个重要的信息,一个是商品ID,另一个是页码。这里我们只抓取一个商品的评论信息,因此商品ID不需要更改。但这个商品的评论有700+条,也就是有近80页需要抓取,因此页码不是一个固定值,需要在0-80之间变化。这里我们将URL分成两部分,通过随机生成页码然后拼接URL的方式进行抓取。
1
2
3
4
5
6
|
#设置URL的第一部分 url1 = 'https://sclub.jd.com/comment/productPageComments.action?productId=10001234327&score=0&sortType=3&page=' #设置URL的第二部分 url2 = '&pageSize=10&callback=fetchJSON_comment98vv41127' #乱序输出0-80的唯一随机数 ran_num = random.sample( range ( 80 ), 80 ) |
为了使抓取过程看起来更加随机,我们没有从第1页一直抓取到第80页。而是使用random生成0-80的唯一随机数,也就是要抓取的页码编号。然后再将页码编号与两部分URL进行拼接。这里我们只知道商品有700+的评论,但并不知道具体数字,所以抓取范围定位从0-80页。
下面是具体的抓取过程,使用for循环每次从0-80的随机数中找一个生成页码编号,与两部分的URL进行拼接。生成要抓取的URL地址并与前面设置好的头文件信息和Cookie信息一起发送请求获取页面信息。将获取到的页面信息进行汇总。每次请求间休息5秒针,避免过于频繁的请求导致返回空值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#拼接URL并乱序循环抓取页面 for i in ran_num: a = ran_num[ 0 ] if i = = a: i = str (i) url = (url1 + i + url2) r = requests.get(url = url,headers = headers,cookies = cookie) html = r.content else : i = str (i) url = (url1 + i + url2) r = requests.get(url = url,headers = headers,cookies = cookie) html2 = r.content html = html + html2 time.sleep( 5 ) print ( "当前抓取页面:" ,url, "状态:" ,r) |
在抓取的过程中输入每一步抓取的页面URL以及状态。通过下面的截图可以看到,在page参数后面的页码是随机生成的并不连续。
抓取完80个页面后,我们还需要对页面进行编码。完成编码后就可以看到其中所包含的中文评论信息了。后面大部分苦逼的工作就是要对这些评论信息进行不断提取和反复的清洗。
1
2
|
#对抓取的页面进行编码 html = str (html, encoding = "GBK" ) |
这里建议将抓取完的数据存储在本地,后续工作可以直接从本地打开文件进行清洗和分析工作。避免每次都要重新抓取数据。这里我们将数据保存在桌面的page.txt文件中。
1
2
3
4
|
#将编码后的页面输出为txt文本存储 file = open ( "c:\\Users \\Desktop\\page.txt" , "w" ) file .write(html) file .close() |
读取文件也比较简单,直接open加read函数就可以完成了。
1
2
|
#读取存储的txt文本文件 html = open ( 'c:\\Users\\ Desktop\\page.txt' , 'r' ).read() |
提取信息并进行数据清洗
京东的商品评论中包含了很多有用的信息,我们需要将这些信息从页面代码中提取出来,整理成数据表以便进行后续的分析工作。这里应该就是整个过程中最苦逼的数据提取和清洗工作了。我们使用正则对每个字段进行提取。对于特殊的字段在通过替换等方式进行提取和清洗。
下面是提取的第一个字段userClient,也就是用户发布评论时所使用的设备类型,这类的字段提取还比较简单,一行代码搞定。查看一下提取出来的字段还比较干净。使用同样的方法我们分别提取了以下这些字段的内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#使用正则提取userClient字段信息 userClient = re.findall(r ',"usefulVoteCount".*?,"userClientShow":(.*?),' ,html) #使用正则提取userLevel字段信息 userLevel = re.findall(r '"referenceImage".*?,"userLevelName":(.*?),' ,html) #使用正则提取productColor字段信息 productColor = re.findall(r '"creationTime".*?,"productColor":(.*?),' ,html) #使用正则提取recommend字段信息 recommend = re.findall(r '"creationTime".*?,"recommend":(.*?),' ,html) #使用正则提取nickname字段信息 nickname = re.findall(r '"creationTime".*?,"nickname":(.*?),' ,html) #使用正则提取userProvince字段信息 userProvince = re.findall(r '"referenceImage".*?,"userProvince":(.*?),' ,html) #使用正则提取usefulVoteCount字段信息 usefulVoteCount = re.findall(r '"referenceImage".*?,"usefulVoteCount":(.*?),' ,html) #使用正则提取days字段信息 days = re.findall(r '"usefulVoteCount".*?,"days":(.*?)}' ,html) #使用正则提取score字段信息 score = re.findall(r '"referenceImage".*?,"score":(.*?),' ,html)< / pre> |
还有一些字段比较负责,无法通过正则一次提取出来,比如isMobile字段,有些值的后面还有大括号。这就需要进一步的提取和清洗工作。
1
2
|
#使用正则提取isMobile字段信息 isMobile = re.findall(r '"usefulVoteCount".*?,"isMobile":(.*?),' ,html) |
使用for循环配合替换功能将字段中所有的}替换为空。替换完成后字段看起来干净多了。
1
2
3
4
5
|
#替换掉最后的} mobile = [] for m in isMobile: n = m.replace( '}' ,'') mobile.append(n) |
productSize字段中包含了胸围和杯罩两类信息,为了获得独立的杯罩信息需要进行二次提取,将杯罩信息单独保存出来。
1
2
|
#使用正则提取productSize字段信息 productSize = re.findall(r '"creationTime".*?,"productSize":(.*?),' ,html) |
使用for循环将productSize中的第三个字符杯罩信息提取出来,并保持在cup字段中。
1
2
3
4
5
|
#提取杯罩信息 cup = [] for s in productSize: s1 = s[ 3 ] cup.append(s1) |
创建评论的日期信息仅依靠正则提取出来的信息还是比较乱,无法直接使用。因此也需要进行二次提取。下面是使用正则提取出的结果。
1
2
|
#使用正则提取时间字段信息 creationTime1 = re.findall(r '"creationTime":(.*?),"referenceName' ,html) |
日期和时间信息处于前20个字符,在二次提取中根据这个规律直接提起每个条目的前20个字符即可。将日期和时间单独保存为creationTime。
1
2
3
4
5
|
#提取日期和时间 creationTime = [] for d in creationTime1: date = d[ 1 : 20 ] creationTime.append(date) |
在上一步日期和时间的基础上,我们再进一步提取出单独的小时信息,方法与前面类似,提取日期时间中的第11和12个字符,就是小时的信息。提取完保存在hour字段以便后续的分析和汇总工作。
1
2
3
4
5
|
#提取小时信息 hour = [] for h in creationTime: date = h[ 10 : 13 ] hour.append(date) |
最后要提取的是评论内容信息,页面代码中包含图片的评论信息是重复的,因此在使用正则提取完后还需要对评论信息进行去重。
1
2
|
#使用正则提取评论信息 content = re.findall(r '"guid".*?,"content":(.*?),' ,html) |
使用if进行判断,排除掉所有包含图片的评论信息,已达到评论去重的目的。
1
2
3
4
5
|
#对提取的评论信息进行去重 content_1 = [] for i in content: if not "img" in i: content_1.append(i) |
完成所有字段信息的提取和清洗后,将这些字段组合在一起生成京东商品评论数据汇总表。下面是创建数据表的代码。数据表生成后还不能马上使用,需要对字段进行格式设置,例如时间和日期字段和一些包含数值的字段。具体的字段和格式设置依据后续的分析过程和目的。这里我们将creationTime设置为时间格式,并设置为数据表的索引列。将days字段设置为数值格式。
1
2
3
4
5
6
7
8
9
10
|
#将前面提取的各字段信息汇总为table数据表,以便后面分析 table = pd.DataFrame({ 'creationTime' :creationTime, 'hour' :hour, 'nickname' :nickname, 'productColor' :productColor, 'productSize' :productSize, 'cup' :cup, 'recommend' :recommend, 'mobile' :mobile, 'userClient' :userClient, 'userLevel' :userLevel, 'userProvince' :userProvince, 'usefulVoteCount' :usefulVoteCount, 'content_1' :content_1, 'days' :days, 'score' :score}) #将creationTime字段更改为时间格式 table[ 'creationTime' ] = pd.to_datetime(table[ 'creationTime' ]) #设置creationTime字段为索引列 table = table.set_index( 'creationTime' ) #设置days字段为数值格式 table[ 'days' ] = table[ 'days' ].astype(np.int64) #查看整理完的数据表 table.head() |
这里建议再次保存清洗和预处理完的数据表。我们这里将数据表保存为csv格式。到了这一步可以选择在Excel中完成后续的数据分析和可视化过程,也可以继续在python中完成。我们这里选择继续在python中完成后续的数据分析和可视化工作。
1
2
|
#保存table数据表 table.to_csv( 'jd_table.csv' ) |
数据分析及可视化
分月评论数据变化趋势
首先查看京东商品评论的时间变化趋势情况,大部分用户在购买商品后会在10天以内进行评论,因此我们可以近似的认为在一个月的时间维度中评论时间的变化趋势代表了用户购买商品的变化趋势。
按月的维度对数据表进行汇总,并提取每个月的nickname的数量。下面是具体的代码和分月数据。
1
2
3
4
|
#对数据表按月进行汇总并生成新的月度汇总数据表 table_month = table.resample( 'M' ,how = len ) #提取按月汇总的nickname month = table_month[ 'nickname' ] |
数据范围从2015年11月到2016年11月。使用柱状图对分月数据进行可视化。从图表中可以看到2016年6月是评论的高峰,也可以近似的认为这个时间段是用户购买该商品的高峰(6月18日是京东店庆日)。排除2016年6月和不完整的11月数据,整齐趋势中冬季评论量较低,夏季较高。这是由于该商品的季节属性导致的,超薄胸罩更适合夏天佩戴(这个属性我们是在用户的评论中发现的,在京东的商品介绍中并不明显,只在标题中以”薄杯”说明)。
1
2
3
4
5
6
7
8
9
10
11
|
#绘制分月评论数量变化趋势图 plt.rc( 'font' , family = 'STXihei' , size = 9 ) a = np.array([ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 ]) plt.bar([ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 ],month,color = '#99CC01' ,alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.xlabel( '月份' ) plt.ylabel( '评论数量' ) plt.title( '分月评论数量变化趋势' ) plt.legend([ '评论数量' ], loc = 'upper right' ) plt.grid(color = '#95a5a6' ,linestyle = '--' , linewidth = 1 ,axis = 'y' ,alpha = 0.4 ) plt.xticks(a,( '15-11' , '12' , '16-01' , '02' , '03' , '04' , '05' , '06' , '07' , '08' , '09' , '10' , '11' )) plt.show() |
通过筛选将数据表分为使用移动设备和未使用移动设备两个表格,再分别查看和对比评论变化趋势。
1
2
3
4
5
6
7
8
9
10
11
12
|
#在table表中筛选使用移动设备的条目并创建新表 mobile_t = table.loc[table[ "mobile" ] = = "true" ] #在table中筛选没有使用移动设备的条目并创建新表 mobile_f = table.loc[table[ "mobile" ] = = "false" ] #按月汇总使用移动设备的数据 mobile_t_m = mobile_t.resample( 'M' ,how = len ) #按月汇总不使用移动设备的数据 mobile_f_m = mobile_f.resample( 'M' ,how = len ) #提取使用移动设备的按月汇总nickname mobile_y = mobile_t_m[ 'nickname' ] #提取没有使用移动设备的按月汇总nickname mobile_n = mobile_f_m[ 'nickname' ] |
从结果中可以看出使用移动设备进行评论的用户在所有的时间段中都要明显高于使用PC的用户。
1
2
3
4
5
6
7
8
9
10
|
#绘制PC与移动设备评论数量变化趋势图 plt.subplot( 2 , 1 , 1 ) plt.plot(mobile_y, 'go' ,mobile_y, 'g-' ,color = '#99CC01' ,linewidth = 3 ,markeredgewidth = 3 ,markeredgecolor = '#99CC01' ,alpha = 0.8 ) plt.ylabel( '移动设备评论数量' ) plt.title( 'PC与移动设备评论数量变化趋势' ) plt.subplot( 2 , 1 , 2 ) plt.plot(mobile_n, 'go' ,mobile_n, 'g-' ,color = '#99CC01' ,linewidth = 3 ,markeredgewidth = 3 ,markeredgecolor = '#99CC01' ,alpha = 0.8 ) plt.xlabel( '月份' ) plt.ylabel( 'PC评论数量' ) plt.show() |
24小时评论数量变化趋势
按小时维度对评论数据进行汇总,查看用户在24小时中的评论变化趋势。这里需要说明的是24小时趋势只能反映用户登录京东商城的趋势,并不能近似推断用户购买商品的时间趋势。
1
2
|
#按24小时分别对table表中的nickname进行计数 hour_group = table.groupby( 'hour' )[ 'nickname' ].agg( len ) |
从24小时评论趋势图来看,发布商品评论的趋势与作息时间一致,并且每日的闲暇时间是发布评论的高峰。如早上的8点,中午的12点和晚上的22点,是一天24小时中的三个评论高峰点。
1
2
3
4
5
6
7
8
9
10
11
|
#汇总24小时评论数量变化趋势图 plt.rc( 'font' , family = 'STXihei' , size = 9 ) a = np.array([ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 ]) plt.bar([ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 ],hour_group,color = '#99CC01' ,alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.xlabel( '24小时' ) plt.ylabel( '评论数量' ) plt.title( '24小时评论数量变化趋势' ) plt.legend([ '评论数量' ], loc = 'upper right' ) plt.grid(color = '#95a5a6' ,linestyle = '--' , linewidth = 1 ,axis = 'y' ,alpha = 0.4 ) plt.xticks(a,( '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '10' , '11' , '12' '13' , '14' , '15' , '16' , '17' , '18' , '19' , '20' , '21' , '22' , '23' )) plt.show() |
将24小时的评论数量分为移动设备和未使用移动设备,查看并对比这两者的变化趋势情况。
1
2
3
4
|
#在使用移动设备的表中按24小时对nickname进行计数 mobile_y_h = mobile_t.groupby( 'hour' )[ 'nickname' ].agg( len ) #在没有使用移动设备的表中按24小时对nickname进行计算 mobile_n_h = mobile_f.groupby( 'hour' )[ 'nickname' ].agg( len ) |
移动设备的评论数量在24小时中的各个时间段都要高于PC的评论数量,并且在晚间更加活跃,持续时间高于PC端。这里我们产生了一个疑问,在一天中的工作时间段中,大部分用户都会在电脑旁,但为什么这些时间段里移动设备的评论数量也要高于PC端呢?这是否与胸罩这个产品的私密性有关联。用户不希望别人看到自己购买的商品或评论的内容,所以选择使用移动设备进行评论?
#汇总PC与移动设备24小时评论数量变化趋势 plt.subplot(2, 1, 1) plt.plot(mobile_y_h,'go',mobile_y_h,'g-',color='#99CC01',linewidth=3,markeredgewidth=3,markeredgecolor='#99CC01',alpha=0.8) plt.ylabel('移动设备评论数量') plt.title('PC与移动设备24小时评论数量变化趋势') plt.subplot(2, 1, 2) plt.plot(mobile_n_h,'go',mobile_n_h,'g-',color='#99CC01',linewidth=3,markeredgewidth=3,markeredgecolor='#99CC01',alpha=0.8) plt.xlabel('24小时') plt.ylabel('PC评论数量') plt.show()
用户客户端分布情况
前面的分析中,我们看到使用移动设备进行评论的用户要远高于PC端的用户,下面我们对用户所使用的设备分布情况进行统计。首先在数据表中按用户设备(userClient)对nickname字段进行计数汇总。
1
2
|
#在table表中按userClient对数据进行汇总 userClient_group = table.groupby( 'userClient' )[ 'nickname' ].agg( len ) |
从用户客户端分布情况来看,移动端的设备占大多数,其中使用iphone的用户要高于Android用户。由于微信购物和QQ购物单独被分了出来,无法确定设备,因此单独进行对比。使用微信购物渠道的用户要高于QQ购物。
1
2
3
4
5
6
7
8
9
10
11
12
|
#汇总用户客户端分布情况 plt.rc( 'font' , family = 'STXihei' , size = 9 ) a = np.array([ 1 , 2 , 3 , 4 , 5 , 6 ]) plt.bar([ 1 , 2 , 3 , 4 , 5 , 6 ],userClient_group,color = '#99CC01' ,alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.xlabel( '客户端分布' ) plt.ylabel( '评论数量' ) plt.title( '用户客户端分布情况' ) plt.legend([ '评论数量' ], loc = 'upper right' ) plt.grid(color = '#95a5a6' ,linestyle = '--' , linewidth = 1 ,axis = 'y' ,alpha = 0.4 ) plt.ylim( 0 , 300 ) plt.xticks(a,( 'PC' , 'Android' , 'iPad' , 'iPhone' , '微信购物' , 'QQ购物' )) plt.show() |
购买后评论天数分布
在购买后评论天数方面,我们将用户发布评论与购买的时间间隔分为7组,分别为购买后1-5天内,5-10天内,10-15天内,15-20天内,20-25天内,25-30天内,以及大于30天。然后统计并对比用户在不同时间区间内发布评论的数量情况。
1
2
3
4
5
6
|
#设置分组条件,并对table表中的days字段进行分组 bins = [ 0 , 5 , 10 , 15 , 20 , 25 , 30 , 92 ] day_group = [ '5天' , '10天' , '15天' , '20天' , '25天' , '30天' , '大于30天' ] table[ 'day_group' ] = pd.cut(table[ 'days' ], bins, labels = day_group) #按新设置的分组对数据进行汇总 days_group = table.groupby( 'day_group' )[ 'nickname' ].agg( len ) |
从图表中看出,购买后5天以内是用户发布评论的高峰,也就我们之前推测评论时间趋势近似于购买时间的依据。随着时间的增加评论数量逐渐下降。
1
2
3
4
5
6
7
8
9
10
11
12
|
#绘制用户购买后评论天数分布图 plt.rc( 'font' , family = 'STXihei' , size = 9 ) a = np.array([ 1 , 2 , 3 , 4 , 5 , 6 , 7 ]) plt.bar([ 1 , 2 , 3 , 4 , 5 , 6 , 7 ],days_group,color = '#99CC01' ,alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.xlabel( '购买后天数' ) plt.ylabel( '发布评论数量' ) plt.title( '购买后评论天数分布' ) plt.legend([ '评论数量' ], loc = 'upper right' ) plt.grid(color = '#95a5a6' ,linestyle = '--' , linewidth = 1 ,axis = 'y' ,alpha = 0.4 ) plt.ylim( 0 , 300 ) plt.xticks(a,( '5天' , '10天' , '15天' , '20天' , '25天' , '30天' , '大于30天' )) plt.show() |
商品评分分布情况
京东商城对商品按5星评分划分为好评,中评和差评三个等级。我们这里来看下用户5星评分的分布情况。在数据表中score字段中的值表示了用户对胸罩产品的打分情况。我们按打分情况对数据进行汇总。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#在table表中按score对数据进行汇总 score_group = table.groupby( 'score' )[ 'nickname' ].agg( len ) #绘制用户评分分布情况图 plt.rc( 'font' , family = 'STXihei' , size = 9 ) a = np.array([ 1 , 2 , 3 , 4 , 5 ]) plt.bar([ 1 , 2 , 3 , 4 , 5 ],score_group,color = '#99CC01' ,alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.xlabel( '评分分布' ) plt.ylabel( '评论数量' ) plt.title( '用户评分分布情况' ) plt.legend([ '评论数量' ], loc = 'best' ) plt.grid(color = '#95a5a6' ,linestyle = '--' , linewidth = 1 ,axis = 'y' ,alpha = 0.4 ) plt.ylim( 0 , 700 ) plt.xticks(a,( '1星' , '2星' , '3星' , '4星' , '5星' )) plt.show() |
从图表中可以看出,大部分用户对商品的评分是5星。4星以下的几乎没有。但从另一个维度来看,在用户对最有用评论的投票(usefulVoteCount)中得票最多的是一个1星的评论。
用户胸罩尺码分布情况
在胸罩的尺寸方面包含两个信息,一个是胸围尺寸,另一个是罩杯。我们在前面的清洗过程中对杯罩创建了单独的字段。下面只对这个字段进行汇总统计。
1
2
|
#在table 表中按cup对数据进行汇总 cup_group = table.groupby( 'cup' )[ 'nickname' ].agg( len ) |
从图表中可以看出,评论用户中最多的是B杯罩,其次为C杯罩,D和E的用户数量较少。
1
2
3
4
5
6
7
8
9
10
11
12
|
#绘制用户胸罩尺码分布图 plt.rc( 'font' , family = 'STXihei' , size = 9 ) a = np.array([ 1 , 2 , 3 , 4 ]) plt.bar([ 1 , 2 , 3 , 4 ],cup_group,color = '#99CC01' ,alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.xlabel( '尺码' ) plt.ylabel( '评论数量' ) plt.title( '用户胸罩尺码分布情况' ) plt.legend([ '评论数量' ], loc = 'upper right' ) plt.grid(color = '#95a5a6' ,linestyle = '--' , linewidth = 1 ,axis = 'y' ,alpha = 0.4 ) plt.ylim( 0 , 350 ) plt.xticks(a,( 'B' , 'C' , 'D' , 'E' )) plt.show() |
胸罩颜色偏好分布
这款胸罩共分为三个颜色,红色,肤色和黑色。我们按颜色对评论数据进行汇总,查看用户对不同胸罩颜色的偏好情况。
1
2
|
#在table表中按productColor对数据进行汇总 color_group = table.groupby( 'productColor' )[ 'nickname' ].agg( len ) |
从不同颜色的评论数量上来看,大部分用户购买的是肤色,购买红色和黑色的用户数量明显少于肤色。
1
2
3
4
5
6
7
8
9
10
11
12
|
#绘制用户颜色选择分布图 plt.rc( 'font' , family = 'STXihei' , size = 9 ) a = np.array([ 1 , 2 , 3 ]) plt.bar([ 1 , 2 , 3 ],color_group,color = '#99CC01' ,alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.xlabel( '颜色分布' ) plt.ylabel( '评论数量' ) plt.title( '用户颜色选择分布' ) plt.legend([ '评论数量' ], loc = 'upper right' ) plt.grid(color = '#95a5a6' ,linestyle = '--' , linewidth = 1 ,axis = 'y' ,alpha = 0.4 ) plt.ylim( 0 , 600 ) plt.xticks(a,( '红色' , '肤色' , '黑色' )) plt.show() |
不同尺码用户对胸罩颜色偏好对比
在前面杯罩尺寸和颜色偏好的基础上,我们将两维度进行交叉分析,查看并对比不同杯罩尺码用户在颜色选择上是有规律或明显差异。这里使用数据透视表,将杯罩尺寸设置为行,颜色设置为列,对nickname进行计数。
1
2
|
#使用数据透视表对胸罩尺码和颜色进行交叉分析 color_size = pd.pivot_table(table,index = [ "cup" ],values = [ "nickname" ],columns = [ "productColor" ],aggfunc = len ,fill_value = 0 ) |
从数据透视表中分别提取出不同尺寸杯罩用户购买的颜色数据。
1
2
3
4
5
|
#提取B,C,D,E尺码的颜色分布 b = color_size.ix[ 'B' ] c = color_size.ix[ 'C' ] d = color_size.ix[ 'D' ] e = color_size.ix[ 'E' ] |
将不同杯罩尺寸用户对颜色的选择分布绘制成四个图表,进行对比和分析。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#汇总不同胸罩尺码颜色分布图 plt.rc( 'font' , family = 'STXihei' , size = 9 ) plt.subplot( 2 , 2 , 1 ) plt.bar([ 1 , 2 , 3 ],b,color = [ "#EE9788" , "#E2D4C9" , "#151419" ],alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.title( 'B Cup' ) plt.xticks(a,( '红色' , '肤色' , '黑色' )) plt.subplot( 2 , 2 , 2 ) plt.bar([ 1 , 2 , 3 ],c,color = [ "#EE9788" , "#E2D4C9" , "#151419" ],alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.title( 'C Cup' ) plt.ylim( 0 , 200 ) plt.xticks(a,( '红色' , '肤色' , '黑色' )) plt.subplot( 2 , 2 , 3 ) plt.bar([ 1 , 2 , 3 ],d,color = [ "#EE9788" , "#E2D4C9" , "#151419" ],alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.title( 'D Cup' ) plt.xticks(a,( '红色' , '肤色' , '黑色' )) plt.subplot( 2 , 2 , 4 ) plt.bar([ 1 , 2 , 3 ],e,color = [ "#EE9788" , "#E2D4C9" , "#151419" ],alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.title( 'E,Cup' ) plt.xticks(a,( '红色' , '肤色' , '黑色' )) plt.show() |
在下面的图表中,B,C,D,E四个杯罩的用户选择肤色的数量都要高于另外两种颜色。整体差别并不明显。如果非要说有什么差别的话,C杯罩用户更偏好红色?D杯罩更喜欢黑色?这个结论明显站不住脚。但有一点可以说,虽然黑色显瘦但E杯罩的用户中没有人选择黑色。
D&E Cup用户城市分布情况
最后我们再看看下D杯罩和E杯罩用户的城市分布情况,在数据表中并不是所有的评论都有城市信息。因此按城市统计出来的数据可能并不准确,仅供参考。
首先从数据表中筛选出cup值为D和E的数据,并保存在新的数据表中。
1
2
|
#从table表中提取cup尺寸为D或E的数据条目并创建新表 table_big = table.loc[(table[ "cup" ] = = "D" )|(table[ "cup" ] = = "E" )] |
在新的数据表中按用户所在城市(userProvince)进行汇总。查看不同城市D和E杯罩的数量。
1
2
|
#按城市对数据进行计数汇总 city_group = table_big.groupby( 'userProvince' )[ 'nickname' ].agg( len ) |
将汇总结果绘制为图表,从图表来看,数量最多的为未知城市,排除未知城市,北京和广东的数量遥遥领先,其次为四川和河南。
1
2
3
4
5
6
7
8
9
10
11
|
#汇总D和E Cup城市分布图 plt.rc( 'font' , family = 'STXihei' , size = 9 ) a = np.array([ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 ]) plt.barh([ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 ],city_group,color = '#99CC01' ,alpha = 0.8 ,align = 'center' ,edgecolor = 'white' ) plt.xlabel( '评论数量' ) plt.ylabel( '城市' ) plt.title( 'D Cup和E Cup城市分布' ) plt.legend([ '评论数量' ], loc = 'upper right' ) plt.grid(color = '#95a5a6' ,linestyle = '--' , linewidth = 1 ,axis = 'x' ,alpha = 0.4 ) plt.yticks(a,( '未知' , '上海' , '云南' , '内蒙古' , '北京' , '四川' , '天津' , '宁夏' , '安徽' , '山东' , '广东' , '江西' , '江苏' , '江西' , '河北' , '河南' , '浙江' , '湖北' , '湖南' , '甘肃' , '福建' , '辽宁' , '重庆' , '青海' , '香港' )) plt.show() |
胸罩评论内容语义分析
前面我们分别对数据表中的字段进行了统计和分析,文章最后我们对商品的评论内容进行语义分析,看看大家在这700+条评论中都在说些什么。
好好先生购买比例
在人工查看了一些评论内容后,我们发现一些有意思的信息。有一部分评论是老公或男朋友发的,这说明一些好好先生会帮老婆或女友购买胸罩。那么这部分用户的比例有多少呢?
我们把评论中包含有关键词“老婆”和“女朋友”的评论单独保存在出来。
1
2
3
4
5
|
#筛选包含”老婆”和”女朋友”的评论 content_2 = [] for i in content_1: if "老婆" in i or "女朋友" in i: content_2.append(i) |
查看这些包含关键词的评论内容,确实是老公和男朋友来购买胸罩并且发布的评论。
1
2
|
#查看评论内容 content_2 |
经过计算,在这款胸罩产品的评论中,由老公或男朋友购买的比例仅为2.35%。
1
2
3
|
#计算老公或男朋友购买胸罩的比例 len (content_2) / len (content_1) * 100 2.3545706371191137 |
商品评论关键词分析
回归到商品评论分析,我们使用结巴分词对所有胸罩的评论信息进行了分词,并提取了权重最高的关键词列表。
1
2
3
4
5
6
7
8
|
#文本数据格式转换 word_str = ''.join(content_1) #提取文字关键词 word_rank = jieba.analyse.extract_tags(word_str, topK = 20 , withWeight = True , allowPOS = ()) #转化为数据表 word_rank = pd.DataFrame(word_rank,columns = [ 'word' , 'rank' ]) #查看关键词及权重 word_rank.sort( 'rank' ,ascending = False ) |
从高权重关键词列表来看,用户评论以正面信息为主,”不错”,”舒服”,”喜欢”等主观感受的正面评论权重较高。
结语
本篇文章我们从商品评论信息的抓取,清洗到分析和数据可视化实现了一个完整的闭环。整个过程中数据的清洗和预处理是最为复杂也是耗时最多的工作。由于抓取的数据量较少,只有700+条数据。因此里面的一些结论可能没有代表性,结论也未必准确,仅供参考。
原文链接:http://bluewhale.cc/2016-12-15/use-python-to-capture-and-analyze-jingdong-product-reviews-data.html