[关闭]
@wade123 2019-04-22T00:58:10.000000Z 字数 18486 阅读 1262

实战项目 8:Pyspider 爬取分析虎嗅网 5 万篇文章

Python入门爬虫与数据分析


摘要: 使用 pyspider 框架爬取虎嗅网 5 万条新闻标题内容,并做数据可视化分析。

本文知识点:

1. 分析背景

1.1. 目标网站

在众多新媒体网站中,「虎嗅」网的文章内容和质量还算不错。在「新榜」科技类公众号排名中,它位居榜单第 3 名,很受欢迎,该网站是一个聚合优质创新信息新媒体平台,专注原创、深度、犀利优质的商业资讯文章,就选择它作为爬取目标。

1.2. 分析内容

2. 数据抓取

使用 pyspider 抓取了虎嗅网的主页文章,文章抓取时期为 2012 年建站至 2018 年 11 月 1 日,共计约 5 万篇文章。抓取 7 个字段信息:文章标题、作者、发文时间、评论数、收藏数、摘要和文章链接。

2.1. 目标网站分析

这是要爬取的 网页界面,可以看到是通过 AJAX 加载的。

右键打开开发者工具查看翻页规律,可以看到 URL 请求是 POST 类型,下拉到底部查看 Form Data,表单需提交参数只有 3 项。经尝试, 只提交 page 参数就能成功获取页面的信息,其他两项参数无关紧要,所以构造分页爬取非常简单。

  1. huxiu_hash_code: 39bcd9c3fe9bc69a6b682343ee3f024a
  2. page: 4
  3. last_dateline: 1541123160

接着,切换选项卡到 Preview 和 Response 查看网页内容,可以看到数据都位于 data 字段里。total_page 为 2004,表示一共有 2004 页的文章内容,每一页有 25 篇文章,总共约 5 万篇,也就是我们要爬取的数量。

以上,我们就找到了所需内容,接下来可以开始构造爬虫,整个爬取思路比较简单。之前的实战项目中,我们练习了 Ajax 网页爬取,可以参考:

多页图片批量下载:以澎湃网美数课栏目信息图为例(实战项目 4)

2.2. pyspider 介绍

之前我们爬虫没有使用过专门用于爬虫的框架,爬虫框架的功能要比手写的爬虫程序功能更强大,下面介绍一个有名的爬虫框架:pyspider 框架,该项目在 GitHub 上收获了超过 12 K 的 Star 数。可以说,学习爬虫的人都知道这个框架。

网上关于这个框架的介绍和实操案例非常多,这里仅简单介绍一下。

我们之前的爬虫都是在 Sublime 、PyCharm 这种 IDE 窗口中执行的,整个爬取过程可以说是处在黑箱中,内部运行的些细节并不太清楚。而 pyspider 一大亮点就在于提供了一个可视化的 WebUI 界面,能够清楚地查看爬虫的运行情况。

pyspider 的架构主要分为 Scheduler(调度器)、Fetcher(抓取器)、Processer(处理器)三个部分。Monitor(监控器)对整个爬取过程进行监控,Result Worker(结果处理器)处理最后抓取的结果。

我们看看该框架的运行流程大致是怎么样的:

该框架比较容易上手,网页右边是代码区,先定义类(Class)然后在里面添加爬虫的各种方法(也可以称为函数),运行的过程会在左上方显示,左下方则是输出结果的区域。

这里,分享几个不错的教程以供参考:

pyspider 中文网:http://www.pyspider.cn/page/1.html

pyspider 爬虫原理剖析:http://python.jobbole.com/81109/

pyspider 爬淘宝图案例实操:https://cuiqingcai.com/2652.html

安装好该框架后,下面我就可以开始爬取了。

2.3. 抓取数据

cmd 命令行执行:pyspider all 命令,然后浏览器输入:http://localhost:5000/ 就可以启动 pyspider 。

点击 Create 新建一个项目,Project Name 命名为:huxiu,因为要爬取的 URL 是 POST 类型,所以这里可以先不填写,之后可以在代码中添加,再次点击 Creat 便完成了该项目的新建。

新项目建立好后会自动生成一部分模板代码,我们只需在此基础上进行修改和完善,然后就可以运行爬虫项目了。现在,简单梳理下代码编写步骤。

  1. from pyspider.libs.base_handler import *
  2. class Handler(BaseHandler):
  3. crawl_config:{
  4. "headers":{
  5. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36',
  6. 'X-Requested-With': 'XMLHttpRequest'
  7. }
  8. }
  9. def on_start(self):
  10. for page in range(2,3): # 先循环 1 页
  11. print('正在爬取第 %s 页' % page)
  12. self.crawl('https://www.huxiu.com/v2_action/article_list',method='POST',data={'page':page}, callback=self.index_page)

