前言
本例子利用Python爬虫爬取cnmo网站2020年至2022年11月的手机数据,并对其进行数据处理和可视化分析。
1、效果展示
如下如最终我们将得到一个包含手机型号、颜色、内存、价格及购买链接等的excel表以及dataframe和可视化效果。
2、需要用到的库
import pandas as pd
import numpy as np
import requests
from bs4 import BeautifulSoup
from lxml import etree
import matplotlib.pyplot as plt
第一个库是数据分析处理的库
第二个是数组计算
第三个是爬虫请求库
- 五个是网页解析
第六个是图形可视化库
3、原理分析
这个大部分原理都很简单。
- 观察列表页的链接地址的规律,并运用dataframe存储每一款手机的参数链接以及手机型号等。
2、获取dataframe中的每个手机的参数链接
3、获取每一款手机的所有颜色和内存等所有组合的数据,并存为同一个dataframe,这里运行非常久,毕竟先读取页面获取每个手机的参数链接,在读取每一个参数链接爬取里面的参数,存为dataframe,一来一回很耗内存,所以后面最好把dataframe存为csv等格式,方便处理数据。
4、将获得的dataframe存为csv方便后期处理数据和可视化
5、最后就是读取csv的数据了,用了pandas中的一些方法,读出来的也是dataframe格式,通过pandas方法,进行转换格式,统计计算,最后通过matloplit可视化。
4、代码实现
先获取参数页和标题也就是下图网页中的内容
通过网页定位可以找到手机型号和参数链接的网页代码,方便我们后面用beautifulsoup和etree进行解析定位爬取手机型号和发布时间、参数链接,存为dataframe
具体代码如下:
url='https://product.cnmo.com/all/product_t1_p{}.html'
def lianjie():
colum_name = ['标题', '时间', '链接']
data_list = []
for i in range(1,20):#翻页,20页
url_lj=url.format(i)
header = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'}
res=requests.get(url_lj,header).text
soup=BeautifulSoup(res,'lxml')
for b in soup.find('ul',class_="all-con-con-ul cf").find_all('li'):
temp = []
title=b.find('a',class_="name").text
time=b.find('p',class_="red").find('span').text.replace(' ','').replace('\n','')
url_canshu='https:'+b.find('div',class_="info").select('a')[1].get('href')
temp.append(title)
temp.append(time)
temp.append(url_canshu)
data_list.append(temp)
data = pd.DataFrame(data_list, columns=colum_name)
return data
如上图获得手机型号和发布时间等数据,存为dataframe,但是我们还要接着存其他的参数信息,所以定义一个函数先创建要存储的dataframe的列名称。具体代码如下:
def chuli(data):
data1=data.dropna(thresh=2)
data1.drop_duplicates(subset=['标题'])
data1['nian']=(data1['时间'].str.split('年',expand=True)[0]).astype('int')
data1['yue'] = ((data1['时间'].str)[-3:-1]).astype('int')
data1=data1.loc[((data1['nian']>=2020)&(data1['nian']<2023)),:]
data1=data1.loc[~((data1['nian']==2022)&(data1['yue']==12)),:]
data1['品牌']=''#列号5
data1['型号']=''
data1['尺寸']=''
data1['价格']=''
data1['手机内存'] = data1['标题'].str.split('(', expand=True)[1]
data1['手机内存']=data1['手机内存'].str.split(')', expand=True)[0]
data1['cpu']=''
data1['相机像素']=''
data1['颜色']=''
return data1
接下来的话,就是把dataframe里面的每一个参数链接提取出来,通过request请求获得该链接里面的手机品牌、内存、颜色、像素、尺寸等参数信息,并且存为同一个dataframe,这里面用到了beautifulsoup和etree去解析定位所要爬的参数,最后将dataframe存储为csv格式,如下图:
实现代码如下:
def canshu(data1):
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'}
for j in range(data1.shape[0]):
r=requests.get(data1.iloc[j,2],headers=headers).text
b=etree.HTML(r)
c=BeautifulSoup(r,'lxml')
data1.iloc[j,5]=b.xpath('/html/body/div[5]/div[2]/a[3]/text()')
data1.iloc[j,6]=b.xpath('/html/body/div[5]/div[4]/div[1]/div[1]/div[1]/h2/b/a/text()')
try:
data1.iloc[j,7]=c.find('div',class_="cell-con-ul").find_all('ul')[2].find('p',paramname="屏幕尺寸").text.replace('\n','').replace(' ','').replace('\r','')
data1.iloc[j,8]=b.xpath('/html/body/div[5]/div[4]/div[1]/div[1]/div[2]/span[2]/text()')
data1.iloc[j,10]=c.find('div',class_="cell-con-ul").find_all('ul')[3].find('p',paramname="CPU型号").text.replace('\n','').replace(' ','').replace('\r','')
data1.iloc[j,11]=c.find('div',class_="cell-con-ul").find_all('ul')[4].find('p',paramname="后置相机").text.replace('\n','').replace(' ','').replace('\r','')
data1.iloc[j,12]=c.find('div',class_="cell-con-ul").find_all('ul')[1].find('p',paramname="手机颜色").text.replace('\n','').replace(' ','').replace('\r','')
except:
pass
data1.to_csv('手机各品牌机型数据.csv',index=False) # 保存数据到CSV文件
print("第",j,"页")
return data1
最后一个步骤就是可视化,要可视化先要读取数据,为了节省运行时间,先把存为csv格式的数据提取出来,存为dataframe,然后在去处理各个字段,比如把内存处理,如12+256GB这个数据处理成手机内存和物理内存两种,如果没有物理内存或者手机内存的设置为0,如果出现1TB这种,直接给1024GB;还有时间的处理,将年和月分别拆成按年和按月两个字段,方便去做季度统计;还有将尺寸处理成浮点型数据,方便后期去计算,里面用到的都是pandas的方法。当处理好之后,就可以使用matloplit进行可视化了,如下图处理好的数据和可视化效果。
具体代码如下:
import pandas as pd
import numpy as np
data=pd.read_csv('手机各品牌机型数据.csv',encoding='gbk')
data1=data.dropna(thresh=12)
data1['尺寸']=(data1['尺寸'].str.split('英寸',expand=True)[0]).astype('float')
data1['手机内存']=data1['手机内存'].str.split('G',expand=True)[0]
data1['物理内存']=data1['手机内存'].str.split('+',expand=True)[1]
data1['手机内存']=(data1['手机内存'].str.split('+',expand=True)[0])
data1=data1.dropna(thresh=13)
data1['季度']=None
for i in range(data1.shape[0]):
if data1.iloc[i,9]=='1TB':
data1.iloc[i, 9]='1024'
if int(data1.iloc[i,9])>16:
data1.iloc[i,13]=data1.iloc[i,9]
if data1.iloc[i,13] is None:
data1.iloc[i, 13]='0'
if data1.iloc[i,13]=='':
data1.iloc[i, 13] = '0'
else:
pass
data1['手机内存']=data1['手机内存'].astype('int')
data1['物理内存']=data1['物理内存'].astype('int')
for j in range(data1.shape[0]):
if data1.iloc[j, 9]>16:
data1.iloc[j, 9]=0
if data1.iloc[j, 9]==1:
data1.iloc[j, 9]==0
if 0<data1.iloc[j,4]<4:
data1.iloc[j,14]='第一季度'
if 3<data1.iloc[j,4]<7:
data1.iloc[j,14]='第二季度'
if 6<data1.iloc[j,4]<10:
data1.iloc[j,14]='第三季度'
if 9<data1.iloc[j,4]<13:
data1.iloc[j,14]='第四季度'
else:
pass
# data1 = data1.loc[((data1['手机内存'] >1) & (data1['手机内存'] <=16)), :]
print(data1)
print("所有手机的平均尺寸是:",data1['尺寸'].mean())
data2=data1.groupby(by=['yue'])['尺寸'].mean().reset_index().round(2)#统计月份平均尺寸
data3=data1.groupby(by=['季度'])['尺寸'].mean().reset_index().round(2)#统计季度平均尺寸
data4=data1.groupby(by=['品牌'])['尺寸'].mean().reset_index().round(2)#统计品牌平均尺寸
data1['价格']=data1['价格'].astype('int')
list_jiage=[]
data5=(data1.loc[((data1['价格']>=1000)&(data1['价格']<2000)),:])['尺寸'].mean().round(2)
data6=(data1.loc[((data1['价格']>=2000)&(data1['价格']<3000)),:])['尺寸'].mean().round(2)
data7=(data1.loc[((data1['价格']>=3000)&(data1['价格']<4000)),:])['尺寸'].mean().round(2)
data8=(data1.loc[((data1['价格']>=4000)&(data1['价格']<5000)),:])['尺寸'].mean().round(2)
data9=(data1.loc[((data1['价格']>=5000)&(data1['价格']<6000)),:])['尺寸'].mean().round(2)
data10=(data1.loc[((data1['价格']>=6000)&(data1['价格']<7000)),:])['尺寸'].mean().round(2)
data11=(data1[data1['价格']>=7000])['尺寸'].mean().round(2)
list_jiage.append(data5)
list_jiage.append(data6)
list_jiage.append(data7)
list_jiage.append(data8)
list_jiage.append(data9)
list_jiage.append(data10)
list_jiage.append(data11)
list_jiage1=['价格大于1000小于2000','价格大于2000小于3000','价格大于3000小于4000','价格大于4000小于5000','价格大于6000小于6000','价格大于6000小于7000','价格大于7000']
print(list_jiage)
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 解决中文显示问题
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 生成图形
plt.plot(data3['季度'], data3['尺寸'], 'go:', label='尺寸变化', linewidth=2) # 颜色绿色,点形圆形,线性虚线,设置图例显示内容,线条宽度为2
for a, b in zip(data3['季度'], data3['尺寸']):
plt.text(a, b, b, ha='center', va='bottom', fontsize=20)
plt.ylabel('尺寸') # 横坐标轴的标题
plt.xlabel('季度') # 纵坐标轴的标题
plt.xticks(np.arange(0, 4, 1)) # 设置横坐标轴的刻度为 0 到 4的数组
plt.ylim([0, 10]) # 设置纵坐标轴范围为 -2 到 2
plt.legend() # 显示图例, 图例中内容由 label 定义
plt.grid() # 显示网格
plt.title('各个季度手机平均尺寸') # 图形的标题
# 显示图形
plt.show()
#定义函数来显示柱子上的数值
def autolabel(rects):
for rect in rects:
height = rect.get_height()
plt.text(rect.get_x()+rect.get_width()/2.-0.08, 1.03*height, '%s' % height, size=6, family="Times new roman")
plt.figure(figsize=(4,3))
cm = plt.bar(data4['品牌'], data4['尺寸'], width=0.5, color=["blue", "green", "yellow", "magenta"])
autolabel(cm)
plt.ylim((0, 10))
plt.xlabel("品牌", size=12)
plt.ylabel("尺寸", size=12)
plt.xticks(rotation=90)
plt.title('各个品牌手机平均尺寸')
plt.show()
plt.plot(list_jiage1, list_jiage, 'go:', label='尺寸变化', linewidth=2) # 颜色绿色,点形圆形,线性虚线,设置图例显示内容,线条宽度为2
for a, b in zip(list_jiage1, list_jiage):
plt.text(a, b, b, ha='center', va='bottom', fontsize=20)
plt.ylabel('尺寸') # 横坐标轴的标题
plt.xlabel('不同价格区间') # 纵坐标轴的标题
plt.xticks(rotation=90) # 设置横坐标轴的刻度为 0 到 4的数组
plt.ylim([0, 10]) # 设置纵坐标轴范围为 -2 到 2
plt.legend() # 显示图例, 图例中内容由 label 定义
plt.grid() # 显示网格
plt.title('不同价格区间的手机平均尺寸') # 图形的标题
plt.show()