一、選題背景
隨着科技經濟的發展,社會中發生的重大事件我們都可以從各大軟件中得知,知乎熱榜是我們了解時事的一個重要途徑,但是如果我們沒有那么時間來刷知乎,但是還是想要了解一天中發生的熱門事件,我們該怎么辦呢?在這里,我想到了通過知乎爬蟲的手段,獲取知乎熱榜的標題和簡介,保存到本地文件,,從而獲取到每一天的知乎熱榜內容,這樣,我們只需要查看本地文件內容,就可以快速的了解今天一天的時事。要達到的數據分析目標是 通過爬蟲知乎熱搜榜爬獲的數據畫出排行與熱度的圖形,從而分析排行與熱度的線性關系。
二、主題式網絡爬蟲設計方案
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爬蟲的興趣,通過爬蟲可以學習到很多知識。