这里,首先定义了一个 Handler 主类,整个爬虫项目都主要在该类下完成。 接着,可以将爬虫基本的一些基本配置,比如 Headers、代理等设置写在下面的 crawl_config 属性中。(如果你还没有习惯从函数(def)转换到类(Class)的代码写法,那么需要先了解一下类的相关知识,之后我也会单独用一篇文章介绍一下。)

下面的 on_start() 方法是程序的入口,也就是说程序启动后会首先从这里开始运行。首先,我们将要爬取的 URL 传入 crawl() 方法,同时将 URL 修改成虎嗅网的:https://www.huxiu.com/v2_action/article_list。由于 URL 是 POST 请求,所以我们还需要增加两个参数:method 和 data。method 表示 HTTP 请求方式,默认是 GET,这里我们需要设置为 POST;data 是 POST 请求表单参数,只需要添加一个 page 参数即可。

接着,通过 callback 参数定义一个 index_page() 方法,用来解析 crawl() 方法爬取 URL 成功后返回的 Response 响应。在后面的 index_page() 方法中,可以使用 PyQuery 提取响应中的所需内容。具体提取方法如下:

  1. import json
  2. from pyquery import PyQuery as pq
  3. def index_page(self, response):
  4. content = response.json['data']
  5. # 注意,在 sublime 中,json 后面需要添加(),pyspider 中则不用
  6. doc = pq(content)
  7. lis = doc('.mod-art').items()
  8. data = [{
  9. 'title': item('.msubstr-row2').text(),
  10. 'url':'https://www.huxiu.com'+ str(item('.msubstr-row2').attr('href')),
  11. 'name': item('.author-name').text(),
  12. 'write_time':item('.time').text(),
  13. 'comment':item('.icon-cmt+ em').text(),
  14. 'favorites':item('.icon-fvr+ em').text(),
  15. 'abstract':item('.mob-sub').text()
  16. } for item in lis ] # 列表生成式结果返回每页提取出 25 条字典信息构成的 list
  17. print(data)
  18. return data

这里,网页返回的 Response 是 json 格式,待提取的信息存放在其中的 data 键值中,由一段 HTML 代码构成。我们可以使用 response.json['data'] 获取该 HTML 信息,接着使用 PyQuery 搭配 CSS 语法提取出文章标题、链接、作者等所需信息。这里使用了列表生成式,能够精简代码并且转换为方便的 list 格式,便于后续存储到 MongoDB 中。我们输出并查看一下第 2 页的提取结果:

  1. # 由 25 个 dict 构成的 list
  2. [{'title': '想要长生不老?杀死体内的“僵尸细胞”吧', 'url': 'https://www.huxiu.com/article/270086.html', 'name': '造就 Talk', 'write_time': '19 小时前', 'comment': '4', 'favorites': '28', 'abstract': '如果有了最终疗法,也不应该是每天都需要接受治疗'},
  3. {'title': '日本步入下流社会,我们还在买买买', 'url': 'https://www.huxiu.com/article/270112.html', 'name': '腾讯《大家》©', 'write_time': '20 小时前', 'comment': '13', 'favorites': '142', 'abstract': '我买,故我在'}
  4. ...
  5. ]

可以看到,成功得到所需数据,然后就可以保存了,可以选择输出为 CSV、MySQL、MongoDB 等方式,这里我们选择保存到 MongoDB 中。

  1. import pandas as pd
  2. import pymongo
  3. import time
  4. import numpy as np
  5. client = pymongo.MongoClient('localhost',27017)
  6. db = client.Huxiu
  7. mongo_collection = db.huxiu_news
  8. def on_result(self,result):
  9. if result:
  10. self.save_to_mongo(result)
  11. def save_to_mongo(self,result):
  12. df = pd.DataFrame(result)
  13. #print(df)
  14. content = json.loads(df.T.to_json()).values()
  15. if mongo_collection.insert_many(content):
  16. print('存储到 mongondb 成功')
  17. # 随机暂停
  18. sleep = np.random.randint(1,5)
  19. time.sleep(sleep)

上面,定义了一个 on_result() 方法,该方法专门用来获取 return 的结果数据。这里用来接收上面 index_page() 返回的 data 数据,在该方法里再定义一个存储到 MongoDB 的方法就可以保存到 MongoDB 中。关于数据如何存储到 MongoDB 中,我们在之前的 一篇文章 中有过介绍,如果忘记了可以回顾一下。

下面,我们来测试一下整个爬取和存储过程。点击左上角的 run 就可以顺利运行单个网页的抓取、解析和存储,结果如下:

上面完成了单页面的爬取,接下来,我们需要爬取全部 2000 余页内容。

