技能雷達圖-代碼分析

之前在搜索一些資料的時候,發現開源中國這個博客網站有一大亮點。那就是一張技能雷達圖。大致如下:


開源中國技能雷達圖

但是遺憾的是CSDN官方并不支持這一實現,其實對于技能雷達圖而言,言簡意賅的能表現出一個技術者擅長的領域,以及不擅長的領域。

抱著玩一玩的態度,我也著手實現了一下針對CSDN博客用戶的技能雷達圖。下面先來看下最終實現的效果圖。

CSDN 博客雷達圖
CSDN 博客用戶專屬技能雷達圖

接下來記錄一下整體的實現流程。


思路

根據用戶指定的博客ID,找到其文章分類情況。然后分別計算分類下文章的得分情況。

這個得分默認按照CSDN官方的規則計算。即:

  • 博文每增加300個瀏覽量,積分加1
  • 被人評論加一分
  • 被人點贊加一分
  • 被人點踩減一分

這里用的規則不是很適合,但是一般而言,一篇博客的瀏覽量,在一定程度上也代表了這篇文章的質量。

核心代碼

計算部分

按照思路,這又是一個簡單爬蟲相關了。代碼中已經做了比較詳細的注釋。

#!usr/bin/env python
# coding: utf8
import requests
import math
import re
from bs4 import BeautifulSoup


class Radar(object):
    """
    技術雷達實現
    """

    def __init__(self, username):
        """初始化用戶名以及相應的域名前綴"""
        self.username = username
        self.domain = "http://blog.csdn.net"

    def download(self, url):
        """下載通用方法"""
        headers = {
            'Host': 'blog.csdn.net',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36'
        }
        response = requests.get(url=url, headers=headers)
        return response.text if response.status_code == 200 else None

    def str_concat(self, *args):
        """ 字符串拼接方法,滿足不定參數的實現"""
        tempstr = ''
        for item in args:
            tempstr += item
        return tempstr

    def get_catagories(self):
        """
        計算username對應的文章類別信息,獲取類別名稱, 對應的URL以及為其設置編號
        """
        url = self.str_concat(self.domain, '/', self.username)
        html = self.download(url=url)
        soup = BeautifulSoup(html, 'html.parser')
        temp_catagories = soup.find_all('div', {'id': 'panel_Category'})[-1].find_all('a')
        catagory = []
        for index, item in enumerate(temp_catagories):
            counts = soup.find_all('div', {'id': 'panel_Category'})[-1].find_all('span')[index+1].get_text().lstrip('(').rstrip(')')
            obj = {
                "url": self.str_concat(self.domain, item.attrs['href']),
                "name": item.get_text(),
                "counts": int(counts)
            }
            catagory.append(obj)
        # 返回類別相關的計算結果
        return catagory

    def trim_list(self, ls):
        """
        列表去重
        """
        return list(set(ls))

    def get_posts(self, catagory_url, counts):
        """
        根據給定的類別URL,獲取該類別下所有的文章的鏈接以及標題信息等
        """
        html = self.download(url=catagory_url)
        # 先使用正則來獲取給類別對應的文章總數,以及總頁數
        posts_number, pages = counts, math.ceil(counts/20)
        html = None
        # 聲明一個保存文章信息的容器列表
        posts = []
        # 對每一頁的鏈接進行爬取,獲取對應頁面文章的標題以及鏈接  int(pages)+1
        for index in range(1, int(pages) + 1):
            # print("正在處理第{}頁".format(index))
            url = self.str_concat(catagory_url, '/', str(index))
            html = self.download(url=url)
            soup = BeautifulSoup(html, 'html.parser')
            page_posts = soup.find_all('span', {'class': 'link_title'})
            for item in page_posts:
                url = re.findall(re.compile('href="(.*?)"'), str(item))
                if(url == []):
                    continue
                posts.append(self.str_concat(self.domain, url[0]))
            # print("第{}頁的數據為:{}個".format(index, len(page_posts)))
        return self.trim_list(posts)

    def get_detail(self, posturl):
        """
        根據指定的博文URL鏈接,計算出改文章大致的得分情況。
        默認按照CSDN官方計算規則實現。
        每300瀏覽量加一分;一個贊加一分;一個評論加一分;一個踩扣一分
        """
        html = self.download(url=posturl)
        soup = BeautifulSoup(html, 'html.parser')
        title = soup.find('span', {'class': 'link_title'}).get_text()
        watches = re.findall(re.compile('.*?(\d+).*?'), soup.find('span',{'class': "link_view"}).get_text())[0]
        comments = re.findall(re.compile('.*?\((\d+)\).*?'), soup.find('span',{'class': "link_comments"}).get_text())[0]
        diggs = soup.find('dl', {'id': 'btnDigg'}).find('dd').get_text()
        buries = soup.find('dl', {'id': 'btnBury'}).find('dd').get_text()
        # 簡單 計算出每篇文章的得分情況
        print('正在計算 {} 的得分情況。'.format(posturl))
        return int(watches)/300+int(comments)+int(diggs)-int(buries)

整合膠水

模塊功能已經完善了,接下來就是將這一個個的小模塊給整合起來,在更高層次上做偽自動化

