使用Python Multiprocessing爬取知乎数据

NathanLVZS   |   Sunday 3 January 2016


Category:  Python


前言

前一两个月的时候在弄一个project,需要爬取知乎的数据。从头开始实现一个爬虫感觉十分地繁琐又十分耗费时间,于是在GitHub上面搜索了一下已有的知乎的爬虫资源,找到zhihu-python这个包,感觉封装地挺不错的,想要拉取的数据都已经有了相对应的方法实现,想要修改、增加一些东西都挺方便,于是决定使用这个包。

zhihu-python是单线程实现的,由于要爬取的数据较多,所以会耗费较多的时间,于是想提高一下速度。苦于没有集群,只好采用多进程的方式进行,使用multiprocessing这个库来实现。

好像知乎没有使用反爬虫机制,爬取过程还算蛮顺利的,就是学校的网络连回大陆不怎么稳定,速度一般。。。

代码传送门

数据需求

我想通过一个种子用户A,爬取A以及以他为起点的关注网络。所需的数据有:

  • 用户的基本信息:id,收到的赞同数,收到的感谢数,关注的人的数量,关注他的人的数量,回答过的问题数量等

  • 用户关注的人

  • 用户回答过的问题的编号

  • 问题对应的话题

一些说明

结果的保存:每个进程保存自己的结果在单独的文件。在程序执行过程中将出错的任务保存到一个队列中,在全部任务结束后由主进程将该队列中的内容输出到一个文件中。

关于layer

Image

根据zhihu-python的实现方式以及数据之间的联系,可将数据爬取分为两个阶段–用户数据的爬取、问题话题数据的爬取,这样代码编写实现起来会快一些,出现问题也方便更快地解决。

用户数据爬取

对于第一阶段,采用广度优先的方式,维护一个队列(使用multiprocessing.JoinableQueue),里面存放任务信息。一开始的时候首先将种子用户任务信息放进去。

主进程的流程

Image

子进程的流程

Image

结束条件

子进程先从任务队列里面获取到一个任务项,当正常处理或者错误处理完成之后,会调用任务队列的task_done方法。任务队列对象中会记录自己还有多少个未完成的任务项,每次调用一次 task_done 方法都会将未完成项的数量减一,该数量为0即说明队列中所有的项都被处理过了,这时所有的子进程就可以结束然后退出了。

在所有子进程开始之后,主进程会阻塞直至任务队列中所有项都完成,可见Queue.join。当所有任务完成之后,主进程将一个共享标志位置位,告诉给子进程任务已经结束。子进程的循环中会判断该标志位是否被置位,如果标志位被置位则跳出循环并结束本进程。

话题数据爬取

对于第二阶段,首先根据第一阶段获取到的问题id数据构建任务队列。跟第一阶段不同的是,这个队列在任务开始的时候就是确定了的,任务执行过程中并不会往队列里添加新的队列项。

主进程的流程

Image

子进程的流程

Image

结束条件

本阶段的任务队列在一开始的时候就已填充完毕,也可以使用第一阶段的方式来结束子进程,不过我尝试了另一种做法。由于使用的是 queue.get(False) ,每次尝试向队列获取任务项时,如果互斥锁已被其他进程获取,则会触发队列空异常,故不能一出现队列空异常就结束。可以使用一个计数器,记录连续捕获队列空异常的次数,如果大于一个合适的阈值,则说明队列中所有的项都已被处理完,可以结束了。

错误处理

采用的错误处理方式基本上就是重试,如果是连接异常,就不断重试。如果是遇到其他异常,对于一个任务项,如果连续5次都出现其他异常,则将该任务项记录起来。在回到主进程之后全部输出到文件。

后注

知乎可能会不时修改一些页面html元素、样式什么的,11月底的时候我的代码还可以抓数据的,这几天整理的时候发现有问题,然后花了一些时间改了下代码。

如果遇到问题,可以去zhihu-python的GitHub页面看一下有没有相关的issue解决方法,尽量保持auth.py和zhihu.py这两个文件跟zhihu-python上的保持同步。这两个文件,我改动的地方都加了注释标记。