作为互联网界的两个对立的物种,产品汪与程序猿似乎就像一对天生的死对头;但是在产品开发链条上紧密合作的双方,只有通力合作,才能更好地推动项目发展。那么产品经理平日里面都在看那些文章呢?我们程序猿该如何投其所好呢?我爬取了人人都是产品经理(http://www.woshipm.com)产品经理栏目下的所有文章,看看产品经理都喜欢看什么。
1. 分析背景
1.1. 为什么选择「人人都是产品经理」
人人都是产品经理是以产品经理、运营为核心的学习、交流、分享平台,集媒体、培训、招聘、社群为一体,全方位服务产品人和运营人,成立8年举办在线讲座500+期,线下分享会300+场,产品经理大会、运营大会20+场,覆盖北上广深杭成都等15个城市,在行业有较高的影响力和知名度。平台聚集了众多BAT美团京东滴滴360小米网易等知名互联网公司产品总监和运营总监。选取这个社区更有代表性。
1.2. 分析内容
分析人人都是产品经理产品经理栏目下的 6574 篇文章的基本情况,包括收藏数、评论数、点赞数等
发掘最受欢迎的文章及作者
分析文章标题长度与受欢迎程度之间的关系
展现产品经理都在看什么
1.3. 分析工具
Python 3.6
Matplotlib
WordCloud
Jieba
2. 数据抓取
使用 Python编写的爬虫抓取了人人都是产品经理社区的产品经理栏目下的所有文章并保存为csv格式,文章抓取时期为 2012年6月至 2019 年 1月 21 日,共计6574篇文章。抓取 了 10 个字段信息:文章标题、作者、作者简介、发文时间、浏览量、收藏量、点赞量、评论量、正文、文章链接。
2.1. 目标网站分析
这是要爬取的网页界面,可以看到是直接加载出来的,没有AJAX,爬取起来毫无难度。
仔细观察要爬取的网页,我们可以看到页面连接有规律可循,连接中page后面的参数就是页面数,所以我们编写爬虫时可以直接用for循环来构造所有页面连接代码如下:
1import requests
2from bs4 import BeautifulSoup
3import csv
4
5headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
6 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
7 'Cache-Control': 'max-age=0',
8 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
9 'Connection': 'keep-alive',
10 'Host': 'www.woshipm.com',
11 'Cookie' : 't=MHpOYzlnMmp6dkFJTEVmS3pDeldrSWRTazlBOXpkRjBzRXpZOU4yVkNZWWl5QVhMVXBjMU5WcnpwQ2NCQS90ZkVsZ3lTU2Z0T3puVVZFWFRFOXR1TnVrbUV2UFlsQWxuemY4NG1wWFRYMENVdDRPQ1psK0NFZGJDZ0lsN3BQZmo%3D; s=Njg4NDkxLCwxNTQyMTk0MTEzMDI5LCxodHRwczovL3N0YXRpYy53b3NoaXBtLmNvbS9XWF9VXzIwMTgwNV8yMDE4MDUyMjE2MTcxN180OTQ0LmpwZz9pbWFnZVZpZXcyLzIvdy84MCwsJUU1JUE0JUE3JUU4JTk5JUJF; Hm_lvt_b85cbcc76e92e3fd79be8f2fed0f504f=1547467553,1547544101,1547874937,1547952696; Hm_lpvt_b85cbcc76e92e3fd79be8f2fed0f504f=1547953708'
12 }
13for page_number in range(1, 549):
14page_url = "http://www.woshipm.com/category/pmd/page/{}".format(page_number)
15print('正在抓取第' + str(page_number) + '页>>>')
16response = requests.get(url=page_url, headers=headers)
页面连链接构造完之后我们可以开始爬取文章详情页,提取所需要的信息,在这里用到的解析库是BeautifulSoup,整个爬虫非常简单,完整代码如下:
1#!/usr/bin/env python
2# -*- encoding: utf-8 -*-
3
4import requests
5from bs4 import BeautifulSoup
6import csv
7
8headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
9 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
10 'Cache-Control': 'max-age=0',
11 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
12 'Connection': 'keep-alive',
13 'Host': 'www.woshipm.com',
14 'Cookie' : 't=MHpOYzlnMmp6dkFJTEVmS3pDeldrSWRTazlBOXpkRjBzRXpZOU4yVkNZWWl5QVhMVXBjMU5WcnpwQ2NCQS90ZkVsZ3lTU2Z0T3puVVZFWFRFOXR1TnVrbUV2UFlsQWxuemY4NG1wWFRYMENVdDRPQ1psK0NFZGJDZ0lsN3BQZmo%3D; s=Njg4NDkxLCwxNTQyMTk0MTEzMDI5LCxodHRwczovL3N0YXRpYy53b3NoaXBtLmNvbS9XWF9VXzIwMTgwNV8yMDE4MDUyMjE2MTcxN180OTQ0LmpwZz9pbWFnZVZpZXcyLzIvdy84MCwsJUU1JUE0JUE3JUU4JTk5JUJF; Hm_lvt_b85cbcc76e92e3fd79be8f2fed0f504f=1547467553,1547544101,1547874937,1547952696; Hm_lpvt_b85cbcc76e92e3fd79be8f2fed0f504f=1547953708'
15 }
16with open('data.csv', 'w', encoding='utf-8',newline='') as csvfile:
17 fieldnames = ['title', 'author', 'author_des', 'date', 'views', 'loves', 'zans', 'comment_num','art', 'url']
18 writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
19 writer.writeheader()
20 for page_number in range(1, 549):
21 page_url = "http://www.woshipm.com/category/pmd/page/{}".format(page_number)
22 print('正在抓取第' + str(page_number) + '页>>>')
23 response = requests.get(url=page_url, headers=headers)
24 if response.status_code == 200:
25 page_data = response.text
26 if page_data:
27 soup = BeautifulSoup(page_data, 'lxml')
28 article_urls = soup.find_all("h2", class_="post-title")
29 for item in article_urls:
30
31 url = item.find('a').get('href')
32 # 文章页面解析,获取文章标题、作者、作者简介、日期、浏览量、收藏量、点赞量、评论量、正文、文章链接
33 response = requests.get(url=url, headers=headers)
34 # time.sleep(3)
35 print('正在抓取:' + url)
36 # print(response.status_code)
37 if response.status_code == 200:
38 article = response.text
39 # print(article)
40 if article:
41 try:
42 soup = BeautifulSoup(article, 'lxml')
43 # 文章标题
44 title = soup.find(class_='article-title').get_text().strip()
45 # 作者
46 author = soup.find(class_='post-meta-items').find_previous_siblings()[1].find('a').get_text().strip()
47 # 作者简介
48 author_des = soup.find(class_='post-meta-items').find_previous_siblings()[0].get_text().strip()
49 # 日期
50 date = soup.find(class_='post-meta-items').find_all(class_='post-meta-item')[0].get_text().strip()
51 # 浏览量
52 views = soup.find(class_='post-meta-items').find_all(class_='post-meta-item')[1].get_text().strip()
53 # 收藏量
54 loves = soup.find(class_='post-meta-items').find_all(class_='post-meta-item')[2].get_text().strip()
55 # 点赞量
56 zans = soup.find(class_='post-meta-items').find_all(class_='post-meta-item')[3].get_text().strip()
57 # 评论量
58 comment = soup.find('ol', class_="comment-list").find_all('li')
59 comment_num = len(comment)
60 # 正文
61 art = soup.find(class_="grap").get_text().strip()
62
63 writer.writerow({'title':title, 'author':author, 'author_des':author_des, 'date':date, 'views':views, 'loves':int(loves), 'zans':int(zans), 'comment_num':int(comment_num), 'art':art, 'url':url})
64 print({'title':title, 'author':author, 'author_des':author_des, 'date':date, 'views':views, 'loves':loves, 'zans':zans, 'comment_num':comment_num})
65 except:
66 print('抓取失败')
67 print("抓取完毕!")
在这里说一点,评论数的爬取,观察文章详情页你可以发现并没有评论数,我这里是直接计算出来的,可以看到评论是嵌套在ol里面,抓起所有的li,然后就可以计算出,代码如下:
1 # 评论量
2 comment = soup.find('ol', class_="comment-list").find_all('li')
3 comment_num = len(comment)
这样,我们运行一下爬虫就能够顺利爬取 594 页的结果了,我这里一共抓取了 6574 条结果,大概也就玩了两把吃鸡就抓完了。
以上,就完成了数据的获取。有了数据我们就可以着手分析,不过这之前还需简单地进行一下数据的清洗、处理。
3. 数据清洗处理
首先,我们需要把csv文件转换为 DataFrame。
1# 将csv数据转为dataframe
2csv_file = "data.csv"
3csv_data = pd.read_csv(csv_file, low_memory=False) # 防止弹出警告
4csv_df = pd.DataFrame(csv_data)
5print(csv_df)
下面我们看一下数据的总体情况,可以看到数据的维度是 6574 行 × 10 列。需要将 views 列更改为数值格式、date 列更改为日期格式。
1print(csv_df.shape) # 查看行数和列数
2print(csv_df.info()) # 查看总体情况
3print(csv_df.head()) # 输出前5行
4#运行结果
5(6574, 10)
6<class 'pandas.core.frame.DataFrame'>
7RangeIndex: 6574 entries, 0 to 6573
8Data columns (total 10 columns):
9title 6574 non-null object
10author 6574 non-null object
11author_des 6135 non-null object
12date 6574 non-null object
13views 6574 non-null object
14loves 6574 non-null int64
15zans 6574 non-null int64
16comment_num 6574 non-null int64
17art 6574 non-null object
18url 6574 non-null object
19dtypes: int64(3), object(7)
20memory usage: 513.7+ KB
21None
22 title ... url
230 2018,我产品生涯的第二年是这样度过的 ... http://www.woshipm.com/pmd/1863343.html
241 从《啥是佩奇》提炼出的产品三部曲 ... http://www.woshipm.com/pmd/1860832.html
252 “采坑,填坑”,项目的那些事儿(第六阶段:测试验收) ... http://www.woshipm.com/pmd/1859168.html
263 如何成为CEO信任的产品经理? ... http://www.woshipm.com/pmd/1857656.html
274 如何让程序员放下手中的刀? ... http://www.woshipm.com/pmd/1858879.html
28
29[5 rows x 10 columns]
date列更改为日期非常简单,代码如下:
1# 修改date列时间,并转换为 datetime 格式
2csv_df['date'] = pd.to_datetime(csv_df['date'])
views列处理思路是增加一列,名字就叫views_num吧,我们可以观察到views列有的数值是整数,有的则是1.7万这种,代码如下:
1#!/usr/bin/env python
2# -*- encoding: utf-8 -*-
3
4import pandas as pd
5import numpy as np
6import matplotlib.pyplot as plt
7import seaborn as sns
8import re
9from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator
10import jieba
11import os
12from PIL import Image
13from os import path
14from decimal import *
15
16#views列处理
17def views_to_num(item):
18 m = re.search('.*?(万)',item['views'])
19 if m:
20 ns = item['views'][:-1]
21 nss = Decimal(ns)*10000
22 else:
23 nss = item['views']
24 return int(nss)
25
26# 数据清洗处理
27def parse_woshipm():
28 # 将csv数据转为dataframe
29 csv_file = "data.csv"
30 csv_data = pd.read_csv(csv_file, low_memory=False) # 防止弹出警告
31 csv_df = pd.DataFrame(csv_data)
32 # print(csv_df.shape) # 查看行数和列数
33 # print(csv_df.info()) # 查看总体情况
34 # print(csv_df.head()) # 输出前5行
35
36 # 修改date列时间,并转换为 datetime 格式
37 csv_df['date'] = pd.to_datetime(csv_df['date'])
38 #将views字符串数字化,增加一列views_num
39 csv_df['views_num'] = csv_df.apply(views_to_num,axis = 1)
40 print(csv_df.info())
41
42
43if __name__ == '__main__':
44 parse_woshipm()
我们再输出一下看看各列数据类型:
1<class 'pandas.core.frame.DataFrame'>
2RangeIndex: 6574 entries, 0 to 6573
3Data columns (total 11 columns):
4title 6574 non-null object
5author 6574 non-null object
6author_des 6135 non-null object
7date 6574 non-null datetime64[ns]
8views 6574 non-null object
9loves 6574 non-null int64
10zans 6574 non-null int64
11comment_num 6574 non-null int64
12art 6574 non-null object
13url 6574 non-null object
14views_num 6574 non-null int64
15dtypes: datetime64[ns](1), int64(4), object(6)
16memory usage: 565.0+ KB
17None
可以看到数据类型已经变成我们想要的了,下面,我们看一下数据是否有重复,如果有,那么需要删除。
1# 判断整行是否有重复值,如果运行结果为True,表明有重复值
2# print(any(csv_df.duplicated()))
3# 显示True,表明有重复值,进一步提取出重复值数量
4data_duplicated = csv_df.duplicated().value_counts()
5# print(data_duplicated)
6#运行结果
7# True
8# False
9# 6562
10# True
11# 12
12# dtype: int64
13# 删除重复值
14data = csv_df.drop_duplicates(keep='first')
15# 删除部分行后,index中断,需重新设置index
16data = data.reset_index(drop=True)
然后,我们再增加两列数据,一列是文章标题长度列,一列是年份列,便于后面进行分析。
1#增加标题长度列和年份列
2data['title_length'] = data['title'].apply(len)
3data['year'] = data['date'].dt.year
以上,就完成了基本的数据清洗处理过程,针对这些数据可以开始进行分析了。
4. 描述性数据分析
通常,数据分析主要分为四类: 「描述型分析」、「诊断型分析」「预测型分析」「规范型分析」。「描述型分析」是用来概括、表述事物整体状况以及事物间关联、类属关系的统计方法,是这四类中最为常见的数据分析类型。通过统计处理可以简洁地用几个统计值来表示一组数据地集中性(如平均值、中位数和众数等)和离散型(反映数据的波动性大小,如方差、标准差等)。
这里,我们主要进行描述性分析,数据主要为数值型数据(包括离散型变量和连续型变量)和文本数据。
4.1. 总体情况
先来看一下总体情况,使用了data.describe() 方法对数值型变量进行统计分析。
mean 表示平均值,std表示标准差,从上面可以简要得出以下几个结论:
产品经理热爱学习,看到好的文章就收藏下来。75%的文章收藏量破百,50%的文章浏览量破百;
产品话少,对别人的文章很少会评头论足。文章的评论数都寥寥无几。
产品不愿意承认别人比自己优秀。绝大部分文章点赞数都是一二十个,所以程序猿们以后不要在产品面前吹嘘技术如何了得了,产品是不会承认你厉害的。
对于非数值型变量(author、date),使用 describe() 方法会产生另外一种汇总统计。
1print(data['author'].describe())
2print(data['date'].describe())
3#结果
4count 6562
5unique 1531
6top Nairo
7freq 315
8Name: author, dtype: object
9count 6562
10unique 1827
11top 2015-01-29 00:00:00
12freq 16
13first 2012-11-25 00:00:00
14last 2019-01-21 00:00:00
15Name: date, dtype: object
unique 表示唯一值数量,top 表示出现次数最多的变量,freq 表示该变量出现的次数,所以可以简单得出以下几个结论:
一共有1531位作者为社区的产品经理栏目贡献了文章,其中贡献量最大的作者叫 Nairo ,贡献了315篇;
在2015年1月29日栏目文章发布数最大,达到了16篇。栏目第一篇文章发布在2012年11月25日。
4.2. 不同时期文章发布的数量变化
从图中可以看到,网站文章发布数量在2012到2015年逐年递增,增幅很大,这可能与网站的知名度提高有关;2015年2季度之后比较平稳。后面的分析代码就不一一贴出,文末会留下代码下载链接。
4.3. 文章浏览量 TOP10
接下来,到了我们比较关心的问题:几万篇文章里,到底哪些文章写得比较好或者比较火
这里以阅读量作为衡量标准,排在第一的是《 小白产品经理看产品:什么是互联网产品》,第一名的浏览量遥遥领先于第二名,接近百万,看来很多社区里面很多都是产品小白。而且看这几篇文章标题,貌似都是介绍什么是产品经理,产品经理干什么,看来社区里面初级产品挺多的。
4.4. 历年文章收藏量 TOP3
在了解文章的总体排名之后,我们来看看历年的文章排名是怎样的。这里,每年选取了收藏量最多的 3 篇文章。
从图中可以看出,2015年是的那篇文章收藏量是最多的,达到了2000,文章内容则是后台产品设计,看来这篇文章里面干货满满。
4.4.1. 最高产作者 TOP20
上面,我们从收藏量指标进行了分析,下面,我们关注一下发布文章的作者。前面提到发文最多的是Nairo,贡献了315篇,这里我们看看还有哪些比较高产的作者。
可以看到第一名遥遥领先,是个狼人,大家可以关注一下这些优质作者。
4.4.2. 平均文章收藏量最多作者 TOP 10
我们关注一个作者除了是因为文章高产以外,可能更看重的是其文章水准。这里我们选择「文章平均收藏量」(总收藏量/文章数)这个指标,来看看文章水准比较高的作者是哪些人。这里,为了避免出现「某作者只写了一篇高收藏率的文章」这种不能代表其真实水准的情况,我们将筛选范围定在至少发布过 5 篇文章的作者们。
对比这张图和前面的发文数量排行榜,我们可以发现这张图的作者均没有上榜,相比于数量,质量可能更重要吧。
4.5. 文章评论数最多 TOP10
说完了收藏量。下面,我们再来看看评论数量最多的文章是哪些。
我们可以看到大部分都与初级产品有关,而且我们可以看到评论多,收藏量也挺多的,我们进一步探寻两者之间关系。
我们可以发现绝大部分文章评论数和收藏量都很小。
4.6. 文章标题长度
下面,我们再来看看文章标题的长度和阅读量之间有没有什么关系。
我们可以看到文章标题长度在20左右时阅读量普遍较高。
4.7. 文本分析
最后,我们从这 5 万篇文章中的正文内容中看看产品经理都在看什么。
我们可以看到设计、工作、数据、功能、需求、项目等都是产品经理们关注的东西,产品们也很辛苦啊,程序猿以后不要吐槽自己多辛苦了。
5. 小结
本文简要分析了人人都是产品经理产品经理栏目下6574篇文章信息,大致了解了产品经理都在看什么。
发掘了那些优秀的文章和作者,能够产品新人指明方向。
告诉了程序猿与产品经理聊天时该说什么。
本文尚未做深入的文本挖掘,而文本挖掘可能比数据挖掘涵盖的信息量更大,更有价值。进行这些分析需要机器学习和深度学习的知识。
本文源代码及词库关注公众号:爱趣IT ,回复 产品 即可获取。
参考资料:
搜狗细胞词库;
写文章不会起标题?爬取虎嗅5万篇文章告诉你(公众号:第2大脑);
文章部分图片来自网络,如不慎侵权请联系删除。