#!/usr/bin/env python
# coding: utf8
from compute import Radar

def compute(username):
    """
    計算出username對應的文章得分情況。
    """
    radar = Radar(username=username)
    catagroies = radar.get_catagories()
    for catagory in catagroies:
        # 先求出每一個分類下的所有的文章鏈接。然后計算出總分數
        counts = catagory['counts']
        posturls = radar.get_posts(catagory_url=catagory['url'], counts=counts)
        score = 0
        for posturl in posturls:
            score += radar.get_detail(posturl=posturl)
        print('{}的總體得分為:{}'.format(catagory['name'], score))
        catagory['score'] = score
    return catagroies

服務器端

為了實現技能雷達圖的目標,需要開一個本地的web服務,因為Flask比較順手,所以就用它好了。

#!/usr/bin/env python
# coding: utf8
import json
from utils import compute
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/api/user/<username>', methods=['GET', 'POST'])
def get_user_score(username):
    catagories = compute(username=username)
    return json.dumps(catagories)


@app.route('/')
def home():
    return render_template('index.html')




if __name__ == "__main__":
    app.run(host='localhost', port=8080, debug=True)

下面的這個接口,作為待會的ajax請求調用。

http://localhost:8080/api/user/username(博客ID)

前端

因為用到了JQuery和echarts,所以要對flask進行靜態資源和模板的設置。

具體的做法如下:

  • 對于模板: 模板文件放到網站根目錄的templates
  • 對于CSS,JS,Image。放到網站根目錄下的static目錄下。
    具體引用時按照文件在服務器上的絕對路徑進行處理。比如static目錄下的js目錄下的jquery.min.js文件,在模板中要這么寫:
<script src="/static/js/jquery.min.js"</script>

其他靜態資源文件同理。

然后就是使用ajax實現對技能數據的獲取以及更新了。詳見下面的完整代碼:

<html>
<head>
    <meta charset="UTF-8">
    <title>技術雷達圖</title>
    <script src="/static/echarts.min.js"></script>
    <script src="/static/jquery-2.2.4.min.js"></script>
</head>

<body>
    <input type="text" id="username" placeholder="請輸入CSDN博主ID"> &nbsp;&nbsp;&nbsp;&nbsp;
    <input type="button" id="compute" value="評估"><br/>
    <div id="container" style="width: 600px;height:400px;"></div>
</body>

<script>
    var mycharts = echarts.init(document.getElementById('container'));
    mycharts.showLoading();
    var names = [];
    var scores = [];
    function prepare_data() {
        $(document).ready(function () {
            $.ajax({
                url: 'http://localhost:8080/api/user/' + $("#username").val(),
                dataType: 'json',
                success: function (result) {
                    mycharts.hideLoading();
                    // 計算相關數據
                    for (var index = 0; index < result.length; index++) {
                        names.push({
                            name: result[index].name,
                            max: 25
                        });
                        scores.push(
                            result[index].score
                        );
                    }
                    console.log(JSON.stringify(names));
                    console.log(JSON.stringify(scores));
                    show_data();
                },
                error: function (err) {
                    console.log(JSON.stringify(err));
                }
            })
        });
    }

    function show_data() {
        var option = {
            title: {
                text: '基礎雷達圖'
            },
            tooltip: {},
            legend: {
                data: ['預算分配(Allocated Budget)', '實際開銷(Actual Spending)']
            },
            radar: {
                indicator: names
            },
            series: [{
                name: '預算 vs 開銷(Budget vs spending)',
                type: 'radar',
                areaStyle: { normal: {} },
                data: [
                    {
                        value: scores,
                        name: '預算分配(Allocated Budget)'
                    }
                ]
            }]
        };
        mycharts.setOption(option);
        // 觸發ajax事件,計算給定用戶名博主的技術雷達計算。
        // 先響應一個加載動畫比較好。
        mycharts.hideLoading();
    }
    $("#compute").click(function () {
        prepare_data();
    });
</script>
</html>

最終效果

CSDN 博客雷達圖

因為系統是實時計算的,所以對于文章比較多的用戶而言,需要等待較長的時間,但是功能上差不多就算是完成了。


CSDN 博客用戶專屬技能雷達圖

總結

回顧一下,這里的計算規則其實還是有很大漏洞的。應該將類別的總積分除以類別下文章的數目,求取一個比較平均的值來降低誤差。

另外,雷達圖上限是隨意指定的,所以沒什么價值。正規而言應該是需要一套比較嚴格的標準來進行參考,這樣才有價值。

echarts確實是一款網頁展示圖標的神器,而且官網上給的例子非常的清晰,準備好數據,交給ajax,這樣就完事了,省心,省力。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,287評論 25 708
  • 1人機交互的源起 小練習 2人 小練習 3機 4交互 5交互設計 6原型設計 7用戶體驗評估與設計 10人機交互的展望
    Junliang閱讀 341評論 0 1
  • 破舊的房屋旁,臉上滿是泥印的兩個小孩緊緊依偎在一起,他們期盼著望著前方,小女孩笑得睫毛彎彎,她摟著旁邊愁眉...
    嘛事1閱讀 1,582評論 3 3