第一次写文章,没有经验

用Java+Redis+ES+Kibana技术对数百万知乎用户进行了数据分析,得到了这些…

架构真经 昨天
作者:artoria
http://tinyurl.com/quscxyl

  1. 前言

我是一个真正的知乎小白。
上班的时候,自己手头的事情处理完了,我除了在掘金摸鱼,就是在知乎逛贴。在我的认知中,知乎是一个高质量论坛,基本上各种“疑难杂症”都能在上面找到相应的专业性回答。但平时逗留在知乎的时间过多,我不知道自己是被知乎上面的精彩故事所吸引,还是为知乎上面的高深技术而着迷。
咱是理科生,不太懂过于高深的哲学,自然不会深层次地剖析自己,只能用数据来说话。于是,就有了这篇博客。
相关的项目源码放在:
https://github.com/zhenye163/spider-zhihu
2. 博客结构图

博客的结构图如上所示。这篇博客主要讲述两件事:爬取知乎用户数据和对用户数据进行分析。这个结构图基本能够概述分析知乎用户信息的思路,具体的思路详述和技术实现细节可看博客后面的内容。
3. 爬取知乎用户数据

3.1 知乎用户页面解析
我的知乎主页信息预览如下:

从该页面的内容来看,我当前需要爬取的知乎信息就在两个红框中。然后每个知乎用户主页对应的URL路径应该不一样,这里URL中标识是我的主页就是mi-zhi-saber,这个URL标识就是知乎里面的url_token。也就是说拿到足够多的url_token,就可以自己组装URL来获取用户的信息。
通过分析知乎页面结构,我们可以按照如下思路来爬取用户信息:
基于用户的个人主页信息,爬取、解析并保存用户信息。
上面用户主页链接对应的页面内容如下:

对比这两个页面,可以推断出里面部分字段的意义(其实字段名称已经足够见名知意了)。综合考虑后,我要爬取的字段及其意义如下

基于用户关注的知乎用户信息,爬取、解析并保存用户信息。
我关注的知乎用户信息页面内容如下:

基于关注用户的知乎用户信息,爬取、解析并保存用户信息。

理论上,选取一个知乎大V作为根节点,迭代爬取关注者和被关注者的信息,可以拿到绝大部分的知乎用户信息。
3.2 选取爬虫框架
要想对知乎用户进行画像,必须拿到足够多的知乎用户数据。简单来说,就是说要用java爬虫爬取足够多的知乎用户数据。
工欲善其事,必先利其器。常见的Java爬虫框架有很多如:webmagic,crawler4j,SeimiCrawler,jsoup等等。这里选用的是SpringBoot + SeimiCrawler,这个方式可以几乎零配置地使用爬虫爬取知乎用户数据。具体如何使用可见于SeimiCrawler官方文档,或者参考我的源码。
搜索Java知音公众号,回复“后端面试”,送你一份Java面试题宝典
3.3 使用反反爬手段
论坛是靠内容存活的。如果有另外一个盗版论坛大量地爬取知乎内容,然后拷贝到自己的论坛上,知乎肯定会流失大量用户。不用想就知道,知乎肯定是采取了一些反爬手段的。
最常见的反爬手段就是User Agent识别和IP限流。简单解释一下,就是知乎会基于用户访问记录日志,分析哪个用户(IP)用哪个浏览器(UA)访问知乎网站的,如果某个用户极其频繁地访问知乎网站,知乎就会把该用户标记为“疑似爬虫的机器人”,然后让该用户进行登录验证或直接将该用户对应的IP地址进行封禁。
然后,所谓的“反反爬手段”,就是应对上面所说的反爬手段的。我采取的“反反爬手段”是:
收集一些常用的UA,然后每次调用接口访问知乎网站的时候会刷新所使用的UA。
自己在项目中维护一个可高用的免费代理池,每次调用接口访问知乎网站的时候会使用高可用代理池的随机一个代理。
实际实践过程中,提供了免费代理的网站有:西刺代理、89免费代理、云代理等等,但实际能够使用的还是只有西刺代理。而且西刺代理的可用数也非常少,导致代理池中可用代理数很少,使用代理池的效果不是很好,这真的是一件很沮丧的事。
免费代理池的架构及其实现思路图:

简述一下思路:
启动项目时,会自动去爬取西刺代理网站前10页的代理(共1000个代理),并将其保存到RabbitMQ中。RabbitMQ设置有10个消费者,每个消费者会检测代理是否可用,检测完毕后会将该代理的信息及其检测结果保存到DB中。
系统设置了一个定时任务,会定时将DB中当前的所有代理再次放到RabbitMQ中,会由10个消费者检测代理是否可用,并将检测结果同时更新到DB中。如果连续3次测试代理不可用,则将该代理从DB中删除。
系统设置了一个定时任务,会定时爬取西刺代理网首页的所有代理,会检测代理的可用性,并将其信息及检测结果再次保存到DB中。这样保证DB中会定时获取更多的代理(实际原因是西刺代理可用代理太少,如果不定时获取更多代理,DB中很快就没有可用的代理了)。
系统设置了一个定时任务,会自动删除redis中所有的代理。然后再将DB中的代理按检测成功次数进行排序,将连续成功次数最多的前10个代理保存进redis中。这样redis中的代理就是高可用的。
3.4 调用接口爬取数据
项目一定程度地屏蔽了代理池以及知乎用户数据解析的实现复习性,以暴露接口的方式提供爬取知乎用户信息的功能。
在配置好Redis/RabbitMQ环境后,成功启动项目,等项目稳定后(需要等到redis中有高可用的代理,否则就是用本机IP直接进行数据爬取,这样的话本机IP很容易会被封)后,即可通过调用如下接口的方式爬取知乎用户信息。
调用接口localhost:8980/users爬取指定知乎用户的信息,修改url_token的值即可爬取不同知乎用户的信息。

调用接口localhost:8980/users/followees爬取关注指定知乎用户的用户信息,修改url_token的值即可爬取关注不同知乎用户的用户信息。

调用接口localhost:8980/users/followers爬取指定知乎用户关注的用户信息,修改url_token的值即可爬取不同知乎用户关注的用户信息。

在实际测试爬取知乎网站用户信息的过程中,如果系统只用一个固定IP进行爬取数据,基本爬取不到10万数据该IP就会被封。使用了代理池这种方式后,由于西刺代理网址上可用的免费代理太少了,最终爬取到167万左右数据后,代理池中基本就没有可用的IP了。不过爬取到这么多的数据已经够用了。
4. 分析知乎用户数据

4.1 数据去重
爬取了167万+知乎用户数据后,需要对原始数据进行简单的清理,这里就是去重。每个知乎用户有唯一的url_token,由于这里爬取的是用户的关注者与被关注者,很容易就会有重复的数据。
数据量有167万+,使用Java自带的去重容器Set/Map明显不合适(内存不够,就算内存足够,去重的效率也有待考量)。

项目中实现了一个简单的布隆算法,能够保证过滤后的知乎用户数据绝对没有重复。
布隆算法的实现思路图如下:

简述布隆算法的实现思路如下:
始化一个位容器(每个容器单位的值只能是0或1),并先规定好要使用映射数据用的n个hash方法,hash方法的结果对应于该位容器的一个下标。
数据之前,需要先判断该容器中是否已经存过该数据。该数据对应所有hash方法的结果,对应在位容器中的下标只要有一个下标对应的单位的值为0,则表示该容器还没有存过该数据,否则就判定为该容器之前存过该数据。
数据之后,需要将该数据所有hash方法结果对应于位容器中的下标的值,都置为1。
这里需要说明一下为什么要使用布隆算法以及布隆算法还有什么缺点。
使用布隆算法的理由:我们是依靠url_token来判断一个用户是否重复的,但url_token的长度是不确定的,这里存放一个url_token所需要的空间按上图DB中来看基本上有10字节以上。如果使用java容器进行去重,那么该容器至少需要的空间:10 * 1670000 byte 即大约15.93MB(这里貌似还是可以使用java容器进行去重,但其实这里还没有考虑容器中还需要存的其他信息)。
而使用布隆算法,需要的空间:n * 1670000 bit ,使用的hash方法一般是3-10个左右,即一般至多只需要15.9KB左右的空间(我在项目中使用的是2 << 24 bit即16KB的容量)。如果数据量继续增大,布隆算法的优势会越来越大。
算法的缺点: 地,这种hash映射存储的方式肯定会有误判的情况。即bitSet容器中明明没有存储该数据,却认为之前已经存储过该数据。但是只要hash方法的个数以及其实现设计得合理,那么这个误判率能够大大降低(笔者水平有限,具体怎么降低并计算误判率可自行谷歌或百度)。而且基于大数据分析来说,一定数据的缺失是可以允许的,只要保证过滤后有足够的不重复的数据进行分析就行。
项目中屏蔽了布隆算法实现的复杂性,直接调用接口localhost:8980/users/filter,即可将DB中的用户数据进行去重。
过滤之后,还有160万左右不重复的数据,说明布隆算法误判率导致的数据流失,对大量的数据来说影响是可以接受的。

4.2 数据导入ElasticSearch
mysql是一个用来持久化数据的工具,直接用来进行数据分析明显效果不太好(而且数据量较大时,查询效率极低),这里就需要使用更加合适的工具—ElasticSearch。简单学习一下ElasticSearch,可以参考elasticsearch官网。
配置好ElasticSearch环境,然后修改配置文件中ElasticSearch相关的配置。调用接口localhost:8980/users/transfer,即可将DB中的用户数据迁移到ES中。
SpringBoot整合ElasticSearch非常简单,直接在项目中导入ElasticSearch的自动配置依赖包

org.springframework.bootgroupId>
spring-boot-starter-data-elasticsearchartifactId>
dependency>
然后让相应的DAO层继承ElasticsearchRepository即可在项目中使用ElasticSearch。具体如何在springboot项目中使用ElasticSearch,可以参考SpringBoot-ElasticSearch官方文档,也可参考我项目中源码。
数据导入ES后,可以在head插件或者kibana插件中查看ES中的数据(head插件或kibana插件可以看去重之后导入ES中的数据有1597696条)。

4.3 kibana分析知乎数据
我们已经拿到足够多的用户数据了,现在需要利用kibana插件来分析数据。我们在Management > Kibana > Index Patterns中将创建关联的索引user后,即可使用kibana插件辅助我们来分析数据。
搜索Java知音公众号,回复“后端面试”,送你一份Java面试题宝典
下面举几个例子来表示如何使用Kibana来分析大数据。
查询关注数在100万及以上的用户

查询关注数在100万及以上的用户

GET user/userInfo/_search
{
“query”: {
“range” : {
“followerCount” : {
“gte”: 1000000
}
}
}
}
查询结果图如下:

简单地解释一下结果集中部分字段的意义。took是指本次查询的耗时,单位是毫秒。hits.total表示的是符合条件的结果条数。hits._score表示的是与查询条件的相关度得分情况,默认降序排序。
聚合查询知乎用户的性别比

查询知乎用户男女性别比

GET /user/userInfo/_search
{
“size”: 0,
“aggs”: {
“messages”: {
“terms”: {
“field”: “gender”
}
}
}
}
查询结果图如下:

直接看数据可能不太直观,我们还可以直接通过kibana插件不画相应的结果图(-1:未填,1:男, 0:女):

从结果图来看,目前知乎的男女比还不算离谱,比例接近3:2(这里让我有点儿怀疑自己爬取的数据有问题)。
聚合查询人口最集中的前10个城市

查询现居地最多的前10个城市

GET /user/userInfo/_search
{
“size”: 0,
“aggs”: {
“messages”: {
“terms”: {
“field”: “home”,
“size”: 10
}
}
}
}
查询结果图如下:

