1. Why Python?

也许现在看这篇文章的你并不是程序员,或者并没有接触过编程;但是随着近几年「人工智能」、「大数据」、「机器学习」课程不断增多。你会在发现「Python」这一名词随处可见。

Python 是一门编程语言,它的应用领域可以横跨 Web 开发、数据科学再到游戏开发、量化投资等。

它简单易用、拥有庞大且坚实的用户群体,往往都是新手编程入门的首选。可以说,Python 是一门能够让你学习 20 年都不一定被淘汰的这么一种编程语言。

1.1 如何安装 Python?

无论你是在 Windows、macOS 还是在 Linux,你都可以轻松的安装 Python。但是各种环境配置显然是让人「从入门到放弃」,所以 Anaconda 是你的第一选择。

Anaconda 是一个用于科学计算的 Python 发行版,它集成了许多 Python 的第三方库,让小白能够更好的直接使用。在Anaconda 的下载页面,你可以直接选择相应平台的安装包。

但是这里只推荐安装 3.x 版本的 Python,原因是 2.x 版本将在 2020 年停止更新维护,所以就像电子产品「买新不买旧」,你应该把学习的成本都放到最新的一版。

你可以在这里查阅 Anaconda 的官方文档,里面有详细的安装教程。

macOS 用户只需要默认下一步即可,Windows 用户在安装时要记得勾选Add Anaconda to my PATH environment variable选项,将 Python 添加到环境变量中

Windows 安装

图源:Anaconda 官方下载文档

安装好应用之后打开 Anaconda 界面选择Jupyter Notebook 或者 Spyder 任意一款 IDE 就可以直接开始你的 Python 之旅了。

Anaconda 界面

1.2 环境配置介绍

在我 MacBook Pro上的 Python 环境配置如下:

  • 版本配置:Python 3.7+ Anaconda
  • IDE(集成开发环境):
    • VScode:数据爬取与调试
    • Jupyter Notebook:数据分析与可视化

Python上的 IDE 你也可以有多种选择,比如 Pycharm、Spyder 或者是 Sublime。但是这些 Python 让我满意的 IDE 并没有,反而倒是 R 语言的 Rstudio 让我特别喜欢(可惜是 R 语言的专属……)

不过配置 VScode 可能需要一点时间成本,如果你不是很想花时间在上面折腾,那你也可以在 Jupyter Notebook 里「All-in-One」

如果你不了解 Jupyter Notebook,那么可以参考我之前的这篇文章,里面有关于 Jupyter Notebook 的相关介绍。

2. 数据获取

所谓的爬虫,在我理解,其实就是「模拟成用户访问服务器或网页获取内容的过程」。所以爬虫本质上就是为获取数据。

通常我们打开网页浏览内容的步骤可能是这样:

  1. 打开浏览器输入网址
  2. 向服务器发送请求
  3. 服务器返回内容(数据)并在网页呈现

换成程序后步骤可能会有点差异,但本质上是相似的。所以基本思路爬取就是:

  1. 使用 Chrome 开发者工具查看返回数据的目标地址(URL)
  2. 使用相应的 HTTP 方法来向目标地址发送请求并获取返回数据
  3. 然后批量循环更改页数来不断获取
  4. 最后将所有获取到的数据结构化

本次我主要是爬取少数派首页文章作者成就两部分截至 9 月 27 日的数据。其中首页文章数据样本量为12243,而作者成就数据样本量为679。

这里使用到了三个重要的第三方库:

  • requests:爬虫的常用库,用来发送 HTTP 请求
  • json:将获取到的数据转换成 JSON 格式(一种数据类型)
  • pandas :其io.json模块下的json_normalize函数,能够将 JSON 数据快速的结构化成DataFrame类型,便于我们后续进行数据处理与分析

好在 Anaconda 已经帮我们提前内置好了这些常用的第三方库,我们直接调用即可!

2.1 API 接口查询

