是我做 Android 时特别喜欢去逛的网站之一。最近想找些东西做简单的数据处理。所以把 Gank 爬下来,先玩玩呗。
开发环境:Python3 、Jupyter Notebook。1 爬取 Gank 中的所有数据。
先查看一下 Gank 的 。 通过 获取发过干货日期集合 history_date_list。根据日期,获取当天的数据。遍历集合,则可以得到所有的数据。
1.1 获取所有 Gank 发布所有日期集合
import requestshistory_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 为 。 而获取到的日期格式为 『2018-11-28』。所以我们转换一下日期转成『2016/05/11』格式。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 中。
import timefrom 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) continuelen(gank_data_list)复制代码
out:
9261复制代码
共爬取了 9261 条数据(截止于 2018-12-27 )。
1.3 保存数据。
将数据生成 DataFrame 并保存于文件 『gank.csv』中,以方便下次再使用。
import pandas as pdimport 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』中的数据即可。
import pandas as pdimport numpy as npgank_df = pd.read_csv('gank.csv', engine='python',encoding='utf-8')复制代码
2 处理并分析数据
首先我们简单查看一下数据的类型
gank_df.info()复制代码
RangeIndex: 9261 entries, 0 to 9260Data columns (total 11 columns):_id 9261 non-null objectcreatedAt 9261 non-null objectdesc 9261 non-null objectimages 2385 non-null objectpublishedAt 9261 non-null objectsource 6262 non-null objecttype 9261 non-null objecturl 9261 non-null objectused 9261 non-null boolwho 8982 non-null object_date 9261 non-null objectdtypes: 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:
由图可知 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:
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:
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:
单从数据上来看,16 年总量最多。其次为 15、17、18年。 但实际却并一定是这样。Gank 是从 2015 年 5 月 18 日(gank_df['publishedAt'].min() 得到 )开始发布数据。如果从 2015 初就开始的话,理论上的数量应该为 3611(计算在下面的代码中)。 果然从 2015、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).daystheory_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复制代码
有点好奇,是不是每次发布数量越多,是因为断更时间更长导致。
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} # 第一项 为 0for 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 个日期。
发布数量与断更时间的比较:
# 发布数量最大的日期 top2ogank_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] #得到时间间隔最大 top20gank_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 urlgank_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 2481www.jianshu.com 182mp.weixin.qq.com 126blog.csdn.net 121zhuanlan.zhihu.com 26url.cn 24blog.mindorks.com 22rkhcy.github.io 14kymjs.com 13Name: domain, dtype: int64复制代码
Android 分享中超过 10 次的网站分别: 、、微信分享、、、、、。
对 iOS 做处理:
iOS_domain_ser = gank_df.groupby('type').get_group("iOS")['domain'].value_counts()iOS_domain_ser[iOS_domain_ser > 10]复制代码
Out:
github.com 1580www.jianshu.com 185swift.gg 58mp.weixin.qq.com 25medium.com 22natashatherobot.com 22www.imlifengfeng.com 21christiantietze.de 20yulingtianxia.com 19realm.io 17www.raywenderlich.com 14useyourloaf.com 13segmentfault.com 12Name: domain, dtype: int64复制代码
iOS 分享中超过 10 次的网站分别: 、、、微信分享、、、 、、、、、。
对前端做处理:
前端_domain_ser = gank_df.groupby('type').get_group("前端")['domain'].value_counts()前端_domain_ser[前端_domain_ser > 5]复制代码
Out:
github.com 304zhuanlan.zhihu.com 79www.jianshu.com 13segmentfault.com 7mp.weixin.qq.com 6refined-x.com 6Name: domain, dtype: int64复制代码
前端分享中超过 5 次的网站分别: 、、、、微信分享、。
3 生成词云
取出所以分享内容的标题,整合提取关键字,做成词云。
from PIL import Imagefrom wordcloud import WordCloud, ImageColorGeneratorimport matplotlib.pyplot as pltimport jiebaimport pandas as pdgank_df = pd.read_csv('gank.csv', engine='python',encoding='utf-8')# 过滤列 type为福利的项,取出列 desc 项中所有的值,将值转成 listdesc_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 用于色彩的提取的。
4 下载福利图
最后把福利图都下载下来吧。
from multiprocessing.pool import ThreadPoolimport requestsimport osimport timeimport pandas as pdimport numpy as npgank_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:
关于我:,,,。