對 Gank.io 作數據分析並可視化

Gank.io 是我作 Android 時特別喜歡去逛的網站之一。最近想找些東西作簡單的數據處理。因此把 Gank 爬下來,先玩玩唄。
開發環境:Python3 、Jupyter Notebook。前端

1 爬取 Gank 中的全部數據。

先查看一下 Gank 的 API。 經過 gank.io/api/day/his… 獲取發過乾貨日期集合 history_date_list。根據日期,獲取當天的數據。遍歷集合,則能夠獲得全部的數據。python

1.1 獲取全部 Gank 發佈全部日期集合

import requests
history_date_url = 'http://gank.io/api/day/history'
history_res = requests.get(history_date_url)
history_date_list = history_res.json().get('results')
複製代碼

經過上面的代碼共取得 651 條的數據(截止於 2018-12-27 )。
Gank 獲取當天的數據的請求 API 爲 gank.io/api/day/201…
而獲取到的日期格式爲 『2018-11-28』。因此咱們轉換一下日期轉成『2016/05/11』格式。android

import re # 正則
history_date_list = [re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\1/\2/\3', date) for date in history_date_list]
複製代碼

1.2 開始爬取 Gank 全部的數據

將數據存放於 gank_data_list 中。git

import time
from tqdm import tqdm_notebook as tqdm #用於顯示進度條

gank_data_list = []
for data in tqdm(history_dates):
    day_url = 'https://gank.io/api/day/' + data
    res = requests.get(day_url)
    if res.status_code == 200:
        try:
            categorys = res.json().get('category')
            result_datas = res.json().get('results')
            for category in categorys:
                data_list = result_datas.get(category)
                gank_data_list.extend(data_list)
            time.sleep(0.5)
        except (KeyError, TimeoutError):
            print("error:" + data)
            continue
len(gank_data_list)
複製代碼

out:github

9261
複製代碼

共爬取了 9261 條數據(截止於 2018-12-27 )。json

1.3 保存數據。

將數據生成 DataFrame 並保存於文件 『gank.csv』中,以方便下次再使用。swift

import pandas as pd
import numpy as np

# 保存數據到 gank.csv.
gank_df = pd.DataFrame(gank_data_list)
gank_df.to_csv( 'gank.csv', index=False,encoding='utf-8')
複製代碼

當下次再使用則直接使用『gank.csv』中的數據便可。segmentfault

import pandas as pd
import numpy as np
gank_df = pd.read_csv('gank.csv', engine='python',encoding='utf-8')
複製代碼

2 處理並分析數據

首先咱們簡單查看一下數據的類型api

gank_df.info()
複製代碼
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 9261 entries, 0 to 9260
Data columns (total 11 columns):
_id            9261 non-null object
createdAt      9261 non-null object
desc           9261 non-null object
images         2385 non-null object
publishedAt    9261 non-null object
source         6262 non-null object
type           9261 non-null object
url            9261 non-null object
used           9261 non-null bool
who            8982 non-null object
_date          9261 non-null object
dtypes: bool(1), object(10)
memory usage: 732.6+ KB
複製代碼

其中列『createdAt』、『publishedAt』應該爲 datetime 類型。
『who』、『images』有缺失值,但對我下面數據分析無影響,因此不用管。安全

轉換 datetime 類型

gank_df['createdAt'] = pd.to_datetime(gank_df['createdAt'])
gank_df['publishedAt'] = pd.to_datetime(gank_df['publishedAt'])
複製代碼

2.1 Gank 發佈類型比例

先總的來看一下,Gank 發佈類型的佔比狀況。

from pyecharts import *

gank_type_counts_ser = gank_df.type.value_counts()
type_pie = Pie("Gank 發佈類型比例", "數據來源:gank.io", title_pos='center')
type_pie.add("佔比", gank_type_counts_ser.index, gank_type_counts_ser.values, is_random=True,
              radius=[30, 75], rosetype='radius',
              is_legend_show=False, is_label_show=True)
type_pie.use_theme('dark')
# type_pie.render(path='Gank 發佈類型比例.png') # 保存圖 
type_pie
複製代碼

Out:

Gank 發佈類型比例

由圖可知 Android 佔比最大,其次爲 iOS。 而從 2015 年 —— 2018 年之間這個佔比又有哪些變化 ?

timeline = Timeline(is_auto_play=True, timeline_bottom=0)
timeline.use_theme('dark')

gank_year_group = gank_df.groupby(pd.Grouper(key='publishedAt', freq='1Y'))
for name, group in gank_year_group:
    type_group_count_ser = group['type'].value_counts()
    type_group_pie = Pie("Gank 發佈類型比例", "數據來源:gank.io", title_pos='center')
    type_group_pie.add("佔比", type_group_count_ser.index, type_group_count_ser.values, is_random=True,
              radius=[30, 75], rosetype='radius',
              is_legend_show=False, is_label_show=True)
    timeline.add(type_group_pie,name.year)

# timeline.render(path='Gank 每一年發佈類型比例趨勢.gif') 
timeline
複製代碼

Out:

Gank 每一年發佈類型比例趨勢

2.2 提交 Gank 的人

一共有多少人在 Gank 中提交過推薦呢。

len(gank_df['who'].unique())
複製代碼

out:

854
複製代碼

能夠看到一共有 854 人提交過代碼。 使用柱形圖繪製 Gank 提交 Top20 的大佬。

gank_df['who'].isna().sum()
複製代碼

out:

279
複製代碼

有 279 條數據提交的人沒有留下名字。謝謝他們。 提交 Gank 數量最多的 20 位的大佬。

who_counts_ser = gank_df['who'].value_counts()

who_bar = Bar("Gank 提交 Top20 的大佬", "來源:gank.io",width=1000, height=600)
who_bar.use_theme('vintage')
who_bar.add("提交次數", who_counts_ser.index[:20], who_counts_ser.values[:20],mark_point=['max'],
            xaxis_rotate = 30
        )
# who_bar.render(path='Gank 提交 Top20 的大佬.png') # 保存圖 
who_bar
複製代碼

Out:

Gank 提交 Top20 的大佬

2.3 Gank 每次發佈時的數量

感受 Gank 的更新好像愈來愈不給力。是否是呢?咱們來看一下有關數據。

year_count_ser = gank_df.groupby(pd.Grouper(key='publishedAt', freq='1Y'))['_id'].count() # groupby each 1 年時間
year_count_bar = Bar("Gank 每一年發佈的數量", "來源:gank.io")
year_count_bar.add("發佈數量", 
                   year_count_ser.index.strftime('%Y'), 
                   year_count_ser.values,
                   is_label_show=True
                    )
# year_count_bar.render('Gank 每一年發佈的數量.png')
year_count_bar
複製代碼

Out:

Gank 每一年發佈的數量

單從數據上來看,16 年總量最多。其次爲 1五、1七、18年。 但實際卻並必定是這樣。Gank 是從 2015 年 5 月 18 日(gank_df['publishedAt'].min() 獲得 )開始發佈數據。若是從 2015 初就開始的話,理論上的數量應該爲 3611(計算在下面的代碼中)。 果真從 201五、2016 年以後,2017 年、2018 年移動端(Gank 的主要內容爲移動端)熱度降低了不少。

from datetime import datetime  

end_dt = datetime.strptime('2015-12-31', '%Y-%m-%d')
start_dt = datetime.strptime('2015-05-18', '%Y-%m-%d')
time_delta = (end_dt - start_dt).days
theory_nums = int(2246 * (365.0 / time_delta))
print(theory_nums)
複製代碼

out:

3611.409691629956
複製代碼

2.4 Gank 每次更新時發佈的數量

Gank 每次更新時,數量又有哪些不同呢?

首先統計每次更新時的日期與數量,
方法 1:

gank_every_counts_ser = gank_df.groupby(pd.Grouper(key='publishedAt', freq='D')).size() # 以天單位計算數量
gank_every_counts_ser = gank_every_counts_ser[gank_every_counts_ser > 0] # 取數量不爲空的日期
gank_every_counts_ser.index = gank_every_counts_ser.index.strftime('%Y-%m-%d') # 將日期格式化爲 index
# gank_every_counts_ser.head()
# gank_every_counts_ser
複製代碼

方法 2:

gank_df['_date'] = gank_df['publishedAt'].map(lambda x : str(x)[:10]) # 生成一日期類,用於分組
gank_every_counts_ser = gank_df.groupby('_date').size() # # 使用日期分組,並統計數量
複製代碼

生成圖:

gank_every_counts_line = Line("Gank 每次發送的數量", "來源:gank.io")
gank_every_counts_line.use_theme('vintage')
gank_every_counts_line.add('日期', 
                    gank_every_counts_ser.index, 
                    gank_every_counts_ser.values,
                    mark_point=['max'],               
                    is_datazoom_show=True  
                    )
# gank_every_counts_line.render('Gank 每次發送的數量.png')
gank_every_counts_line
複製代碼

Gank 每次發送的數量

有點好奇,是否是每次發佈數量越多,是由於斷更時間更長致使。

2.5. 發佈數量與斷更時間的比較

獲得發佈數量最多的 20 第二天期,經過這個日期算過離上一次更新相差的日期。

首先咱們前一次發佈時間減後一天發佈時間,獲得發佈的時間間隔字典 date_interval_dict。

# 獲得發佈時間間隔。
gank_every_counts_ser_index = gank_every_counts_ser.index
# date_interval_dict 每次發佈時間的間隔天數 dict.
date_interval_dict = { gank_every_counts_ser_index[0] : 0} # 第一項 爲 0
for i in range(1, gank_every_counts_ser_index.shape[0]):
    day = (pd.to_datetime(gank_every_counts_ser_index[i]) - pd.to_datetime(gank_every_counts_ser_index[i-1])).days
    date_interval_dict[gank_every_counts_ser_index[i]] = day
# date_interval_dict
複製代碼

而後獲得發送數量最多的 20 個日期。

發佈數量與斷更時間的比較:

# 發佈數量最大的日期 top2o
gank_every_ser_top20 = gank_every_counts_ser.sort_values(ascending=False)[:20]
# 經過上一項的數值獲得當天離上一天的時間間隔
date_interval_value = [date_interval_dict[index] for index in gank_every_ser_top20.index]
gank_interval_line = Line("發佈數量與斷更時間的比較", "來源:gank.io")
gank_interval_line.use_theme('vintage')
gank_interval_line.add('發佈數量', 
                    gank_every_ser_top20.index, 
                    gank_every_ser_top20.values,
                    mark_point=['max']        
                    )
gank_interval_line.add('間隔天數', 
                    gank_every_ser_top20.index, 
                    date_interval_value,
                    mark_point=['max']        
                    )
# gank_interval_line.render('發佈數量與斷更時間的比較.png')
gank_interval_line
複製代碼

Out:

發佈數量與斷更時間的比較

從上圖看出這二者之間好像並沒有關聯。
不過能不能換一個思路,不是斷更時間越長,下一次發佈的數量相對越多呢。

2.6. 斷更時間與發佈數量的比較

date_interval_ser_top20 = pd.Series(date_interval_dict).sort_values(ascending=False)[:20] #獲得時間間隔最大 top20

gank_interval_line2 = Line("斷更時間與發佈數量的比較", "來源:gank.io")
gank_interval_line2.use_theme('vintage')
gank_interval_line2.add('間隔天數', 
                    date_interval_ser_top20.index, 
                    date_interval_ser_top20.values,
                    mark_point=['max']        
                    )
# gank_interval_line2.add('數量平均數', 
# date_interval_ser_top20.index, 
# [int(gank_every_counts.mean()) for _ in range(20)] ,
# mark_point=['max'] 
# )
gank_interval_line2.add('發佈數量', 
                    date_interval_ser_top20.index, 
                    gank_every_counts_ser[date_interval_ser_top20.index],
                    is_label_show=True
                    )
# gank_interval_line2.render('斷更時間與發佈數量的比較.png')
gank_interval_line2
複製代碼

Out:

斷更時間與發佈數量的比較

經過 gank_every_counts_ser.mean() 獲得, 每次發佈的平均值爲 14 條,由上圖好像也看不出來斷更時間與發佈數量有必然的關係 。 因此說更斷的緣由,主要仍是由於數量少。

2.7 每一年中,那幾個月你們最活躍的呢。

year_month_line = Line("最活躍的月份", "來源:gank.io")
year_month_line.use_theme('shine')
year_group = gank_df.groupby(pd.Grouper(key='publishedAt', freq='1Y')) 
for name, group in year_group:
    year_mouth_count_ser = group.groupby(pd.Grouper(key='publishedAt', freq='1M'))['_id'].count()
    year_month_line.add(str(name.year), 
                    year_mouth_count_ser.index.strftime('%m').tolist(), 
                    year_mouth_count_ser.values.tolist(),
                    mark_point=['max'] )
year_month_line.render('最活躍的月份.png')
year_month_line
複製代碼

Out:

最活躍的月份

3 月至 5 月最你們都活躍的時間。

2.8 分享最多的網站 & 博客

對(Android,iOS, 前端)分別作處理。

根據列 url 生成一個新列 domain。 而後使用 type 對數據分類統計。

# 生成新列
def get_domain(url):
    domain = re.findall(r'http[s]?://(.*?)/.*?',url)
    if domain:
        return domain[0]
    else:
        return url
gank_df['domain'] = gank_df['url'].map(get_domain)
複製代碼

對 Android 作處理:

android_domain_ser = gank_df.groupby('type').get_group("Android")['domain'].value_counts()
android_domain_ser[android_domain_ser > 10]
複製代碼

Out:

github.com            2481
www.jianshu.com        182
mp.weixin.qq.com       126
blog.csdn.net          121
zhuanlan.zhihu.com      26
url.cn                  24
blog.mindorks.com       22
rkhcy.github.io         14
kymjs.com               13
Name: domain, dtype: int64
複製代碼

Android 分享中超過 10 次的網站分別: Github簡書、微信分享、CSDN 博客知乎專欄MindroksHuYounger 的我的博客張濤開源實驗室

對 iOS 作處理:

iOS_domain_ser = gank_df.groupby('type').get_group("iOS")['domain'].value_counts()
iOS_domain_ser[iOS_domain_ser > 10]
複製代碼

Out:

github.com               1580
www.jianshu.com           185
swift.gg                   58
mp.weixin.qq.com           25
medium.com                 22
natashatherobot.com        22
www.imlifengfeng.com       21
christiantietze.de         20
yulingtianxia.com          19
realm.io                   17
www.raywenderlich.com      14
useyourloaf.com            13
segmentfault.com           12
Name: domain, dtype: int64
複製代碼

iOS 分享中超過 10 次的網站分別: Github簡書Swift 翻譯組、微信分享、MediumNatasha The Robotchristiantietze玉令天下的博客realmraywenderlichUse Your LoafSegmentFault

對前端作處理:

前端_domain_ser = gank_df.groupby('type').get_group("前端")['domain'].value_counts()
前端_domain_ser[前端_domain_ser > 5]
複製代碼

Out:

github.com            304
zhuanlan.zhihu.com     79
www.jianshu.com        13
segmentfault.com        7
mp.weixin.qq.com        6
refined-x.com           6
Name: domain, dtype: int64
複製代碼

前端分享中超過 5 次的網站分別: Github知乎專欄簡書SegmentFault、微信分享、雅X共賞的前端技術博客


3 生成詞雲

取出因此分享內容的標題,整合提取關鍵字,作成詞雲。

from PIL import Image
from wordcloud import WordCloud, ImageColorGenerator
import matplotlib.pyplot as plt
import jieba
import pandas as pd


gank_df = pd.read_csv('gank.csv', engine='python',encoding='utf-8')
# 過濾列 type爲福利的項,取出列 desc 項中全部的值,將值轉成 list
desc_dict = gank_df[gank_df['type'] != '福利']['desc'].tolist()
# 將 list 轉成 str ,用於分詞。
desc_texts = ' '.join(desc_dict)

# 分詞
word_cut_list = jieba.cut(desc_texts, cut_all=True)
word_space_cut_split = ' '.join(word_cut_list)

# 
coloring = np.array(Image.open("gank_frog.jpg"))
my_wordcloud = WordCloud(background_color="white", 
                         width = 1280,
                         height = 720,
                         max_words=500,
                         mask=coloring, 
                         max_font_size=200, 
                         random_state=42, 
                         font_path=r'simkai.ttf' # 中文字體
                        ).generate(word_space_cut_split)
image_colors = ImageColorGenerator(coloring)
fig = plt.figure(figsize=(32,18))
image_colors = ImageColorGenerator(coloring)
plt.imshow(my_wordcloud.recolor(color_func=image_colors))
plt.imshow(my_wordcloud)
plt.axis("off")
plt.savefig('gank_word_cloud.png')
plt.show()
複製代碼

詞雲

代碼中 gank_forg.jpg 用於色彩的提取的。

+1s


4 下載福利圖

最後把福利圖都下載下來吧。

from multiprocessing.pool import ThreadPool
import requests
import os
import time
import pandas as pd
import numpy as np

gank_df = pd.read_csv('gank.csv', engine='python',encoding='utf-8')
girls_df = gank_df[gank_df['type']=='福利'][['publishedAt','url']]

downloads_url = [(str(row['publishedAt'])[:10],row['url']) for _ ,row in girls_df.iterrows()]


# 存放的文件夾
image_folder = '福利'
# 若是不存在則生成
if not os.path.exists(image_folder):
    os.makedirs(image_folder)

def download_image(entry):
    path, uri = entry
    path = os.path.join(image_folder,f"{path}.jpg")
    if not os.path.exists(path):
        try:
            res = requests.get(uri, stream=True)
            if res.status_code == 200:
                with open(path, 'wb') as f:
                    f.write(res.content)
                time.sleep(2)
                #print(f'已下載:{path}')
        except Exception as e:
            print(f"請求出錯: {path}")
    return path

# 開啓多線程下載圖片
results = ThreadPool(8).map(download_image, downloads_url)
複製代碼

out:

請求出錯: 福利\2017-10-23.jpg
複製代碼

有一張圖片由於不是安全連接,沒法下載。


5.代碼與文件地址:

Github: github.com/sfyc23/Pyth…


關於我:微博簡書掘金Github

相關文章
相關標籤/搜索