一般网页的爬虫可以通过解析页面源码和访问 API 接口来获取数据:
  • 前一种方法比较麻烦,你需要将获取到的内容并通过像BeautifulSouplxml这样的库来解析并对网页元素(Elements)进行定位,最后才能获取到相应的内容。
  • 而通过 API 接口就很方便了,向它发送请求后直接就返回相应的数据,省去了中间一系列的过程。做爬虫一般更喜欢这种方式,但是通常网站不会轻易暴露。
好在我派开放了相应的 API 接口,对于整个爬取工作方便了不少!
API(Application Programming Interface)是一些预先定义的函数,或指软件系统不同组成部分衔接的约定。目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问原码,或理解内部工作机制的细节。——源百度百科

用 Chrome 进入到少数派首页后,使用按下F12调出开发者工具进入到Network选项框,然后按下F5(在 macOS 上使用⌘+R)重新刷新页面进行加载,之后就可以发现一系列get开头的东西,说明这就是少数派服务器返回数据的一些接口。

Chrome 开发者工具界面

选取其中一个 URL 访问后返回以下数据说明这就是返回数据的 API 接口了。

API 访问测试

当然这只是网页数据接口的查看方法,如果你要想做 APP 的爬虫,那么你可能需要进一步了解「抓包」之类的内容。

2.2 首页文章爬取

有点尴尬的是,在我写完首页爬虫代码没多久,我派的首页文章 API 接口就好像被隐藏了……(有可能是因为访问的版本问题)

不过好在以前的 API 接口仍然可以使用:

首页 API 接口

通过尝试访问发现get?后面接的limitoffset参数主要分别控制每次访问显示的文章数和页数(当然这并不是完全意义上的翻页,更有点像滚动的意思,因此会访问到重复的数据,需要最后对数据去重)。

所以我们只需要通过更改这两个参数的数值就可以获取到不同页面的内容。

但是这并不是说数值就是不能穷尽的,通过我探索发现offset的最大值为 12210,所以我最后就以这一值作为最终的控制条件,只要页数没达到这一值,就让爬虫程序一直爬下去。

首页爬取代码

需要注意的是concat_data函数的if循环中,我使用了 Python 内置的time库中的sleep函数,一般来说反爬机制严格的网站如果你访问过于频繁会被限制访问,所以模拟人的浏览习惯就是爬取一次后就让程序暂停一会儿再重新工作,而该函数就可以起到这样的效果。

不过我派也不存在反爬机制,所以获取数据只需要等待即可。

2.3 作者成就数据爬取

获取首页文章的数据之后,接下来就是获取作者成就的数据。其爬取思路也是类似的,可以发现https://beta.sspai.com/api/v1/user/slug/info/get?slug=就是返回相关数据的入口,后面只要填入slug的参数即可访问。该参数对应的是我们个人主页地址上的这一部分,大致跟域名是一个性质:

个人主页 URL

这里我只获取与首页文章有关作者的slug

所以我们通过前面获取到的首页数据,取出当中的slug值,通过去重后再放到上述地址中,对 JSON 数据进行结构化。

作者成就爬取代码

在爬取过程中我发现当传入一名昵称为「SunSetYe」作者的slug时程序就会报错,到对应个人页面后发现其相应的slug已经修改,所以就手动替换其在首页文章数据中的slug值。

3. 数据分析:Pandas

NumpyPandasMatplotlib库可以说是Python 数据科学领域中的「三剑客」:

  • Numpy往往涉及科学计算层面
  • Pandas则是以Numpy为基础进行开发,兼具速度和友好,是用 Python 数据分析的核心库之一。
  • Matplotlib涉及可视化层面,如果你熟悉Matlab的绘图方式,那么你就会发现这二者十分相似。但是 Python 的第三方可视化库也非常丰富,有兴趣的朋友可以自行探索。

本文仅用 Pandas 就已经可以帮助我们完成很多数据操作以及分析的任务。

但在数据分析之前需要对获取到的数据进行处理,业内人士称为「数据清洗」。

因为我们获取到的数据往往并不是都「干净」、有序的、结构化的,所以数据分析可能 80% 的时间都用在数据清洗和特征工程上,后续的分析也就花大概 20% 的时间。

