一、选题背景
随着科技经济的发展,社会中发生的重大事件我们都可以从各大软件中得知,知乎热榜是我们了解时事的一个重要途径,但是如果我们没有那么时间来刷知乎,但是还是想要了解一天中发生的热门事件,我们该怎么办呢?在这里,我想到了通过知乎爬虫的手段,获取知乎热榜的标题和简介,保存到本地文件,,从而获取到每一天的知乎热榜内容,这样,我们只需要查看本地文件内容,就可以快速的了解今天一天的时事。要达到的数据分析目标是 通过爬虫知乎热搜榜爬获的数据画出排行与热度的图形,从而分析排行与热度的线性关系。
二、主题式网络爬虫设计方案
1、主题式网络爬虫名称:爬取知乎热搜榜
2.爬取内容:爬取网页热搜排名,标题,热度值。
数据特征:内容是随机改变的主要以文字和数字为主。
3.方案概述:首先访问网页得到状态码200,分析网页源代码,找出所需要的的标签,逐个提取标签保存到相同路径csv文件中,读取改文件,进行数据清洗,数据模型分析,数据可视化处理,绘制分布图,用最小二乘法分析两个变量间的二次拟合方程和绘制拟合曲线。
技术难点:获取知乎热搜榜的url网址,获取相对应的标签;数据输出时列名无法对齐;文本分析用到的jieba分词需要上网搜索;画折线图出现无法将数据转换为浮点型的错误;利用最小二乘法拟合曲线;
三、主题页面的结构特征分析
1、搜爬取的知乎网站的url为
url = 'https://tophub.today/n/mproPpoq6O'
通过游览器搜索右击点击‘’检查‘’查看‘’网络‘’与“元素”,获取网页
主题页面的结构和特征分析:所要爬取的热度数据在标签‘td’里面,标题在标签‘<a href> .... <a>’里面。
四、网络爬虫程序设计
1.数据爬取与采集
1 url = 'https://tophub.today/n/mproPpoq6O' 2 header = {'user-agent':'Mozilla/5.0'} 3 r = requests.get(url, headers=header) 4 r.raise_for_status() 5 r.encoding = r.apparent_encoding 6 r.text 7 html = r.text 8 title = re.findall('<a href=.*? target="_blank" .*?>(.*?)</a>',html)[3:23] 9 redu = re.findall('<td>(.*?)</td>',html)[0:120] 10 #print(title) 11 #print(redu) 12 print('{:^95}'.format('知乎热度榜单')) 13 print('{:^5}\t{:^40}\t{:^10}'.format('排名','标题','热度(单位:万)')) 14 num = 20 15 lst = [] 16 for i in range(num): 17 print('{:^5}\t{:^40}\t{:^10}'.format(i+1, title[i], redu[i][:-3])) 18 lst.append([i+1, title[i], redu[i][:-3]]) 19 df = pd.DataFrame(lst, columns=['排名','标题','热度(单位:万)']) 20 pd.set_option('display.unicode.ambiguous_as_wide',True) #使输出的列与列值对齐 21 pd.set_option('display.unicode.east_asian_width',True) #使输出的列与列值对齐 22 #df.to_excel('rank.xlsx') 23 rank = r'rank.xlsx' 24 df

