本來想參照:https://mp.weixin.qq.com/s/e7Wd7aEatcLFGgJUDkg-EQ搞一個往年程式語言動態圖的,奈何找不到資料,有資料來源的歡迎在評論區留言。
這裡找到了一個,是2020年6月的程式語言排行,供大家看一下:https://www.tiobe.com/tiobe-index/
我們要實現的效果是:
大學排名來源:http://www.zuihaodaxue.com/ARWU2003.html
部分截圖:
在http://www.zuihaodaxue.com/ARWU2003.html中的年份可以選擇,我們解析的頁面就有了:
"http://www.zuihaodaxue.com/ARWU%s.html" % str(year)
初步獲取頁面的html資訊的程式碼:
def get_one_page(year): try: headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36' } url = "http://www.zuihaodaxue.com/ARWU%s.html" % str(year) response=requests.get(url,headers=headers) if response.status_code == 200: return response.content except RequestException: print('爬取失敗')
我們在頁面上進行檢查:
資料是儲存在表格中的,這樣我們就可以利用pandas獲取html中的資料,基本語法:
tb = pd.read_html(url)[num]
其中的num是標識網頁中的第幾個表格,這裡只有一個表格,所以標識為0。初步的解析程式碼就有了:
def parse_on_page(html,i): tb=pd.read_html(html)[0] return tb
我們還要將爬取下來的資料儲存到csv檔案中,基本程式碼如下:
def save_csv(tb): start_time=time.time() tb.to_csv(r'university.csv', mode='a', encoding='utf_8_sig', header=True, index=0) endtime = time.time()-start_time print('程式執行了%.2f秒' %endtime)
最後是一個主函式,別忘了還有需要匯入的包:
import requests from requests.exceptions import RequestException import pandas as pd import time def main(year): for i in range(2003,year): html=get_one_page(i) tb=parse_on_page(html,i) #print(tb) save_csv(tb) if __name__ == "__main__": main(2004)
執行之後,我們在同級目錄下就可以看到university.csv,部分內容如下:
存在兩個問題:
(1)缺少年份
(2)最後一列沒有用
(3)國家由於是圖片表示,沒有爬取下來
(4)排名100以後的是一個區間
我們接下來一一解決:
(1)刪掉沒用的列
def parse_on_page(html,i): tb=pd.read_html(html)[0] # 重新命名錶格列,不需要的列用數字表示 tb.columns = ['world rank','university', 2, 'score',4] tb.drop([2,4],axis=1,inplace=True) return tb
新的結果:
(2) 對100以後的進行唯一化,增加一列index作為排名標識
tb['index_rank'] = tb.index tb['index_rank'] = tb['index_rank'].astype(int) + 1
(3)新增加年份
tb['year'] = i
(4)新增加國家
首先我們進行檢查:
發現國家在td->a>img下的影像路徑中有名字:UnitedStates。 我們可以取出src屬性,並用正則匹配名字即可。
def get_country(html): soup = BeautifulSoup(html,'lxml') countries = soup.select('td > a > img') lst = [] for i in countries: src = i['src'] pattern = re.compile('flag.*\/(.*?).png') country = re.findall(pattern,src)[0] lst.append(country) return lst
然後這麼使用:
# read_html沒有爬取country,需定義函式單獨爬取 tb['country'] = get_country(html)
最終解析的整體函式如下:
def parse_on_page(html,i): tb=pd.read_html(html)[0] # 重新命名錶格列,不需要的列用數字表示 tb.columns = ['world rank','university', 2, 'score',4] tb.drop([2,4],axis=1,inplace=True) tb['index_rank'] = tb.index tb['index_rank'] = tb['index_rank'].astype(int) + 1 tb['year'] = i # read_html沒有爬取country,需定義函式單獨爬取 tb['country'] = get_country(html) return tb
執行之後:
最後我們要提取屬於中國部分的相關資訊:
首先將年份改一下,獲取到2019年為止的資訊:
if __name__ == "__main__": main(2019)
然後我們提取到中國高校的資訊,直接看程式碼理解:
def analysis(): df = pd.read_csv('university.csv') # 包含港澳臺 # df = df.query("(country == 'China')|(country == 'China-hk')|(country == 'China-tw')|(country == 'China-HongKong')|(country == 'China-Taiwan')|(country == 'Taiwan,China')|(country == 'HongKong,China')")[['university','year','index_rank']] # 只包括內地 df = df.query("(country == 'China')") df['index_rank_score'] = df['index_rank'] # 將index_rank列轉為整形 df['index_rank'] = df['index_rank'].astype(int) # 美國 # df = df.query("(country == 'UnitedStates')|(country == 'USA')") #求topn名 def topn(df): top = df.sort_values(['year','index_rank'],ascending = True) return top[:20].reset_index() df = df.groupby(by =['year']).apply(topn) # 更改列順序 df = df[['university','index_rank_score','index_rank','year']] # 重新命名列 df.rename (columns = {'university':'name','index_rank_score':'type','index_rank':'value','year':'date'},inplace = True) # 輸出結果 df.to_csv('university_ranking.csv',mode ='w',encoding='utf_8_sig', header=True, index=False) # index可以設定
本來是想爬取從2003年到2019年的,執行時發現從2005年開始,頁面不一樣了,多了一列:
方便起見,我們就只從2005年開始了,還需要修改一下程式碼:
# 重新命名錶格列,不需要的列用數字表示 tb.columns = ['world rank','university', 2,3, 'score',5] tb.drop([2,3,5],axis=1,inplace=True)
最後是整體程式碼:
import requests from requests.exceptions import RequestException import pandas as pd import time from bs4 import BeautifulSoup import re def get_one_page(year): try: headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36' } url = "http://www.zuihaodaxue.com/ARWU%s.html" % str(year) response=requests.get(url,headers=headers) if response.status_code == 200: return response.content except RequestException: print('爬取失敗') def parse_on_page(html,i): tb=pd.read_html(html)[0] # 重新命名錶格列,不需要的列用數字表示 tb.columns = ['world rank','university', 2,3, 'score',5] tb.drop([2,3,5],axis=1,inplace=True) tb['index_rank'] = tb.index tb['index_rank'] = tb['index_rank'].astype(int) + 1 tb['year'] = i # read_html沒有爬取country,需定義函式單獨爬取 tb['country'] = get_country(html) return tb def save_csv(tb): start_time=time.time() tb.to_csv(r'university.csv', mode='a', encoding='utf_8_sig', header=True, index=0) endtime = time.time()-start_time print('程式執行了%.2f秒' %endtime) # 提取國家名稱 def get_country(html): soup = BeautifulSoup(html,'lxml') countries = soup.select('td > a > img') lst = [] for i in countries: src = i['src'] pattern = re.compile('flag.*\/(.*?).png') country = re.findall(pattern,src)[0] lst.append(country) return lst def analysis(): df = pd.read_csv('university.csv') # 包含港澳臺 # df = df.query("(country == 'China')|(country == 'China-hk')|(country == 'China-tw')|(country == 'China-HongKong')|(country == 'China-Taiwan')|(country == 'Taiwan,China')|(country == 'HongKong,China')")[['university','year','index_rank']] # 只包括內地 df = df.query("(country == 'China')") df['index_rank_score'] = df['index_rank'] # 將index_rank列轉為整形 df['index_rank'] = df['index_rank'].astype(int) # 美國 # df = df.query("(country == 'UnitedStates')|(country == 'USA')") #求topn名 def topn(df): top = df.sort_values(['year','index_rank'],ascending = True) return top[:20].reset_index() df = df.groupby(by =['year']).apply(topn) # 更改列順序 df = df[['university','index_rank_score','index_rank','year']] # 重新命名列 df.rename (columns = {'university':'name','index_rank_score':'type','index_rank':'value','year':'date'},inplace = True) # 輸出結果 df.to_csv('university_ranking.csv',mode ='w',encoding='utf_8_sig', header=True, index=False) # index可以設定 def main(year): for i in range(2005,year): html=get_one_page(i) tb=parse_on_page(html,i) save_csv(tb) print(i,'年排名提取完成完成') analysis() if __name__ == "__main__": main(2019)
執行之後會有一個university_ranking.csv,部分內容如下:
接下來就是視覺化過程了。
1、 首先,到作者的github主頁:
https://github.com/Jannchie/Historical-ranking-data-visualization-based-on-d3.js
2、克隆倉庫檔案,使用git
# 克隆專案倉庫 git clone https://github.com/Jannchie/Historical-ranking-data-visualization-based-on-d3.js # 切換到專案根目錄 cd Historical-ranking-data-visualization-based-on-d3.js # 安裝依賴 npm install
這裡如果git clone超時可參考:
https://www.cnblogs.com/xiximayou/p/12305209.html
需要注意的是,這裡的npm是我之前裝node.js裝了的,沒有的自己需要裝以下。
在執行npm install時會報錯:
先執行:
npm init
之後一直回車即可:
再執行npm install
任意瀏覽器開啟bargraph.html
網頁,點選選擇檔案,然後選擇前面輸出的university_ranking.csv
檔案,看下效果:
只能製作動圖上傳了。
可以看到,有了大致的視覺化效果,但還存在很多瑕疵,比如:表順序顛倒了、字型不合適、配色太花哨等。可不可以修改呢?
當然是可以的,只需要分別修改資料夾中這幾個檔案的引數就可以了:
-
config.js 全域性設定各項功能的開關,比如配色、字型、文字名稱、反轉圖表等等功能;
-
color.css 修改柱形圖的配色;
-
stylesheet.css 具體修改配色、字型、文字名稱等的css樣式;
-
visual.js 更進一步的修改,比如圖表的透明度等。
知道在哪裡修改了以後,那麼,如何修改呢?很簡單,只需要簡單的幾步就可以實現:
-
開啟網頁,
右鍵-檢查
,箭頭指向想要修改的元素,然後在右側的css樣式表裡,雙擊各項引數修改引數,修改完元素就會發生變化,可以不斷微調,直至滿意為止。
-
把引數複製到四個檔案中對應的檔案裡並儲存。
-
Git Bash執行
npm run build
,之後重新整理網頁就可以看到優化後的效果。(我發現這一步其實不需要,而且會報錯,我直接修改config.js之後執行也成功了)
這裡我主要修改的是config.js的以下項:
// 倒序,使得最短的條位於最上方 reverse: true, // 附加資訊內容。 // left label itemLabel: "本年度第一大學", // right label typeLabel: "世界排名", //為了避免名稱重疊 item_x: 500, // 時間標籤座標。建議x:1000 y:-50開始嘗試,預設位置為x:null,y:null dateLabel_x: 1000, dateLabel_y: -50,
最終效果:
至此,就全部完成了。
看起來簡單,還是得要自己動手才行。