如果在数据清洗处理工作上没有做好的话,那么得到的结果就是「garbage in, garbage out」(垃圾进,垃圾出)

3.1 首页文章数据预处理

首先们导入 pandas库,并通过该库提供的read_excel函数来读取爬取到的数据。之后使用info()函数和head()函数来查看一下整个数据的大致情况。

import pandas as pd

page = pd.read_excel(r'./data/page_data.xlsx') #如果是 Windows 用户需要修改路径

print(page.info())

page.head()

首页数据由于字段比较多,但是并非都是我们需要的,因此我们仅仅只需要聚焦那么一些需要的字段,其他的则选择忽略。这里我主要选取了九个字段(当然并不是每个都一定用得上):

  • id:文章 id
  • title:标题
  • post_type:文章类型
  • free:是否为订阅文章
  • author_slug:作者域名(用来关联用户成就数据)
  • author_nickname:作者昵称
  • released_time:发布时间
  • comment_count:评论数
  • like_count:点赞/充电数
  • view_count:观看数

选取与处理过程如下:

page_cols = [

    'id', 'title', 'post_type', 'free',

    'author.slug', 'author.nickname', 'released_time',

    'comment_count', 'like_count', 'view_count'


]

page = page[page_cols]

page.columns = page.columns.str.replace('.', '_')

page['released_time'] = pd.to_datetime(page['released_time'])

page = (page

           .assign(released_date = page['released_time'].dt.date,

                      released_year = page['released_time'].dt.year,

                      released_month = page['released_time'].dt.month,

                      released_day = page['released_time'].dt.day,

                      released_at = page['released_time'].dt.time)

       )

  • 首先通过传入字段名列表,直接提取出对应字段下的数据;然后通过columns属性读取字段的名称,再通过str接口来将字段中的.替换成_,以便于和函数方法相区分开;
  • 其次将released_timepd.to_datetime()函数转换成独特的日期类型,字段拆成最细,以便后续的时间维度尽可能被划分。
  • 最后调用assign()函数来一次性创建所有拆分的时间字段;如果你使用过 R 语言中的dplyr包当中的mutate()函数,那么你很快就可以熟悉assign()的用法。

查看一下是否有缺失值,如果有则进一步处理,如果没有则说明数据比较完整。爬取到的数据基本比较完整,没有其他缺失字段,那就可以做后面的数据分析工作了。

page.isnull().any()

3.2 作者成就数据预处理

作者成就数据的处理和首页文章数据的处理有类似之处,这里我主要选取了主要有 10 个字段:

  • data.slug:域名,可以和首页数据相关联
  • data.nickname:昵称
  • data.created_at:创建时间
  • data.article_view_count:文章累计阅读量
  • data.articles_word_count:累计写作字数
  • data.liked_count:累计点赞/充电数
  • data.followed_count:粉丝量
  • data.power_plus_flag:是否购买 Power+
  • data.occupation1:身份标识 1
  • data.occupation2:身份标识 2

user = pd.read_excel(r'./data/user_data.xlsx') #同首页数据操作

print(user.info())

user.head()

user_cols = [

    'data.slug', 'data.nickname', 'data.created_at',

    'data.article_view_count', 'data.articles_word_count', 'data.liked_count', 

    'data.followed_count', 'data.power_plus_flag', 'data.occupation1', 'data.occupation2'

]

user = user[user_cols]

稍微有点不同的地方在爬取到的数据字段名称和首页文章数据的字段名称不统一,需要将其命名统一。

这里我们可以发现所有字段前面都带有data.的前缀,所以我们通过正则方法提取之后的字段,如果你对正则表达式不了解可以查看王树义老师的这篇文章。当然,你也可以直接使用 Python 中字符串的split(sep='.')方法对其进行分割。

提取成功后再将获取到值传入整个数据集的columns属性。

user.columns = user.columns.str.extract(r'data.(.+)')[0].tolist()

user.rename(columns = {'slug': 'author_slug', 

                                   'nickname': 'author_nickname', 

                                   'article_view_count': 'view_count',

                                   'articles_word_count': 'word_count',

                                   'liked_count': 'like_count'}, 

                  inplace=True)