需要修改两个地方,首先在 on_start() 方法中将 for 循环页数 3 改为 2002。改好以后,如果我们直接点击 run ,会发现还是只能爬取第 2 页的结果。这是因为,pyspider 以 URL 的 MD5 值作为 唯一 ID 编号,ID 编号相同的话就视为同一个任务,便不会再重复爬取。由于 GET 请求的 分页 URL 通常是有差异的,所以 ID 编号会不同,也就自然能够爬取多页。但这里 POST 请求的分页 URL 是相同的,所以爬完第 2 页,后面的页数便不会再爬取。

那有没有解决办法呢? 当然是有的,我们需要重新写下 ID 编号的生成方式,方法很简单,在 on_start() 方法前面添加下面 2 行代码即可:

  1. def get_taskid(self,task):
  2. return md5string(task['url']+json.dumps(task['fetch'].get('data','')))

这样,我们再点击 run 就能够顺利爬取 2000 页的结果了,我这里一共抓取了 49,996 条结果,耗时 2 小时左右完成。

以上,就完成了数据的获取。有了数据我们就可以着手分析,不过这之前还需简单地进行一下数据的清洗、处理。

3. 数据清洗处理

首先,我们需要从 MongoDB 中读取数据,并转换为 DataFrame。

  1. client = pymongo.MongoClient(host='localhost', port=27017)
  2. db = client['Huxiu']
  3. collection = db['huxiu_news']
  4. # 将数据库数据转为 DataFrame
  5. data = pd.DataFrame(list(collection.find()))

下面我们看一下数据的总体情况,可以看到数据的维度是 49996 行 × 8 列。发现多了一列无用的 _id 需删除,同时 name 列有一些特殊符号,比如© 需删除。另外,数据格式全部为 Object 字符串格式,需要将 comment 和 favorites 两列更改为数值格式、 write_time 列更改为日期格式。

  1. print(data.shape) # 查看行数和列数
  2. print(data.info()) # 查看总体情况
  3. print(data.head()) # 输出前 5 行
  4. # 结果:
  5. (49996, 8)
  6. Data columns (total 8 columns):
  7. _id 49996 non-null object
  8. abstract 49996 non-null object
  9. comment 49996 non-null object
  10. favorites 49996 non-null object
  11. name 49996 non-null object
  12. title 49996 non-null object
  13. url 49996 non-null object
  14. write_time 49996 non-null object
  15. dtypes: object(8)
  16. _id abstract comment favorites name title url write_time
  17. 0 5bdc2 “在你们看到… 22 50 普象工业设计小站© 看了苹果屌 https:// 10 小时前
  18. 1 5bdc2 中国”绿卡”号称“世界最难拿” 9 16 经济观察报© 递交材料厚 https:// 10 小时前
  19. 2 5bdc2 鲜衣怒马少年时 2 13 小马宋 金庸小说陪 https:// 11 小时前
  20. 3 5bdc2 预告还是预警? 3 10 Cuba Libre 阿里即将发 https:// 11 小时前
  21. 4 5bdc2 库克:咋回事? 2 3 Cuba Libre 【虎嗅早报 https:// 11 小时前

代码实现如下:

  1. # 删除无用 _id 列
  2. data.drop(['_id'],axis=1,inplace=True)
  3. # 替换掉特殊字符©
  4. data['name'].replace('©','',inplace=True,regex=True)
  5. # 字符更改为数值
  6. data = data.apply(pd.to_numeric,errors='ignore')
  7. # 更该日期格式
  8. data['write_time'] = data['write_time'].replace('.*前','2018-10-31',regex=True)
  9. # 为了方便,将 write_time 列,包含几小时前和几天前的行,都替换为 10 月 31 日最后 1 天。
  10. data['write_time'] = pd.to_datetime(data['write_time'])

下面,我们看一下数据是否有重复,如果有,那么需要删除。

  1. # 判断整行是否有重复值
  2. print(any(data.duplicated()))
  3. # 显示 True,表明有重复值,进一步提取出重复值数量
  4. data_duplicated = data.duplicated().value_counts()
  5. print(data_duplicated) # 显示 2 True ,表明有 2 个重复值
  6. # 删除重复值
  7. data = data.drop_duplicates(keep='first')
  8. # 删除部分行后,index 中断,需重新设置 index
  9. data = data.reset_index(drop=True)
  10. #结果:
  11. True
  12. False 49994
  13. True 2