从这里的查询结果,很容易就可以看出,“深圳”和“深圳市”、“广州”和“广州市”其实各自指的都是同一地方。但是当前ES不能智能地识别并归类(ps: 可能有方法可以归类但笔者不会…)。因此这里需要后续手动地将类似信息进行处理归类。
模糊搜索
全字段匹配,“模糊”搜索含有“知乎”的数据,搜索结果图如下:

4.4 echarts作图
从上面的kibana画图效果来看,真的一般般。这里更推荐使用kibana收集数据,利用百度开源的数据可视化工具echarts来作图。
最终的数据汇总以及echarts绘图效果图如下:
关注数层级统计

很明显地,绝大部分知乎用户都是“知乎小白”或者“知乎路人”。这里的“知乎超大V(1000000+)”的用户只有3个:“丁香医生”、“知乎日报”、“张佳玮”。
行业信息统计
手动整理后的行业信息图如下:

很明显地能够看出,大部分知乎用户所处的行业都与计算机或者互联网相关。
公司信息统计
统计了出现频率最多的前15名所属公司统计图如下:

可以看到,“腾讯”、“阿里”的员工数量遥遥领先。虽然“百度”还是排名第三,但已经不在一个数量级。(“BAT”的时代真的一去不复返了吗?)
职位信息统计
基于职位信息统计图,利用中文在线词云生成器优词云,生成出现频率最多的前100名的职位词云图:

可以看出,除了学生以外,很多知乎用户都从事计算机或者软件编程相关的工作,也就是说,知乎用户中“程序猿/媛”所占的比重极其的大。
大学信息统计
统计了出现频率最多的前20名毕业院校统计图如下:

可以看到,填写了毕业院校的知乎用户(其实还有绝大部分人没有完善该信息),这些毕业院校的实力和名气那是杠杠的。
专业信息统计
统计了出现频率最多的前20名专业统计图如下:

可以看到,“计算机科学与技术”和“软件工程”这两个专业的人数遥遥领先。
居住城市信息统计
统计了出现频率最多的前20名居住城市统计图如下:

很明显地,“帝都”和“魔都”的人数遥遥领先。(这里可以做一个相关性不大、准确度不高的推论:杭州将是下一个“新一线城市”最有力的竞争者。)
5. 总结

从最终的信息统计结果来看,大部分的知乎用户信息不算完善(信息比例)。但这些统计结果图,都是基于知乎用户已经完善的信息进行整理并分析的。很明显地可以看出,已完善信息的知乎用户,基本都在发达城市大公司任职,而且其中的很大一部分是“程序猿/媛”。
也就是说,如果我(码农一枚)在工作中遇到什么专业难题时,在知乎中寻求到的答案是专业可信的。
阅读 98

在看1

