Gank.io 是我作 Android 時特別喜歡去逛的網站之一。最近想找些東西作簡單的數據處理。因此把 Gank 爬下來,先玩玩唄。
開發環境:Python3 、Jupyter Notebook。前端
先查看一下 Gank 的 API。 經過 gank.io/api/day/his… 獲取發過乾貨日期集合 history_date_list。根據日期,獲取當天的數據。遍歷集合,則能夠獲得全部的數據。python
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]
複製代碼
將數據存放於 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
將數據生成 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')
複製代碼
首先咱們簡單查看一下數據的類型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'])
複製代碼
先總的來看一下,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:
由圖可知 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 中提交過推薦呢。
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 的更新好像愈來愈不給力。是否是呢?咱們來看一下有關數據。
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:
單從數據上來看,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
複製代碼
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
複製代碼
有點好奇,是否是每次發佈數量越多,是由於斷更時間更長致使。
獲得發佈數量最多的 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:
從上圖看出這二者之間好像並沒有關聯。
不過能不能換一個思路,不是斷更時間越長,下一次發佈的數量相對越多呢。
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 條,由上圖好像也看不出來斷更時間與發佈數量有必然的關係 。 因此說更斷的緣由,主要仍是由於數量少。
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 月最你們都活躍的時間。
對(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 博客、知乎專欄、Mindroks、HuYounger 的我的博客、張濤開源實驗室。
對 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 翻譯組、微信分享、Medium、Natasha The Robot、 christiantietze、玉令天下的博客、realm、raywenderlich、Use Your Loaf、SegmentFault。
對前端作處理:
前端_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共賞的前端技術博客。
取出因此分享內容的標題,整合提取關鍵字,作成詞雲。
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 用於色彩的提取的。
最後把福利圖都下載下來吧。
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
複製代碼
有一張圖片由於不是安全連接,沒法下載。
Github: github.com/sfyc23/Pyth…