很早就有采集知乎用户数据的想法,要实现这个想法,需要写一个网络爬虫(Web Spider)。因为在学习 python,正好 python 写爬虫也是极好的选择,于是就写了一个基于 python 的网络爬虫。
几个月前写了爬虫的初版,后来因为一些原因,暂时搁置了下来,最近重新拾起这个想法。首先优化了代码的结构,然后在学弟的提醒下,从多线程改成了多进程,一台机器上运行一个爬虫程序,会启动几百个子进程加速抓取。
但是一台机器的性能是有极限的,所以后来我使用 MongoDB 和 Redis 搭建了一个主从结构的分布式爬取系统,来进一步加快抓取的速度。
然后我就去好几个服务器厂商申请免费的试用,比如百度云、腾讯云、Ucloud…… 加上自己的笔记本,断断续续抓取了一个多周,才采集到300万知乎用户数据。中间还跑坏了运行网站的云主机,还好 自动备份 起作用,数据没有丢失,但那又是另外一个故事了……
完整版的爬虫链接
关注微信公众号 datayx 然后回复 知乎 即可获取。
我们要抓取知乎用户数据,首先要知道在哪个页面可以抓取到用户的数据。知乎用户的个人信息在哪里呢,当然是在用户的主页啦,我们以轮子哥为例 ~
红框里的便我们要抓取的用户关键信息(的一部分)。
最上面是我们的目标URL:https://www.zhihu.com/people/excited-vczh/answers
。
观察一下这个URL的组成:
http://www.zhihu.com + /people + /excited-vczh + /answer
可以发现只有 excited-vczh 这部分是会变化的,它代表着知乎用户的唯一ID,在知乎的数据格式中,它的键名叫做 urlToken。
所以我们可以用拼接字符串的形式,得到我们待抓取页面的URL:
url = '%s/people/%s/answers'%(host,urlToken)
页面URL有了,而且从上图我们可以发现 不登录 也可以访问用户主页,这说明我们可以不用考虑模拟登陆的问题,可以自由的获取用户主页面源码。
那么我们如何从用户主页的源码中获取用户的数据呢?一开始我以为需要挨个匹配页面中对应的部分,但我查看源码的时候发现知乎把用户数据集集中放到了源码的一个地方,那就是 id="data" 的 div 的 data-state 属性的值中,看下图:
从上图我们可以发现,date-state 的属性值中藏有用户的信息,比如我们可以依次找到用户的教育经历(educations)、简介(headline)、参与的 Live 数量(participatedLiveCount)、关注的收藏夹数量(followingFavlistsCount)、被收藏的次数(favoritedCount)、关注他的用户数(followerCount)、关注的话题数量(followingTopicCount)、用户描述(description)等信息。通过观察我们也可以发现,数据应该是以 JSON 格式存储。
知道了用户数据都藏在 date-state 中,我们 用 BeautifulSoup 把该属性的值取出来,然后作为 JSON 格式读取,再把数据集中存储用户数据的部分提取出来即可,看代码:
# 解析
htmls = BS(html,'html.parser')
# 获得该用户藏在主页面中的json格式数据集
data = s.find('div',attrs={'id':'data'})['data-state']
data = json.loads(data)data = data['entities']['users'][urlToken]
如此,我们便得到了某一个用户的个人信息。
刚刚我们讨论到可以通过抓取用户主页面源码来获取个人信息,而用户主页面可以通过拼接字符串的形式得到 URL,其中拼接的关键是
如何获取用户唯一ID —— urlToken?
我采用的方法是 抓取用户的关注者列表。
每个用户都会有关注者列表,比如轮子哥的:
和获取个人信息同样的方法,我们可以在该页面源码的 date-state 属性值中找到关注他的用户(一部分):
名为 ids 的键值中存储有当前列表页的所有用户的 urlToken,默认列表的每一页显示20个用户,所以我们写一个循环便可以获取当前页该用户的所有关注者的 urlToken。
# 解析当前页的 html url = '%s/people/%s/followers?page=%d'%(host,urlToken,page)html = c.get_html(url)s = BS(html,'html.parser')# 获得当前页的所有关注用户data = s.find('div',attrs={'id':'data'})['data-state']data = json.loads(data)items = data['people']['followersByUser'][urlToken]['ids']for item in items:
if item!=None and item!=False and item!=True and item!='知乎用户'.decode('utf8'):
node = item.encode('utf8')
follower_list.append(node)
再写一个循环遍历关注者列表的所有页,便可以获取用户的所有关注者的 urlToken。
有了每个用户在知乎的唯一ID,我们便可以通过拼接这个ID得到每个用户的主页面URL,进一步获取到每个用户的个人信息。
我选择抓取的是用户的关注者列表,即关注这个用户的所有用户(follower)的列表,其实你也可以选择抓取用户的关注列表(following)。我希望抓取更多知乎非典型用户(潜水用户),于是选择了抓取关注者列表。当时抓取的时候有这样的担心,万一这样抓不到主流用户怎么办?毕竟很多知乎大V虽然关注者很多,但是主动关注的人相对都很少,而且关注的很可能也是大V。但事实证明,主流用户基本都抓取到了,看来基数提上来后,总有缝隙出现。
频繁抓取会被知乎封IP,也就是常说的反爬虫手段之一,不过俗话说“道高一尺,魔高一丈”,既然有反爬虫手段,那么就一定有反反爬虫手段,咳,我自己起的名……
言归正传,如果知乎封了你的IP,那么怎么办呢?很简单,换一个IP。这样的思想催生了 代理IP池 的诞生。所谓代理IP池,是一个代理IP的集合,使用代理IP可以伪装你的访问请求,让服务器以为你来自不同的机器。
于是我的 应对知乎反爬虫机制的策略 就很简单了:全力抓取知乎页面 --> 被知乎封IP --> 换代理IP --> 继续抓 --> 知乎继续封 --> 继续换 IP..... (手动斜眼)
使用 代理IP池,你可以选择用付费的服务,也可以选择自己写一个,或者选择用现成的轮子。我选择用七夜写的 qiyeboy/IPProxyPool
https://github.com/qiyeboy/IPProxyPool
搭建代理池服务,部署好之后,修改了一下代码让它只保存https协议的代理IP,因为 使用http协议的IP访问知乎会被拒绝。
搭建好代理池服务后,我们便可以随时在代码中获取以及使用代理 IP 来伪装我们的访问请求啦!
(其实反爬手段有很多,代理池只是其中一种)
多线程/多进程只是最大限度的利用了单台机器的性能,如果要利用多台机器的性能,便需要分布式的支持。
如何搭建一个简单的分布式爬虫?
我采用了 主从结构,即一台主机负责调度、管理待抓取节点,多台从机负责具体的抓取工作。
具体到这个知乎爬虫来说,主机上搭建了两个数据库:MongoDB 和 Redis。MongoDB 负责存储抓取到的知乎用户数据,Redis 负责维护待抓取节点集合。从机上可以运行两个不同的爬虫程序,一个是抓取用户关注者列表的爬虫(list_crawler),一个是抓取用户个人资料的爬虫(info_crawler),他们可以配合使用,但是互不影响。
我们重点讲讲主机上维护的集合,主机的 Redis 数据库中一共维护了5个集合:
这里插一句,之所以采用集合(set),而不采用队列(queue),是因为集合天然的带有唯一性,也就是说可以加入集合的节点一定是集合中没有出现过的节点,这里在5个集合中流通的节点其实是 urlToken。
(其实集合可以缩减为3个,省去失败集合,失败则重新投入原来的集合,但我为了测速所以保留了5个集合的结构)
他们的关系是:
举个具体的栗子:从一个 urlToken 在 waiting 集合中出现开始,经过一段时间,它被 info_crawler 爬虫程序从 waiting 集合中随机获取到,然后在 info_crawler 爬虫程序中抓取个人信息,如果抓取成功将个人信息存储到主机的 MongoDB 中,将该 urlToken 放到 info_success 集合中;如果抓取失败则将该 urlToken 放置到 info_failed 集合中。下一个阶段,经过一段时间后,list_crawler 爬虫程序将从 info_success 集合中随机获取到该 urlToken,然后尝试抓取该 urlToken 代表用户的关注者列表,如果关注者列表抓取成功,则将抓取到的所有关注者放入到 waiting 集合中,将该 urlToken 放到 list_success 集合中;如果抓取失败,将该 urlToken 放置到 list_failed 集合中。
如此,主机维护的数据库,配合从机的 info_crawler 和 list_crawler 爬虫程序,便可以循环起来:info_crawler 不断从 waiting 集合中获取节点,抓取个人信息,存入数据库;list_crawler 不断的补充 waiting 集合。
主机和从机的关系如下图:
主机是一台外网/局域网可以访问的“服务器”,从机可以是PC/笔记本/Mac/服务器,这个架构可以部署在外网也可以部署在内网。
最近用 python 爬虫抓取了知乎用户个人资料(公开信息),去重之后有300+万条记录,为了得到这些数据,还不小心跑崩了一台服务器…… 当然主要是配置太低。
手头有了数据也不能闲着,于是就有了这篇分析报告,这篇报告做了一些浅显的数据分析,主要目的是练练手,大家看个热闹,高手勿笑。
数据量:3,289,329 人。
数据采集工具:分布式 python 爬虫
分析工具:ElasticSearch + Kibana
分析角度包括:地理位置、男女比例、各类排名、所在高校、活跃程度等。
注意:
下面让我们看看知乎用户分布中有哪些有趣的现象(下面的图点击可以放大) ↓
首先来看看知乎用户的男女比例,以当前样本数据统计的结果接近 1:1,其中男生偏多一些。(其实还有很大一部分性别不明的用户,被我略掉了 ╮(╯▽╰)╭)
蓝色为男生,红色为女生。具体数据为:
男生:1,202,234 人,占 51.55% 女生:1,129,874 人,占 48.45%
再来看看全国(全球?)都有哪些地方的人在玩知乎:
由上图可以看到,知乎用户中一线城市用户占较大的比重,北上广深皆在词云的中心位置(文字越大,比重越大)。我们再来看看具体的排名(前十名):
知乎用户居住地前十名依次是:北京、上海、杭州、成都、南京、武汉、广州、深圳、西安、重庆。
你可能发现了Y坐标里每座城市的用户数并不多,这是因为有大概260万的知乎没有填写“居住地”这一栏~ 以下分析也有可能出现用户没有填写某一栏信息的情况,我会把这些用户忽略掉,以保证展示图表的准确性。
以下显示知乎主流职业,同样根据知乎用户个人信息里填写的职业为准:
由上图可以看到,知乎用户中学生用户占据多数,其它的比如产品经理、程序员、运营、HR数量也不少。我们看一下具体的排名(前十名):
由上图可以看到,知乎用户中“学生”的比重独占鳌头,我们把“学生”去掉,来看看正经些的知乎职业排名:
去除学生后,知乎主流职业比重从大到小依次是(前十名):产品经理、自由职业、程序员、工程师、设计师、腾讯、教师、人力资源(HR)、运营、律师。可以看到,除了一些互联网公司的常见职位外,教师和律师用户在知乎中也占据不小的比重。
下面我们结合 性别 和 居住地 来分析一下知乎的主流职业。
知乎主流职业的性别分布:
上面这张饼图的内圈表示各主流职业在前十名中所占的比重,外圈表示该职业从业者中的男女比例,蓝色为男性,红色为女性。我们再用一张直方图来表示:
同样蓝色代表男性,红色代表女性,从左到右的职业在知乎中的数量依次下降。可以看到,知乎大部分主流职业中主要为男性占主导。前 10 名的主流职业中有 8 个职业的男性从业者比女性多,其中以 程序员 的男女比例差距最为悬殊(-_-|||),以 设计师 男女比例差距最小,看来设计师从业人员男女比例较为均衡。其它的,比如产品经理、自由职业者、律师中,均为男性从业者多于女性。而前 10 名剩下的 2 个职业——教师、人力资源(HR)——女性从业者要多于男性,其中以 人力资源(HR) 男女比例差距最为悬殊,教师的男女比例虽不那么夸张,但女性也远远多于男性(也许是因为男性教师不怎么上知乎?)。
看完了知乎各职业的性别分布,我们再用一张热力图,来观察知乎主流职业(前五名)在各个地区的分布情况:
这里我为了展示方便,去掉了产品经理,你只需要知道产品经理在各地人数都是最多的…… 不明白知乎上怎么这么多产品经理,可能是为了方便宣传他们的产品?
从上图可以看出,知乎各主流职业大部分集中在北京和上海两地,更准确地说,大部分都集中在北京,但是人力资源(HR)是个例外,他们更多集中在上海。我们再来看看其他职业,程序员分布最多的城市依次是:北京、上海、广州、杭州、厦门。北京占得比重最大,简直绿的发黑,看来北京是程序员的天堂?其中深圳没有上榜,让我很奇怪。设计师分布最多的城市依次是:北京、上海、杭州、深圳、武汉。设计师的地区分布比较平均,各个城市都有一定数量。
知乎中学生用户占很大的比重,我们来看看他们都来自哪些学校!词云中字体越大,表示所占比重越大。
我们再列出详细的比重排名:
以上展示的结果不一定准确,可能有很大一部分学生用户没有填写自己的学校。仅从上图可以看出,知乎活跃高校用户从大到小依次是:浙江大学、武汉大学、华中科技大学、中山大学、北京大学、上海交通大学、复旦大学、南京大学、四川大学、清华大学。
既然分析到学校了,我们顺便看看各高校的男女比例,嘿嘿。
发现一个有意思的现象,各高校大部分都是男生在玩知乎……
再看一下哪些高校在知乎获得的赞数最多:
第一是同济大学,土木工程,恩,这是哪位大佬撑起来的;第二是华南理工大学,软件工程,这个我知道,轮子哥是皇家理工的嘛;第三,重庆第一工程尸培训基地,恩???这是什么鬼(黑人问号);继续往后看,恩……??家里蹲大学??!!还有一个兰州大学,牛肉面工艺专业???WHAT??!!∑(っ°Д°;)っ
知乎大佬都这么调皮吗……
这个图似乎不太准,大家忽略就好……
我们再来看看各地区有哪些高校是知乎重度用户:
北京玩知乎最多的高校依次是:北京大学、北京邮电大学、中国传媒大学、中国人民大学、清华大学。 上海玩知乎最多的高校依次是:上海交通大学、复旦大学、同济大学、上海大学、上海财经大学。 杭州玩知乎最多的高校依次是:浙江大学、浙江工业大学、杭州电子科技大学、浙江大学,计算机科学、浙江大学,软件工程。浙江大学是重度用户啊…… 成都玩知乎最多的高校依次是:电子科技大学、四川大学、西南交通大学、电子科技大学,软件工程、四川师范大学。 广州玩知乎最多的高校依次是:中山大学(SYSU)、华南理工大学(SCUT)、华南农业大学(SCAU)、广东外语外贸大学、广东工业大学。
我们再来看一下知乎各高校用户的活跃程度,按每个学校用户回答问题的总数排名:
排名依次是:武汉大学、浙江大学、中山大学、华南理工大学、北京大学、华中科技大学、复旦大学、上海交通大学、西北农林科技大学。
好了,高校分析告一段落,我们再来看看知乎用户的各类排名。
下图中词云的字越大,表示收到的赞数越多:
我们再来一张直方图,配合着看:
张佳玮大大无可争议的获得第一名,360+万赞,恐怖。其次是马前卒、唐缺、vczh、肥肥猫、朱炫、Seasee Youl、ze ran、鬼木知、豆子。知乎获赞总数前五名有两位都是作家(张佳玮和唐缺),看来作家在知乎回答问题获赞方面还是很吃香,果然表达能力是观点获得认可的一个重要支撑 。
下图词云的字越大,表示关注者越多,看看有没有你熟悉的大V 呢?:
同样我们配合着一张直方图来看:
知乎关注人数最多的前10位大V依次是:张佳玮、李开复、黄继新、周源、张亮、张小北、李淼、朱炫、葛巾、田吉顺。这些是知乎真正的大V,关注者数量极多。关注者最多的张佳玮有137万粉丝(抓取时),他的粉丝还在不断上涨,到现在已经有138万粉丝。关注者最少田吉顺也有57万粉丝,轮子哥(vczh)稍微少一些,排在第11位。
这些大V在知乎很活跃…… 下图词云文字越大,表示回答问题数量越多。
具体的排名为:
知乎回答问题数最多的10位大V从大到小依次为:vczh、李东、赵钢、另一只袜子、四海之内、M3小蘑菇、kun yu、白猫转转风、yskin、肛里拉出个电锯。微软的工作貌似很闲,看轮子哥(vczh)整天刷知乎……
我们再来加上这几位用户在知乎的获得的赞数,看看“回答问题的数量”和“获得的赞数”之间有没有什么联系:
由上图我们可以粗略的得出结论,回答问题的数量和获得赞数的关系没有很大。上图中只有 kun yu 和 vczh 回答问题的数量和获赞数基本持平,榜单前十名的其它用户获得赞数虽然也不少,但和他们回答问题数量的排名比起来,还不在一个量级上。这从侧面也说明了一个问题,回答问题的质量更加重要,某些高质量的会更容易吸引用户点赞。
我们再来看一个有意思的统计,知乎参加 live 最多的100位用户,以及他们最多参加过多少场 live。(live 是知乎推出的一种类似于直播的问答形式,大V开一场 live,分享他所在领域的知识,用户买门票参与 live,是一种知识变现的方式)
再来看看他们最多参加过多少场 live:
最多的那个大V竟然参加过 1600+ 场 live,真的很有精力和金钱,哈哈。
以上,便是以知乎300万用户个人资料为样本,做出的简单数据分析。
http://yangyingming.com/article/389/