写下你的留言

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SeimiCrawler(Java 爬虫框架)更新 支持在Request对象中,通过header(map)来自定义本次请求的header,以及支持通过seimiCookies来自定义cookies,自定义cookies会直接进入cookiesStore,对同域下第二次请求依然有效 优化默认启动方式,改造cn.wanghaomiao.seimi.boot.Run支持CommandLineParser,可以使用 -c 和-p来传参,其中-c用来指定crawlernames,多个用','分隔,-p指定一个端口,可以选择性的启动一个内嵌的http服务,并开启使用内嵌http接口 maven-compiler-plugin打包插件升级为1.3.0,完善Linux下的脚本,并增加启动配置文件,可以到maven-compiler-plugin主页详细查看 默认下载器改为Apache Httpclient,备用为下载器OkHttp3实现 优化部分代码 demo日志默认全部输出至控制台 SeimiCrawler(Java 爬虫框架)简介 SeimiCrawler是一个敏捷的,独立部署的,支持分布式的Java爬虫框架,希望能在最大程度上降低新手开发一个可用性高且性能不差的爬虫系统的门槛,以及提升开发爬虫系统的开发效率。在SeimiCrawler的世界里,绝大多数人只需关心去写抓取的业务逻辑就够了,其余的Seimi帮你搞定。设计思想上SeimiCrawler受Python的爬虫框架Scrapy启发,同时融合了Java语言本身特点与Spring的特性,并希望在国内更方便且普遍的使用更有效率的XPath解析HTML,所以SeimiCrawler默认的HTML解析器是JsoupXpath(独立扩展项目,非jsoup自带),默认解析提取HTML数据工作均使用XPath来完成(当然,数据处理亦可以自行选择其他解析器)。并结合SeimiAgent彻底完美解决复杂动态页面渲染抓取问题。 SeimiCrawler(Java 爬虫框架)展示   相关阅读 同类推荐:站长常用源码
SeimiCrawler An agile,powerful,distributed crawler framework. SeimiCrawler的目标是成为Java世界最好用最实用的爬虫框架。简介      SeimiCrawler是一个敏捷的,支持分布式的Java爬虫开发框架,希望能在最大程度上降低新手开发一个可用性高且性能不差的爬虫系统的门槛,以及提升开发爬虫系统的开发效率。在SeimiCrawler的世界里,绝大多数人只需关心去写抓取的业务逻辑就够了,其余的Seimi帮你搞定。设计思想上SeimiCrawler受Python的爬虫框架Scrapy启发很大,同时融合了Java语言本身特点与Spring的特性,并希望在国内更方便且普遍的使用更有效率的XPath解析HTML,所以SeimiCrawler默认的HTML解析器是JsoupXpath,默认解析提取HTML数据工作均使用XPath来完成(当然,数据处理亦可以自行选择其他解析器)。 原理示例基本原理集群原理快速开始 添加maven依赖(已经同步到中央maven库,最新版本参见项目主页):     cn.wanghaomiao     SeimiCrawler     0.1.0 在包crawlers下添加爬虫规则,例如:@Crawler(name = "basic") public class Basic extends BaseSeimiCrawler {     @Override     public String[] startUrls() {         return new String[]{"http://www.cnblogs.com/"};     }     @Override     public void start(Response response) {         JXDocument doc = response.document();         try {             List<Object> urls = doc.sel("//a[@class='titlelnk']/@href");             logger.info("{}", urls.size());             for (Object s:urls){                 push(new Request(s.toString(),"getTitle"));             }         } catch (Exception e) {             e.printStackTrace();         }     }     public void getTitle(Response response){         JXDocument doc = response.document();         try {             logger.info("url:{} {}", response.getUrl(), doc.sel("//h1[@class='postTitle']/a/text()|//a[@id='cb_post_title_url']/text()"));             //do something         } catch (Exception e) {             e.printStackTrace();         }     } } 然后随便某个包下添加启动Main函数,启动SeimiCrawler:public class Boot {     public static void main(String[] args){         Seimi s = new Seimi();         s.start("basic");     } } 以上便是一个最简单的爬虫系统开发流程。 更多文档      目前可以参考demo工程中的样例,基本包含了主要的特性用法。更为细致的文档移步SeimiCrawler主页中进一步查看 标签:爬虫
•SeimiCrawler一个敏捷强大的Java爬虫框架 •1.简介 •2.需要 •3.快速开始 ◦3.1.maven依赖 ◦3.2.在SpringBoot中 ◦3.3.常规用法 •4.原理 ◦4.1.基本原理 ◦4.2.集群原理 •5.如何开发 ◦5.1.约定 ◦5.2.第一个爬虫规则类-crawler ◾5.2.1.注解@Crawler ◾5.2.2.实现startUrls() ◾5.2.3.实现start(Response response) ◾5.2.4.Response数据提取 ◾5.2.4.1.内部属性一览 ◾5.2.5.回调函数 ◾5.2.6.Request内部一览 ◾5.2.7.自定义UserAgent(可选) ◾5.2.8.启用cookies(可选) ◾5.2.9.启用proxy(可选) ◾5.2.10.设置delay(可选)* ◾5.2.11.设置请求URL白名单匹配规则 ◾5.2.12.设置请求URL黑名单匹配规则 ◾5.2.13.设置动态代理 ◾5.2.14.是否开启系统去重 ◾5.2.15.关于自动跳转 ◾5.2.16.异常请求处理 ◾5.2.17.SeimiAgent支持 ◾5.2.17.1.基本配置 ◾5.2.17.1.1.直接运行 ◾5.2.17.1.2.SpringBoot项目 ◾5.2.17.2.使用 ◾5.2.18.启动爬虫系统 ◾5.2.18.1.SpringBoot(推荐) ◾5.2.18.2.直接运行,独立启动 ◦5.3.工程化打包部署 ◾5.3.1.SpringBoot(推荐) ◾5.3.2.独立直接运行 ◦5.4.定时调度 ◦5.5.自动解析Bean ◾5.5.1.注解@Xpath ◾5.5.2.使用 ◦5.6.拦截器 ◾5.6.1.注解@Interceptor ◾5.6.2.接口SeimiInterceptor ◾5.6.3.拦截器样例 ◦5.7.关于SeimiQueue ◾5.7.1.配置使用DefaultRedisQueue ◾5.7.1.1.SpringBoot项目 ◾5.7.1.2.直接运行(非SpringBoot) ◾5.7.2.自行实现SeimiQueue ◾5.7.3.SeimiQueue样例 ◦5.8.集成主流数据持久化 ◾5.8.1.准备工作 ◾5.8.2.写一个DAO ◾5.8.3.开始使用DAO ◦5.9.分布式 ◦5.10.通过http服务接口操作 ◾5.10.1.Request必填参数 ◾5.10.2.SpringBoot(推荐) ◾5.10.3.直接运行 ◾5.10.3.1.发送抓取请求 ◾5.10.3.2.接口描述 ◾5.10.3.3.查看抓取状态 •6.常见问题汇总 ◦6.1.如何设置网络代理 ◦6.2.如何开启cookie ◦6.3.如何启用分布式模式 ◾6.3.1.参考 ◾6.3.2.特别注意 ◦6.4.如何设置复杂的起始请求 •7.社区讨论 •8.项目源码
SeimiCrawler An agile,powerful,standalone,distributed crawler framework. SeimiCrawler的目标是成为Java里最实用的爬虫框架,大家一起加油。 简介 SeimiCrawler是一个敏捷的,独立部署的,支持分布式的Java爬虫框架,希望能在最大程度上降低新手开发一个可用性高且性能不差的爬虫系统的门槛,以及提升开发爬虫系统的开发效率。在SeimiCrawler的世界里,绝大多数人只需关心去写抓取的业务逻辑就够了,其余的Seimi帮你搞定。设计思想上SeimiCrawler受Python的爬虫框架Scrapy启发,同时融合了Java语言本身特点与Spring的特性,并希望在国内更方便且普遍的使用更有效率的XPath解析HTML,所以SeimiCrawler默认的HTML解析器是JsoupXpath(独立扩展项目,非jsoup自带),默认解析提取HTML数据工作均使用XPath来完成(当然,数据处理亦可以自行选择其他解析器)。并结合SeimiAgent彻底完美解决复杂动态页面渲染抓取问题。 号外 2016.04.14 用于实现浏览器级动态页面渲染以及抓取的SeimiAgent已经发布。SeimiAgent基于Qtwebkit开发,主流浏览器内核(chrome,safari等),可在服务器端后台运行,并通过http协议发布对外调用API,支持任何语言或框架从SeimiAgent获取服务,彻底的解决动态页面渲染抓取等问题。具体可以参考SeimiAgent主页。SeimiCrawler已经在v0.3.0中内置支持SeimiAgent的使用并添加了demo,具体请查看demo或是官方文档。 2016.01.05 专门为SeimiCrawler工程打包部署的maven-seimicrawler-plugin已经发布可用,详细请继续参阅maven-seimicrawler-plugin或是下文工程化打包部署章节。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值