前言
如今微信、微博等社交平台每天都在产生大量的社交数据,如何找出个人在社交网络中处于那个节点,每个人的社交网络拓扑图是什么样子?今天就让我们用“scrapy + 数据库”的形式,打造一款分布式爬虫,快速获取某社交平台的社交网络信息,并通过可视化工具展示该社交网络。
可行性分析
分布式的理念在于将需要耗费大量计算能力的工作分配给多台机器协同运作,从而降低运算时间。对于分布式爬虫而言,要实现堕胎机器协同,必须使爬虫任务可以被分配,我们可以通过以下两种方式实现这一点;
- 明确任务总数(需要发起的
Requests),将任务划分后交给不同机器处理,或者用全部Requests搭建任务池,每台机器都从任务池里领取任务,任务完成则将该任务Request移出任务池。这样做的效率是最高的,然而事先明确任务总数的做法,对于递归式操纵(发起下一次Request必须依赖从上一次Request中获取到的数据)并不能完全适应。 - 明确每台机器的爬取界限,搭建已完成的
Requests缓存池,让每台机器在发起Request请求之前确认该请求不在缓存池中,任务完成则将该任务Request放入缓存池(相当于对Requests去重),这样爬虫任务每次迭代都要进行去重,效率虽然有所降低,但却可以适用于任务总数不确定、递归式爬取的情况。
对于社交网络而言,我们不能事先知道处于社交网络中的所有个体,而要依赖递归(事先知道一个个体的信息,获取该个体的关注对象,获取该个体关注对象的信息,获取该个体关注对象的关注对象……)获取整个社交网络的信息,属于典型的递归式爬取。所以在搭建分布式爬虫时,需要使用第二种方式。
编写utils,自定义scrapy请求方式getURL
首先我们需要在scrapy工程文件夹下建立utils.py文件,存放我们自定义的方法。比如:scrapy输出的日志信息太多,不方便快速定位错误信息,我们可以自定义logger方法,通过在信息前后插入分界线及空行的形式,快速找到我们想要的日志信息;发起request请求时需要验证header与cookie信息,我们已经用burp将验证信息保存到了本地,在需要用到这些验证信息时,可以通过自定义的headerParser方法读入内存;scrapy属于典型的多线程框架,当我们将请求yield出去后,yield之后的代码会继续执行,而不会等yield出去的请求解析完毕,而保证代码执行的先后顺序,对于我们这个项目来说至关重要,所以我们还需要自定义getURL方法,手动发起请求。
下图是我们自定义的requests_get方法,通过随机切换验证信息获取网站响应,请求失败则返回空的response对象。getURL方法就是对该方法的包装,通过多次调用requests_get方法,尝试获取网站响应,响应成功则返回响应对象。
编写nodesSpider,获取节点信息
nodesSpider有两个任务,首先对于传给nodesSpider的response对象(一个节点),nodesSpider将解析response对象中的uid(个人识别号,类似于QQ号),pid(个人主页识别号)等信息,并将信息传递给pipeline处理。其次对于每个节点,nodesSpider将检查该节点是否有关注对象,尝试获取该节点的关注对象信息,并将获取的response交给relationsSpider处理。
编写relationsSpider,获取连接信息
relationsSpider的任务非常简单,对于传给relationsSpider的response对象(一个节点的关注对象),relationsSpider将解析节点id及节点关注对象id,并生成一一对应的关系传递给pipeline处理。
编写pipeline,将items写入数据库
对于我们的项目而言,要实现分布式运作,pipeline十分重要。首先我们需要连接上共享数据库(无论是sqlite、MySQL、或redis,理论上是一样的),而且最好将操纵数据库的cursor对象定义为全局变量(我们还会在之后的socialSpider中经常用到)。其次我们需要确保在spider运行之前数据库中存在三张表,NODES用来存放nodesSpider产生的对象,RELATIONS用来存放relationsSpider产生的对象,REQUESTS用来建立requests(网络请求)缓存池。最后我们只需要将nodesSpider与relationsSpider传递出来的对象存入相应的表格。
编写socialSpider,实现递归爬取
最后我们将编写socialSpider,实现对以上所有资源的总调度。在socialSpider中有一个start_uid变量,这是我个人在该社交网络中的识别号,socialSpider程序开始时,我们将首先获取我个人的关注对象,再依次获取我个人关注对象的关注对象,以此类推实现递归与遍历。parse方法将帮助我们实现这一过程,首先对于每一个start_uid,parse方法将尝试抢夺对该节点的request发起权,如果获得发起权,则请求该节点的信息,并将响应交给nodesSpider处理(nodesSpider将自动帮我们获取该节点的关注对象信息),然后parse方法将判断可递归查询的节点id是否已经被用尽,如果则立即停止程序,否则parse方法将从数据库中取出下一个节点id。
社交网络图展示
通过运行socialSpider,我们可以获得该社交平台中的节点信息表与连接信息表,经过一系列的数据清洗及可视化展现,我们可以通过以下图表展示数据获取的过程。
图中所有的点即我们一直提到的节点信息,每一个节点代表该社交网络中的一个个体,其中黑色的点代表未知的个体(程序目前还未查询该节点信息),彩色的点(或者说圆)是我们已经知道的节点,其颜色代表社交主页的皮肤属性,大小代表粉丝数量的多寡,标签代表英文昵称。
迭代1次,产生了以0为中心向外辐射的社交网络图。
迭代3次,可见节点之间除了向外辐射与彼此直连外,还可能会通过中间人实现连接。在社会学的理论中,两个节点关系的亲疏甚至可以通过中间人的多寡来判定,中间人越多代表两个节点的交集越广泛、关系更牢靠。
迭代7次,中间人出现的频率更高了,甚至可以说在0的周围产生了更加亲密的社交网络(围绕深蓝色点展开的网络)。
迭代10次,社交网络已经开始另辟蹊径查找新的子网络(相当于现实生活中的小圈子),见左上角与现有网络连接较少的黄色点。
迭代30次,由于0关注了一些大V,他们粉丝量更多所以节点更大,相比而言原先蓝色点形成的子网络似乎微不足道。同时这些大V的专业性更强(比如‘ACG’),所以彼此关注的情况相对较少,向外辐射的情况更多。
迭代100次,可以看到社交网络增长速度十分之快,我们已经很难分清节点之间的关系,原先的网络早已被淹没了,我们只能看到一些在该社交平台中存在感更强的大V,以及社交网络疯狂向外扩展的趋势。在之后的迭代中这种情况还会更加严重,所以图片就放到这里为止了。