然后,我们再增加两列数据,一列是文章标题长度列,一列是年份列,便于后面进行分析。

  1. data['title_length'] = data['title'].apply(len)
  2. data['year'] = data['write_time'].dt.year
  3. Data columns (total 9 columns):
  4. abstract 49994 non-null object
  5. comment 49994 non-null int64
  6. favorites 49994 non-null int64
  7. name 49994 non-null object
  8. title 49994 non-null object
  9. url 49994 non-null object
  10. write_time 49994 non-null datetime64[ns]
  11. title_length 49994 non-null int64
  12. year 49994 non-null int64

以上,就完成了基本的数据清洗处理过程,针对这 9 列数据可以开始进行分析了。

4. 描述性数据分析

通常,数据分析主要分为四类: 「描述型分析」、「诊断型分析」「预测型分析」「规范型分析」。「描述型分析」是用来概括、表述事物整体状况以及事物间关联、类属关系的统计方法,是这四类中最为常见的数据分析类型。通过统计处理可以简洁地用几个统计值来表示一组数据地集中性(如平均值、中位数和众数等)和离散型(反映数据的波动性大小,如方差、标准差等)。

这里,我们主要进行描述性分析,数据主要为数值型数据(包括离散型变量和连续型变量)和文本数据。

4.1. 总体情况

先来看一下总体情况。

  1. print(data.describe())
  2. comment favorites title_length
  3. count 49994.000000 49994.000000 49994.000000
  4. mean 10.860203 34.081810 22.775333
  5. std 24.085969 48.276213 9.540142
  6. min 0.000000 0.000000 1.000000
  7. 25% 3.000000 9.000000 17.000000
  8. 50% 6.000000 19.000000 22.000000
  9. 75% 12.000000 40.000000 28.000000
  10. max 2376.000000 1113.000000 224.000000

这里,使用了 data.describe() 方法对数值型变量进行统计分析。从上面可以简要得出以下几个结论:

对于非数值型变量(name、write_time),使用 describe() 方法会产生另外一种汇总统计。

  1. print(data['name'].describe())
  2. print(data['write_time'].describe())
  3. # 结果:
  4. count 49994
  5. unique 3162
  6. top 虎嗅
  7. freq 10513
  8. Name: name, dtype: object
  9. count 49994
  10. unique 2397
  11. top 2014-07-10 00:00:00
  12. freq 274
  13. first 2012-04-03 00:00:00
  14. last 2018-10-31 00:00:00

unique 表示唯一值数量,top 表示出现次数最多的变量,freq 表示该变量出现的次数,所以可以简单得出以下几个结论:

4.2. 不同时期文章发布的数量变化

可以看到 ,以季度为时间尺度的 6 年间,前几年发文数量比较稳定,大概在 1750 篇左右,个别季度数量激增到 2000 篇以上。2016 年之后文章开始增加到 2000 篇以上,可能跟网站知名度提升有关。首尾两个季度日期不全,所以数量比较少。

具体代码实现如下:

  1. def analysis1(data):
  2. # # 汇总统计
  3. # print(data.describe())
  4. # print(data['name'].describe())
  5. # print(data['write_time'].describe())
  6. data.set_index(data['write_time'],inplace=True)
  7. data = data.resample('Q').count()['name'] # 以季度汇总
  8. data = data.to_period('Q')
  9. # 创建 x,y 轴标签
  10. x = np.arange(0,len(data),1)
  11. ax1.plot(x,data.values, #x、y 坐标
  12. color = color_line , #折线图颜色为红色
  13. marker = 'o',markersize = 4 #标记形状、大小设置
  14. )
  15. ax1.set_xticks(x) # 设置 x 轴标签为自然数序列
  16. ax1.set_xticklabels(data.index) # 更改 x 轴标签值为年份
  17. plt.xticks(rotation=90) # 旋转 90 度,不至太拥挤
  18. for x,y in zip(x,data.values):
  19. plt.text(x,y + 10,'%.0f' %y,ha = 'center',color = colors,fontsize=fontsize_text )
  20. # '%.0f' %y 设置标签格式不带小数
  21. # 设置标题及横纵坐标轴标题
  22. plt.title('虎嗅网文章数量发布变化(2012-2018)',color = colors,fontsize=fontsize_title)
  23. plt.xlabel('时期')
  24. plt.ylabel('文章(篇)')
  25. plt.tight_layout() # 自动控制空白边缘
  26. plt.savefig('虎嗅网文章数量发布变化.png',dpi=200)
  27. plt.show()

4.3. 文章收藏量 TOP 10

接下来,到了我们比较关心的问题:几万篇文章里,到底哪些文章写得比较好或者比较火?