除此之外,获取到的作者成就数据只有创建时间字段,而「成为少数派 XX 天」这一部分就没有现成的数据,这里我们就手动算一下,即成为少数派 XX 天 = 现在日期-创建日期。在计算日期差值之前同样还是要将其转换成独特的日期格式。

user['created_at'] = pd.to_datetime(user['created_at'], unit='s')

user['today'] = pd.date_range('2019-10-01', '2019-10-01', periods=len(user))

user['sspai_days'] = (user['today'] - user['created_at']).dt.days

user.drop('today', axis=1, inplace=True)

4. 数据分析与可视化

对数据进行预处理后就可以开始后续的分析了。

在数据统计部分我仍然使用的是 Pandas 库,而可视化部分我主要使用的是 Pyecharts 库。

Echarts 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达力的语言,很适合用于数据处理。当数据分析遇上数据可视化时,pyecharts 诞生了。——源 Pyecharts 官网简介

用于制作交互可视化图形的 Python 第三方库有 Plotly、Bokeh 等,

而 Pyecharts 是国产的交互式可视化库,在配色上让我比较满意,能做出漂亮的图形。

4.1 首页文章数据

首页文章数据我主要选取了五个部分:

  • 2012 年-2019 年每月首页推送的文章数量统计
  • 阅读量 TOP3 的文章
  • 点赞/充电数 TOP3 的文章
  • 评论数 TOP3 的文章
  • 综合排序 TOP10 的文章

4.1.1 首页文章数统计

每月文章统计

我派首页推送文章数量最多的时候是2014 年 7 月,整个月首页推送的文章数量达到了 282 篇

从整个趋势来看变化地幅度还是蛮大的,但是数量最少每个月也大约有 70 篇左右,说明我派对文章内容质量是宁缺毋滥的。

4.1.2 阅读量 TOP3 的文章

我根据发布日期、标题和作者昵称分组,对view_count阅读量字段进行加总,然后将其按从大到小的顺序进行并转换成DataFrame类型(在网页展示上形状比较友好),最后使用head()函数查看前三个观测值。(后续的部分也是同样操作,只需要更换字段名称)

整个流程我使用了「链式调用」(Method Chaining)的风格,如果你使用过 R 语言的magrittr包,那你就对%>%这个管道操作符并不陌生,也很容易能读懂我的整个代码。

但需要注意的是,在 Python 中对DataFrame类型链式调用时,需要将所有流程囊括在()括号之中。

(page.groupby(['released_date', 'title', 'author_nickname'])['view_count']

 .sum()

 .sort_values(ascending=False)

 .to_frame()

 .head(3)

)

结果如下:

阅读量 TOP3 的文章

2014 年 4 月16 号推送的《认识与入门 Markdown》一文阅读量到目前为止差不多达到了 200W。(Markdown 确实是个好东西)

其余两篇的文章阅读量也都突破了 100W+。

4.1.3 点赞/充电数 TOP3 的文章

点赞/充电数 TOP3 的文章

点赞/充电数最高的是果核 Group在 2019 年 2 月21 日关于 iPhone 拍照教程的一篇文章,目前已经突破了 2000。

然后 Umi 的两篇文章质量也很高,都突破了 1500。(表示很羡慕:p)

4.1.4 评论数 TOP3 的文章

评论数 TOP3 的文章

前两名分别是关于 Price TagNotion 的文章,评论数都超过了 420 条。说明这两个软件让有很多大家可以聊的地方。

第三名是今年 8 月 28 日我派发起的话题讨论,评论数达到了 372 条。看来今年的产品并不是很让大家满意呀。

4.1.5 综合排序TOP10

通过对点赞/充电数、阅读量以及评论的综合排序,最终筛选出如下 10 篇文章:

综合排序 TOP10

这里我是按点赞/充电数来进行从大到小的降序排列,因为按我的理解点赞/充电数高的说明文章内容是深受大家认可的。

可以看出这些文章大多数都是在 2018 年-2019 年上半年间发布的

4.2 作者成就数据

