一、主题式网络爬虫设计方案
1.网络爬虫名称:2020年手机品牌排行榜
2.网络爬虫爬取的内容与数据特征分析:爬取手机品牌名称、评分、占有等数据,分析各类数据之间的特征与关系
3.网络爬虫设计方案概述:
思路:找到索要爬取的网页,按F12查看网页所有代码,找到所要爬取的数据及分析标签,导入相应库,开始对数据进行爬取
进一步对数据提取、处理、可视化、绘制图形、保存数据
技术难点:对于有些数据,用各种标签无法实现爬取,以及爬取到的数据含有不需要的字符串,需要通过其他手段进行处理
二、主题页面的结构特征分析
1.主题页面的结构与特征分析:
需要爬去的内容如下:
2.Htmls页面解析:观察到徐要爬取的数据的标题都为"div" class="rank-list__item clearfix"
3.节点(标签)查找方法与遍历方法:进一步找到节点标签,使用find_all遍历查找数据
三、网络爬虫程序设计
1.数据爬取与采集:
#导入相应的库 import requests from bs4 import BeautifulSoup import bs4 import pandas as pd import matplotlib.pyplot as plt import numpy as np import re import scipy as sp from scipy.optimize import leastsq import matplotlib as mpl from numpy import genfromtxt import seaborn as sns #准备工作---爬取数据 def getHTMLText(): #爬取的网址 url = ‘http://top.zol.com.cn/compositor/57/manu_attention.html‘ try: #爬虫伪装 headers = {‘User-Agent‘:‘Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36‘} #用requests爬取网页信息 r=requests.get(url,timeout=30,headers=headers) #产生异常信息 r.raise_for_status() #设置编码标准 r.encoding=r.apparent_encoding soup=BeautifulSoup(r.text,‘lxml‘) #返回爬取的网页内容 return soup #若发生报错,则返回空字符串 except: return ""
结果如下:
2.对数据进行清洗和处理:用for循环遍历数据,但由于一些数据用for循环不好获取,这里我选择用正则表达式进行获取和数据处理
对于一些数据含有字符串、符号、0等数据替换成缺失值
#数据清洗和处理及保存 def data_analysis(soup): #建立需要的空列表 name=[] grade=[] occupancy=[] attention=[] praise=[] price=[] minimum_price=[] highest_price=[] style=[] link=[] brand=[] #用for循环爬取需要的数据 #先遍历大的class类型 for data in soup.find_all("div",class_="rank-list__item clearfix"): for link1 in data("a",class_="name"): name.append(link1.get_text()) for link2 in data("div",class_="score clearfix"): grade.append(link2.get_text().strip()) for link3 in data("div",class_="rank-list__cell cell-4"): occupancy.append(link3.get_text().strip()) for link4 in data("div",class_="rank-list__cell cell-6"): praise.append(link4.get_text().strip()) for link5 in data("div",class_="rank__price"): price.append(link5.get_text().strip()) #用正则获取链接 link_=[] link_=re.findall(r‘(http:.+html)‘,str(data)) #将连接存入列表 link.append(link_[0]) brand_=[] brand_=re.findall(r‘(https:.+jpg)‘,str(data)) brand.append(brand_[0]) #获取关注度 attention=re.findall(‘<span style="width: (.*)"></span>‘,str(soup)) #将价格区间分成最低价和最高价,并将暂无报价的以空字符保存 minimum_price=re.findall("¥(\d*)-|暂无报价",str(price)) highest_price=re.findall("-(\d*)|暂无报价",str(price)) #将种类单独存放 style=re.findall(r"共(\d*)款",str(price)) #用正则将数据中的“分”字去除,便于后续的数据处理 grade=re.findall("(\d\d.\d|\d)",str(grade)) #将occupancy,attention,praise列表中的“%”符号删去,便于后续的数据处理 occupancy=re.findall("(\d+.?\d)%|-",str(occupancy)) attention=re.findall("(\d+)%",str(attention)) praise=re.findall("(\d\d.\d|\d\d)%",str(praise)) #将以下数据中的 “0” “” “0%” 的部分替换成缺失值 for j in range(len(occupancy)): if occupancy[j]=="": occupancy[j]=(np.nan) if grade[j]=="0": grade[j]=(np.nan) if attention[j]=="0": attention[j]=(np.nan) if attention[j]=="0%": attention[j]=(np.nan) if minimum_price[j] and highest_price[j]==" ": minimum_price[j]=(np.nan) highest_price[j]=(np.nan)
部分结果如下:已经得到预期所要的结果
4.数据分析与可视化:对所有数据用DataFrame结构处理,
查看数据维度,查找缺失值、重复值,删除缺失值、重复值所对应的行
#对所有数据用DataFrame结构处理 data1=pd.DataFrame([name,grade,occupancy,attention,praise,minimum_price,highest_price,style], index=["品牌","评分","占有率","关注度","好评率","最低价","最高价","种类"]).T #使数据输出时列名对齐 pd.set_option(‘display.unicode.ambiguous_as_wide‘, True) pd.set_option(‘display.unicode.east_asian_width‘, True) #查看数据维度 print("数据维度: {:}行{:}列\n".format(data1.shape[0],data1.shape[1]),"\n") #使用describe查看统计信息 print(data1.describe(),"\n") #查看数据是否有缺失值 print(data1.isnull(),"\n") #只显示存在缺失值的行列,清楚的确定缺失值得位置 print(data1[data1.isnull().values==True],"\n") #统计各列缺失值情况 print(data1.isnull().sum(),"\n") #查找重复值 print(data1.duplicated(),"\n") #删除重复值 data1=data1.drop_duplicates() #删除缺失值对应的行 data=data1.dropna()
结果如下:
开始绘制各种图形:由于数据带有引号,绘制图形过程中存在报错,所以将数据转化为浮点型
在绘制过程中将每一个图形进行保存
#正常显示中文字体 mpl.rcParams[‘font.sans-serif‘] = [‘KaiTi‘] mpl.rcParams[‘font.serif‘] = [‘KaiTi‘] #解决保存图像是负号‘-‘显示为方块的问题,或者转换负号为字符 mpl.rcParams[‘axes.unicode_minus‘] = False #数据分析 #设置画布大小(横10竖8) plt.figure(figsize=(10,8)) #创建子图1 axes1=plt.subplot(2,1,1) #绘制品牌与评分之间的柱状图 plt.bar(data[‘品牌‘],data[‘评分‘]) #创建子图1的标题 plt.title(‘前十名品牌综合评分‘) #创建子图1的x轴名称 plt.xlabel(‘品牌名称‘) #创建子图1的y轴名称 plt.ylabel("综合评分") axes2=plt.subplot(2,1,2) plt.bar(data[‘品牌‘],data[‘好评率‘]) plt.title(‘前十名品牌好评率‘) plt.xlabel(‘品牌名称‘) plt.ylabel("好评率") #将图片进行保存 plt.savefig(‘D:/排行版图片/1‘) #显示图像 plt.show() # 接下来只对前十名做数据分析 data_=data[0:10] #绘制饼状图 plt.figure(figsize=(6,6)) plt.pie(data_[‘占有率‘],labels=data_[‘品牌‘]) plt.title("品牌占有率") plt.savefig(‘D:/排行版图片/2‘) plt.show() #创建最低价与品牌之间的饼状图 plt.figure(figsize=(6,6)) plt.pie(data_[‘最低价‘],labels=data_[‘品牌‘]) plt.title("品牌最低价") plt.savefig(‘D:/排行版图片/3‘) plt.show() #创建最高价与品牌之间的饼状图 plt.figure(figsize=(6,6)) plt.pie(data_[‘最高价‘],labels=data_[‘品牌‘]) plt.title("品牌最高价") plt.savefig(‘D:/排行版图片/4‘) plt.show() #创建种类与品牌之间的饼状图 plt.figure(figsize=(6,6)) plt.pie(data_[‘种类‘],labels=data_[‘品牌‘]) plt.title("品牌种类") plt.savefig(‘D:/排行版图片/5‘) plt.show() #绘制柱状图 plt.figure(figsize=(15,6)) #创建10个数字,用于x轴的分组 x=np.arange(10) #绘制最低价,最高价,种类的柱状图 y1=data_[‘最低价‘] y2=data_[‘最高价‘] y3=data_[‘种类‘] #设置间距 bar_width=0.3 #在偏移间距位置绘制柱状图 plt.bar(x,y1,bar_width,align="center",color="c",label="最低价",alpha=0.5) plt.bar(x+bar_width,y2,bar_width,align="center",color="b",label="最高价",alpha=0.5) plt.bar(x+2*bar_width,y3,bar_width,align="center",color="r",label="种类",alpha=0.5) plt.xlabel("名称") plt.ylabel("数目") #设置x轴组别的位置,且以品牌名称作为组别名称 plt.xticks(x+bar_width,data_["品牌"]) #显示图例,位置有系统选择最佳位置 plt.legend() plt.savefig(‘D:/排行版图片/6‘) plt.show() #绘制盒图 plt.boxplot((X,Y,data_[‘占有率‘].astype(‘float64‘),data_[‘好评率‘].astype(‘float64‘),y3.astype(‘float64‘)), labels=("品牌","评分","占有率","好评率","种类")) plt.title("各数据的盒图情况") plt.savefig(‘D:/排行版图片/8‘) plt.show()
结果如下:
5.根据数据之间的关系,分析两个变量之间的相关系数,画出散点图,并建立变量之间的回归方程
#绘制拟合直线方程 X=(data[‘评分‘])[::-1] Y=(data[‘占有率‘])[::-1] #去除X,Y数据中的引号(转化为64位系数的浮点型) X = X.astype(‘float64‘) Y = Y.astype(‘float64‘) #设置拟合函数 #二次函数标准形式 def func(p,x): a,b,c=p return a*x*x+b*x+c #设置误差函数 def error_func(p,x,y): return func(p,x)-y #设置初始值,数值可随意指定 p0=[73.4,99.3,99.5] #使用最小二乘法对数据进行拟合 Para=leastsq(error_func,p0,args=(X,Y)) a,b,c=Para[0] #打印参数 print("a=",a,"b=",b,"c=",c) plt.figure(figsize=(10,6)) #绘制散点图 plt.scatter(X,Y,color="green",label="样本数据",linewidth=2) #绘制拟合直线 x=np.linspace(47.4,99.3,20) y=a*x*x+b*x+c plt.plot(x,y,color="red",label="拟合数据",linewidth=2) plt.legend() plt.title("品牌评分与占有率的关系") plt.xlabel("评分") plt.ylabel("占有率") #设置网格线 plt.grid() plt.savefig(‘D:/排行版图片/7‘) plt.show()
6.数据持久化:
#数据保存 #将品牌链接增加到data1数据中 data1[‘品牌链接‘]=link #将品牌商标链接增加到data1数据中 data1[‘品牌商标链接‘]=brand #创建Excel表并写入数据 #创建文件名 wb =‘D:\\2020年手机品牌排行榜.xlsx‘ #将数据保存 data1.to_excel(wb)
7. 附上完整程序代码
#导入相应的库 import requests from bs4 import BeautifulSoup import bs4 import pandas as pd import matplotlib.pyplot as plt import numpy as np import re import scipy as sp from scipy.optimize import leastsq import matplotlib as mpl from numpy import genfromtxt import seaborn as sns #准备工作---爬取数据 print("-------开始爬取数据--------\n") def getHTMLText(): #爬取的网址 url = ‘http://top.zol.com.cn/compositor/57/manu_attention.html‘ try: #爬虫伪装 headers = {‘User-Agent‘:‘Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36‘} #用requests爬取网页信息 r=requests.get(url,timeout=30,headers=headers) #产生异常信息 r.raise_for_status() #设置编码标准 r.encoding=r.apparent_encoding soup=BeautifulSoup(r.text,‘lxml‘) #返回爬取的网页内容 return soup #若发生报错,则返回空字符串 except: return "" print("-------数据爬取完成--------\n") #数据清洗和处理及保存 def data_analysis(soup): #建立需要的空列表 print("-------开始建立数据--------\n") #存储品牌名称 name=[] #存储品牌综合评分 grade=[] #存储品牌占有率 occupancy=[] #存储品牌关注度 attention=[] #存储品牌好评率 praise=[] #存储品牌价格区间及种类 price=[] #存储最低价 minimum_price=[] #存储最高价 highest_price=[] #存储品牌种类 style=[] #存储品牌链接 link=[] #存储品牌商标链接 brand=[] #用for循环爬取需要的数据 #先遍历大的class类型 for data in soup.find_all("div",class_="rank-list__item clearfix"): #获取品牌名称 for link1 in data("a",class_="name"): name.append(link1.get_text()) #获取品牌综合评分 for link2 in data("div",class_="score clearfix"): grade.append(link2.get_text().strip()) #获取品牌占有率 for link3 in data("div",class_="rank-list__cell cell-4"): occupancy.append(link3.get_text().strip()) #获取好评率 for link4 in data("div",class_="rank-list__cell cell-6"): praise.append(link4.get_text().strip()) #获取价格区间和种类 for link5 in data("div",class_="rank__price"): price.append(link5.get_text().strip()) """ 由于一些数据不好用for循环爬取 所以可以选择用正则 进行爬去 """ #用正则获取链接 #定义一个空列表 link_=[] link_=re.findall(r‘(http:.+html)‘,str(data)) #将连接存入列表 link.append(link_[0]) #用正则获取商标链接 #定义一个空列表 brand_=[] brand_=re.findall(r‘(https:.+jpg)‘,str(data)) #将商标连接存入列表 brand.append(brand_[0]) #获取关注度 attention=re.findall(‘<span style="width: (.*)"></span>‘,str(soup)) """ 由于price数据中 存储着三种类型的数据 无法进行后续的数据处理 可以考虑将price数据分成三个数据来处理 分别为 minimum_price(最低价) highest_pric(最高价) style(种类) """ #将价格区间分成最低价和最高价,并将暂无报价的以空字符保存 minimum_price=re.findall("¥(\d*)-|暂无报价",str(price)) highest_price=re.findall("-(\d*)|暂无报价",str(price)) #将种类单独存放 style=re.findall(r"共(\d*)款",str(price)) """ 由于数据中存在文字和一些符号 会影响后续的数据处理 所以对数据进行如下处理 """ #用正则将数据中的“分”字去除 grade=re.findall("(\d\d.\d|\d)",str(grade)) #将occupancy,attention,praise列表中的“%”符号删去 occupancy=re.findall("(\d+.?\d)%|-",str(occupancy)) attention=re.findall("(\d+)%",str(attention)) praise=re.findall("(\d\d.\d|\d\d)%",str(praise)) #打印品牌前三名 print("-----2020年手机品牌前三名如下-----") print("第一名: {}".format(name[0]),"\n") print("第二名: {}".format(name[1]),"\n") print("第三名: {}".format(name[2]),"\n") #以下是查看收集的数据 ‘‘‘ print(len(name),name,"\n") print(len(grade),grade,"\n") print(len(occupancy),occupancy,"\n") print(len(attention),attention,"\n") print(len(praise),praise,"\n") print(len(price),price,"\n") print(len(minimum_price),minimum_price,"\n") print(len(highest_price),highest_price,"\n") print(len(style),style,"\n") print(len(link),link) print(len(brand),brand) ‘‘‘ """ 由于数据中存在0数据和空数据 影响检测数据缺失值 所以手动将这些数据改为缺失值 """ #将以下数据中的 “0” “” “0%” 的部分替换成缺失值 for j in range(len(occupancy)): if occupancy[j]=="": occupancy[j]=(np.nan) if grade[j]=="0": grade[j]=(np.nan) if attention[j]=="0": attention[j]=(np.nan) if attention[j]=="0%": attention[j]=(np.nan) if minimum_price[j] and highest_price[j]==" ": minimum_price[j]=(np.nan) highest_price[j]=(np.nan) print("--------数据建立已完成--------\n") print("--------开始处理数据--------\n") #对所有数据用DataFrame结构处理 data1=pd.DataFrame([name,grade,occupancy,attention,praise,minimum_price,highest_price,style], index=["品牌","评分","占有率","关注度","好评率","最低价","最高价","种类"]).T #使数据输出时列名对齐 pd.set_option(‘display.unicode.ambiguous_as_wide‘, True) pd.set_option(‘display.unicode.east_asian_width‘, True) print(data1) #查看数据维度 print("数据维度: {:}行{:}列\n".format(data1.shape[0],data1.shape[1]),"\n") #使用describe查看统计信息 print(data1.describe(),"\n") #查看数据是否有缺失值 print(data1.isnull(),"\n") #只显示存在缺失值的行列,清楚的确定缺失值得位置 print(data1[data1.isnull().values==True],"\n") #统计各列缺失值情况 print(data1.isnull().sum(),"\n") #查找重复值 print(data1.duplicated(),"\n") #删除重复值 data1=data1.drop_duplicates() #删除缺失值对应的行 data=data1.dropna() print(data) print("--------数据处理已完成--------\n") print("--------开始绘制图形--------\n") #正常显示中文字体 mpl.rcParams[‘font.sans-serif‘] = [‘KaiTi‘] mpl.rcParams[‘font.serif‘] = [‘KaiTi‘] #解决保存图像是负号‘-‘显示为方块的问题,或者转换负号为字符 mpl.rcParams[‘axes.unicode_minus‘] = False #数据分析 #设置画布大小(横10竖8) plt.figure(figsize=(10,8)) #创建子图1 axes1=plt.subplot(2,1,1) #绘制品牌与评分之间的柱状图 plt.bar(data[‘品牌‘],data[‘评分‘]) #创建子图1的标题 plt.title(‘前十名品牌综合评分‘) #创建子图1的x轴名称 plt.xlabel(‘品牌名称‘) #创建子图1的y轴名称 plt.ylabel("综合评分") #创建子图2 axes2=plt.subplot(2,1,2) #绘制品牌与好评率之间的柱状图 plt.bar(data[‘品牌‘],data[‘好评率‘]) #创建子图2的标题 plt.title(‘前十名品牌好评率‘) #创建子图2的x轴名称 plt.xlabel(‘品牌名称‘) #创建子图2的y轴名称 plt.ylabel("好评率") #将图片进行保存 plt.savefig(‘D:/排行版图片/1‘) #显示图像 plt.show() # 接下来只对前十名做数据分析 # 取各数据前十个元素 data_=data[0:10] print(data_) #绘制饼状图 #创建占有率与品牌之间的饼状图 plt.figure(figsize=(6,6)) plt.pie(data_[‘占有率‘],labels=data_[‘品牌‘]) plt.title("品牌占有率") plt.savefig(‘D:/排行版图片/2‘) plt.show() #创建最低价与品牌之间的饼状图 #设置画布大小(横6竖6) plt.figure(figsize=(6,6)) plt.pie(data_[‘最低价‘],labels=data_[‘品牌‘]) plt.title("品牌最低价") plt.savefig(‘D:/排行版图片/3‘) plt.show() #创建最高价与品牌之间的饼状图 #设置画布大小(横6竖6) plt.figure(figsize=(6,6)) plt.pie(data_[‘最高价‘],labels=data_[‘品牌‘]) plt.title("品牌最高价") plt.savefig(‘D:/排行版图片/4‘) plt.show() #创建种类与品牌之间的饼状图 #设置画布大小(横6竖6) plt.figure(figsize=(6,6)) plt.pie(data_[‘种类‘],labels=data_[‘品牌‘]) plt.title("品牌种类") plt.savefig(‘D:/排行版图片/5‘) plt.show() #绘制柱状图 #设置画布大小(横15竖6) plt.figure(figsize=(15,6)) #创建10个数字,用于x轴的分组 x=np.arange(10) #绘制最低价,最高价,种类的柱状图 y1=data_[‘最低价‘] y2=data_[‘最高价‘] y3=data_[‘种类‘] #设置间距 bar_width=0.3 #在偏移间距位置绘制柱状图 plt.bar(x,y1,bar_width,align="center",color="c",label="最低价",alpha=0.5) plt.bar(x+bar_width,y2,bar_width,align="center",color="b",label="最高价",alpha=0.5) plt.bar(x+2*bar_width,y3,bar_width,align="center",color="r",label="种类",alpha=0.5) plt.xlabel("名称") plt.ylabel("数目") #设置x轴组别的位置,且以品牌名称作为组别名称 plt.xticks(x+bar_width,data_["品牌"]) #显示图例,位置有系统选择最佳位置 plt.legend() plt.savefig(‘D:/排行版图片/6‘) plt.show() #绘制拟合直线方程 #将数据有从小到大排列 X=(data[‘评分‘])[::-1] Y=(data[‘占有率‘])[::-1] """ 由于数据带有引号 是字符串的形式 无法绘制拟合直线方程 所以将数据转化为浮点型 """ #去除X,Y数据中的引号(转化为64位系数的浮点型) X = X.astype(‘float64‘) Y = Y.astype(‘float64‘) #设置拟合函数 #二次函数标准形式 def func(p,x): a,b,c=p return a*x*x+b*x+c #设置误差函数 def error_func(p,x,y): return func(p,x)-y #设置初始值,数值可随意指定 p0=[73.4,99.3,99.5] #使用最小二乘法对数据进行拟合 Para=leastsq(error_func,p0,args=(X,Y)) #将需要的参数保留下来 a,b,c=Para[0] #打印参数 print("a=",a,"b=",b,"c=",c) #设置画布大小(横10竖6) plt.figure(figsize=(10,6)) #绘制散点图 plt.scatter(X,Y,color="green",label="样本数据",linewidth=2) #绘制拟合直线 #x轴开始值为47.4,末尾值为99.3,之间20个数据 x=np.linspace(47.4,99.3,20) y=a*x*x+b*x+c plt.plot(x,y,color="red",label="拟合数据",linewidth=2) #显示图例,位置有系统选择最佳位置 plt.legend() plt.title("品牌评分与占有率的关系") plt.xlabel("评分") plt.ylabel("占有率") #设置网格线 plt.grid() plt.savefig(‘D:/排行版图片/7‘) plt.show() #绘制盒图 #用astype将数据转化为浮点型 plt.boxplot((X,Y,data_[‘占有率‘].astype(‘float64‘),data_[‘好评率‘].astype(‘float64‘),y3.astype(‘float64‘)), labels=("品牌","评分","占有率","好评率","种类")) plt.title("各数据的盒图情况") plt.savefig(‘D:/排行版图片/8‘) plt.show() print("--------所有图形已绘制完成--------\n") print("--------所有图形已保存完成--------\n") print("--------图形保存路径为: D:排行版图片--------\n") #数据保存 print("--------开始保存数据--------\n") """ 将一开始的两个链接—— 手机品牌链接 手机品牌商标链接 添加到data1数据中 再将data1数据进行保存 """ #将品牌链接增加到data1数据中 data1[‘品牌链接‘]=link #将品牌商标链接增加到data1数据中 data1[‘品牌商标链接‘]=brand #创建Excel表并写入数据 #创建文件名 wb =‘D:\\2020年手机品牌排行榜.xlsx‘ #将数据保存 data1.to_excel(wb) print("--------数据保存已完成--------\n") print("--------数据保存保存路径为: D:2020年手机品牌排行榜--------\n") if __name__ == ‘__main__‘: soup=getHTMLText() data_analysis(soup)
四、结论
1.结论:经过对主题数据的分析与可视化可以了解到,vivo手机的品牌综合评分最高,而华为占据第二,
但华为的好评率与品牌种类非常的高,苹果作为大多数人的喜爱,好评率当然是最高的,但排名相对靠后
可以看得出,国内手机品牌销量越来越好,竞争也是非常的激烈
2.对本次程序设计任务完成的情况做一个简单的小结:
此次设计让我了解到自身还是有非常大的不足,也通过此次设计让我学习了正则表达式,
在获取数据时是非常强大的,高效简洁