序号 title favorites comment
1 读完这 10 本书,你就能站在智商鄙视链的顶端了 1113 13
2 京东打脸央视:你所谓的翻新 iPhone 均为正品,我们保留向警方报案的权利 867 10
3 离职创业?先读完这 22 本书再说 860 9
4 货币如水,覆水难收 784 39
5 自杀经济学 778 119
6 2016 年已经起飞的 5 只黑天鹅,都在罗振宇这份跨年演讲全文里 774 39
7 真正强大的商业分析能力是怎样炼成的? 746 18
8 腾讯没有梦想 705 32
9 段永平连答 53 问,核心是“不为清单” 703 27
10 王健林的滑铁卢 701 92

此处选取了「favorites」(收藏数量)作为衡量标准。毕竟,一般好的文章,我们都会有收藏的习惯。

第一名「读完这 10 本书,你就能站在智商鄙视链的顶端了 」以 1113 次收藏位居第一,并且遥遥领先于后者,看来大家都怀有「想早日攀上人生巅峰,一览众人小」的想法啊。打开这篇文章的链接,文中提到了这几本书:《思考,快与慢》、《思考的技术》、《麦肯锡入职第一课:让职场新人一生受用的逻辑思考力》等。一本都没看过,看来这辈子是很难登上人生巅峰了。

发现两个有意思的地方。

第一,文章标题都比较短小精炼。

第二,文章收藏量虽然比较高,但评论数都不多,猜测这是因为 大家都喜欢做伸手党

4.4. 历年文章收藏量 TOP3

在了解文章的总体排名之后,我们来看看历年的文章排名是怎样的。这里,每年选取了收藏量最多的 3 篇文章。

year title favorites
2012 产品的思路——来自腾讯张小龙的分享(全版) 187
Fab CEO:创办四家公司教给我的 90 件事 163
张小龙:微信背后的产品观 162
2013 创业者手记:我所犯的那些入门错误 473
马化腾三小时讲话实录:千亿美金这个线,其实很恐怖 391
雕爷亲身谈:白手起家的我如何在 30 岁之前赚到 1000 万。读《MBA 教不了的创富课》 354
2014 85 后,突变的一代 528
雕爷自述:什么是我做餐饮时琢磨、而大部分“外人”无法涉猎的思考? 521
据说这 40 张 PPT 是蚂蚁金服的内部培训资料…… 485
2015 读完这 10 本书,你就能站在智商鄙视链的顶端了 1113
京东打脸央视:你所谓的翻新 iPhone 均为正品,我们保留向警方报案的权利 867
离职创业?先读完这 22 本书再说 860
2016 蝗虫般的刷客大军:手握千万手机号,分秒间薅干一家平台 554
准 CEO 必读的这 20 本书,你读过几本? 548
运营简史:一文读懂互联网运营的 20 年发展与演变 503
2017 2016 年已经起飞的 5 只黑天鹅,都在罗振宇这份跨年演讲全文里 774
真正强大的商业分析能力是怎样炼成的? 746
王健林的滑铁卢 701
2018 货币如水,覆水难收 784
自杀经济学 778
腾讯没有梦想 705

可以看到,文章收藏量基本是逐年递增的,但 2015 年的 3 篇文章的收藏量却是最高的,包揽了总排名的前 3 名,不知道这一年的文章有什么特别之处。

以上只罗列了一小部分文章的标题,可以看到标题起地都蛮有水准的。关于标题的重要性,有这样通俗的说法:「一篇好文章,标题占一半」,一个好的标题可以大大增强文章的传播力和吸引力。文章标题虽只有短短数十字,但要想起好,里面也是很有很多技巧的。

好在,这里提供了 5 万个标题可以参考。

代码实现如下:

  1. def analysis2(data):
  2. # # 总收藏排名
  3. # top = data.sort_values(['favorites'],ascending = False)
  4. # # 收藏前 10
  5. # top.index = (range(1,len(top.index)+1)) # 重置 index,并从 1 开始编号
  6. # print(top[:10][['title','favorites','comment']])
  7. # 按年份排名
  8. # # 增加一列年份列
  9. # data['year'] = data['write_time'].dt.year
  10. def topn(data):
  11. top = data.sort_values('favorites',ascending=False)
  12. return top[:3]
  13. data = data.groupby(by=['year']).apply(topn)
  14. print(data[['title','favorites']])
  15. # 增加每年 top123 列,列依次值为 1、2、3
  16. data['add'] = 1 # 辅助
  17. data['top'] = data.groupby(by='year')['add'].cumsum()
  18. data_reshape = data.pivot_table(index='year',columns='top',values='favorites').reset_index()
  19. # print(data_reshape) # ok
  20. data_reshape.plot(
  21. # x='year',
  22. y=[1,2,3],
  23. kind='bar',
  24. width=0.3,
  25. color=['#1362A3','#3297EA','#8EC6F5'] # 设置不同的颜色
  26. # title='虎嗅网历年收藏数最多的 3 篇文章'
  27. )
  28. plt.xlabel('Year')
  29. plt.ylabel('文章收藏数量')
  30. plt.title('历年 TOP3 文章收藏量比较',color = colors,fontsize=fontsize_title)
  31. plt.tight_layout() # 自动控制空白边缘,以全部显示 x 轴名称
  32. # plt.savefig('历年 Top3 文章收藏量比较.png',dpi=200)
  33. plt.show()

