一、分析背景
1、1 网站选取
关于虎嗅,虽然是小众的互联网媒体,没有像擅长于用户推荐的今日头条、专注于时政的澎湃新闻那样广为人知。但其有内涵、有质量,能看到最新的消息与某些观点的深入分析,包括微信公众号,大部分会订阅虎嗅的公众号。
再者,文本重点在于展现和学习文本挖掘的思路和整体框架。至于其载体是虎嗅还是澎湃,显得或许没有那么重要了。
1.2 分析目的
1、熟悉熟悉分析流程,尝试和学习一些有意思的东西;
2、展现数据之美,体现数据的奥妙和给人带来的强烈的视觉冲击;
3、基于文本挖掘,分析虎嗅网这家网站的运营方向,专注领域,不同文章的书写手法,受欢迎文章所具备的特点等。
1.3 使用到的数据分析工具
- Python 3.5.0
- PyCharm 2018.3
- Pyspider 0.3.10
- MongoDB 4.0.4
- Studio-3T
- jieba
- WordCloud
- R 3.5.1
- RStudio 3.5.1
- Jupyter 4.4.0
二、前期准备
2.1 Pyspider
2.1.1Pyspider简介
Pyspider是一个非常高效、简单的框架,而且提供了一个WebUI界面。你可以在WebUI界面里编写你的爬虫代码,管理爬虫状态,查看当前调用的任务。
- Pyspider内置了PyQuery解析,可以使用任何你喜欢的html解析包;
- 数据库支持MongoDB、MySQl、Redis、SQLite等;
- 支持抓取经过JavaScript渲染的页面
- 多进程处理
2.1.2 Pyspider安装及配置
节省篇幅,话不多说,链接附上:[Pyspider安装及配置]https://blog.csdn.net/qq_42336565/article/details/80697482
2.2 MongoDB
2.2.1 MongoDB简介
MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
2.2.2 MongoDB安装及配置
按照下面的教程来安装:
https://jingyan.baidu.com/article/a3f121e493e592fc9052bbfe.html 需要强调的一点是:第10步:
![](https://img.haomeiwen.com/i13155641/2a7b32c4da4e4141.png)
这一步你要是很任性地像安装其他软件一样,选择了自定义的安装路径,或者在这一步:
![](https://img.haomeiwen.com/i13155641/93e3b25e35b48ce8.png)
左下角,你勾上了Install MongoDB Compass,那你有可能就玩完了。。。接下来安装进程:
![](https://img.haomeiwen.com/i13155641/40a59b448005a140.png)
进度条可能会卡在70%左右,一直不变,这个问题折磨了好久,老泪纵横,最后还是乖乖地默认安装路径。MongoDB还真是傲娇得很呐!
三、数据获取及预处理
3.1数据爬取
鉴于虎嗅网主页是主编精挑细选出来的,很据代表性,能反映虎嗅网的整体状况,本文使用 Pyspider 抓取了来自[虎嗅网] https://www.huxiu.com/的主页文章。
3.1.1 使用PyCharm
照常,我们用PyCharm来做,检查虎嗅原网页:
![](https://img.haomeiwen.com/i13155641/4995003a9317d158.png)
设置服务器代理
def get_one_page(my_headers,url):
randdom_header = random.choice(my_headers)
req = urllib.request.Request(url)
req.add_header("User-Agent", randdom_header)
req.add_header("GET", url)
response = urllib.request.urlopen(req)
return response
#代理服务器
my_headers = [
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14",
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)"
]
获取原网页
def get_href(html):
pattern = re.compile('<div class="mod-b mod-art clearfix "'
'.*?"transition" href="(.*?)"'
'.*?</div>', re.S)
items =re.findall(pattern, html)
return items
使用正则表达式,解析原网页
def parse_one_page(href):
pattern = re.compile('<div class="article-wrap">'
'.*?class="t-h1">(.*?)</h1>'
'.*?article-time pull-left">(.*?)</span>'
'.*?article-share pull-left">(.*?)</span>'
'.*?article-pl pull-left">(.*?)</span>'
# '.*?text-remarks.*?</p><p><br/></p><p>(.*?)<!--.*?认证-->'
'.*?author-name.*?<a href=".*?" target="_blank">(.*?)</a>'
'.*?author-one">(.*?)</div>'
'.*?author-article-pl.*?target="_blank">(.*?)</a></li>'
'.*?</div>', re.S)
将获得的参数值转化成键值对
items =re.findall(pattern, href)
for item in items:
yield {
'title': item[0].strip(),
'time': item[1],
'share': item[2][2:],
'recoment': item[3][2:],
# 'content': re.compile(r'<[^>]+>',re.S).sub('',item[4]).strip(),
'anthor': item[4].strip(),
'intro': item[5],
'passNum': item[6]
}
循环遍历,抓取第一页所有文章
for i in range(len(url_html)):
url_ord = "https://www.huxiu.com" + url_html[i]
ord_text = get_one_page(my_headers, url_ord).read().decode('utf-8')
for item in parse_one_page(ord_text):
print(item)
write_to_file(item)
保存到文件text.txt中
def write_to_file(content):
with open('text.txt','a',encoding='utf-8') as f:
f.write(json.dumps(content, ensure_ascii=False)+'\n')
f.close()
爬取结果:
{"title": "裁员凶猛", "time": "2018-12-10 20:35", "share": "3", "recoment": "2", "anthor": "华夏时报©", "intro": "", "passNum": "54篇文章"}
{"title": "新东方烹饪学校都要上市了,你竟然还看不起职校", "time": "2018-12-10 20:31", "share": "3", "recoment": "0", "anthor": "敲敲格", "intro": "空山无人", "passNum": "150篇文章"}
{"title": "互联网太可怕,我还是做回煤老板吧", "time": "2018-12-10 20:22", "share": "6", "recoment": "2", "anthor": "故事FM©", "intro": "", "passNum": "16篇文章"}
{"title": "《海王》,大型“海底捞”现场", "time": "2018-12-10 20:01", "share": "3", "recoment": "2", "anthor": "mrpuppybunny", "intro": "", "passNum": "316篇文章"}
{"title": "从成龙说起:这届中国男明星不行", "time": "2018-12-10 19:51", "share": "10", "recoment": "2", "anthor": "腾讯《大家》©", "intro": "精选大家文章,畅享阅读时光。", "passNum": "119篇文章"}
{"title": "互联网寒冬,不是深渊,而是阶梯", "time": "2018-12-10 19:36", "share": "14", "recoment": "2", "anthor": "瞎说职场©", "intro": "", "passNum": "8篇文章"}
{"title": "【虎嗅晚报】和山寨Supreme合作?三星:这是意大利Supreme", "time": "2018-12-10 19:29", "share": "1", "recoment": "0", "anthor": "敲敲格", "intro": "空山无人", "passNum": "150篇文章"}
{"title": "老干妈:不上市的底气与逻辑", "time": "2018-12-10 18:28", "share": "9", "recoment": "6", "anthor": "中国经济信息杂志©", "intro": "信息改变生存质量", "passNum": "5篇文章"}
{"title": "2018,一文看尽AI发展真相", "time": "2018-12-10 18:16", "share": "18", "recoment": "0", "anthor": "新智元", "intro": "人工智能全产业平台", "passNum": "52篇文章"}
{"title": "《任天堂明星大乱斗:特别版》:我们都爱“大杂烩”", "time": "2018-12-10 17:54", "share": "1", "recoment": "1", "anthor": "我不叫塞尔达", "intro": "", "passNum": "81篇文章"}
{"title": "罗玉凤不认命", "time": "2018-12-10 17:46", "share": "25", "recoment": "12", "anthor": "盖饭人物ThePeople©", "intro": "冷眼看人间,心如火焰。", "passNum": "3篇文章"}
{"title": "星巴克被骂上热搜,新会员体系把老用户都气哭了", "time": "2018-12-10 16:40", "share": "10", "recoment": "9", "anthor": "运营研究社", "intro": "", "passNum": "13篇文章"}
{"title": "雀巢紧急召回一批问题奶粉,可致婴儿恶心呕吐", "time": "2018-12-10 16:36", "share": "5", "recoment": "0", "anthor": "每日经济新闻©", "intro": "", "passNum": "78篇文章"}
{"title": "8012年了,我们为什么还沉迷于“捏脸”游戏?", "time": "2018-12-10 16:27", "share": "9", "recoment": "3", "anthor": "看理想©", "intro": "“看理想”诞生于知名出版品牌“...", "passNum": "57篇文章"}
{"title": "广州三重奏:认识中国“南方”的一个视角", "time": "2018-12-10 16:25", "share": "21", "recoment": "0", "anthor": "东方历史评论©", "intro": "《东方历史评论》杂志官方微信账...", "passNum": "3篇文章"}
{"title": "得了癌症,怎么告诉孩子", "time": "2018-12-10 16:00", "share": "19", "recoment": "1", "anthor": "谢熊猫君", "intro": "", "passNum": "7篇文章"}
{"title": "从校园到职场:什么是职场经验", "time": "2018-12-10 15:43", "share": "38", "recoment": "5", "anthor": "caoz的梦呓©", "intro": "", "passNum": "70篇文章"}
{"title": "我们不禁要问:这些怪物在科学的地图周围在做什么呢?", "time": "2018-12-10 15:10", "share": "15", "recoment": "1", "anthor": "一席©", "intro": "", "passNum": "37篇文章"}
{"title": "一边卖命,一边求生,300万中国底层现状", "time": "2018-12-10 14:36", "share": "31", "recoment": "6", "anthor": "一条©", "intro": "每天一条原创短视频,每天讲述一...", "passNum": "1篇文章"}
{"title": "像拍《海王》一样拍《西游记》,会是什么样?", "time": "2018-12-10 14:30", "share": "13", "recoment": "10", "anthor": "壹条电影©", "intro": "", "passNum": "4篇文章"}
{"title": "DC翻身作《海王》,其实是一部环保教育宣传片", "time": "2018-12-10 14:00", "share": "6", "recoment": "8", "anthor": "PingWest品玩©", "intro": "有品好玩的科技,一切与你有关", "passNum": "71篇文章"}
{"title": "平台&大媒体都在输血本地新闻, 这样的合作模式真的可持续吗?", "time": "2018-12-10 14:00", "share": "8", "recoment": "2", "anthor": "全媒派©", "intro": "", "passNum": "78篇文章"}
到目前为止,第一页的文章已经处理掉了,本以为一切自然一帆风顺的,万事皆大欢喜,不就多爬几页嘛,一个循环不就得了。事实证明我想得简单了。
当点击第一页下面这个“加载更多”时,发现其经过了JavaScript渲染。
![](https://img.haomeiwen.com/i13155641/ad47745debd914d0.png)
分析该请求的方式和地址,包括参数,如下图所示:
![](https://img.haomeiwen.com/i13155641/0675b53ddc98158c.png)
得到以下信息:
- 页面请求地址为:https://www.huxiu.com/v2_action/article_list
- 请求方式:POST
- 请求参数比较重要的是一个叫做page的参数
3.1.2 使用PySpider爬取动态加载页面
on_start 函数内部编写循环事件,我们本次爬取2000页;
@every(minutes=24 * 60)
def on_start(self):
for page in range(1,2000):
print("正在爬取第 {} 页".format(page))
self.crawl('https://www.huxiu.com/v2_action/article_list', method="POST",data={"page":page},callback=self.parse_page,validate_cert=False)
页面生成完毕之后,开始调用parse_page 函数,用来解析 crawl() 方法爬取 URL 成功后返回的 Response 响应。
def parse_page(self, response):
content = response.json["data"]
doc = pq(content)
lis = doc('.mod-art').items()
data = [{
'title': item('.msubstr-row2').text(),
'url':'https://www.huxiu.com'+ str(item('.msubstr-row2').attr('href')),
'name': item('.author-name').text(),
'write_time':item('.time').text(),
'comment':item('.icon-cmt+ em').text(),
'favorites':item('.icon-fvr+ em').text(),
'abstract':item('.mob-sub').text()
} for item in lis ]
return data
最后,定义一个 on_result() 方法,该方法专门用来获取 return 的结果数据。这里用来接收上面 parse_page() 返回的 data 数据,在该方法可以将数据保存到 MongoDB 中。
def on_result(self, result):
if result:
self.save_to_mongo(result)
def save_to_mongo(self, result):
df = pd.DataFrame(result)
content = json.loads(df.T.to_json()).values()
if mongo_collection.insert_many(content):
print('存储到mongodb成功')
sleep = np.random.randint(1,5)
time.sleep(sleep)
pyspider 以 URL的 MD5 值作为 唯一 ID 编号,ID 编号相同,就视为同一个任务, 不会再重复爬取。
GET 请求的分页URL 一般不同,所以 ID 编号会不同,能够爬取多页。
POST 请求的URL是相同的,爬取第一页之后,后面的页数便不会再爬取。
为了爬取第2页及之后,重新写下 ID 编号的生成方式,在 on_start() 方法前面添加下面代码:
def get_taskid(self,task):
return md5string(task['url']+json.dumps(task['fetch'].get('data','')))
数据保存到 了MongoDB 中:
![](https://img.haomeiwen.com/i13155641/3c79f8de5c2ccc59.png)
共计2000页, 28222篇文章。抓取 了 7 个字段信息:文章标题、作者、发文时间、评论数、收藏数、摘要和文章链接。
3.2 数据清洗
首先,我们需要从 MongoDB 中读取数据,并转换为 DataFrame。
client = pymongo.MongoClient(host='localhost', port=27017)
db = client['Huxiu']
collection = db['News']
# 将数据库数据转为dataFrame
data = pd.DataFrame(list(collection.find()))
下面我们看一下数据的行数和列数,整体情况及数据的前五行。
#查看行数和列数
print(data.shape)
#查看总体情况
print(data.info())
结果:
(28222, 8)
RangeIndex: 28222 entries, 0 to 28221
Data columns (total 8 columns):
_id 28222 non-null object
abstract 28222 non-null object
comment 28222 non-null object
favorites 28222 non-null object
name 28222 non-null object
title 28222 non-null object
url 28222 non-null object
write_time 28222 non-null object
可以看到数据的维度是 28222行 × 8 列。发现多了一列无用的 _id 需删除,同时 name 列有一些特殊符号,比如© 需删除。另外,数据格式全部为 Object 字符串格式,需要将 comment 和 favorites 两列更改为数值格式、 write_time 列更改为日期格式。
# 删除无用的_id列
data.drop(['_id'], axis=1, inplace=True)
# 删除特殊符号@
data['name'].replace('@','',inplace=True,regex=True)
data_duplicated = data.duplicated().value_counts()
# 将数据列改为数值列
data = data.apply(pd.to_numeric, errors='ignore')
# 修改时间,并转换为datetime格式
data['write_time'] = pd.to_datetime(data['write_time'])
data = data.reset_index(drop=True)
下面,我们看一下数据是否有重复,如果有,那么需要删除。
# 删除重复值
data = data.drop_duplicates(keep='first')
我们再增加两列数据,一列是文章标题长度列,一列是年份列,便于后面进行分析
# 增加标题长度列
data['title_length'] = data['title'].apply(len)
# 年份列
data['year'] = data['write_time'].dt.year
以上,就完成了基本的数据清洗处理过程,针对这 9 列数据开始进行分析。
四、数据统计分析
4.1 整体情况
先来看一下总体情况:
print(data.describe())
结果:
comment favorites title_length year
count 27236.000000 27236.000000 27236.000000 27236.000000
mean 9.030988 40.480761 23.010501 2016.382288
std 14.912655 52.381115 8.376050 1.516007
min 0.000000 0.000000 3.000000 2012.000000
25% 3.000000 12.000000 17.000000 2016.000000
50% 6.000000 24.000000 23.000000 2017.000000
75% 11.000000 48.000000 28.000000 2017.000000
max 914.000000 787.000000 124.000000 2018.000000
使用了 data.describe() 方法对数值型变量进行统计分析。从上面可以简要得出以下几个结论:
- 读者的评论和收藏热情都不算太高。大部分文章(75 %)的评论数量为十几条,收藏数量不过几十个。这和一些微信大 V 公众号动辄百万级阅读、数万级评论和收藏量相比,虎嗅网的确相对小众一些。不过也正是因为小众,也才深得部分人的喜欢。
- 评论数最多的文章有914 条,收藏数最多的文章有 787 个收藏量,说明还是有一些潜在的比较火或者质量比较好的文章。
- 最长的文章标题长达 124 个字,大部分文章标题长度在 20 来个字左右,所以标题最好不要太长或过短。
print(data['name'].describe())
print(data['write_time'].describe())
结果:
count 27236
unique 3334
top 虎嗅
freq 2289
count 27236
unique 1390
top 2017-04-25 00:00:00
freq 44
first 2012-06-27 00:00:00
last 2018-10-20 00:00:00
unique 表示唯一值数量,top 表示出现次数最多的变量,freq 表示该变量出现的次数,所以可以简单得出以下几个结论:
- 在文章来源方面,3334 个作者贡献了这 27236篇文章,其中自家官网「虎嗅」写的数量最多,有2289篇,这也很自然。
- 在文章发表时间方面,最早的一篇文章来自于 2012年 6 月 27日。 6 年多时间,发文数最多的 1 天 是 2017 年4 月 25 日,一共发了 44 篇文章。
4.2 虎嗅网文章发布数量变化
def analysis1(data):
data.set_index(data['write_time'], inplace=True)
data = data.resample('Q').count()['name'] # 以季度汇总
data = data.to_period('Q')
# 创建x,y轴标签
x = np.arange(0, len(data), 1)
axl.plot(x, data.values,
color = color_line,
marker = 'o', markersize = 4
)
axl.set_xticks(x) # 设置x轴标签为自然数序列
axl.set_xticklabels(data.index) # 更改x轴标签值为年份
plt.xticks(rotation=90) # 旋转90度,不至于太拥挤
for x,y in zip(x,data.values):
plt.text(x,y + 10, '%.0f' %y,ha = 'center', color = colors, fontsize=fontsize_text)
# 设置标题及横纵坐标轴标题
plt.title('虎嗅网文章数量发布变化(2012-2018)', color = colors, fontsize=fontsize_title)
plt.xlabel('时期')
plt.ylabel('文章(篇)')
plt.tight_layout() # 自动控制空白边缘
plt.savefig('虎嗅网文章数量发布变化.png', dip=200)
plt.show()
结果:
![](https://img.haomeiwen.com/i13155641/2625721c1ea0331b.png)
可以看到 ,以季度为时间尺度的 6 年间,12年-15年发文数量比较稳定,大概在400篇左右。但2016 年之后文章开始增加到 2000 篇以上,可能跟虎嗅网于2015年2月上市有关。首尾两个季度日期不全,所以数量比较少。
4.3 文章收藏量TOP10
几万篇文章里,到底哪些文章写得比较好或者比较火?
top = data.sort_values('favorites', ascending=False)
top.index=(range(1,len(top.index)+1))
print(top[:10][['title','favorites','comment']])
结果:
title favorites comment
1 货币如水,覆水难收 787 39
2 自杀经济学 781 119
3 2016年已经起飞的5只黑天鹅,都在罗振宇这份跨年演讲全文里 774 39
4 真正强大的商业分析能力是怎样炼成的? 747 18
5 藏在县城的万亿生意 718 35
6 腾讯没有梦想 707 32
7 段永平连答53问,核心是“不为清单” 706 27
8 王健林的滑铁卢 703 92
9 7-11不死 691 17
10 游戏策划人士:为什么我的儿子不沉迷游戏? 644 33
发现两个有意思的地方:第一,文章标题都比较短小精炼。第二,文章收藏量虽然比较高,但评论数都不多,猜测这是因为——大家都喜欢做伸手党?
4.4 历年TOP3文章收藏比较
在了解文章的总体排名之后,我们来看看历年的文章排名是怎样的。这里,每年选取了收藏量最多的 3 篇文章。
def analysis2(data):
def topn(data):
top = data.sort_values('favorites', ascending=False)
return top[:3]
data = data.groupby(by=['year']).apply(topn)
print(data[['title', 'favorites']])
# 增加每年top123列,列依次值为1、2、3
data['add'] = 1 # 辅助
data['top'] = data.groupby(by='year')['add'].cumsum()
data_reshape = data.pivot_table(index='year', columns='top', values='favorites').reset_index()
print(data_reshape)
data_reshape.plot(
y = [1,2,3],
kind = 'bar',
width = 0.3,
color = ['#1362A3', '#3297EA', '#8EC6F5']
)
# 添加x轴标签
years = data['year'].unique()
plt.xticks(list(range(7)), years)
plt.xlabel('Year')
plt.ylabel('文章收藏数量')
plt.title('历年TOP3文章收藏比较', color = colors, fontsize = fontsize_title)
plt.tight_layout()
plt.savefig('历年TOP3文章收藏比较.png', dpi=200)
plt.show()
结果:
![](https://img.haomeiwen.com/i13155641/381ede2c481c64b6.png)
可以看到,文章收藏量是逐年递增的。
year title favorites
2012 3199 怎么做难做的本地生活服务? 106
2236 经营微博的十个经典案例 101
3449 《大数据时代》,一场生活、工作与思维的大变革 91
2013 3795 情色网站启示:请尊重你不懂的领域 246
235 周鸿祎的一部分,由他读的这些书构成 245
4071 如果你不想老被思考的陷阱绊倒,建议熟读《清醒思考的艺术:你最好让别人去犯的52种思维错误》
221
2014 660 阿里巴巴怎么看O2O对商业生态的破局与重构? 443
2413 别等中文译本了,Peter Thiel《Zero to One》中的13条逆向创业观点 308
2563 这是迄今还原得最完整的——“雷军系” 278
2015 4107 看了这套PPT,你就知道房地产商将要如何玩转社区O2O的啦 452
3298 不要再徒手创业了,这些好用的工具软件请拿走 372
4373 非常赞的文章!告诉你一个你知其然却不知其所以然的硅谷 357
2016 418 蝗虫般的刷客大军:手握千万手机号,分秒间薅干一家平台 554
12618 准CEO必读的这20本书,你读过几本? 548
17225 运营简史:一文读懂互联网运营的20年发展与演变 505
2017 21898 2016年已经起飞的5只黑天鹅,都在罗振宇这份跨年演讲全文里 774
144 真正强大的商业分析能力是怎样炼成的? 747
11471 王健林的滑铁卢 703
2018 19686 货币如水,覆水难收 787
19230 自杀经济学 781
16362 藏在县城的万亿生意 718
可以看到标题起地都蛮有水准的。关于标题的重要性,有这样通俗的说法:「一篇好文章,标题占一半」,一个好的标题可以大大增强文章的传播力和吸引力。文章标题虽只有短短数十字,但要想起好,里面也是很有很多技巧的。
4.5 发文数量最多的TOP20作者
上面,我们从收藏量指标进行了分析,下面,我们关注一下发布文章的作者(个人/媒体)。前面提到发文最多的是虎嗅官方,有一万多篇文章,这里我们筛除官媒,看看还有哪些比较高产的作者。
def analysis3(data):
data = data.groupby(data['name'])['title'].count()
data = data.sort_values(ascending=False)
print(data)
# pandas 直接绘制,invert_yaxis()颠倒顺序
data[1:21].plot(kind='barh',color=color_line).invert_yaxis()
for y,x in enumerate(list(data[1:21].values)):
plt.text(x+12,y+0.2,'%s' %round(x,1),ha='center',color=colors)
plt.xlabel('文章数量')
plt.ylabel('作者')
plt.title('发文数量最多的TOP20作者', color = colors, fontsize=fontsize_title)
plt.tight_layout()
plt.savefig('发文数量最多的TOP20作者.png',dpi=200)
plt.show()
结果:
![](https://img.haomeiwen.com/i13155641/aeb8dfc834128d12.png)
可以看到,前 20 名作者的发文量差距都不太大。发文比较多的有「娱乐资本论」、「发条橙子」、「界面」、「新浪科技」这类媒体号;也有虎嗅官网团队的作者:张博文、周超臣等;还有部分独立作者:假装FBI等。可以尝试关注一下这些高产作者。
4.6 文章评论数与收藏量的关系
def analysis6(data):
plt.scatter(data['favorites'], data['comment'], s=8, color='#1362A3')
plt.xlabel('文章收藏量')
plt.ylabel('文章评论数')
plt.title('文章评论数与收藏量关系', color = colors, fontsize=fontsize_title)
plt.tight_layout()
plt.savefig('文章评论数与收藏量关系.png', dpi=200)
plt.show()
结果:
![](https://img.haomeiwen.com/i13155641/13b57236a8162e51.png)
可以看到,大多数点都位于左下角,意味着这些文章收藏量和评论数都比较低。但也存在少部分位于上方和右侧的异常值,表明这些文章呈现 「多评论、少收藏」或者「少评论、多收藏」的特点。
4.7 文章收藏量与标题长度关系
def analysis7(data):
plt.scatter(
x=data['favorites'],
y=data['title_length'],
s=8,
)
plt.xlabel('文章收藏量')
plt.ylabel('文章标题长度')
plt.title('文章收藏量和标题长度关系', color = colors, fontsize=fontsize_title)
plt.tight_layout()
plt.savefig('文章收藏量和标题长度关系.png', dpi=200)
plt.show()
结果:
![](https://img.haomeiwen.com/i13155641/b75eb423aa361d98.png)
大致可以看出两点现象:
- 第一 ,收藏量高的文章,他们的标题都比较短(右侧的部分散点)。
- 第二,标题很长的文章,它们的收藏量都非常低(左边形成了一条垂直线)。
看来,文章起标题时最好不要起太长的。
4.8 三分之一以上文章的标题喜欢使用问号
def analysis10(data):
data1 = data[data['title'].str.contains("(.*\?.*)|(.*\?.*)")]
data2 = data[data['title'].str.contains("(.*\!.*)|(.*\!.*)")]
# 带有问号的标题数量
quantity1 = data1.shape[0]
# 带有叹号的标题数量
quantity2 = data2.shape[0]
# 剩余数量
quantity = data.shape[0] - data1.shape[0] - data2.shape[0]
sizes = [quantity2,quantity1,quantity]
labels = [u'叹号标题',u'问号标题',u'陈述性标题']
colors_pie = ['#1362A3','#3297EA','#8EC6F5'] #每块颜色定义
explode = [0,0.05,0]
plt.pie(
sizes,
autopct='%.1f%%',
labels= labels,
colors =colors_pie,
shadow = False, #无阴影设置
startangle =90, #逆时针起始角度设置
explode = explode,
# textprops={'fontsize': 14, 'color': 'w'} # 设置文字颜色
textprops={'fontsize': 12, 'color': 'w'} # 设置文字颜色
)
plt.title('一半以上的文章的标题喜欢用问号',color=colors,fontsize=fontsize_title)
plt.axis('equal')
plt.axis('off')
plt.legend(loc = 'upper right')
plt.tight_layout() # 自动控制空白边缘,以全部显示x轴名称
plt.savefig('title问号.png',dpi=200)
plt.show()
结果:
![](https://img.haomeiwen.com/i13155641/eb4444c85b8d8587.png)
五、绘制词云图
我们通过绘制2013-2018年词云图来说明来探究每年的热词和热点事件。其中,分词函数用的是王师兄推荐的jieba分词。
def analysis9(data):
jieba.load_userdict("userdict.txt")
text=''
for i in data['title'].values:
# for i in data[data.year == 2018]['title'].values:
# 替换无用字符
symbol_to_replace = '[!"#$%&\'()*+,-./:;<=>?@,。?★、…【】《》?“”‘’![\\]^_`{|}~]+'
# data['name'].str.replace(symbol_to_replace,'',inplace=True,regex=True)
i = re.sub(symbol_to_replace,'',i)
# print(i)
text+=' '.join(jieba.cut(i,cut_all=False))
# text = jieba.del_word('如何')
d = path.dirname(__file__) if "__file__" in locals() else os.getcwd()
background_Image = np.array(Image.open(path.join(d, "tiger.jpg")))
# background_Image = plt.imread('./tiger.jpg')
font_path = 'C:\Windows\Fonts\simhei.ttf' # 思源黑,黑体simhei.ttf
# 添加stopswords
stopwords = set()
# 先运行对text进行词频统计再排序,再选择要增加的停用词
stopwords.update(['如何','怎么','一个','什么','为什么','还是','我们','为何','可能','不是','没有','哪些','成为','可以','背后','到底','就是','这么','不要','怎样','为了','能否','你们','还有','这样','这个','真的','那些'])
wc = WordCloud(
# background_color = '#3F3F3F',
# background_color = 'white',
background_color = 'black',
font_path = font_path,
mask = background_Image,
stopwords = stopwords,
max_words = 200,
# width = 1000,height=600,
margin =2,
max_font_size = 100,
random_state = 42,
scale = 2,
# colormap = 'viridis'
)
wc.generate_from_text(text)
process_word = WordCloud.process_text(wc, text)
# 下面是字典排序
sort = sorted(process_word.items(),key=lambda e:e[1],reverse=True) # sort为list
print(sort[:50]) # 输出前词频最高的前50个,然后筛选出不需要的stopwords,添加到前面的stopwords.update()方法中
img_colors = ImageColorGenerator(background_Image)
wc.recolor(color_func=img_colors) # 颜色跟随图片颜色
plt.imshow(wc,interpolation='bilinear')
plt.axis('off')
plt.tight_layout() # 自动控制空白边缘,以全部显示x轴名称
plt.savefig('huxiu5.png',dpi=200)
plt.show()
结果:
2018年:
![](https://img.haomeiwen.com/i13155641/d6b92803e65194ce.png)
2017年:
![](https://img.haomeiwen.com/i13155641/ce4e45aeea486142.png)
2016年:
![](https://img.haomeiwen.com/i13155641/f684f9188d5656f7.png)
2015年:
![](https://img.haomeiwen.com/i13155641/9e1d13dbeabde0df.png)
2014年:
![](https://img.haomeiwen.com/i13155641/0e54a679a0bfbbe6.png)
2013年:
![](https://img.haomeiwen.com/i13155641/e1acd82f02d26fce.png)
可以看到每年的关键词都有一些相同之处,但也不同的地方:
- 中国、互联网、公司、苹果、腾讯、阿里等这些热门关键词一直都是热门,唯有BAT中的百度较为低调,唯有2016、2017年百度因为“魏泽西”事件,被迫“崭露头角”。当时,百度的负面新闻甚嚣尘上,像这种[百度大势已去——从魏则西事件说起]https://www.jianshu.com/p/1284ead05ff2的谩骂和批评声不绝于耳。
- 每年会有新热点涌现:
比如 2013 年的微信(刚开始火),从日后的用户数据报道也可见一端[2013微信活跃用户增1104% 全球增长最快]http://www.ebrun.com/20140529/100398.shtml、
2015,李克强总理提出“大众创业、万众创新”,掀起了创业热潮,而2016年与2015年相比,态势减弱;
2016 年的直播(各大直播平台如雨后春笋般出现)、2017年的AI手机兴起;2018年的小米上市、滴滴事件。 - 不断有新的热门技术出现:2014 – 2015 年的 O2O、2016 年的 VR、直播、2017 年的 人工智能 、2018 年的「区块链」。这些科技前沿技术也是这几年大家口耳相传的热门词汇。
通过这几幅图,就看出了这几年科技互联网行业及热点信息的风云变化。
六、文本挖掘(主题模型)
6.1 理论
在上面,我们用到了词云图,词云图是根据词在文档中出现的频数来决定和绘制的,这很直观地显示出了每年最火、最热、使用率最高的词。但是,词云图并不能分析和描绘文档和文档之间的相互关系,更不能探究文字间的潜在语义信息,而LDA很多地弥补和改善了诸多缺点。
主题模型的具体理论是很复杂的,我们暂且可以把他当做一个黑箱。了解参数,实现算法,得到我们想要的东西。主题模型在计算机科学和数据科学的学术讲座中,讲者在介绍到LDA时,都往往会把原理这部分直接跳过去。就像我们不需要把汽车发动机原理搞清楚就可以开车一样,我们同样可以使用LDA主题词模型来提取主题词。当然,你也可以参考这篇文章:[通俗理解LDA主题模型]https://blog.csdn.net/yhao2014/article/details/51098037。
6.2 实现
在实现主题模型之前,本文利用R语言中的segmentCN中文分词函数之后去除了停止词。当然这里同样可以使用Python中的jieba去除停用词。
停用词是指在信息检索中,为节省存储空间和提高搜索效率,在处理自然语言数据(或文本)之前或之后会自动过滤掉某些字或词,这些字或词即被称为Stop Words(停用词)。这些停用词都是人工输入、非自动化生成的,生成后的停用词会形成一个停用词表。
本文用到的停用词库中含有1959个常用停止词,
这里给出云盘链接,需要者自取:https://pan.baidu.com/s/1j7hxe6uFP_rUiHNxsKnkqw
提取码:au37
## 一、数据预处理(初始数据整理)
## 1.4读取资料库
setwd("G:\\课程\\研一\\回归分析")
#clipboard指的是HuXiu.txt用notepad打开后复制,防止中文乱码
csv <- read.table("clipboard",header=T, stringsAsFactors=F,quote = "",encoding="utf-8")
mystopwords<-unlist(read.table("StopWords.txt",stringsAsFactors=F,quote = ""))
###解决乱码问题
head(csv)
dim(csv)
colnames(csv)<-c("text")
## 1.5.数据预处理(中文分词、stopword处理)
#install.packages("tm",lib="G:\\R语言\\R语言学习\\安装包")
#install.packages("Rcpp",lib="G:\\R语言\\R语言学习\\安装包")
#install.packages("slam",lib="G:\\R语言\\R语言学习\\安装包")
#install.packages("xml2",lib="G:\\R语言\\R语言学习\\安装包")
#install.packages("rJava",lib="G:\\R语言\\R语言学习\\安装包")
library(xml2,lib="G:\\R语言\\R语言学习\\安装包")
library(Rcpp,lib="G:\\R语言\\R语言学习\\安装包")
library(slam,lib="G:\\R语言\\R语言学习\\安装包")
library(NLP,lib="G:\\R语言\\R语言学习\\安装包")
library(tm,lib="G:\\R语言\\R语言学习\\安装包")
#只有RJava配置成功了,Rwordseg安装才可能成功,前者是后者的依赖包
#install.packages("rJava",lib="G:\\R语言\\R语言学习\\安装包")
library(rJava,lib="G:\\R语言\\R语言学习\\安装包")
#手动下载安装包Rwordseg,然后本地安装
library(Rwordseg,lib="G:\\R语言\\R语言学习\\安装包")
#(1)移除数字函数
removeNumbers = function(x) { ret = gsub("[0-90123456789]","",x) }
#(2)segmentCN分词函数
#中文分词,也可以考虑使用 rmmseg4j、rsmartcn
wordsegment<- function(x) {
library(Rwordseg)
segmentCN(x)
}
#(3)去除停止词函数
removeStopWords = function(x,words) {
ret = character(0)
index <- 1
it_max <- length(x)
while (index <= it_max) {
if (length(words[words==x[index]]) <1) ret <- c(ret,x[index])
index <- index +1
}
ret
}
#(1)移除数字
sample.words <- lapply(data[,1], removeNumbers)
dim(as.matrix(sample.words))
head(sample.words)
#(2)中文分词
sample.words <- lapply(sample.words, wordsegment)
dim(as.matrix(sample.words))
sample.words[1:6]
#(3)移除停止词
#先处理中文分词,再处理 stopwords,防止全局替换丢失信息,
#下面这句运行时间较长
sample.words <- lapply(sample.words, removeStopWords, mystopwords)
dim(as.matrix(sample.words))
sample.words<-as.matrix(sample.words)
head(sample.words)
text<-sample.words[,1]
colnames(sample.words)<-c("text")
write.csv(as.matrix(sample.words),"delateddata.txt")
write.csv(as.matrix(sample.words),"delateddata.csv")
去掉停止词后的结果为下:
![](https://img.haomeiwen.com/i13155641/b8106a3fa87a7bc6.png)
接着,我们继续使用Jupyter来进行LDA模型;
在Jupyter Notebook中新建一个Python 3笔记本,起名为LDA。
![](https://img.haomeiwen.com/i13155641/09ca1e2575bb9629.png)
导入文件处理包os
import os
os.chdir('G:\\课程\\研一\\回归分析') # 打印当前工作目录
为了处理表格数据,我们依然使用数据框工具Pandas。先调用它。
import pandas as pd
读入我们的数据文件,clipboard指的是delateddata.txt用notepad打开复制,为防止乱码,注意参数encoding设置为utf-8;
df=pd.read_clipboard()
查看数据的前几行,
df.head()
结果为下:
![](https://img.haomeiwen.com/i13155641/922e9b410a510a2c.png)
查看数据维度
df.shape
![](https://img.haomeiwen.com/i13155641/94a10dca38df955c.png)
导入jieba分词包
import jieba
我们此次需要处理的,不是单一文本数据,而是28197条文本数据,因此我们需要把这项工作并行化。这就需要首先编写一个函数,处理单一文本的分词。
def chinese_word_cut(mytext):
return " ".join(jieba.cut(mytext))
有了这个函数之后,我们就可以不断调用它来批量处理数据框里面的全部文本(正文)信息了。你当然可以自己写个循环来做这项工作。但这里我们使用更为高效的apply函数。
df["title_cutted"] = df.title.apply(chinese_word_cut)
执行完毕之后,我们需要查看一下,文本是否已经被正确分词。
df.title_cutted.head()
结果为下:
![](https://img.haomeiwen.com/i13155641/569b53e696932361.png)
接下来我们对这些本文做向量化,所谓文本向量化,指的就是形成一个28197(文档个数)*n(文本中所有词的数量)的0-1矩阵,特定词在这个文档出现记为1,否则为0。若选取所有词的话,这必然是一个很大的矩阵,因此在之前的操作中,本文从所有的词中选取了1000关键词。
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
处理的文本里面有大量的词汇。我们不希望处理所有词汇。因为一来处理时间太长,二来那些很不常用的词汇对我们的主题抽取意义不大。所以这里做了个限定,只从文本中提取1000个最重要的特征关键词,然后停止。
n_features = 1000
下面我们开始关键词提取和向量转换过程:
tf_vectorizer = CountVectorizer(strip_accents = 'unicode',
max_features=n_features,
stop_words='english',
max_df = 0.5,
min_df = 10)
tf = tf_vectorizer.fit_transform(df.title_cutted)
现在,开始我们的LDA模型,先导入软件包
from sklearn.decomposition import LatentDirichletAllocation
至于主题数量,到底确定几个主题,这个确实不能事知道的,我们可以一步步不断地优化和观察各种数量的主题分类,取其最优。也可制定具体的量化标准,确定几个主题是最佳,附上参考链接:https://blog.csdn.net/lwhsyit/article/details/82750218
此文在17点中,阐述了如何寻找最佳主题数量,并制定了量化标准,像下图这样,由于时间关系,不做具体说明:
![](https://img.haomeiwen.com/i13155641/8fe821690dc255c5.png)
我们暂定5个分类:
n_topics = 5
lda = LatentDirichletAllocation(n_topics=n_topics, max_iter=50,
learning_method='online',
learning_offset=50.,
random_state=0)
lda.fit(tf)
最后一句运行时间较长,
主题没有一个确定的名称,而是用一系列关键词刻画的。我们定义以下的函数,把每个主题里面的前若干个关键词显示出来:
def print_top_words(model, feature_names, n_top_words):
for topic_idx, topic in enumerate(model.components_):
print("Topic #%d:" % topic_idx)
print(" ".join([feature_names[i]
for i in topic.argsort()[:-n_top_words - 1:-1]]))
print()
定义好函数之后,我们暂定每个主题输出前20个关键词。
n_top_words = 20
以下命令会帮助我们依次输出每个主题的关键词表:
tf_feature_names = tf_vectorizer.get_feature_names()
print_top_words(lda, tf_feature_names, n_top_words)
执行结果为下:
Topic #0:
电影 产品 科技 融资 ai 直播 公司 媒体 嗅评 经济 驾驶 员工 创新 回应 区块 票房 创始人 故事 网易 美团
Topic #1:
微信 视频 小米 华为 乐视 数据 上市 人工智能 营销 社交 巨头 头条 共享 支付宝 支付 北京 单车 雷军 bat 医疗
Topic #2:
苹果 创业 百度 未来 手机 ceo 晚报 世界 背后 内容 发布 平台 微软 vr 特斯拉 消费 生意 技术 流量 小米
Topic #3:
中国 早报 亿美元 市场 游戏 谷歌 美国 滴滴 企业 汽车 iphone 用户 资本 估值 三星 uber 全球 特朗普 贾跃亭 赚钱
Topic #4:
互联网 阿里 公司 投资 电商 京东 收购 时代 金融 马云 亚马逊 亿元 品牌 创业者 行业 广告 智能 机会 日本 万达
接下来,来点有趣的,使其变得可视化:
import pyLDAvis
import pyLDAvis.sklearn
pyLDAvis.enable_notebook()
pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
data = pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
pyLDAvis.show(data)
以上代码需要一段时间,耐心等待:
图的左侧,用圆圈代表不同的主题,圆圈的大小代表了每个主题分别包含文章的数量。
图的右侧,列出了最重要(频率最高)的30个关键词列表。注意当你没有把鼠标悬停在任何主题之上的时候,这30个关键词代表全部文本中提取到的30个最重要关键词。
如果你把鼠标悬停在2号上面:
![](https://img.haomeiwen.com/i13155641/66c5b2a213ea31eb.png)
右侧的关键词列表会立即发生变化,红色展示了每个关键词在当前主题下的频率。
以上是认为设定主题数为5的情况。可如果我们把主题数量设定为10呢?(如果你要继续运行代码,需要点一下Kernel下面的Interrupt按钮)
n_topics = 10
lda = LatentDirichletAllocation(n_topics=n_topics, max_iter=50,
learning_method='online',
learning_offset=50.,
random_state=0)
lda.fit(tf)
print_top_words(lda, tf_feature_names, n_top_words)
pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
data = pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
pyLDAvis.show(data)
程序输出给我们10个主题下最重要的20个关键词:
Topic #0:
中国 阿里 滴滴 乐视 金融 亚马逊 特朗普 广告 头条 日本 流量 音乐 体育 离职 ofo 万科 报告 成立 影业 版权
Topic #1:
苹果 早报 亿美元 汽车 晚报 人工智能 亿元 技术 微软 创业者 巨头 智能 资本 vr 上市 生意 一场 bat 印度 电视
Topic #2:
谷歌 数据 融资 支付 北京 硅谷 事件 医疗 财报 计划 蚂蚁 价值 影响 程序 大战 生活 亿美金 泡沫 调查 解读
Topic #3:
美国 科技 企业 背后 媒体 嗅评 uber 零售 区块 雷军 增长 荣耀 ip 设计 聊聊 好莱坞 罗永浩 拯救 年轻人 真相
Topic #4:
小米 手机 微信 产品 iphone 特斯拉 发布 三星 模式 消费 美团 马斯克 人类 告诉 升级 明星 时间 逻辑 危机 马化
Topic #5:
创业 投资 电商 京东 收购 时代 共享 内容 品牌 平台 机会 万达 网络 创始人 单车 ipo 成功 产业 融资 城市
Topic #6:
视频 电影 世界 经济 驾驶 赚钱 创新 行业 商业 故事 app 网易 机器人 转型 自动 付费 国产 上海 焦虑 网站
Topic #7:
游戏 直播 马云 社交 支付宝 oo facebook 领域 投资人 改变 微博 盈利 股东 项目 入股 合作 独角兽 平台 揭秘 尴尬
Topic #8:
公司 互联网 百度 华为 估值 早报 贾跃亭 回应 票房 市值 员工 刘强 风口 李彦宏 业务 联想 文化 香港 运营商 上市
Topic #9:
市场 未来 ceo 中国 ai 用户 营销 全球 股价 体验 淘宝 监管 银行 十年 发布会 小时 特币 无人机 案例 粉丝
你会发现当主题设定为10的时候,会有一些抱团现象出现。
![](https://img.haomeiwen.com/i13155641/29d457b58cac6999.png)
本文分别绘制了主题数量为4—10的LDA图,经过分析,选取分类数量为5,可以分析:
-
主题1:无人驾驶
相信大家都还记得上一届百度AI开发者大会,时任百度COO的陆奇在介绍Apollo计划之前,连线了一场视频直播:李彦宏正嘚瑟地坐在一辆百度和博世一起开发的、基于Apollo技术的自动驾驶汽车里,摆大爷劲儿。眼球是赚足了,李大爷也结结实实地在北京五环上吃到了一张罚单。2017年4月19日,百度正式宣布推出阿波罗(Apollo)计划。用百度的话说,阿波罗计划是百度AI中重要的一部分,它要向汽车行业及自动驾驶领域的合作伙伴提供一个开放、完整、安全的软件平台,帮助他们结合车辆和硬件系统,快速搭建一套属于自己的完整的自动驾驶系统。
新型汽车启动自然有其原因,中国市场不可忽视,而像通用、福特、宝马、奔驰等此前花了大量金钱投入研发的车企也等于花了一大笔“冤枉钱”,百度可谓是自动驾驶圈中的“半壁江山”。
谷歌无人驾驶[谷歌开启无人驾驶商用时代:无人出租车即将开始接单]
https://baijiahao.baidu.com/s?id=1619098384974738839&wfr=spider&for=pc滴滴无人驾驶[https://baijiahao.baidu.com/s?id=1614383642553153149&wfr=spider&for=pc]
Uber无人驾驶[自动驾驶遭遇信任危机,Uber叫停美国无人驾驶测试]https://baijiahao.baidu.com/s?id=1595626162824376577&wfr=spider&for=pc
还有跑到美国不敢回来,融资造车的乐视老总贾跃亭http://news.bitauto.com/hao/wenzhang/841505
而贾跃亭与恒大许家印签约,前段时间还在因为资本问题打官司。恒大与特朗普集团又有合作。
-
主题2:电商
像关键词中出现的互联网、电商、阿里、京东、马云、 亚马逊等以及线下的万达、零售等,主题还是相当明显的。
-
主题3 : 产业发展
像电影产业、科技产业、直播产业、媒体、最新的区 块链技术等,这一主题集中于各种产业的产业发展。 -
主题4:互联网战略
这一专题阐述的是像苹果、特斯拉、百度、微软、小米这些互联网公司,以及拉斯拉老总马斯克、百度老总李彦宏这些领导人,他们对未来科技的战略部署,像:手机、平台、技术、流量、app、音乐、虚拟现实、人类体验等。 -
主题5:用户社交
像微信、视频、华为、小米、乐视、微博等,这一主题反映了用户社交方面的情况。
七、总结
- 本文分析了优秀的文章,好的标题应该怎么写,这对未来投稿、写推送等很有帮助,也推荐关注了一些优秀作者;
- 文本分析了2013-2018年来的热点事件和互联网技术这些年来的各种变革,新的技术涌现,旧的技术不断成熟或者被市场淘汰,这对我们对整体的大局把控有一定帮助;
- 文本利用主题模型分析了虎嗅网文章的专注领域,包括电商、互联网战略、互联网下的用户社交和IT界新兴技术无人驾驶等。说明虎嗅网主要集中于互联网,以互联网巨头BAT的热点和未来战略为牵动展开分析和文章评论。这也恰恰成为了它小众的原因之一,不像今日头条一样,涵盖了生活、学习、情感、工作、娱乐、时政、国际等各种方面的新闻。
八、参考文献
- [以虎嗅网4W+文章的文本挖掘为例,展现数据分析的一整套流程]
http://www.360doc.com/content/17/1212/14/27972427_712404479.shtml
注:文本旨在抛砖引玉,如有不同意见,欢迎商榷。
Written By LXP
网友评论