"提高速度,减小资源使用,易于维护是软件开发不变的核心" -- by 我自己
这个commit内容的运行并不会有什么惊喜的改变,但是如果你的机器想像我所使用的这台,10年前的thinkpad,双核,当年可是高端配置,你就能比较明显的发现速度上的提高。而且,与其说这一个commit是我在介绍我运用的python的功能,我觉得我很容易会写成为啥我喜欢在linux下开发程序的舔文。
我经常开玩笑说,如果做c++的,没有经历过两件事的话,不会知道这语言调试起来有多痛苦。一个是内存泄露,一个是多线程bug。这两种错误,需要你有极高的耐心,一些理论知识和越多越好的实践经验,最重要的是,有时候要有一点运气。不然,你会陷入到一种,我是改了,到底有没有改好的自我怀疑之中。
而现实中的情况是,为了提高速率,使用并发是一个理所当然的技术选择。而在windows上,系统的设计让你几乎只有多线程这一条路走,当然在c++中,feature,promise这些concurrency关键词的引入使得这方面有了更多的选择。而线程之间的同步很容易让你陷入到一个崩溃的境地。而linux,秉承着KISS(Keep it simple,stupid)的原则,讲究一个进程尽量设计成功能单一,性能稳定的小程序。而进程间的通信又是无比的方便,关键是,像windows系统所逼迫出来的大家长式的程序,一个线程挂了,很多时候整个进程就hang住了。而Linux在守护进程的管理下,一个进程挂了,只要再重新启动一下就好了。在我胡扯这些文章的时候,我的输入法崩溃过n次,但是我不需要担心,系统会自动帮我重新加载IME模块,而其余的部分完全可以继续一如既往的工作。
前面说了这么多,就是为了这里铺垫的,我想对我的爬虫程序的速度提高一点,毕竟,更快是每个程序员不屑,哦不,是不懈的追求。而python中对于编写并发有很多种方法,在linux上我毫不犹豫的选择多进程的方法。
python的多进程,在本质上就是调用了fork方法,作为linux的经典方法,fork是一个伟大而又特别的函数。接口简单,使用简单。特别的是调用一次,返回两次。当然,作为使用python语言本身,你完全可以对这个细节不做任何了解,依然不会对你使用python多进程有任何影响。

python对于多进程的支持也十分简单,首先调用Pool的构造函数构造出一个进程池(当然,这些函数所在的包,都可以在代码的开头找到)。然后使用apply_async函数,传入要执行的进程函数,后面依次都是参数,接着就是关闭进程和使用join等待所有进程结束。核心用法不过四行,你就可以创建多进程函数,相比传统C++,实在是方便了太多。这里我想扯一点题外话,在程序的164行,我使用的是range,而在python3中,引入了新的一个叫做xrange的东西。如果你足够严格,这个地方用xrange会更好。那么,他们有什么差别呢?一句话的话,range会一次性生成整个列表,而xrange只会一个一个生成你想要的。再简单点,xrange更节省资源。
那么剩下的就是看看CrawlTheItem到底是啥了。其实这个函数里面的内容在前面一直存在,就是前面的爬虫主体函数。当然,因为加了参数的原因,函数略有改动。但是,说白了,核心改动也就是这个地方:

这里可以写的更好,但是目前我就暂时图个方便。逻辑简单,因为第一页的url不同,所以,只能特殊情况特殊处理了。而对于后面的页码,采用,循环的方式再合适不过了,只不过,原先我们只要从第一页开始循环起,到这里,我们是从开始页循环到结束页。
作为一个程序员,而我们的程序之中会把结果输出到一个文件中。文件,资源呐,有资源的地方就有竞争,但是你看这个程序,并没有任何同步代码。而如果你运行了程序,会发现,文件中并不会出现把下一行后一半的文字输出到前一行的前一半文字之后这种类似的情况。为什么?啊,不得不又吹下linux了,在linux中,进程中写文件,操作系统会保证其原子性。也就是说,对于两个进程,你只管输出,不用担心其他的问题。而使用多进程的另一个好处就是,在你运行的这些进程之中,如果有一个进程挂了,并不会影响你另一个进程的执行。最终,你只不过是会少一些数据而已,不至于得到了错误的数据。
再回到上面一张图,我这里只用了两个进程,因为只有两个核,用三个进程必然至少有一个得不到执行。如果想使用更多的进程咋办?你只要调整下range里的步长参数,也就是最后一个参数,就可以完成进程的划分。当然,这个进程函数写的还不是那么的好,不过大体意思嘛是这个意思。
那么,按照下面这个步骤运行这个命令,如何证明我的程序是并发运行的呢?

在linux上,你只需要打开另外一个终端,输入top,你会看到这样的结果

第一行和最后一行,你会看到有两个python解释器在同时运行,有图有真相的证明了,程序确实是并行的在运行。