4.4.1. 最高产作者 TOP20

上面,我们从收藏量指标进行了分析,下面,我们关注一下发布文章的作者(个人/媒体)。前面提到发文最多的是虎嗅官方,有一万多篇文章,这里我们筛除官媒,看看还有哪些比较高产的作者。

可以看到,前 20 名作者的发文量差距都不太大。发文比较多的有「娱乐资本论」、「Eastland」、「发条橙子」这类媒体号;也有虎嗅官网团队的作者:发条橙子、周超臣、张博文等;还有部分独立作者:假装 FBI、孙永杰等。可以尝试关注一下这些高产作者。

代码实现如下:

  1. def analysis3(data):
  2. data = data.groupby(data['name'])['title'].count()
  3. data = data.sort_values(ascending=False)
  4. # pandas 直接绘制,.invert_yaxis()颠倒顺序
  5. data[1:21].plot(kind='barh',color=color_line).invert_yaxis()
  6. for y,x in enumerate(list(data[1:21].values)):
  7. plt.text(x+12,y+0.2,'%s' %round(x,1),ha='center',color=colors)
  8. plt.xlabel('文章数量')
  9. plt.ylabel('作者')
  10. plt.title('发文数量最多的 TOP20 作者',color = colors,fontsize=fontsize_title)
  11. plt.tight_layout()
  12. plt.savefig('发文数量最多的 TOP20 作者.png',dpi=200)
  13. plt.show()

4.4.2. 平均文章收藏量最多作者 TOP 10

我们关注一个作者除了是因为文章高产以外,可能更看重的是其文章水准。这里我们选择「文章平均收藏量」(总收藏量/文章数)这个指标,来看看文章水准比较高的作者是哪些人。

这里,为了避免出现「某作者只写了一篇高收藏率的文章」这种不能代表其真实水准的情况,我们将筛选范围定在至少发布过 5 篇文章的作者们。

name total_favorites ariticls_num avg_favorites
重读 1947 6 324
楼台 2302 8 287
彭萦 2487 9 276
曹山石 1187 5 237
饭统戴老板 7870 36 218
笔记侠 1586 8 198
辩手李慕阳 11989 62 193
李录 2370 13 182
高晓松 889 5 177
宁南山 2827 16 176

可以看到,前 10 名作者包括:遥遥领先的 重读、两位高产又有质量的 辩手李慕阳饭统戴老板 ,还有大众比较熟悉的 高晓松、**宁南山 **等。

如果你将这份名单和上面那份高产作者名单进行对比,会发现他们没有出现在这个名单中。相比于数量,质量可能更重要吧。

下面,我们就来看看排名第一的 重读 都写了哪些高收藏量文章。

order title favorites write_time
1 我采访出 200 多万字素材,还原了阿里系崛起前传 231 2018/10/31
2 阿里史上最强人事地震回顾:中供铁军何以被生生解体 494 2018/4/9
3 马云“斩”卫哲:复原阿里史上最震撼的人事地震 578 2018/3/15
4 重读一场马云发起、针对卫哲的批斗会 269 2017/8/31
5 阿里“中供系”前世今生:马云麾下最神秘的子弟兵 203 2017/5/10
6 揭秘马云麾下最神秘的子弟兵:阿里“中供系”的前世今生 172 2017/4/26

居然写的都是清一色关于马老板家的文章。

了解了前十名作者之后,我们顺便也看看那些处于最后十名的都是哪些作者。

name total_favorites ariticls_num avg_favorites
于斌 25 11 2
朝克图 33 23 1
东风日产 24 13 1
董晓常 14 8 1
蔡钰 31 16 1
马继华 12 11 1
angeljie 7 5 1
薛开元 6 6 1
pookylee 15 24 0
Yang Yemeng 0 7 0

一对比,就能看到他们的文章收藏量就比较寒碜了。尤其好奇最后一位作者 Yang Yemeng ,他写了 7 篇文章,竟然一个收藏都没有。

来看看他究竟写了些什么文章。

原来写的全都是英文文章,看来大家并不太钟意阅读英文类的文章啊。

