在写爬虫时,多线程或多进程有时候是必要的,我们爬取的数据量有时过于庞大,使用单一线程可能会使我们的时间成本显著增加。
线程与进程
- 何谓进程
“进程”在计算机科学中是指正在执行的程序的实例。它是程序代码与程序运行时所需的资源(如内存、CPU 时间等)结合的产物。
进程是资源单位。
- 何谓线程
“线程”是操作系统中执行程序的基本单元,是比进程更小的执行单位。一个进程可以包含多个线程,这些线程共享进程的资源(如内存空间、文件句柄等),但每个线程有自己的栈空间和程序计数器。
线程是执行单位。
如果把进程比作公司,那么线程就是公司里的员工,一个公司里可以有多个员工,也必须至少有一个员工。
一般而言,我们更倾向于用多线程去解决任务。
案例
下面是一些有关多线程,多进程,线程池的程序代码以及一些 Python 的细节说明。
案例 1
1 | from threading import Thread |
Thread
是一个线程对象,利用target
参数可以把我们需要进行的多线程任务 (在案例中func
)传入,通过start()
方法,来使该线程的执行状态为可以开始,至于具体什么时候开始,则与 CPU 有关。__name__
是一个 Python 自带的参数,它的功能是用于区分程序是直接被编译还是在import
中被调用,如果是直接被编译,那么__name__
的值就是 ‘__main’,如果是被import
,那么它的值就是import
后面的字符串。
案例 2
第二种实现多线程的方法如下:
1 | class MyThread(Thread): |
这段代码工作的原理是:在默认情况下,Thread
类的实例在 start
时会运行 run()
方法,上述代码通过继承类 Thread
并重定义 run()
方法,实现了目标结果。
案例 3
1 | from multiprocessing import Process |
可以看到多进程和多线程的代码非常类似,只是换了模块而已。这要感谢 Python 提供的如此良好的 API。
案例 4
线程池的需求是必要的。试想如果我们有 1000 个网页要爬取,难道要开 1000 个线程吗?还是只开 50 个线程来回使用?显然后者是更优的一种做法。
1 | from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor |
这里 Python 自带的 concurrent.futures
模块里自带了线程池和进程池。本代码使用线程池做的演示,进程池完全类似。
注解 1:上面的所有示例代码中多线程的任务有些是不带参数,有些只带了 1 个参数。事实上,对于
Thread
实例来说,如果采取第一种实现方式,则可以通过Thread(target=func, args=("t1",));
这样的方式进行传参,注意到args
后面的,
是必须的,表示args
是一个元组。而对于线程池来说,就可以像案例 4 的代码一样,在submit()
方法中直接传入参数即可,也可以按照顺序排列,不用名言是哪个参数。
最后是一个线程池与爬虫结合起来的部分代码
案例 5
1 | def download_one_page(url): |
- 要点 1:xpath 路径中
position()>1
表示的是所有顺位比 1 大的,在代码中就代表tr[2], tr[3], tr[4]...
等等。 - 要点 2:
(item.replace("\\","").replace("/","") for item in txt)
代表的是数据生成器(可以粗略的理解为迭代器),而非元组。可以用for
循环或者next
进行遍历。