2.对数据进行清洗和处理
1 #读取csv文件
2 rank=pd.DataFrame(pd.read_csv('知乎热搜榜.xlsx'))
3 print(rank.head())
1 #删除无效列 2 df.drop('热度(单位:万)',axis=1,inplace=True)
1 #检查是否有空值 2 df['热度(单位:万)'].isnull.value_counts()
1 #检查是否有重复值 2 df.duplicated()
1 #异常值处理 2 df.describe()
3.文本分析:jieba分词
1 def fit(text): 2 return jieba.cut(text) 3 df['标题']=df['标题'].apply(fit) 4 df.sample(5)
4.数据分析与可视化
1 #数据分析 2 from sklearn.linear_model import LinearRegression 3 X = df.drop("标题",axis=1) 4 predict_model = LinearRegression() 5 predict_model.fit(X,df['热度(单位:万)']) 6 print("回归系数为:",predict_model.coef_)
1 #绘制排名与热度的折线图与柱状图 2 def python(): 3 plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 4 x = df['排名'] 5 y = df['热度(单位:万)'] 6 plt.xlabel('排名') 7 plt.ylabel('热度(单位:万)') 8 plt.plot(x,y) 9 plt.scatter(x,y) 10 plt.title('排名与热度的折线图') 11 plt.show() 12 python() 13 plt.rcParams['font.sans-serif'] = ['SimHei'] 14 plt.bar(range(1,9),redu[:8]) 15 plt.xlabel('排名') 16 plt.ylabel('热度(单位:万)') 17 plt.title('排名与热度的柱状图') 18 plt.show()
5.根据数据之间的关系,分析两个变量之间的相关系数,画出散点图,并建立变 量之间的回归方程
1 #画出散点图,求出排名与截距,构造出一元方程 2 df['热度(单位:万)']=list(map(int,df['热度(单位:万)'])) #将热度的数据类型转换成与排名相同的int类型 3 plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 4 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 5 N=20 6 x=np.random.rand(N) 7 y=np.random.rand(N) 8 size=50 9 plt.xlabel("排名") 10 plt.ylabel("热度") 11 plt.scatter(x,y,size,color='b',alpha=0.5,marker="o") 12 #散点图 kind='reg' 13 sns.jointplot(x="排名",y="热度(单位:万)",data=df,kind='reg') 14 # kind='hex' 15 sns.jointplot(x="排名",y="热度(单位:万)",data=df,kind='hex') 16 # kind='kde' 17 sns.jointplot(x="排名",y="热度(单位:万)",data=df,kind="kde",space=0,color='g') 18 #求出排名、截距 19 x=np.array(df['排名']).reshape(-1,1) 20 y=np.array(df['热度(单位:万)']).reshape(-1,1) 21 predict_model.fit(x,y) 22 x=np.array(df['排名']).reshape(-1,1) 23 y=np.array(df['热度(单位:万)']).reshape(-1,1) 24 predict_model.fit(x,y) 25 print("回归系数为:",predict_model.coef_) 26 print("截距:",predict_model.intercept_) 27 #有图可知所构造的一元方程的斜率为-49.72,截距为913.56 28 #所以方程为y=-49.72+913.56
1 import matplotlib.pyplot as plt 2 plt.rcParams['font.sans-serif']='SimHei' 3 #设置中文显示 4 plt.figure(figsize=(6,6)) 5 #将画布设定为正方形,则绘制的饼图是正圆 6 label=['热度0-400万第一区限','热度400-800万第二区限','热度800-1200万第三区限','热度1200-1600万第四区限'] 7 #定义饼图的标签,标签是列表 8 explode=[0.01,0.01,0.01,0.01] 9 #设定各项距离圆心n个半径 10 values=[13,4,2,1] 11 # 13,4,2,1 分别为热度在0-400万,400-800,800-1200,1200-1600 区间的个数 12 plt.pie(values,explode=explode,labels=label,autopct='%1.1f%%') 13 #绘制饼图 14 plt.title('热度数值分布值饼图') 15 #绘制标题 16 plt.savefig('./热度数值分布值饼图') 17 #保存图片 18 plt.show()
1 #选择排名和热度两个特征变量,绘制分布图,用最小二乘法分析两个变量间的二次拟合方程和拟合曲线 2 import scipy.optimize as opt 3 colnames=[" ","排名","标题","热度(单位:万)"] 4 df = pd.read_excel('rank.xlsx',skiprows=1,names=colnames) 5 X = df['排名'] 6 Y = df['热度(单位:万)'] 7 Z = df['标题'] 8 def A(): 9 plt.scatter(X,Y,color="blue",linewidth=2) 10 plt.title("排名",color="g") 11 plt.grid() 12 plt.show() 13 def B(): 14 plt.scatter(X,Y,color="b",linewidth=2) 15 plt.title("热度",color="g") 16 plt.grid() 17 plt.show() 18 def fit1(p,x): 19 p=a,b,c #a、b、c 分别为二次函数的系数、截距 20 return a*x*x+b*x+c #用于拟合的二次函数 21 def fit2(p,x,y): 22 return func(p,x)-y 23 def main(): 24 plt.figure(figsize=(10,6)) 25 p0=[0,0,0] #第一次猜测的函数拟合参数 26 Para = opt.leastsq(error,p0,args=(X,Y)) #进行最小二乘拟合 27 a,b,c=Para[0] 28 print("a=",a,"b=",b,"c=",c) 29 plt.scatter(X,Y,color="r",linewidth=2) 30 x=np.linspace(0,20,20) 31 y=a*x*x+b*x+c 32 plt.plot(x,y,color="r",linewidth=2,) 33 plt.title("热度值分布") 34 plt.grid() 35 plt.show() 36 print(A()) 37 print(B()) 38 print(main()) 39 #有图可知所拟合的二次函数的系数和截距分别为3.53,-127.49,1235.256 40 #所以此二次函数为y=3.53*x*x-127.49*x+1235.256
6.数据持久化
1 #我采用的是使用df.to_csv 将df中的数据保存到csv中,具体路径为C:\Users\86181\rank.xlsx 以此已实现数据持久化 2 filename='rank.csv' 3 headers=['排名','标题','热度'] 4 index=[i for i in range(1,len(html)+1)] 5 df2=pd.DataFrame(html,columns=headers,index=index) 6 pd.DataFrame.to_csv(df2,filename)
7.将以上各部分的代码汇总,附上完整程序代码
1 import requests 2 import jieba 3 import xlrd 4 import pandas as pd 5 import openpyxl 6 import matplotlib 7 import matplotlib.pyplot as plt 8 import numpy as np 9 import seaborn as sns 10 from sklearn.linear_model import LinearRegression 11 #睡眠模拟用户 12 time.sleep(1+random.random()) 13 #获取html网页 14 url = 'https://tophub.today/n/mproPpoq6O' 15 header = {'user-agent':'Mozilla/5.0'} 16 #请求超时时间为30秒 17 r = requests.get(url,timeout = 30,headers=header) 18 #如果状态码不是200,则引发日常 19 r.raise_for_status() 20 #配置编码 21 r.encoding = r.apparent_encoding 22 #获取源代码 23 r.text 24 html = r.text 25 title = re.findall('<a href=.*? target="_blank" .*?>(.*?)</a>',html)[3:23] 26 redu = re.findall('<td>(.*?)</td>',html)[0:120] 27 #print(title) 28 #print(redu) 29 print('{:^95}'.format('知乎热度榜单')) 30 print('{:^5}\t{:^40}\t{:^10}'.format('排名','标题','热度(单位:万)')) 31 num = 20 32 lst = [] 33 for i in range(num): 34 print('{:^5}\t{:^40}\t{:^10}'.format(i+1, title[i], redu[i][:-3])) 35 lst.append([i+1, title[i], redu[i][:-3]]) 36 df = pd.DataFrame(lst, columns=['排名','标题','热度(单位:万)']) 37 pd.set_option('display.unicode.ambiguous_as_wide',True) 38 #使输出的列与列值对齐 39 pd.set_option('display.unicode.east_asian_width',True) 40 #使输出的列与列值对齐 41 #df.to_excel('rank.xlsx') 42 rank = r'rank.xlsx' 43 df 44 #读取csv文件 45 rank=pd.DataFrame(pd.read_csv('知乎热搜榜.xlsx')) 46 print(rank.head()) 47 #删除无效列 48 df.drop('标题',axis=1,inplace=True) 49 #检查是否有空值 50 df['热度(单位:万)'].isnull.value_counts() 51 #检查是否有重复值 52 df.duplicated() 53 #异常值处理 54 df.describe() 55 #利用jieba分词进行文本分析 56 def fit(text): 57 return jieba.cut(text) 58 df['标题']=df['标题'].apply(fit) 59 df.sample(5) 60 #数据分析 61 from sklearn.linear_model import LinearRegression 62 X = df.drop("标题",axis=1) 63 predict_model = LinearRegression() 64 predict_model.fit(X,df['热度(单位:万)']) 65 print("回归系数为:",predict_model.coef_) 66 #绘制排名与热度的折线图与柱状图 67 import requests 68 import jieba 69 import xlrd 70 import pandas as pd 71 import openpyxl 72 import matplotlib 73 import matplotlib.pyplot as plt 74 import numpy as np 75 import seaborn as sns 76 from sklearn.linear_model import LinearRegression 77 #睡眠模拟用户 78 time.sleep(1+random.random()) 79 #获取html网页 80 url = 'https://tophub.today/n/mproPpoq6O' 81 header = {'user-agent':'Mozilla/5.0'} 82 #请求超时时间为30秒 83 r = requests.get(url,timeout = 30,headers=header) 84 #如果状态码不是200,则引发日常 85 r.raise_for_status() 86 #配置编码 87 r.encoding = r.apparent_encoding 88 #获取源代码 89 r.text 90 html = r.text 91 title = re.findall('<a href=.*? target="_blank" .*?>(.*?)</a>',html)[3:23] 92 redu = re.findall('<td>(.*?)</td>',html)[0:120] 93 #print(title) 94 #print(redu) 95 print('{:^95}'.format('知乎热度榜单')) 96 print('{:^5}\t{:^40}\t{:^10}'.format('排名','标题','热度(单位:万)')) 97 num = 20 98 lst = [] 99 for i in range(num): 100 print('{:^5}\t{:^40}\t{:^10}'.format(i+1, title[i], redu[i][:-3])) 101 lst.append([i+1, title[i], redu[i][:-3]]) 102 df = pd.DataFrame(lst, columns=['排名','标题','热度(单位:万)']) 103 def python(): 104 plt.rcParams['font.sans-serif'] = ['SimHei'] 105 # 用来正常显示中文标签 106 x = df['排名'] 107 y = df['热度(单位:万)'] 108 plt.xlabel('排名') 109 plt.ylabel('热度(单位:万)') 110 plt.plot(x,y) 111 plt.scatter(x,y) 112 plt.title('排名与热度的折线图') 113 plt.show() 114 python() 115 plt.rcParams['font.sans-serif'] = ['SimHei'] 116 plt.bar(range(1,9),redu[:8]) 117 plt.xlabel('排名') 118 plt.ylabel('热度(单位:万)') 119 plt.title('排名与热度的柱状图') 120 plt.show() 121 #画出散点图,求出排名与截距,构造出一元方程 122 df['热度(单位:万)']=list(map(int,df['热度(单位:万)'])) 123 #将热度的数据类型转换成与排名相同的int类型 124 plt.rcParams['font.sans-serif'] = ['SimHei'] 125 # 用来正常显示中文标签 126 plt.rcParams['axes.unicode_minus'] = False 127 # 用来正常显示负号 128 N=20 129 x=np.random.rand(N) 130 y=np.random.rand(N) 131 size=50 132 plt.xlabel("排名") 133 plt.ylabel("热度") 134 plt.scatter(x,y,size,color='b',alpha=0.5,marker="o") 135 #散点图 kind='reg' 136 sns.jointplot(x="排名",y="热度(单位:万)",data=df,kind='reg') 137 # kind='hex' 138 sns.jointplot(x="排名",y="热度(单位:万)",data=df,kind='hex') 139 # kind='kde' 140 sns.jointplot(x="排名",y="热度(单位:万)",data=df,kind="kde",space=0,color='g') 141 #求出排名、截距 142 x=np.array(df['排名']).reshape(-1,1) 143 y=np.array(df['热度(单位:万)']).reshape(-1,1) 144 predict_model.fit(x,y) 145 x=np.array(df['排名']).reshape(-1,1) 146 y=np.array(df['热度(单位:万)']).reshape(-1,1) 147 predict_model.fit(x,y) 148 print("回归系数为:",predict_model.coef_) 149 print("截距:",predict_model.intercept_) 150 #有图可知所构造的一元方程的斜率为-49.72,截距为913.56 151 #所以方程为y=-49.72+913.56 152 import matplotlib.pyplot as plt 153 plt.rcParams['font.sans-serif']='SimHei' 154 #设置中文显示 155 plt.figure(figsize=(6,6)) 156 #将画布设定为正方形,则绘制的饼图是正圆 157 label=['热度0-400万第一区限','热度400-800万第二区限','热度800-1200万第三区限','热度1200-1600万第四区限'] 158 #定义饼图的标签,标签是列表 159 explode=[0.01,0.01,0.01,0.01] 160 #设定各项距离圆心n个半径 161 values=[13,4,2,1] 162 # 13,4,2,1 分别为热度在0-400万,400-800,800-1200,1200-1600 区间的个数 163 plt.pie(values,explode=explode,labels=label,autopct='%1.1f%%') 164 #绘制饼图 165 plt.title('热度数值分布值饼图') 166 #绘制标题 167 plt.savefig('./热度数值分布值饼图') 168 #保存图片 169 plt.show() 170 #选择排名和热度两个特征变量,绘制分布图,用最小二乘法分析两个变量间的二次拟合方程和拟合曲线 171 import scipy.optimize as opt 172 colnames=[" ","排名","标题","热度(单位:万)"] 173 df = pd.read_excel('rank.xlsx',skiprows=1,names=colnames) 174 X = df['排名'] 175 Y = df['热度(单位:万)'] 176 Z = df['标题'] 177 def A(): 178 plt.scatter(X,Y,color="blue",linewidth=2) 179 plt.title("排名",color="g") 180 plt.grid() 181 plt.show() 182 def B(): 183 plt.scatter(X,Y,color="b",linewidth=2) 184 plt.title("热度",color="g") 185 plt.grid() 186 plt.show() 187 def fit1(p,x): 188 p=a,b,c 189 #a、b、c 分别为二次函数的系数、截距 190 return a*x*x+b*x+c 191 #用于拟合的二次函数 192 def fit2(p,x,y): 193 return func(p,x)-y 194 def main(): 195 plt.figure(figsize=(10,6)) 196 p0=[0,0,0] 197 #第一次猜测的函数拟合参数 198 Para = opt.leastsq(error,p0,args=(X,Y)) 199 #进行最小二乘拟合 200 a,b,c=Para[0] 201 print("a=",a,"b=",b,"c=",c) 202 plt.scatter(X,Y,color="r",linewidth=2) 203 x=np.linspace(0,20,20) 204 y=a*x*x+b*x+c 205 plt.plot(x,y,color="r",linewidth=2,) 206 plt.title("热度值分布") 207 plt.grid() 208 plt.show() 209 print(A()) 210 print(B()) 211 print(main()) 212 #有图可知所拟合的二次函数的系数和截距分别为3.53,-127.49,1235.256 213 #所以此二次函数为y=3.53*x*x-127.49*x+1235.256 214 #我采用的是使用df.to_csv 将df中的数据保存到csv中,具体路径为C:\Users\86181\rank.xlsx 以此已实现数据持久化 215 filename='rank.csv' 216 headers=['排名','标题','热度'] 217 index=[i for i in range(1,len(html)+1)] 218 df2=pd.DataFrame(html,columns=headers,index=index) 219 pd.DataFrame.to_csv(df2,filename)
五、总结
1.经过对数据的分析和可视化,从回归方程和拟合曲线可以看出散点大部分都落在曲线上,说明热度是随着排名的递增而递增的。又从饼状图图可以看出热度大部分停留在0-400W之间,通过爬虫可分析排名与热度之间的关系,从而画出各种图形。
2.小结:在这次对知乎热搜榜的分析的过程中,我从中学会了不少函数及用法。遇见了很多问题,例如通过爬虫爬取的数据因为列名是中文导致的列名不能对齐、在画图是因为x轴、y轴数据类型不相同而出错要先将两者的数据类型设置为同一类型、文本分析用到的jieba分词的用法需要通过上网搜索,通过观看视频,百度搜索,CSDN搜索等方法去找寻答案。这三个星期来也养成了独立思考的习惯,极大提升了我对python爬虫的兴趣,通过爬虫可以学习到很多知识。