具体实现代码:

  1. def analysis4(data):
  2. data = pd.pivot_table(data,values=['favorites'],index='name',aggfunc=[np.sum,np.size])
  3. data['avg'] = data[('sum','favorites')]/data[('size','favorites')]
  4. # 平均收藏数取整
  5. # data['avg'] = data['avg'].round(decimals=1)
  6. data['avg'] = data['avg'].astype('int')
  7. # flatten 平铺列
  8. data.columns = data.columns.get_level_values(0)
  9. data.columns = ['total_favorites','ariticls_num','avg_favorites']
  10. # 筛选出文章数至少 5 篇的
  11. data=data.query('ariticls_num > 4')
  12. data = data.sort_values(by=['avg_favorites'],ascending=False)
  13. # # 查看平均收藏率第一名详情
  14. # data = data.query('name == "重读"')
  15. # # 查看平均收藏率倒数第一名详情
  16. # data = data.query('name == "Yang Yemeng"')
  17. # print(data[['title','favorites','write_time']])
  18. print(data[:10]) # 前 10 名
  19. print(data[-10:]) # 后 10 名

4.5. 文章评论数最多 TOP10

说完了收藏量。下面,我们再来看看评论数量最多的文章是哪些。

order title comment favorites
1 喜瓜 2.0—明星社交应用的中国式引进与创新 2376 3
2 百度,请给“儿子们”好好起个名字 1297 9
3 三星 S5 为什么对凤凰新闻客户端下注? 1157 1
4 三星 Tab S:马是什么样的马?鞍又是什么样的鞍? 951 0
5 三星,正在重塑你的营销观 914 1
6 马化腾,你就把微信卖给运营商得了! 743 20
7 【文字直播】罗永浩 VS 王自如 网络公开辩论 711 33
8 看三星 Hub 如何推动数字内容消费变革 684 1
9 三星要重新定义软件与内容商店新模式,SO? 670 0
10 三星 Hub——数字内容交互新模式 611 0

基本上都是和 三星 有关的文章,这些文章大多来自 2014 年,那几年 三星 好像是挺火的,不过这两年国内基本上都见不到三星的影子了,世界变化真快。

发现了两个有意思的现象。

