python動態柱狀圖圖表視覺化:歷年軟科中國大學排行
本來想參照: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: ,
// 時間標籤座標。建議x:1000 y:-50開始嘗試,預設位置為x:null,y:null
dateLabel_x: ,
dateLabel_y: -,
最終效果:
至此,就全部完成了。
看起來簡單,還是得要自己動手才行。