Python如何在网络爬虫程序中使用多进程进行数据爬取

一、多进程基础

1.1 多进程程序

关于多进程相关的基础知识,已经在另一篇文章中有过详细描述,此处不再赘述。有需要的可以参考:
Python并发编程之multiprocessing模块中的进程

1.2 进程池

关于进程池相关的基础知识,可以参考另一篇文章, 此处不再赘述:
Python并发编程之进程池

二、在网络爬虫中使用多进程

2.1 需求背景

有一个图片网站,我已经可以成功提取到每一篇文章的url(即下文中的content_url), 然后针对每一篇文章(即每一个content_url), 我无法通过requests获取到完整的html代码,里面图片的url是动态加载的。然后我使用了selenium webdriver来访问每一篇文章(每一个content_url), 获取文章内所有图片的url, 然后进行下载。

说简单点就是,每一篇文章的处理,都是阻塞的(selenium加载网页+数据提取+图片下载)。这一篇文章处理完,才能处理下一篇文章。

2.2 从单进程版本入手

由于使用了Selenium webdriver,它的初始化以及相应的页面加载代码执行阶段所耗费的时间,再加上多个图片下载的时间,总的耗时其实是非常可观的。

单进程版本的爬虫程序,在遍历每一篇文章时,都需要执行Selenium webdriver的初始化->等待加载页面->数据提取->若干图片下载,也就是说,处理每一篇文章时,都会有一段可观的阻塞过程,而这个时间内,我们却不能做其他的事,只能干等。

代码片段如下:

# ContentWorker类负责处理每一篇文章
class ContentWorker():
    def __init__(self, dir, content) -> None:
        self.dir = dir
        self.title = content['title']
        self.num = content['num']
        self.content_url = content['url']
        self.count = 0
    
    def run(self):
        content_parser = ContentPageParser(self.content_url)
        # 这里通过Selenium webdriver获取本篇文章内所有图片的url
        content_parser.visit_content_page_with_firefox()
        img_url_list = content_parser.get_img_src()
        folder_name = self.title + '_' + self.num
        path = os.path.join(self.dir, folder_name)
        if os.path.isdir(path):
            pass
        else:
            os.makedirs(path)
        
        # 这里通过多线程下载所有图片
        pic_download_threads = []
        for img_url in img_url_list:
            self.count += 1
            file_name = 'img_' + str(self.count)
            pic_download_threads.append(PicDownloader(str(self.count), img_url, file_name, path))

        for working_thread in pic_download_threads:
            working_thread.start()
        
        for working_thread in pic_download_threads:
            working_thread.join(3)


class Worker():
    def __init__(self, dir) -> None:
        self.dir = dir
    
    def run(self):
        home_parser = HomePageParser()
        home_parser.parse_home_page()
        all_content = home_parser.get_all_content() # 这里是获取到的每一篇文章的url

        # 遍历每一篇文章
        for content in all_content:
            content_worker = ContentWorker(self.dir, content)
            # 这个run函数里会调用Selenium webdriver来抓取所有图片的url,并使用多线程下载
            content_worker.run()

2.3 将单进程版本改为多进程版本

那么如何将其改进为多进程版本呢?

首先我们定义一个函数content_worker_func(), 实现业务逻辑, 即原先ContentWorker类的run()所实现的业务逻辑。

def content_worker_func(dir, content):
    title = content['title']
    num = content['num']
    content_url = content['url']
    count = 0
    
    content_parser = ContentPageParser(content_url)
    content_parser.visit_content_page_with_firefox()
    img_url_list = content_parser.get_img_src()
    folder_name = title + '_' + num
    path = os.path.join(dir, folder_name)
    if os.path.isdir(path):
        pass
    else:
        os.makedirs(path)
    
    pic_download_threads = []
    for img_url in img_url_list:
        count += 1
        file_name = 'img_' + str(count)
        pic_download_threads.append(PicDownloader(str(count), img_url, file_name, path))

    for working_thread in pic_download_threads:
        working_thread.start()
    
    for working_thread in pic_download_threads:
        working_thread.join(3)

然后,我们需要定义一个进程池。这需要用到multiprocessing.Pool,

multiprocessing.Pool([numprocess [, initializer [, initargs]]])

numprocess是要创建的进程数。如果不指定,将使用cpu_count()的值。本例中没有指定,那么默认有几个cpu的core,就会创建几个进程。所以该程序在多核cpu上效果会比较明显。

然后调用进程池的apply_async方法,这将会在一个池工作进程中异步地执行content_worker_func(*args, **kwargs), 然后返回结果。

示例代码如下:

work_pool = multiprocessing.Pool()
for content in all_content:
    work_pool.apply_async(content_worker_func, args=(self.dir, content))

2.4 将多进程应用到爬虫程序中

假设进程池的实例p

  • p.close()
    关闭进程池,防止进行进一步操作。如果所有操作持续挂起,它们将在工作进程终止之前完成。
  • p.join()
    等待所有工作进程退出。此方法只能在close()或terminate()方法之后调用。

示例代码:

class Worker():
    def __init__(self, dir) -> None:
        self.dir = dir
    
    def start_work(self):
        home_parser = HomePageParser()
        home_parser.parse_home_page()
        all_content = home_parser.get_all_content()
        
        # 通过进程池实现并发,异步地处理每一篇文章,并下载其中的图片
        work_pool = multiprocessing.Pool()
        for content in all_content:
            work_pool.apply_async(content_worker_func, args=(self.dir, content))
        
        work_pool.close()
        work_pool.join()

if __name__ == '__main__':
    worker = Worker('d:test/mp1')
    worker.start_work()

此时,多个进程可以并发地异步执行,每个进程内部可以启动自己的Selenium webdriver,并执行多线程图片下载。进程之间互不干扰。通过进程池和多线程的结合,总的图片爬取速度会得到极大飞跃。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

smart_cat

你的鼓励将是我写作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值