第一,上面关于 三星 和前面 阿里 的这些批量文章,它们「霸占」了评论和收藏榜,结合知乎上曾经的一篇关于介绍虎嗅这个网站的文章:[虎嗅网其实是这样的](
https://www.zhihu.com/question/20799239/answer/20698562) ,貌似能发现些微妙的事情。

第二,这些文章评论数和收藏数两个指标几乎呈极端趋势,评论量多的文章收藏量却很少,评论量少的文章收藏量却很多。

我们进一步观察下这两个参数的关系。

可以看到,大多数点都位于左下角,意味着这些文章收藏量和评论数都比较低。但也存在少部分位于上方和右侧的异常值,表明这些文章呈现 「多评论、少收藏」或者「少评论、多收藏」的特点。

4.6. 文章标题长度

下面,我们再来看看文章标题的长度和收藏量之间有没有什么关系。

大致可以看出两点现象:

第一,收藏量高的文章,他们的标题都比较短(右侧的部分散点)。

第二,标题很长的文章,它们的收藏量都非常低(左边形成了一条垂直线)。

看来,文章起标题时最好不要起太长的。

实现代码如下:

  1. def analysis5(data):
  2. plt.scatter(
  3. x=data['favorites'],
  4. y =data['comment'],
  5. s=data['title_length']/2,
  6. )
  7. plt.xlabel('文章收藏量')
  8. plt.ylabel('文章评论数')
  9. plt.title('文章标题长度与收藏量和评论数之间的关系',color = colors,fontsize=fontsize_title)
  10. plt.tight_layout()
  11. plt.show()

文章标题形式

下面,我们看看作者在起文章标题的时候,在标点符号方面有没有什么偏好。

可以看到,五万篇文章中,大多数文章的标题是陈述性标题。三分之一(34.8%) 的文章标题使用了问号「?」,而仅有 5% 的文章用了叹号「!」。通常,问号会让人们产生好奇,从而想去点开文章;而叹号则会带来一种紧张或者压迫感,使人不太想去点开。所以,可以尝试多用问号而少用叹号。

使用 Matplotlib 的饼图来绘制:

  1. def analysis10(data):
  2. data1 = data[data['title'].str.contains("(.*\?.*)|(.*\?.*)")]
  3. data2 = data[data['title'].str.contains("(.*\!.*)|(.*\!.*)")]
  4. # 带有问号的标题数量
  5. quantity1 = data1.shape[0]
  6. # 带有叹号的标题数量
  7. quantity2 = data2.shape[0]
  8. # 剩余数量
  9. quantity = data.shape[0] - data1.shape[0] - data2.shape[0]
  10. sizes = [quantity2,quantity1,quantity]
  11. labels = [u'叹号标题',u'问号标题',u'陈述性标题']
  12. colors_pie = ['#1362A3','#3297EA','#8EC6F5'] #每块颜色定义
  13. explode = [0,0.05,0]
  14. plt.pie(
  15. sizes,
  16. autopct='%.1f%%',
  17. labels= labels,
  18. colors =colors_pie,
  19. shadow = False, #无阴影设置
  20. startangle =90, #逆时针起始角度设置
  21. explode = explode,
  22. # textprops={'fontsize': 14, 'color': 'w'} # 设置文字颜色
  23. textprops={'fontsize': 12, 'color': 'w'} # 设置文字颜色
  24. )
  25. plt.title('三分之一文章的标题喜欢用问号',color=colors,fontsize=fontsize_title)
  26. plt.axis('equal')
  27. plt.axis('off')
  28. plt.legend(loc = 'upper right')
  29. plt.tight_layout() # 自动控制空白边缘,以全部显示 x 轴名称
  30. plt.savefig('title 问号.png',dpi=200)
  31. plt.show()

4.7. 文本分析

最后,我们从这 5 万篇文章中的标题和摘要中,来看看虎嗅网的文章主要关注的都是哪些主题领域。

这里首先运用了 jieba 分词包对标题进行了分词,然后用 WordCloud 做成了词云图,因虎嗅网含有「虎」字,故选取了一张老虎头像。(关于 jieba 和 WordCloud 两个包,之后再详细介绍)

可以看到文章的主题内容侧重于:互联网、知名公司、电商、投资这些领域。这和网站本身对外宣传的核心内容,即「关注互联网与移动互联网一系列明星公司的起落轨迹、产业潮汐的动力与趋势,以及互联网与移动互联网如何改造传统产业」大致相符合。

代码实现如下:

  1. def analysis6(data):
  2. text=''
  3. for i in data['title'].values:
  4. symbol_to_replace = '[!"#$%&\'()*+,-./:;<=>?@,。?★、…【】《》?“”‘’![\\]^_`{|}~]+'
  5. i = re.sub(symbol_to_replace,'',i)
  6. text+=' '.join(jieba.cut(i,cut_all=False))
  7. d = path.dirname(__file__) if "__file__" in locals() else os.getcwd()
  8. background_Image = np.array(Image.open(path.join(d, "tiger.png")))
  9. font_path = 'C:\Windows\Fonts\SourceHanSansCN-Regular.otf' # 思源黑字体
  10. # 添加 stopswords
  11. stopwords = set()
  12. # 先运行对 text 进行词频统计再排序,再选择要增加的停用词
  13. stopwords.update(['如何','怎么','一个','什么','为什么','还是','我们','为何','可能','不是','没有','哪些','成为','可以','背后','到底','就是','这么','不要','怎样','为了','能否','你们','还有','这样','这个','真的','那些'])
  14. wc = WordCloud(
  15. background_color = 'black',
  16. font_path = font_path,
  17. mask = background_Image,
  18. stopwords = stopwords,
  19. max_words = 2000,
  20. margin =2,
  21. max_font_size = 100,
  22. random_state = 42,
  23. scale = 2,
  24. )
  25. wc.generate_from_text(text)
  26. process_word = WordCloud.process_text(wc, text)
  27. # 下面是字典排序
  28. sort = sorted(process_word.items(),key=lambda e:e[1],reverse=True) # sort 为 list
  29. print(sort[:50]) # 输出前词频最高的前 50 个,然后筛选出不需要的 stopwords,添加到前面的 stopwords.update()方法中
  30. img_colors = ImageColorGenerator(background_Image)
  31. wc.recolor(color_func=img_colors) # 颜色跟随图片颜色
  32. plt.imshow(wc,interpolation='bilinear')
  33. plt.axis('off')
  34. plt.tight_layout() # 自动控制空白边缘
  35. plt.savefig('huxiu20.png',dpi=200)
  36. plt.show()

上面的关键词是这几年总体的概况,而科技互联网行业每年的发展都是不同的,所以,我们再来看看历年的一些关键词,透过这些关键词看看这几年互联网行业、科技热点、知名公司都有些什么不同变化。

可以看到每年的关键词都有一些相同之处,但也不同的地方:

通过这一幅图,就看出了这几年科技互联网行业、明星公司、热点信息的风云变化。

以上就是一个完整的爬虫结合数据分析的实战项目。

文中完整代码和素材,可以在下面的链接中得到:

https://github.com/makcyun/web_scraping_with_python/tree/master/pyspider%E7%88%AC%E5%8F%96%E8%99%8E%E5%97%85%E7%BD%915%E4%B8%87%E7%AF%87%E6%96%87%E7%AB%A0

下一篇文章,我们练习使用更加强大的 Scrapy 框架。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注