python動態柱狀圖圖表視覺化:歷年軟科中國大學排行

西西嘛呦發表於2020-06-23

本來想參照: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,

最終效果:

至此,就全部完成了。

看起來簡單,還是得要自己動手才行。

相關文章