作者成就数据部分我主要选取了六方面:

  • 作者头衔统计
  • 累积阅读量 TOP5 的作者
  • 累积写作字数 TOP5 的作者
  • 累积点赞/充电数 TOP5 的作者
  • 综合排序 TOP10 的作者
  • TOP10 作者的年度数据变化

4.2.1 少数派作者头衔统计

大部分推送首页的作者应该是没有相应头衔的,所以这里有头衔的作者则会列入当中。

少数派作者组成

从这个简单的组成来看,最多的是签约作者,其次是少数派成员,授权作者(Id Auth)次之。(有点好奇我派成员的准入标准是啥XD)

4.2.2 👀让我康康:累积阅读量 TOP5 作者

阅读成就

从阅读量看,第一属于我派编辑部!但是如果除去我派编辑部,那么第一名就是「化学心情下 2」这一朋友,阅读量累积到目前已经有 870W+

除此之外其他的另外三位作者累积阅读量也都已经过 600W+。

4.2.3 ✍️笔耕不辍:累积写作字数 TOP5 的作者

写作字数成就

从写作字数来看,第一名的仍然是「化学心情 2」!这位作者所有文章字数加起来竟然写了接近 110W 字!同时突破 100W 字的则是我派编辑部。第一名和第五名差不多有 2 倍的差距。

4.2.4 ⚡️能量满满:累积点赞/充电数 TOP5 的作者

点赞/充电数成就

在累积点赞/充电数排名上,UmiMinja我派编辑部均突破了 2W+。其余的两位作者都在 1.5W左右。

4.2.5 综合排序 TOP10 作者

成就综合 TOP10

综合排序就是根据前面三个指标进行的综合排序,最终选出 10 位作者。

而前四名分别是我派编辑部化学心情下 2子不语 Rex以及 ElijiahLee

4.2.6 TOP10 作者 2013 年-2019 年数据变化

由于作者成就数据是汇总好的数据,如果要按照各年度来统计还需要额外爬取每个作者的所有文章;这里我就简单偷个懒采用 TOP10 作者在首页上的已有的数据进行加总,但是这当中就不包含写作字数了。

TOP10 作者各年数据

从图中可以看到 Clyde 的阅读量在 2015 年的时候大幅增长(那段时间大家都喜欢看 Android 的东西吗?)

总的来说,TOP10 的各位作者在 2016 年之后阅读量都有一定幅度的增长,侧面可以反映出来我派读者也有所增加。

5. 结尾

本文仅从爬虫和数据分析层面来展现 Python 所能做一些的事情。

Python 的入门门槛并不高,它能成为目前大数据领域 AI 或者机器学习各种课程推崇的语言,正是因为它简单且直观。

我本身就是一个文科生,非理工科专业,最开始学习的并不是 Python 而是 R 语言;但是我从 R 语言转到 Python 的速度非常之快,说明其入门成本并不高,有兴趣的朋友可以尝试一下。

不过,我并不推荐以代码的方式去在可视化细节方面花时间,因为代码对可视化细节的调整远不如手动操作来的快。代码的优势是在于前期快速地对数据分布进行基础的可视化探索,而非拘泥于细节

点击式的方法可以参考之前作者「王隐在录音」这篇关于 Matrix 爬取与可视化 的文章。

由于篇幅限制,除了以上维度分析之外,其实还有许多字段我可能没有用到,但这都是有东西可以深挖的。比如说标题,可以做个分词统计与词云图;又或者结合文章的 id 号来批量获取到每个文章下对应的内容,做更高级的自然语言处理,比如 LDA 主题分析、文本聚类等。

如果你是个数据分析高手或者对于机器学习、自然语言等方向有兴趣并且有一定的 Coding 能力,那么你可以用我本次文章的数据去做一些机器学习或者自然语言处理方面的尝试。相关源码和数据已经放到我的 Github 上,可以点击获取相关文件。

可能没接触过编程的人看源码会有点困难,但是如果你是有点基础或熟练的 Python 使用者,那么很轻易就能看懂。

(题图背景来自少数派下设微博号「吃派的水獭」的这条微博


22
2