導讀:最近在朋友圈看到一個很酷炫的動態資料視覺化表,介紹了新中國成立後各省GDP的發展歷程,非常驚嘆竟然還有這種操作,也想試試。於是,照葫蘆畫瓢虎,在網上爬取了歷年中國大學學術排行榜,製作了一個中國大學排名Top20強動態表。
作者:蘇克1900
來源:第2大腦(ID:Mocun6)
01 作品介紹
這裡先放一下這個動態表是什麼樣的:
不知道你看完是什麼感覺,至少我是挺震驚的,想看看作者是怎麼做出來的,於是追到了作者的B站主頁,發現了更多有意思的動態影片:
這些作品的作者是:@Jannchie見齊
他的主頁:
https://space.bilibili.com/1850091/#/video
這些會動的圖表是如何做出來的呢?他用到的是一個動態圖形顯示資料的JavaScript庫:D3.js,一種前端技術。難怪不是一般地酷炫。
那麼,如果不會D3.js是不是就做不出來了呢?當然不是,Jannchie非常Open地給出了一個手把手的簡單教程:
https://www.bilibili.com/video/av28087807
他同時還開放了程式原始碼,你只需要做2步就能夠實現:
-
到他的Github主頁下載原始碼到本地電腦:
https://github.com/Jannchie/Historical-ranking-data-visualization-based-on-d3.js
-
開啟dist檔案夾裡面的exampe.csv檔案,放進你想要展示的資料,再用瀏覽器開啟bargraph.html網頁,就可以實現動態效果了。
下麵,我們稍微再說詳細一點,實現這種效果的關鍵點是要有資料。觀察一下上面的作品可以看到,橫向柱狀圖中的資料要滿足兩個條件:一是要有多個對比的物件,二是要在時間上連續。這樣才可以做出動態效果來。
看完後我立馬就有了一個想法:想看看近十年中國的各個大學排名是個什麼情況。下麵我們就透過實體來操作下。
02 案例操作:中國大學Top20強
1. 資料來源
世界上最權威的大學排名有4類,分別是:
-
原上海交通大學的ARWU
http://www.shanghairanking.com/ARWU2018.html
-
英國教育組織的QS
https://www.topuniversities.com/university-rankings/world-university-rankings/2018
-
泰晤士的THE
https://www.timeshighereducation.com/world-university-rankings
-
美國的usnews
https://www.usnews.com/best-colleges/rankings
關於,這四類排名的更多介紹,可以看這個:
https://www.zhihu.com/question/20825030/answer/71336291
這裡,我們選取相對比較權威也比較符合國情的第一個ARWU的排名結果。開啟官網,可以看到有英文版和中文版排名,這裡選取中文版。 排名非常齊全,從2003年到最新的2018年都有,非常好。
2. 抓取資料
2.1 分析url
首先,分析一下URL:
1http://www.zuihaodaxue.com/ARWU2018.html
2http://www.zuihaodaxue.com/ARWU2017.html
3...
4http://www.zuihaodaxue.com/ARWU2009.html
可以看到,url非常有規律,只有年份數字在變,很簡單就能構造出for迴圈。
格式如下:
1url = 'http://www.zuihaodaxue.com/ARWU%s.html' % (str(year))
下麵就可以開始寫爬蟲了。
2.2 獲取網頁內容
1import requests
2try:
3 url = 'http://www.zuihaodaxue.com/ARWU%s.html' % (str(year))
4 response = requests.get(url,essay-headers = essay-headers)
5 # 2009-2015用'gbk',2016-2018用'utf-8'
6 if response.status_code == 200:
7 # return response.text # text會亂碼,content沒有問題
8 return response.content
9 return None
10except RequestException:
11print('爬取失敗')
上面需要註意的是,不同年份網頁採用的編碼不同,傳回response.test會亂碼,傳回response.content則不會。關於編碼亂碼的問題,以後單獨寫一篇文章。
2.3 解析表格
用read_html函式一行程式碼來抓取表格,然後輸出:
1tb = pd.read_html(html)[0]
2print(tb)
可以看到,很順利地表格就被抓取了下來:
但是表格需要進行處理,比如刪除掉不需要的評分列,增加年份列等,程式碼實現如下:
1tb = pd.read_html(html)[0]
2# 重新命名錶格列,不需要的列用數字表示
3tb.columns = ['world rank','university', 2,3, 'score',5,6,7,8,9,10]
4tb.drop([2,3,5,6,7,8,9,10],axis = 1,inplace = True)
5# 刪除後面不需要的評分列
6# rank列100名後是區間,需需唯一化,增加一列index作為排名
7tb['index_rank'] = tb.index
8tb['index_rank'] = tb['index_rank'].astype(int) + 1
9
10# 增加一列年份列
11tb['year'] = i
12# read_html沒有爬取country,需定義函式單獨爬取
13tb['country'] = get_country(html)
14return tb
需要註意的是,國家沒有被抓取下來,因為國家是用的圖片表示的,定位到國家程式碼位置:
可以看到美國是用英文的USA表示的,那麼我們可以單獨提取出src屬性,然後用正則提取出國家名稱就可以了,程式碼實現如下:
1# 提取國家名稱
2def get_country(html):
3 soup = BeautifulSoup(html,'lxml')
4 countries = soup.select('td > a > img')
5 lst = []
6 for i in countries:
7 src = i['src']
8 pattern = re.compile('flag.*\/(.*?).png')
9 country = re.findall(pattern,src)[0]
10 lst.append(country)
11 return lst
然後,我們就可以輸出一下結果:
1 world rank university score index_rank year country
20 1 哈佛大學 100.0 1 2018 USA
31 2 斯坦福大學 75.6 2 2018 USA
42 3 劍橋大學 71.8 3 2018 UK
53 4 麻省理工學院 69.9 4 2018 USA
64 5 加州大學-伯克利 68.3 5 2018 USA
75 6 普林斯頓大學 61.0 6 2018 USA
86 7 牛津大學 60.0 7 2018 UK
97 8 哥倫比亞大學 58.2 8 2018 USA
108 9 加州理工學院 57.4 9 2018 USA
119 10 芝加哥大學 55.5 10 2018 USA
1210 11 加州大學-洛杉磯 51.2 11 2018 USA
1311 12 康奈爾大學 50.7 12 2018 USA
1412 12 耶魯大學 50.7 13 2018 USA
1513 14 華盛頓大學-西雅圖 50.0 14 2018 USA
1614 15 加州大學-聖地亞哥 47.8 15 2018 USA
1715 16 賓夕法尼亞大學 46.4 16 2018 USA
1816 17 倫敦大學學院 46.1 17 2018 UK
1917 18 約翰霍普金斯大學 45.4 18 2018 USA
2018 19 蘇黎世聯邦理工學院 43.9 19 2018 Switzerland
2119 20 華盛頓大學-聖路易斯 42.1 20 2018 USA
2220 21 加州大學-舊金山 41.9 21 2018 USA
資料很完美,接下來就可以按照D3.js模板中的example.csv檔案的格式作進一步的處理了。
3. 資料處理
這裡先將資料輸出為university.csv檔案,結果見下表:
10年一共5011行×6列資料。接著,讀入該表作進一步資料處理,程式碼如下:
1df = pd.read_csv('university.csv')
2# 包含港澳臺
3# 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']]
4
5# 只包括內地
6df = df.query("(country == 'China')")
7df['index_rank_score'] = df['index_rank']
8# 將index_rank列轉為整形
9df['index_rank'] = df['index_rank'].astype(int)
10
11# 美國
12# df = df.query("(country == 'UnitedStates')|(country == 'USA')")
13
14#求topn名
15def topn(df):
16 top = df.sort_values(['year','index_rank'],ascending = True)
17 return top[:20].reset_index()
18df = df.groupby(by =['year']).apply(topn)
19
20# 更改列順序
21df = df[['university','index_rank_score','index_rank','year']]
22# 重新命名列
23df.rename (columns = {'university':'name','index_rank_score':'type','index_rank':'value','year':'date'},inplace = True)
24
25# 輸出結果
26df.to_csv('university_ranking.csv',mode ='w',encoding='utf_8_sig', essay-header=True, index=False)
27# index可以設定
上面需要註意兩點:
-
可以提取包含港澳臺在內的大中華區所有的大學,也可以只提取內地的大學,還可以提取世界、美國等各種排名。
-
定義了一個求Topn的函式,能夠按年份分別求出各年的前20名大學名單。
開啟輸出的university_ranking.csv檔案:
結果非常好,可以直接作為D3.js的匯入檔案了。
3.1 完整程式碼
將程式碼再稍微完善一下,完整地程式碼如下所示:
1import pandas as pd
2import csv
3import requests
4from requests.exceptions import RequestException
5from bs4 import BeautifulSoup
6import time
7import re
8
9start_time = time.time() #計算程式執行時間
10# 獲取網頁內容
11def get_one_page(year):
12 try:
13 essay-headers = {
14 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
15 }
16 # 英文版
17 # url = 'http://www.shanghairanking.com/ARWU%s.html' % (str(year))
18 # 中文版
19 url = 'http://www.zuihaodaxue.com/ARWU%s.html' % (str(year))
20 response = requests.get(url,essay-headers = essay-headers)
21 # 2009-2015用'gbk',2016-2018用'utf-8'
22 if response.status_code == 200:
23 # return response.text # text會亂碼,content沒有問題
24 # https://stackoverflow.com/questions/17011357/what-is-the-difference-between-content-and-text
25 return response.content
26 return None
27 except RequestException:
28 print('爬取失敗')
29
30# 解析表格
31def parse_one_page(html,i):
32 tb = pd.read_html(html)[0]
33 # 重新命名錶格列,不需要的列用數字表示
34 tb.columns = ['world rank','university', 2,3, 'score',5,6,7,8,9,10]
35 tb.drop([2,3,5,6,7,8,9,10],axis = 1,inplace = True)
36 # 刪除後面不需要的評分列
37
38 # rank列100名後是區間,需需唯一化,增加一列index作為排名
39 tb['index_rank'] = tb.index
40 tb['index_rank'] = tb['index_rank'].astype(int) + 1
41 # 增加一列年份列
42 tb['year'] = i
43 # read_html沒有爬取country,需定義函式單獨爬取
44 tb['country'] = get_country(html)
45 # print(tb) # 測試表格ok
46 return tb
47 # print(tb.info()) # 查看錶資訊
48 # print(tb.columns.values) # 檢視串列名稱
49
50# 提取國家名稱
51def get_country(html):
52 soup = BeautifulSoup(html,'lxml')
53 countries = soup.select('td > a > img')
54 lst = []
55 for i in countries:
56 src = i['src']
57 pattern = re.compile('flag.*\/(.*?).png')
58 country = re.findall(pattern,src)[0]
59 lst.append(country)
60 return lst
61 # print(lst) # 測試提取國家是否成功ok
62
63# 儲存表格為csv
64def save_csv(tb):
65 tb.to_csv(r'university.csv', mode='a', encoding='utf_8_sig', essay-header=True, index=0)
66
67 endtime = time.time()-start_time
68 # print('程式運行了%.2f秒' %endtime)
69
70def analysis():
71 df = pd.read_csv('university.csv')
72 # 包含港澳臺
73 # 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']]
74 # 只包括內地
75 df = df.query("(country == 'China')")
76
77 df['index_rank_score'] = df['index_rank']
78 # 將index_rank列轉為整形
79 df['index_rank'] = df['index_rank'].astype(int)
80 # 美國
81 # df = df.query("(country == 'UnitedStates')|(country == 'USA')")
82 #求topn名
83 def topn(df):
84 top = df.sort_values(['year','index_rank'],ascending = True)
85 return top[:20].reset_index()
86 df = df.groupby(by =['year']).apply(topn)
87 # 更改列順序
88 df = df[['university','index_rank_score','index_rank','year']]
89 # 重新命名列
90 df.rename (columns = {'university':'name','index_rank_score':'type','index_rank':'value','year':'date'},inplace = True)
91
92 # 輸出結果
93 df.to_csv('university_ranking.csv',mode ='w',encoding='utf_8_sig', essay-header=True, index=False)
94 # index可以設定
95
96def main(year):
97 # generate_mysql()
98 for i in range(2009,year): #抓取10年
99 # get_one_page(i)
100 html = get_one_page(i)
101 # parse_one_page(html,i) # 測試表格ok
102 tb = parse_one_page(html,i)
103 save_csv(tb)
104 print(i,'年排名提取完成完成')
105 analysis()
106# # 單行程
107if __name__ == '__main__':
108 main(2019)
109 # 2016-2018採用gb2312編碼,2009-2015採用utf-8編碼
至此,我們已經有university_ranking.csv基礎資料,下麵就可以進行視覺化呈現了。
4. 視覺化呈現
首先,到作者的github主頁:
https://github.com/Jannchie/Historical-ranking-data-visualization-based-on-d3.js
4.1 克隆倉庫檔案
如果你平常使用github或者Git軟體的話,那麼就找個合適檔案存放目錄,然後直接在 GitBash裡分別輸入下麵3條命令就搭建好環境了:
1# 克隆專案倉庫
2git clone https://github.com/Jannchie/Historical-ranking-data-visualization-based-on-d3.js
3# 切換到專案根目錄
4cd Historical-ranking-data-visualization-based-on-d3.js
5# 安裝依賴
6npm install
如果你此前沒有用過上面的軟體,你可以直接點選Download Zip下載下來然後解壓即可,不過還是強烈建議使用第一種方法,因為後面如果要自定義視覺化效果的話,需要修改程式碼然後執行npm run build命令才能夠看到效果。
4.2 效果呈現
好,所有基本準備都已完成,下麵就可以試試看效果了。
任意瀏覽器開啟bargraph.html網頁,點選選擇檔案,然後選擇前面輸出的university_ranking.csv檔案,看下效果:
可以看到,有了大致的視覺化效果,但還存在很多瑕疵,比如:表順序顛倒了、字型不合適、配色太花哨等。可不可以修改呢?當然是可以的,只需要分別修改檔案夾中這幾個檔案的引數就可以了:
-
config.js 全域性設定各項功能的開關,比如配色、字型、文字名稱、反轉圖表等等功能;
-
color.css 修改柱形圖的配色;
-
stylesheet.css 具體修改配色、字型、文字名稱等的css樣式;
-
visual.js 更進一步的修改,比如圖表的透明度等。
知道在哪裡修改了以後,那麼,如何修改呢?很簡單,只需要簡單的幾步就可以實現:
-
開啟網頁,右鍵-檢查,箭頭指向想要修改的元素,然後在右側的css樣式表裡,雙擊各項引數修改引數,修改完元素就會發生變化,可以不斷微調,直至滿意為止。
-
把引數複製到四個檔案中對應的檔案裡並儲存。
-
Git Bash執行npm run build,之後掃清網頁就可以看到最佳化後的效果。
最後,再新增一個合適的BGM就可以了。以下是我最佳化之後的效果:
▲BGM:ツナ覚醒
如果你不太會調整,沒有關係,我會分享最佳化後的配置檔案。
以上,就是實現動態視覺化表的步驟。 同樣地,只要更改資料源可以很方便地做出世界、美國等大學的動態效果,可以看看:
中國(含港澳臺)大學排名:
http://pc1lljdwb.bkt.clouddn.com/Greater_China_uni_ranking.mp4
美國大學排名:
http://pc1lljdwb.bkt.clouddn.com/USA_uni_ranking.mp4
文章所有的素材,在公眾號後臺回覆大學排名就可以得到,或者到我的github下載:
https://github.com/makcyun/web_scraping_with_python
感興趣的話就動手試試吧。
據統計,99%的大咖都完成了這個神操作
▼
福利時間
更多精彩
在公眾號後臺對話方塊輸入以下關鍵詞
檢視更多優質內容!
PPT | 報告 | 讀書 | 書單
Python | 機器學習 | 深度學習 | 神經網路
區塊鏈 | 揭秘 | 乾貨 | 數學
猜你想看
Q: 你的母校上榜了嗎?
歡迎留言與大家分享
覺得不錯,請把這篇文章分享給你的朋友
轉載 / 投稿請聯絡:baiyu@hzbook.com
更多精彩,請在後臺點選“歷史文章”檢視