编写多进程爬虫

不管是多进程还是多线程,核心的问题都是任务如何分配,爬虫本身倒不是什么重点,本文的示例代码仅仅是讨论多进程爬虫的编写模式,并不是真实的案例代码,希望能够给你一些启发

1、任务分配

多进程爬虫的任务分配,和多线程一样,通过队列进行分配,先在主进程中将任务push到队列中,多进程启动后,每个进程都尝试从队列里获取任务,这里的任务,在实际应用中可能就是一个需要爬取的url。

2、消费者进程如何退出

任务消费结束后,多进程需要终止,在上一篇多线程爬虫文章中,采取的方法是用非阻塞的方法获取任务,引发异常后判断队列是否为空,若为空则退出线程,今天介绍另一种退出方法。

生成任务时,将一个结束标识push到任务队列中,表明这是最后一个任务,一个进程获得这个标识后,可以确定,队列里已经没有任务可以消费了,所有的进程都需要退出,这个进程将结束标识push到队列中,然后退出。

每个进程获得结束标识后都将结束标识再次push到队列中,然后自身退出,这样就能保证所有的进程最终都能顺利退出。

3、爬取的结果如何落库

每个进程之间是互不影响的,爬取数据后写入数据库,由于进程建立各自的连接,因此也不会影响彼此,但有种情况是个例外,如果你想将爬取到的数据写入到一个文件中,那么多个进程同时操作一个文件就会引发问题。

对于这种情况,只需要一个额外的处理爬取结果的进程就可以了。除了任务队列以外,新增一个爬虫结果的队列,进程爬取数据后,将结果写入到结果队列,同时专门安排一个进程处理这个结果队列,这样就避免了多个进程同时操作一个文件的问题。

4、 多进程爬虫架构图

这是一个简单的多进程爬虫的架构图

5、 实例代码

import time, os
from multiprocessing import Process, Queue


def crawl(result_q, task):
    """
    真正的爬虫代码在这里写
    :param task:
    :return:
    """
    # sleep模拟处理任务
    time.sleep(1)
    result_q.put(task)
    

def worker(task_q, result_q):
    while True:
        task = task_q.get(True)
        if task == 'end':
            task_q.put(task)
            break

        crawl(result_q, task)
        pid = os.getpid()
        print('进程{pid}获得任务{task}'.format(pid=pid, task=task))


def write_worker(result_q):
    while True:
        result = result_q.get(True)
        if result == 'end':
            break

        # 将结果写入到文件中
        print(result)


# 创建任务队列
task_q = Queue()
for i in range(10):
    task_q.put(i)

task_q.put('end')

# 爬虫结果队列
result_q = Queue()
result_process = Process(target=write_worker, args=(result_q, ))
result_process.start()

# 创建多进程
process_lst = []
for i in range(3):
    p = Process(target=worker, args=(task_q, result_q))
    process_lst.append(p)


# 启动
for p in process_lst:
    p.start()

# join
for p in process_lst:
    p.join()


result_q.put('end')
result_process.join()

print('执行结束')

发布于 2019-08-06 14:02