(一)、選題的背景
因為我是個動漫愛好者,所以很喜歡看動漫劇,又叫做番劇,所以我都特別關注嗶哩嗶哩的動漫番劇排行榜的各番劇排名,評分,觀看次數等等。
但是我不知道這幾個數量值有什么關聯。
所以我選擇爬取bilibili的番劇綜合排行榜的排名,番劇名,番劇鏈接,播放量,收藏量,評分,介紹。
並分析其中的排名,播放量,收藏量,評分幾個數量之間是否有明顯的聯系。
(二)、主題式網絡爬蟲設計方案
1.主題式網絡爬蟲名稱
python爬取嗶哩嗶哩網站的番劇排行榜和其中各番劇詳情頁信息
2.主題式網絡爬蟲爬取的內容與數據特征分析
番劇綜合排行榜的排名,番劇名,番劇鏈接,介紹為文本類型。
播放量,收藏量,評分為數值類型。
3.主題式網絡爬蟲設計方案概述
先設計好爬取代碼,然后處理好數據后將其記錄在表格和數據表中,保存並進行數據分析。
(三)、主題頁面的結構特征分析
1.主題頁面的結構與特征分析
我的目的是要爬取bilibili的番劇綜合排行榜的排名,番劇名,番劇鏈接,播放量,收藏量,評分,介紹。
而排行榜網頁中只包含:排名,番劇名,番劇鏈接,播放量,收藏量。
若想要知道它們的評分與番劇介紹需要進入詳情頁面查找。
2.Htmls 頁面解析
排行榜里的各條信息都包含在這個li標簽中
詳情頁面中的評分和介紹都包含在這個div標簽中
3.節點(標簽)查找方法與遍歷方法
經過分析,我打算先從第一個頁面(排行榜頁面)查找出每條符合條件的li標簽,再逐個分析,從中提取想要的信息,比如說番劇名稱,播放量,收藏數,番劇鏈接。
再通過爬取上一步提取的每條番劇鏈接,從第二個頁面查找出每條符合條件的div標簽,再逐個分析,從中提取想要的信息。
(四)、網絡爬蟲程序設計
數據爬取與
首先開始編寫獲取網頁信息的函數(因為我們需要用到該代碼兩次,所以寫為函數比較方便):
1 def get(url):#獲取網頁信息函數 2 head = { 3 "User-Agent": "Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36(KHTML, like Gecko) Chrome / 96.0.4664.110 Safari / 537.36" 4 } 5 #模擬瀏覽器頭部信息 6 request = urllib.request.Request(url, headers=head) 7 #攜帶着這個包含着我的設備型號的頭部信息去訪問這個網址 8 html = "" 9 #用字符串對它進行存儲 10 time.sleep(0.1) 11 #添加延時,防止錯誤 12 13 try: 14 #處理可能會發生的錯誤,比如說網絡問題等,並輸出錯誤代碼,如404 15 response = urllib.request.urlopen(request) 16 html = response.read().decode("utf-8") 17 18 except urllib.error.URLError as e: 19 20 if hasattr(e, "code"): 21 print(e.code) 22 23 if hasattr(e, "reason"): 24 print(e.reason) 25 26 soup = BeautifulSoup(html,"html.parser") 27 #解析數據 28 return soup
測試輸出獲取的信息,分析信息內容得知該網頁可以用這種方式爬取我們想要的數據。
通過網頁檢查工具得知,排名里的番劇信息都分別包含於一個class="rank-item"的標簽li之中,所以
1 for item1 in soup.find_all('li',class_="rank-item"): 2 # 查找符合條件的li標簽,逐一分析即遍歷(在輸出的標簽li列表中,逐一進行查找標簽li中我們需要的信息,並保存於新的列表datalist中)
測試輸出了一條符合條件的li標簽,分析其中的內容:得到想要的信息位置特征。
比如說包含番劇鏈接信息的文本的前面有“<a href="文本,后面有"" target"文本
用re庫正則表達式提取想要的信息,例如:
1 f_link = re.compile(r'(.*?)" target') 2 # 查找番劇鏈接 3 f_title = re.compile(r'<a class="title".*target="_blank">(.*?)</a>') 4 # 查找番劇標題 5 f_watchtext = re.compile(r'<img alt="play".*/>(.*?)</span> <span class="data-box">',re.S) 6 # 查找番劇播放量 7 f_liketext = re.compile(r'<img alt="follow".*/>(.*?)</span>',re.S) 8 # 查找番劇收藏數
想要獲取番劇的評分和介紹,需要點擊進入番劇詳情頁面獲取。並不能在排行榜頁面直接獲取。
所以要再分別爬取排名中各個番劇詳情鏈接,以得到評分和介紹數據。
所以要在li標簽的遍歷中用獲取到的詳情頁面鏈接link作為目標,再進行爬取,用於獲取評分和番劇介紹數據:
1 soup_1= get(link) 2 #用獲取到的鏈接link作為目標,用於獲取評分和番劇介紹數據
並且,如果某些詳情頁面沒有評分信息,為防止報錯,將沒有介紹信息的番劇,介紹欄寫上“暫無”,將沒有評分信息的番劇的評分以總體評分的平均分來記錄,方便后期數據分析。
1 if re.findall(f_score,item2) == []: 2 #如果找不到番劇評分 3 list2.append(f_no) 4 #記錄無評分信息的番劇的排名 5 score = "暫無" 6 scorelist2.append("暫無") 7 #在評分列表2中占一個位 8 else: 9 score = re.findall(f_score,item2)[0] 10 scorelist1.append(float(score)) 11 #記錄在評分列表1中,用於求平均分 12 scorelist2.append(score) 13 #記錄下評分列表2中,用於最終輸出
1 score = round(mean(scorelist1),1) 2 #求番劇評分的平均值,保留一位小數 3 for i in list2: 4 scorelist2[i-1] = str(score) 5 #將沒有評分信息的項替換為評分平均值
總的爬取部分見完整代碼,太長了就不單獨放進來,最終爬取結果:
對數據進行清洗和處理:
去掉查找的結果中的換行符和空格,分析結果,發現播放量和收藏數中有“億”“萬”等單位,而非純數字,所以編寫函數進行
1 def num_zh(num):#將含"萬"和"億"的數據轉為純數字 2 num = str(num) 3 numYi = num.find('億') 4 numWan = num.find('萬') 5 6 if numYi != -1 and numWan != -1: 7 return int(float(num[:numYi])*1e8 + float(num[numYi+1:numWan])*1e4) 8 9 elif numYi != -1 and numWan == -1: 10 return int(float(num[:numYi])*1e8) 11 12 elif numYi == -1 and numWan != -1: 13 return int(float(num[numYi+1:numWan])*1e4) 14 15 elif numYi == -1 and numWan == -1: 16 return float(num)
轉換后:
數據持久化:
將表格保存在本地,以便數據分析。為防止錯誤,用的是相對保存路徑。並且在保存時檢查文件是否已存在,防止多次運行時因為文件已存在而發生錯誤。
1 #判斷文件是否已存在,防止錯誤 2 my_file = "嗶站番劇綜合排行榜top50.xls" # 文件路徑 3 if os.path.exists(my_file): 4 # 如果文件已存在 5 print('文件已存在,正在覆蓋!') 6 os.remove(my_file) 7 # 刪除
1 book = xlwt.Workbook(encoding="utf-8",style_compression=0) 2 #創建workbook對象 3 sheet = book.add_sheet('嗶站番劇綜合排行榜top50',cell_overwrite_ok=True) 4 #創建工作表 5 col = ("排名","鏈接","標題","播放量","收藏數","評分","介紹") 6 for i in range(0,7): 7 sheet.write(0,i,col[i]) 8 #列名 9 for i in range(0,50): 10 data = datalist[i] 11 for j in range(0,7): 12 sheet.write(i+1,j,data[j]) 13 #數據 14 savepath = ".\\嗶站番劇綜合排行榜top50.xls" 15 book.save(savepath) 16 print('已保存表格!')
輸出結果:
並且輸出一個數據表db文件,進一步保存數據。為防止錯誤,用的是相對保存路徑。
在保存輸出數據表數據的過程中,需要對數據進行特別處理,比如在每個字符串類型的數據前后加上雙引號。
1 my_file = "top50.db" # 文件路徑 2 if os.path.exists(my_file): 3 # 如果文件已存在 4 print('文件已存在,正在覆蓋!') 5 os.remove(my_file) 6 # 刪除
1 sql = ''' 2 create table top50 3 ( 4 id integer primary key autoincrement, 5 no numeric, 6 link text, 7 title varchar, 8 watch numeric, 9 like numeric, 10 score numeric, 11 info varchar 12 ) 13 ''' # 創建數據表,以上分別是"排名"類型為數字,"鏈接"類型為文本,"標題"類型為字符串,"播放量"類型為數字,"收藏數"類型為數字,"評分"類型為數字,"介紹"類型為文本 14 dbpath = "top50.db" 15 conn = sqlite3.connect(dbpath) 16 cursor = conn.cursor() 17 cursor.execute(sql) 18 conn.commit() 19 conn.close() 20 21 conn = sqlite3.connect(dbpath) 22 cur = conn.cursor() 23 for data in datalist: 24 #將所有字符串類型的數據的前后都加上雙引號 25 for index in range(len(data)): 26 27 #跳過不需要加雙引號的數據 28 if index == 0 or index == 3 or index == 4 or index == 5: 29 continue 30 data[index] = '"'+data[index]+'"' 31 sql = ''' 32 insert into top50 ( 33 no,link,title,watch,like,score,info) 34 values(%s)'''%",".join(data) 35 print(sql) 36 cur.execute(sql) 37 conn.commit() 38 39 cur.close() 40 conn.close() 41 print('已保存數據表!')
輸出結果:
進一步數據清洗:
1 #讀取表格信息 2 df=pd.read_excel(r'嗶站番劇綜合排行榜top50.xls') 3 R=pd.DataFrame(df)
1 #檢查是否有重復值 2 print(R.duplicated()) 3 #檢查是否有空值 4 print(R['排名'].isnull().value_counts()) 5 print(R['鏈接'].isnull().value_counts()) 6 print(R['標題'].isnull().value_counts()) 7 print(R['播放量'].isnull().value_counts()) 8 print(R['收藏數'].isnull().value_counts()) 9 print(R['評分'].isnull().value_counts()) 10 print(R['介紹'].isnull().value_counts())
輸出結果:
說明數據沒有相關問題。
數據分析與可視化:
1 ############排名和各類數量關系柱狀圖############ 2 plt.xticks(fontsize=8) 3 plt.yticks(fontsize=12) 4 plt.rcParams['font.sans-serif']=['SimHei'] 5 s = pd.Series(df.播放量,df.排名) 6 s.plot(kind="bar",title="排名和播放量") 7 8 #x標簽 9 plt.xlabel("排名") 10 #y標簽 11 plt.ylabel("播放量") 12 plt.show() 13 14 s = pd.Series(df.收藏數,df.排名) 15 s.plot(kind="bar",title="排名和收藏數") 16 plt.xticks(fontsize=8) 17 plt.yticks(fontsize=12) 18 #x標簽 19 plt.xlabel("排名") 20 #y標簽 21 plt.ylabel("收藏數") 22 plt.show() 23 24 s = pd.Series(df.評分,df.排名) 25 s.plot(kind="bar",title="排名和評分") 26 plt.xticks(fontsize=8) 27 plt.yticks(fontsize=12) 28 #x標簽 29 plt.xlabel("排名") 30 #y標簽 31 plt.ylabel("評分") 32 plt.show()
從排名和播放量和排名和收藏數兩個表可以看推測,排名前幾的番劇都是新出的番劇,所以即使熱度很高,播放量和收藏數不會有舊的優秀番劇那么高。
相信隨時間的過去,這些排名前幾的新番劇都積累比現在高很多的播放量和收藏數,同時被新出的番劇擠下前幾名,而位於中上位置。
根據數據之間的關系,分析兩個變量之間的相關系數,畫出散點圖,並建立變
量之間的回歸方程:
1 ############播放量和收藏數散點圖############ 2 plt.rcParams['font.sans-serif']=['Arial Unicode MS'] 3 plt.rcParams['axes.unicode_minus']=False 4 plt.xticks(fontsize=12) 5 plt.yticks(fontsize=12) 6 #散點 7 plt.scatter(df_1.播放量,df.收藏數, color='b') 8 plt.rcParams['font.sans-serif']=['SimHei'] 9 #x標簽 10 plt.xlabel('播放量') 11 #y標簽 12 plt.ylabel('收藏數') 13 plt.show()
1 ############排名和收藏數散點圖############ 2 plt.rcParams['font.sans-serif']=['Arial Unicode MS'] 3 plt.rcParams['axes.unicode_minus']=False 4 plt.xticks(fontsize=12) 5 plt.yticks(fontsize=12) 6 #散點 7 plt.scatter(df.排名,df.收藏數, color='r') 8 plt.rcParams['font.sans-serif']=['SimHei'] 9 #x標簽 10 plt.xlabel('排名') 11 #y標簽 12 plt.ylabel('收藏數') 13 plt.show()
1 ############播放量和收藏數線性回歸############ 2 predict_model=LinearRegression() 3 x=df_1[["播放量"]] 4 y=df["收藏數"] 5 predict_model.fit(x,y) 6 print("回歸方程系數為{}".format( predict_model.coef_)) 7 print("回歸方程截距:{0:2f}".format( predict_model.intercept_)) 8 9 x0=np.array(df_1['播放量']) 10 y0=np.array(df['收藏數']) 11 def func(x,c0): 12 a,b,c=c0 13 return a*x**2+b*x+c 14 def errfc(c0,x,y): 15 return y-func(x,c0) 16 c0=[0,2,3] 17 c1=opt.leastsq(errfc,c0,args=(x0,y0))[0] 18 a,b,c=c1 19 print(f"擬合方程為:y={a}*x**2+{b}*x+{c}") 20 chinese=matplotlib.font_manager.FontProperties(fname='C:\Windows\Fonts\simsun.ttc') 21 plt.plot(x0,y0,"ob",label="樣本數據") 22 plt.plot(x0,func(x0,c1),"r",label="擬合曲線") 23 #x標簽 24 plt.xlabel("播放量") 25 #y標簽 26 plt.ylabel("收藏數") 27 plt.legend(loc=3,prop=chinese) 28 plt.show()
1 ############排名和收藏數線性回歸############ 2 predict_model=LinearRegression() 3 x=df[["排名"]] 4 y=df["收藏數"] 5 predict_model.fit(x,y) 6 print("回歸方程系數為{}".format( predict_model.coef_)) 7 print("回歸方程截距:{0:2f}".format( predict_model.intercept_)) 8 9 x0=np.array(df['排名']) 10 y0=np.array(df['收藏數']) 11 def func(x,c0): 12 a,b,c=c0 13 return a*x**2+b*x+c 14 def errfc(c0,x,y): 15 return y-func(x,c0) 16 c0=[0,2,3] 17 c1=opt.leastsq(errfc,c0,args=(x0,y0))[0] 18 a,b,c=c1 19 print(f"擬合方程為:y={a}*x**2+{b}*x+{c}") 20 chinese=matplotlib.font_manager.FontProperties(fname='C:\Windows\Fonts\simsun.ttc') 21 plt.plot(x0,y0,"or",label="樣本數據") 22 plt.plot(x0,func(x0,c1),"b",label="擬合曲線") 23 #x標簽 24 plt.xlabel("排名") 25 #y標簽 26 plt.ylabel("收藏數") 27 plt.legend(loc=3,prop=chinese) 28 plt.show()
1 ############評分密度直方圖############ 2 plt.figure(figsize=(6,6)) 3 plt.suptitle('評分密度直方圖',fontsize=20) 4 plt.xticks(fontsize=20) 5 plt.yticks(fontsize=20) 6 plt.xlabel("評分密度",fontsize=20) 7 plt.ylabel(" ",fontsize=20) 8 sns.distplot(df["評分"]) 9 plt.grid() 10 plt.show()
1 ############評分分布餅圖############ 2 matplotlib.rcParams['font.sans-serif'] = ['SimHei'] 3 matplotlib.rcParams['axes.unicode_minus'] = False 4 # 設置字體 5 pf = [0, 0, 0, 0, 0, 0, 0] #用來記錄特定條件出現的次數 6 #開始逐個分析評分 7 for i in range(len(df.評分)): 8 if df.評分[i] > 9.5: 9 pf[0] = pf[0] + 1 10 if df.評分[i] > 9.0 and df.評分[i] <= 9.5: 11 pf[1] = pf[1] + 1 12 if df.評分[i] > 8.5 and df.評分[i] <= 9.0: 13 pf[2] = pf[2] + 1 14 if df.評分[i] > 8.0 and df.評分[i] <= 8.5: 15 pf[3] = pf[3] + 1 16 if df.評分[i] > 7.5 and df.評分[i] <= 8.0: 17 pf[4] = pf[4] + 1 18 if df.評分[i] > 7.0 and df.評分[i] <= 7.5: 19 pf[5] = pf[5] + 1 20 if df.評分[i] <= 7.0: 21 pf[6] = pf[6] + 1 22 #print(pf) 23 label = '>9.5', '9.0-9.5', '8.5-9.0', '8.0-8.5', '7.5-8.0', '7.0-7.5', '<7.0'#標題 24 color = 'red', 'orange', 'yellow', 'pink', 'green', 'blue' #顏色 25 cs = [0, 0, 0, 0, 0, 0, 0] 26 #用來顯示百分比占比 27 explode = [0, 0, 0, 0, 0, 0, 0] 28 #每塊離中心的距離; 29 for i in range(0,7): # 計算 30 #print(i) 31 cs[i] = pf[i] * 2 32 explode[i] = pf[i] / 500 33 #print(cs) 34 #開始配置圖形 35 pie = plt.pie(cs, colors=color, explode=explode, labels=label, shadow=True, autopct='%1.1f%%') 36 for font in pie[1]: 37 font.set_size(12) 38 for digit in pie[2]: 39 digit.set_size(8) 40 plt.axis('equal') 41 #配置標題,字號大小等: 42 plt.title(u'各個評分占比', fontsize=18) 43 plt.legend(loc=0, bbox_to_anchor=(0.8, 1)) 44 leg = plt.gca().get_legend() 45 ltext = leg.get_texts() 46 plt.setp(ltext, fontsize=8) 47 #顯示視圖 48 plt.show()
經過觀察以上輸出視圖,我發現其中只有排名和收藏數有明顯的聯系,排名越前收藏數越高。排行榜前50名的評分都集中在比較高的位置。
完整代碼:
1 import os 2 #包含判斷文件存在性功能的的庫 3 from bs4 import BeautifulSoup 4 #導入網頁解析相關的庫 5 import re 6 #導入進行文字匹配相關的庫 7 import time 8 #導入延時操作相關的庫 9 import urllib.request,urllib.error 10 #導入制定URL獲取網頁數據相關的庫 11 import xlwt 12 #導入excel操作相關的庫 13 import sqlite3 14 #導入SQLite數據庫操作相關的庫 15 from numpy import * 16 #用於求列表的平均值 17 import xlrd 18 import pandas as pd 19 import pandas as np 20 import matplotlib.pyplot as plt 21 import matplotlib 22 import scipy.optimize as opt 23 import seaborn as sns 24 from sklearn.linear_model import LinearRegression 25 #導入數據清洗和分析相關的庫 26 27 28 ############爬取信息############ 29 def get(url):#獲取網頁信息函數 30 head = { 31 "User-Agent": "Mozilla / 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36(KHTML, like Gecko) Chrome / 96.0.4664.110 Safari / 537.36" 32 } 33 #模擬瀏覽器頭部信息 34 request = urllib.request.Request(url, headers=head) 35 #攜帶着這個包含着我的設備型號的頭部信息去訪問這個網址 36 html = "" 37 #用字符串對它進行存儲 38 time.sleep(0.1) 39 #添加延時,防止錯誤 40 41 try: 42 #處理可能會發生的錯誤,比如說網絡問題等,並輸出錯誤代碼,如404 43 response = urllib.request.urlopen(request) 44 html = response.read().decode("utf-8") 45 46 except urllib.error.URLError as e: 47 48 if hasattr(e, "code"): 49 print(e.code) 50 51 if hasattr(e, "reason"): 52 print(e.reason) 53 54 soup = BeautifulSoup(html,"html.parser") 55 #解析數據 56 return soup 57 58 def num_zh(num):#將含"萬"和"億"的數據轉為純數字 59 num = str(num) 60 numYi = num.find('億') 61 numWan = num.find('萬') 62 63 if numYi != -1 and numWan != -1: 64 return int(float(num[:numYi])*1e8 + float(num[numYi+1:numWan])*1e4) 65 66 elif numYi != -1 and numWan == -1: 67 return int(float(num[:numYi])*1e8) 68 69 elif numYi == -1 and numWan != -1: 70 return int(float(num[numYi+1:numWan])*1e4) 71 72 elif numYi == -1 and numWan == -1: 73 return float(num) 74 75 baseurl = "https://www.bilibili.com/v/popular/rank/bangumi" 76 soup = get(baseurl) 77 # 測試輸出html信息,print(soup) 78 datalist = [] 79 # 創建列表用來保持數據 80 f_no = 0#排名編號 81 list1 = [] 82 list2=[] 83 scorelist1=[] 84 scorelist2=[] 85 #建立列表用於存放數據 86 87 for item1 in soup.find_all('li',class_="rank-item"): 88 #查找符合條件的li標簽,逐一分析即遍歷(在輸出的標簽li列表中,逐一進行查找標簽li中我們需要的信息,並保存於新的列表datalist中) 89 item1 = str(item1) 90 f_no = f_no+1 91 # 編寫排名編號 92 print('第'+str(f_no)+'條') 93 f_link = re.compile(r'<a href="(.*?)" target') 94 # 查找番劇鏈接 95 f_title = re.compile(r'<a class="title".*target="_blank">(.*?)</a>') 96 # 查找番劇標題 97 f_watchtext = re.compile(r'<img alt="play".*/>(.*?)</span> <span class="data-box">',re.S) 98 # 查找番劇播放量 99 f_liketext = re.compile(r'<img alt="follow".*/>(.*?)</span>',re.S) 100 # 查找番劇收藏數 101 102 print('排名:'+str(f_no)) 103 list1.append(str(f_no)) 104 link = 'https:'+re.findall(f_link,item1)[0]#補充鏈接頭部缺少的https: 105 print('番劇鏈接:'+link) 106 list1.append(link) 107 title = re.findall(f_title,item1)[0] 108 print('番劇標題:'+title) 109 list1.append(title) 110 watchtext = re.findall(f_watchtext,item1)[0].replace("\n","").strip()#去除內容中的空格與換行符 111 watch = str(num_zh(watchtext)) 112 print('播放量:'+watch) 113 list1.append(watch) 114 liketext = re.findall(f_liketext,item1)[0].replace("\n","").strip()#去除內容中的空格與換行符 115 like = str(num_zh(liketext)) 116 print('收藏數:'+like) 117 list1.append(like) 118 119 soup_1= get(link) 120 #用獲取到的鏈接link作為目標,爬取番劇的詳情頁面,用於獲取評分和番劇介紹數據 121 #測試輸出 print(soup_1) 122 123 item2 = soup_1.find_all('div',class_="media-right") 124 item2 = str(item2) 125 126 f_info = re.compile(r'<span class="absolute">(.*?)</span>',re.S) 127 # 查找番劇介紹 128 f_score = re.compile(r'<h4 class="score">(.*?)</h4>') 129 # 查找番劇評分 130 131 if re.findall(f_info,item2) == []: 132 #如果找不到番劇介紹 133 info = '暫無' 134 else: 135 info = re.findall(f_info,item2)[0].replace("\n","") 136 137 list1.append(info) 138 139 if re.findall(f_score,item2) == []: 140 #如果找不到番劇評分 141 list2.append(f_no) 142 #記錄無評分信息的番劇的排名 143 score = "暫無" 144 scorelist2.append("暫無") 145 #在評分列表2中占一個位 146 else: 147 score = re.findall(f_score,item2)[0] 148 scorelist1.append(float(score)) 149 #記錄在評分列表1中,用於求平均分 150 scorelist2.append(score) 151 #記錄下評分列表2中,用於最終輸出 152 print('評分:'+score) 153 print('介紹:'+info) 154 score = round(mean(scorelist1),1) 155 #求番劇評分的平均值,保留一位小數 156 for i in list2: 157 scorelist2[i-1] = str(score) 158 #將沒有評分信息的項替換為評分平均值 159 160 for i in range(0,50): 161 #將記錄的數據,按一定格式記錄於最終的表datalist中 162 data=[] 163 data.append(list1[i*6]) 164 data.append(list1[i*6+1]) 165 data.append(list1[i*6+2]) 166 data.append(list1[i*6+3]) 167 data.append(list1[i*6+4]) 168 data.append(scorelist2[i]) 169 data.append(list1[i*6+5]) 170 datalist.append(data) 171 #測試輸出 print(datalist) 172 173 174 ############將數據保存在表格中############ 175 book = xlwt.Workbook(encoding="utf-8",style_compression=0) 176 #創建workbook對象 177 sheet = book.add_sheet('嗶站番劇綜合排行榜top50',cell_overwrite_ok=True) 178 #創建工作表 179 col = ("排名","鏈接","標題","播放量","收藏數","評分","介紹") 180 for i in range(0,7): 181 sheet.write(0,i,col[i]) 182 #列名 183 for i in range(0,50): 184 data = datalist[i] 185 for j in range(0,7): 186 sheet.write(i+1,j,data[j]) 187 #數據 188 savepath = ".\\嗶站番劇綜合排行榜top50.xls"# 文件路徑 189 190 #判斷文件是否已存在,防止錯誤 191 my_file = "嗶站番劇綜合排行榜top50.xls" # 文件路徑 192 if os.path.exists(my_file): 193 # 如果文件已存在 194 print('文件已存在,正在覆蓋!') 195 os.remove(my_file) 196 # 刪除 197 198 book.save(savepath) 199 print('已保存表格!') 200 201 202 ############將數據保存在數據表中############ 203 sql = ''' 204 create table top50 205 ( 206 id integer primary key autoincrement, 207 no numeric, 208 link text, 209 title varchar, 210 watch numeric, 211 like numeric, 212 score numeric, 213 info varchar 214 ) 215 ''' # 創建數據表,以上分別是"排名"類型為數字,"鏈接"類型為文本,"標題"類型為字符串,"播放量"類型為數字,"收藏數"類型為數字,"評分"類型為數字,"介紹"類型為文本 216 dbpath = "top50.db"# 文件路徑 217 218 #判斷文件是否已存在,防止錯誤 219 my_file = "top50.db" # 文件路徑 220 if os.path.exists(my_file): 221 # 如果文件已存在 222 print('文件已存在,正在覆蓋!') 223 os.remove(my_file) 224 # 刪除 225 226 conn = sqlite3.connect(dbpath) 227 cursor = conn.cursor() 228 cursor.execute(sql) 229 conn.commit() 230 conn.close() 231 232 conn = sqlite3.connect(dbpath) 233 cur = conn.cursor() 234 for data in datalist: 235 #將所有字符串類型的數據的前后都加上雙引號 236 for index in range(len(data)): 237 238 #跳過不需要加雙引號的數據 239 if index == 0 or index == 3 or index == 4 or index == 5: 240 continue 241 data[index] = '"'+data[index]+'"' 242 sql = ''' 243 insert into top50 ( 244 no,link,title,watch,like,score,info) 245 values(%s)'''%",".join(data) 246 print(sql) 247 cur.execute(sql) 248 conn.commit() 249 250 cur.close() 251 conn.close() 252 print('已保存數據表!') 253 print("爬取完畢") 254 255 256 ############數據清洗############ 257 #讀取表格信息 258 df=pd.read_excel(r'嗶站番劇綜合排行榜top50.xls') 259 R=pd.DataFrame(df) 260 261 #檢查是否有重復值 262 print(R.duplicated()) 263 #檢查是否有空值 264 print(R['排名'].isnull().value_counts()) 265 print(R['鏈接'].isnull().value_counts()) 266 print(R['標題'].isnull().value_counts()) 267 print(R['播放量'].isnull().value_counts()) 268 print(R['收藏數'].isnull().value_counts()) 269 print(R['評分'].isnull().value_counts()) 270 print(R['介紹'].isnull().value_counts()) 271 272 273 ############數據分析############ 274 df_1=df.sort_values('播放量',ascending=True) 275 #根據播放量升序排序 276 277 278 ############排名和各類數量關系柱狀圖############ 279 plt.xticks(fontsize=8) 280 plt.yticks(fontsize=12) 281 plt.rcParams['font.sans-serif']=['SimHei'] 282 s = pd.Series(df.播放量,df.排名) 283 s.plot(kind="bar",title="排名和播放量") 284 #x標簽 285 plt.xlabel("排名") 286 #y標簽 287 plt.ylabel("播放量") 288 #顯示圖形 289 plt.show() 290 291 s = pd.Series(df.收藏數,df.排名) 292 s.plot(kind="bar",title="排名和收藏數") 293 plt.xticks(fontsize=8) 294 plt.yticks(fontsize=12) 295 #x標簽 296 plt.xlabel("排名") 297 #y標簽 298 plt.ylabel("收藏數") 299 #顯示圖形 300 plt.show() 301 302 s = pd.Series(df.評分,df.排名) 303 s.plot(kind="bar",title="排名和評分") 304 plt.xticks(fontsize=8) 305 plt.yticks(fontsize=12) 306 #x標簽 307 plt.xlabel("排名") 308 #y標簽 309 plt.ylabel("評分") 310 #顯示圖形 311 plt.show() 312 313 314 ############播放量和收藏數散點圖############ 315 plt.rcParams['font.sans-serif']=['Arial Unicode MS'] 316 plt.rcParams['axes.unicode_minus']=False 317 plt.xticks(fontsize=12) 318 plt.yticks(fontsize=12) 319 #散點 320 plt.scatter(df_1.播放量,df.收藏數, color='b') 321 plt.rcParams['font.sans-serif']=['SimHei'] 322 #x標簽 323 plt.xlabel('播放量') 324 #y標簽 325 plt.ylabel('收藏數') 326 #顯示圖形 327 plt.show() 328 329 330 ############排名和收藏數散點圖############ 331 plt.rcParams['font.sans-serif']=['Arial Unicode MS'] 332 plt.rcParams['axes.unicode_minus']=False 333 plt.xticks(fontsize=12) 334 plt.yticks(fontsize=12) 335 #散點 336 plt.scatter(df.排名,df.收藏數, color='r') 337 plt.rcParams['font.sans-serif']=['SimHei'] 338 #x標簽 339 plt.xlabel('排名') 340 #y標簽 341 plt.ylabel('收藏數') 342 #顯示圖形 343 plt.show() 344 345 346 ############播放量和收藏數線性回歸############ 347 predict_model=LinearRegression() 348 x=df_1[["播放量"]] 349 y=df["收藏數"] 350 predict_model.fit(x,y) 351 print("回歸方程系數為{}".format( predict_model.coef_)) 352 print("回歸方程截距:{0:2f}".format( predict_model.intercept_)) 353 354 x0=np.array(df_1['播放量']) 355 y0=np.array(df['收藏數']) 356 def func(x,c0): 357 a,b,c=c0 358 return a*x**2+b*x+c 359 def errfc(c0,x,y): 360 return y-func(x,c0) 361 c0=[0,2,3] 362 c1=opt.leastsq(errfc,c0,args=(x0,y0))[0] 363 a,b,c=c1 364 print(f"擬合方程為:y={a}*x**2+{b}*x+{c}") 365 chinese=matplotlib.font_manager.FontProperties(fname='C:\Windows\Fonts\simsun.ttc') 366 plt.plot(x0,y0,"ob",label="樣本數據") 367 plt.plot(x0,func(x0,c1),"r",label="擬合曲線") 368 #x標簽 369 plt.xlabel("播放量") 370 #y標簽 371 plt.ylabel("收藏數") 372 plt.legend(loc=3,prop=chinese) 373 #顯示圖形 374 plt.show() 375 376 377 ############排名和收藏數線性回歸############ 378 predict_model=LinearRegression() 379 x=df[["排名"]] 380 y=df["收藏數"] 381 predict_model.fit(x,y) 382 print("回歸方程系數為{}".format( predict_model.coef_)) 383 print("回歸方程截距:{0:2f}".format( predict_model.intercept_)) 384 385 x0=np.array(df['排名']) 386 y0=np.array(df['收藏數']) 387 def func(x,c0): 388 a,b,c=c0 389 return a*x**2+b*x+c 390 def errfc(c0,x,y): 391 return y-func(x,c0) 392 c0=[0,2,3] 393 c1=opt.leastsq(errfc,c0,args=(x0,y0))[0] 394 a,b,c=c1 395 print(f"擬合方程為:y={a}*x**2+{b}*x+{c}") 396 chinese=matplotlib.font_manager.FontProperties(fname='C:\Windows\Fonts\simsun.ttc') 397 plt.plot(x0,y0,"or",label="樣本數據") 398 plt.plot(x0,func(x0,c1),"b",label="擬合曲線") 399 #x標簽 400 plt.xlabel("排名") 401 #y標簽 402 plt.ylabel("收藏數") 403 plt.legend(loc=3,prop=chinese) 404 #顯示圖形 405 plt.show() 406 407 408 ############評分密度直方圖############ 409 plt.figure(figsize=(6,6)) 410 plt.suptitle('評分密度直方圖',fontsize=20) 411 plt.xticks(fontsize=20) 412 plt.yticks(fontsize=20) 413 plt.xlabel("評分密度",fontsize=20) 414 plt.ylabel(" ",fontsize=20) 415 sns.distplot(df["評分"]) 416 plt.grid() 417 #顯示圖形 418 plt.show() 419 420 421 ############評分分布餅圖############ 422 matplotlib.rcParams['font.sans-serif'] = ['SimHei'] 423 matplotlib.rcParams['axes.unicode_minus'] = False 424 # 設置字體 425 pf = [0, 0, 0, 0, 0, 0, 0] #用來記錄特定條件出現的次數 426 #開始逐個分析評分 427 for i in range(len(df.評分)): 428 if df.評分[i] > 9.5: 429 pf[0] = pf[0] + 1 430 if df.評分[i] > 9.0 and df.評分[i] <= 9.5: 431 pf[1] = pf[1] + 1 432 if df.評分[i] > 8.5 and df.評分[i] <= 9.0: 433 pf[2] = pf[2] + 1 434 if df.評分[i] > 8.0 and df.評分[i] <= 8.5: 435 pf[3] = pf[3] + 1 436 if df.評分[i] > 7.5 and df.評分[i] <= 8.0: 437 pf[4] = pf[4] + 1 438 if df.評分[i] > 7.0 and df.評分[i] <= 7.5: 439 pf[5] = pf[5] + 1 440 if df.評分[i] <= 7.0: 441 pf[6] = pf[6] + 1 442 #print(pf) 443 label = '>9.5', '9.0-9.5', '8.5-9.0', '8.0-8.5', '7.5-8.0', '7.0-7.5', '<7.0'#標題 444 color = 'red', 'orange', 'yellow', 'pink', 'green', 'blue' #顏色 445 cs = [0, 0, 0, 0, 0, 0, 0] 446 #用來顯示百分比占比 447 explode = [0, 0, 0, 0, 0, 0, 0] 448 #每塊離中心的距離; 449 for i in range(0,7): # 計算 450 #print(i) 451 cs[i] = pf[i] * 2 452 explode[i] = pf[i] / 500 453 #print(cs) 454 #開始配置圖形 455 pie = plt.pie(cs, colors=color, explode=explode, labels=label, shadow=True, autopct='%1.1f%%') 456 for font in pie[1]: 457 font.set_size(12) 458 for digit in pie[2]: 459 digit.set_size(8) 460 plt.axis('equal') 461 #配置標題,字號大小等: 462 plt.title(u'各個評分占比', fontsize=18) 463 plt.legend(loc=0, bbox_to_anchor=(0.8, 1)) 464 leg = plt.gca().get_legend() 465 ltext = leg.get_texts() 466 plt.setp(ltext, fontsize=8) 467 #顯示視圖 468 plt.show()
總結:
以上爬取了我感興趣的網站信息,讓我更加了解自己喜歡的東西,也驗證了我的猜想,果然新興的熱門番劇,往往擁有非常高的熱度卻沒有非常高的播放量,它們的播放量還需要時間積累,而積累的時間內會有新的熱門番劇發行,從而導致排名前幾的熱度高卻播放量一般,前幾偏后幾名的擁有非常高的播放量,卻熱度不再霸榜,再后面就是相對平平無奇的了。
在做這項爬取任務時,我體會到python的魅力所在,體會到編程的樂趣。
在編寫時常常因為數據類型錯誤,網絡響應沒有代碼運行的快,查找不到相關信息引發報錯,文件已存在等等一系列問題導致運行失敗,而我仔細尋找原因,一個個去修正。
如果數據類型錯誤,則一一對正。如果網絡響應問題報錯,則加入延時。通過查找不到相關信息引發報錯,則相應對策,如輸出“暫無”等等。這使我從中學到很多經驗。