相关爬虫实战案例:[爬虫实战] 多进程/多线程/协程-异步爬取豆瓣Top250

相关爬虫专栏:JS逆向爬虫实战  爬虫知识点合集  爬虫实战案例  逆向知识点合集


前言:

面对海量的目标数据,传统单线程、同步的爬取方式往往效率低下,不易采集大量数据。为了突破性能瓶颈,我们需要引入异步思想,利用多进程多线程协程这三大并发利器,将单车道拓宽成多车道,从而大幅提升爬虫的效率。

一、为什么需要异步?同步的痛点

想象一下,你用爬虫去访问一个网站,这个过程就像打电话。同步爬虫意味着你打一个电话,必须等到对方接通、说完了话、挂断电话后,你才能去打下一个电话。在这个等待过程中,你的程序(CPU)大部分时间都在空闲等待,等待网络响应(I/O 等待),而不是在处理数据。这就是I/O 密集型任务的痛点:计算资源没有得到充分利用。

异步逻辑的核心,就是让程序在等待一个任务(比如网络请求)完成的同时,能够去执行其他任务,从而提高资源的利用率,缩短总体的完成时间。

下面我们用一段测试代码来感受一下:

import time
import requestsdef fetch_sync(url):"""模拟同步网络请求"""print(f"开始同步请求: {url}")try:response = requests.get(url, timeout=5)time.sleep(1) # 模拟处理时间print(f"完成同步请求: {url}, 状态码: {response.status_code}")return len(response.text)except requests.exceptions.RequestException as e:print(f"请求失败: {url}, 错误: {e}")return 0if __name__ == "__main__":urls = ["http://www.baidu.com","http://www.qq.com","http://www.sina.com.cn"]start_time = time.time()for url in urls:fetch_sync(url)end_time = time.time()print(f"\n同步爬取总耗时: {end_time - start_time:.2f} 秒")

上述代码会逐个发起请求,一个请求未完成,下一个请求不会开始,导致总耗时是所有请求耗时之和。

二、多进程:分身术,真正的并行

工作原理

多进程是操作系统层面真正的并行。每个进程都有自己独立的内存空间,互不干扰,就像是你的多个“分身”,每个分身都在独立的电脑上工作。Python 的全局解释器锁(GIL) 限制了单个 Python 进程在任何给定时间只能执行一条 Python 字节码。但多进程能绕过 GIL,因为每个进程都有自己的 Python 解释器和 GIL,因此它们可以同时利用多核 CPU。

适用场景

  • CPU 密集型任务: 例如大数据处理、复杂计算、图像识别等,这些任务需要大量的 CPU 计算。

  • 数据独立、互不干扰的爬取任务: 当你需要爬取不同网站,或者网站的不同部分,且这些任务之间没有共享状态或复杂依赖时,多进程是理想选择。比如,同时爬取豆瓣电影 Top 250 的不同页,每页数据之间独立。

  • 规避网站反爬: 通过多进程配合代理 IP,可以分散请求来源,降低被封禁的风险。

优缺点

  • 优点: 真正的并行,能充分利用多核 CPU;隔离性强,一个进程崩溃通常不会影响其他进程;能规避 GIL 限制。

  • 缺点: 资源开销大,创建和管理进程的开销较大;进程间通信(如数据共享)相对复杂;不适合大量并发。

多进程爬取示例:

import time
import requests
import multiprocessing # 导入 multiprocessing 模块def fetch_multiprocess(url):"""模拟多进程网络请求"""print(f"进程 {multiprocessing.current_process().pid} 开始请求: {url}")try:response = requests.get(url, timeout=5)time.sleep(1) # 模拟处理时间print(f"进程 {multiprocessing.current_process().pid} 完成请求: {url}, 状态码: {response.status_code}")return len(response.text)except requests.exceptions.RequestException as e:print(f"进程 {multiprocessing.current_process().pid} 请求失败: {url}, 错误: {e}")return 0if __name__ == "__main__":urls = ["http://www.baidu.com","http://www.qq.com","http://www.sina.com.cn","http://www.google.com", # 注意:国内可能无法访问"http://www.douban.com"]start_time = time.time()# 创建一个进程池,通常进程数等于CPU核心数with multiprocessing.Pool(processes=3) as pool: # map函数将urls列表中的每个元素作为参数传递给fetch_multiprocess函数# 并行执行,等待所有结果返回results = pool.map(fetch_multiprocess, urls)end_time = time.time()print(f"\n多进程爬取总耗时: {end_time - start_time:.2f} 秒")print(f"获取到的数据长度列表: {results}")

 Pool 会启动多个进程,同时处理 urls 列表中的任务。你会看到不同进程ID同时打印“开始请求”和“完成请求”,总耗时会显著低于同步版本。

三、多线程:微操,I/O 并发的好手

工作原理

多线程是在同一个进程内创建多个执行流。它们共享进程的内存空间,但每个线程有自己的独立栈。在 Python 中,由于 GIL 的存在,多线程并不能实现真正的并行(即同时在多个 CPU 核心上运行 Python 代码)。GIL 确保在任何时候只有一个线程执行 Python 字节码。然而,当一个线程执行 I/O 操作(如网络请求、文件读写)时,GIL 会被释放,允许其他线程运行。因此,多线程非常适合 I/O 密集型任务。

上述内容可能难以理解,这里我们重点讲一下相关概念:

想象一下,Python解释器是一个只有一个麦克风的KTV包间

  • 麦克风 就是 全局解释器锁(GIL)

  • 唱歌的人 就是 线程

现在,即使包间里坐了很多人(多个线程),但因为只有一个麦克风(GIL),所以在任何一个时刻,只有一个人能拿起麦克风唱歌(执行Python代码)。其他人要想唱,就必须等前面那个人唱完一句、放下麦克风后,才能去抢。

这会带来什么问题?

  • 当任务是“动脑子”的(CPU密集型):比如需要连续不断地唱歌(进行大量计算)。就算你请再多人来,麦克风也只有一个,大家只能轮流唱,速度快不起来。这就是为什么在多核CPU上,Python的多线程对计算任务提速不明显。也就是我们常调侃的:一核有难,多核围观。

  • 当任务是“等人”的(I/O密集型):比如一个人点了一首需要加载很久的MV(等待网络数据或读写文件)。在等待MV加载的时候,他会很自觉地把麦克风放下(释放GIL),这时其他人就可以拿起麦克风唱自己的歌。所以,在这种“等待”任务多的情况下,多线程还是能提高效率的。

所以我们总结一下:

GIL就是Python里的一个“独占麦克风”规则,它限制了同一时间只能有一个线程执行代码,这使得Python的多线程无法利用多核CPU来并行计算。要想真正并行,通常需要使用多进程。

适用场景

  • I/O 密集型任务: 爬虫就是典型的 I/O 密集型任务。在等待网络响应时,CPU 可以切换到其他线程去发起新的请求,或处理已返回的数据。

  • 任务间存在共享数据或资源: 线程共享内存,数据传递相对方便,但需要注意线程安全问题(例如,对共享数据进行加锁)。

优缺点

  • 优点: 资源开销小,创建和管理比进程轻量;数据共享相对方便;在等待 I/O 时能有效利用 CPU 时间,提高 I/O 并发效率。

  • 缺点: 受 GIL 限制,无法真正利用多核 CPU 并行计算;共享数据需加锁以避免竞态条件;稳定性相对较差,一个线程崩溃可能影响整个进程。

多线程爬取示例:

import time
import requests
import threading # 导入 threading 模块
from concurrent.futures import ThreadPoolExecutor # 更推荐使用线程池def fetch_multithread(url):"""模拟多线程网络请求"""print(f"线程 {threading.current_thread().name} 开始请求: {url}")try:response = requests.get(url, timeout=5)time.sleep(1) # 模拟处理时间print(f"线程 {threading.current_thread().name} 完成请求: {url}, 状态码: {response.status_code}")return len(response.text)except requests.exceptions.RequestException as e:print(f"线程 {threading.current_thread().name} 请求失败: {url}, 错误: {e}")return 0if __name__ == "__main__":urls = ["http://www.baidu.com","http://www.qq.com","http://www.sina.com.cn","http://www.google.com","http://www.douban.com"]start_time = time.time()# 创建一个线程池,max_workers定义最大同时运行的线程数with ThreadPoolExecutor(max_workers=5) as executor: # submit方法提交任务,返回Future对象# as_completed按任务完成的顺序返回Futurefutures = [executor.submit(fetch_multithread, url) for url in urls]results = [f.result() for f in futures] # 获取所有结果end_time = time.time()print(f"\n多线程爬取总耗时: {end_time - start_time:.2f} 秒")print(f"获取到的数据长度列表: {results}")

ThreadPoolExecutor 会管理线程的创建和复用,同时发起多个网络请求。虽然仍受 GIL 影响,但在 I/O 等待时 GIL 会释放,允许其他线程执行,因此对于网络爬虫这种 I/O 密集型任务,效率提升依然显著。

四、协程:轻量级调度,I/O 异步的极致

工作原理

协程(Coroutines)是一种用户态的轻量级线程,它不受 GIL 限制。协程的切换是由程序自身控制的,而非操作系统。当一个协程遇到 I/O 操作(如网络请求)时,它会主动让出 CPU 控制权,允许另一个协程运行,直到 I/O 操作完成。这个过程是非阻塞的,而且上下文切换的开销极小。Python 3.5+ 引入的 async/await 语法让协程的使用更加方便。

适用场景

  • 高并发 I/O 密集型任务: 爬虫、网络服务器等。当需要同时处理成千上万个网络请求时,协程的效率远超多线程。

  • 对响应时间敏感的任务: 协程的低开销切换能更快地响应 I/O 事件。

优缺点

  • 优点: 极高的并发能力和极低的开销;避免 GIL 限制,实现高效 I/O 并发;编码通过 async/await 语法更接近同步逻辑。

  • 缺点: 存在异步传染性,相关代码可能都需要是 async/await 风格;依赖支持异步的库(如 aiohttp);调试相对复杂。

协程爬取示例:

import time
import asyncio # 导入 asyncio 模块
import aiohttp # 导入异步HTTP客户端库,需要 pip install aiohttpasync def fetch_coroutine(url, session):"""模拟协程网络请求"""print(f"协程开始请求: {url}")try:async with session.get(url, timeout=5) as response: # 注意这里是 async withtext = await response.text() # await 等待I/O完成# await asyncio.sleep(1) # 模拟处理时间print(f"协程完成请求: {url}, 状态码: {response.status}")return len(text)except aiohttp.ClientError as e:print(f"协程请求失败: {url}, 错误: {e}")return 0async def main_coroutine():urls = ["http://www.baidu.com","http://www.qq.com","http://www.sina.com.cn","http://www.google.com","http://www.douban.com"]start_time = time.time()async with aiohttp.ClientSession() as session: # 创建一个异步会话tasks = []for url in urls:task = asyncio.create_task(fetch_coroutine(url, session)) # 创建并调度协程任务tasks.append(task)results = await asyncio.gather(*tasks) # 等待所有协程任务完成end_time = time.time()print(f"\n协程爬取总耗时: {end_time - start_time:.2f} 秒")print(f"获取到的数据长度列表: {results}")if __name__ == "__main__":asyncio.run(main_coroutine()) # 运行主协程

协程通过 async/await 语法,在 session.get()response.text() 等 I/O 操作时主动让出控制权,允许其他协程运行。asyncio.gather 会同时运行所有任务,并等待它们全部完成。这是最高效的 I/O 并发方式,尤其适合处理成千上万个并发请求。


总结与选择

特性/方案

多进程 (multiprocessing.Pool)

多线程 (ThreadPoolExecutor)

协程 (asyncio/aiohttp)

并行/并发

真正的并行 (CPU & I/O)

并发 (仅I/O,受GIL影响)

并发 (仅I/O,不受GIL影响)

GIL 影响

不受影响

限制并行

间接规避 (I/O 让出)

资源开销

中等

适用场景

CPU密集型,独立爬取任务

I/O密集型(网络请求),小规模并发

I/O密集型,高并发请求

编码复杂度

中等

中等 (需锁)

较高 (异步语法)

数据共享

复杂 (队列/管道)

需加锁 (共享内存)

简单 (单线程内)

在选择时:

  • 对于大多数爬虫任务(I/O 密集型)且追求极致效率: 协程是最高效、最推荐的方案,尤其在需要处理大量并发请求时。

  • 如果对异步编程不熟悉,且爬虫任务是 I/O 密集型: 多线程仍然是一个非常好的入门选择,它能有效提升效率。

  • 如果爬虫中包含大量数据解析、图片处理等 CPU 密集型任务,或者需要规避某些反爬机制: 多进程是更好的选择,它可以真正利用多核 CPU 资源。

在实际应用中,你也可以结合使用这些技术,例如:多进程 + 协程,即每个进程内再运行异步协程,以达到 CPU 并行和 I/O 并发的双重加速效果。理解它们的原理和适用场景,才能为你的爬虫选择最合适的武器,让数据获取变得更快、更高效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/91019.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/91019.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/91019.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle RAC 11.2.0.4 更新SYSASM和SYS密码

前言 从技术角度看,SYSASM是Oracle 10g R2引入的ASM管理员角色,而SYS是数据库实例的超级用户,SYS账户无法管理ASM磁盘组。SYSASM权限是集群级别的,比如在添加磁盘组时,这个操作会影响所有节点;而SYS用户的权…

Vue》》总结

官网 vue路由的query参数、mixin 混入 vue cli 脚手架之配置代理 VUE SAP、 MPA,,组件开发、VDOM、双向数据绑定 Vue Props 、Mixin 、路由守卫 vue router query参数 Vue props以及其他通信方式, vue响应式 原理 追加响应式数据,数组响应式 …

Nginx 中的负载均衡策略

Nginx 是一个高性能的 HTTP 和反向代理服务器,广泛用于负载均衡场景。它支持多种负载均衡策略,可以帮助你优化资源利用、提高响应速度和增加系统的可用性。以下是 Nginx 中几种常见的负载均衡策略及其配置方法: 1. 轮询(Round Rob…

用 Python 将分组文本转为 Excel:以四级词汇为例的实战解析

一、背景引入:从“人工整理”到“自动化处理”的转变 在英语学习过程中,我们经常会接触各种分组整理的词汇表,比如“Group1”对应一组单词及释义,随后是“Group2”、“Group3”等等。如果你下载了一个 .txt 格式的四级词汇表&…

Ffmpeg滤镜

打开设备 添加滤镜 循环录制文件 #include "libavdevice/avdevice.h" #include "libavformat/avformat.h" #include "libavcodec/avcodec.h" #include "libavfilter/avfilter.h" #include "libavfilter/buffersink.h" #incl…

HarmonyOS AI辅助编程工具(CodeGenie)UI生成

UI Generator基于BitFun Platform AI能力平台,用于快速生成可编译、可运行的HarmonyOS UI工程,支持基于已有UI布局文件(XML),快速生成对应的HarmonyOS UI代码,其中包含HarmonyOS基础工程、页面布局、组件及…

【第三节】ubuntu server配置远程连接

首先在ubuntu server中查看ip,打开虚拟机,输入ip addr show ,这个命令很好记,几乎就是英文自然语言 下面我们准备远程连接工具,我选择的开源的ET,全称是electerm,圈起来的是必须输入的内容,输入完成后点击保…

CCS-MSPM0G3507-7-模块篇-MPU6050的基本使用

前言本篇我们接收对MPU6050的基本使用,获取ID,通过IIC协议获取寄存器的值,至于高级滤波算法,比如卡尔曼滤波,或者上面的,后面再更新基本配置最好选择PA0和PA1,5V开漏然后给上代码MPU6050.c#incl…

spring-ai agent概念

目录agent 概念理解记忆能力工具计划agent 概念理解 agent 智能体,突出智能 大模型的感觉 告诉你怎么做(也不一定正确)不会帮你做 Agent的感觉 直接准确的帮你做完(比如,告诉 AI Agent 帮忙下单一份外卖&#xff0c…

NO.4数据结构数组和矩阵|一维数组|二维数组|对称矩阵|三角矩阵|三对角矩阵|稀疏矩阵

数组的储存 【定义】 数组: 由 n(≥1) 个相同类型的数据元素构成的有限序列, 是线性表的推广。 一旦被定义, 维数和长度就不可再改变, 一般对其只有元素的存取和修改操作。 一维数组 Arr[a0,…,an−1] Arr[…

如何把Arduino IDE中ESP32程序bin文件通过乐鑫flsah_download_tool工具软件下载到ESP32中

目录前言获取Arduino IDE中ESP32程序bin文件flsah_download_tool工具软件下载程序bin文件到ESP32中总结前言 Arduino IDE丰富的驱动库给ESP32的开发带来了很多便利,当我们下载程序的时候,直选选择好ESP32开发板型号和端口号即可下载程序到开发板中&…

2025XYD Summer Camp 7.11 模考

T1TTT 组询问,每组询问给定 n,mn,mn,m,求 (nm)−1⋅∑i1n∑j1mlcm⁡(i,j) (nm)^{-1}\cdot\sum_{i1}^n\sum_{j1}^m\operatorname{lcm}(i,j) (nm)−1⋅i1∑n​j1∑m​lcm(i,j) 对 109710^971097 取模。 T≤20000T\le 20000T≤20000,n,m≤107n,m…

uniapp 微信小程序点击开始倒计时

一、示例 当点击按钮时就开始倒计时代码 <template><view class"sq_box"><button class"button" click"topay">按钮</button><u-modal v-model"modalShow" :show-cancel-button"true" :content&…

【网络】Linux 内核优化实战 - net.netfilter.nf_conntrack_tcp_timeout_established

目录一、核心概念1. **TCP 连接状态跟踪**2. **参数作用**二、默认值与典型场景1. **默认值**2. **典型场景**三、如何调整该参数1. **查看当前值**2. **临时修改&#xff08;重启后失效&#xff09;**3. **永久修改**四、相关参数与配合优化1. **其他 TCP 状态超时参数**2. **…

鸿蒙app 开发中的Record<string,string>的用法和含义

Record<string, string> 在鸿蒙 App 开发中的用法在 TypeScript 中&#xff0c;Record<string, string> 是一个映射类型&#xff08;Mapped Type&#xff09;&#xff0c;用于描述一个对象的结构。在鸿蒙 App 开发中&#xff0c;它常用于定义接口、组件属性或函数参…

Webpack、Vite配置技巧与CI/CD流程搭建全解析

Webpack、Vite配置技巧与CI/CD流程搭建全解析 在现代前端开发中&#xff0c;构建工具配置和自动化部署流程是提升开发效率和项目质量的关键环节。本文将深入探讨Webpack和Vite这两大构建工具的核心配置技巧&#xff0c;并详细介绍CI/CD流程的搭建方法。 一、Webpack核心配置技巧…

输入npm install后发生了什么

一、准备阶段&#xff1a;配置与环境检查读取配置优先级npm install 首先加载多层级的配置&#xff08;优先级从高到低&#xff09;4&#xff1a;项目级 .npmrc用户级 .npmrc&#xff08;如 ~/.npmrc&#xff09;全局 npmrcnpm 内置默认配置可通过 npm config ls -l 查看所有配…

SpringBoot集成Redis、SpringCache

1 Redis介绍 1.1 Redis作为缓存 由于Redis的存取效率非常高,在开发实践中,通常会将一些数据从关系型数据库(例如MySQL)中读取出来,并写入到Redis中,后续当需要访问相关数据时,将优先从Redis中读取所需的数据,以此,可以提高数据的读取效率,并且对一定程度的保护关系型…

静态路由综合配置实验报告

一、实验拓扑二、实验需求1.除了R5的环回地址固定5.5.5.0/24&#xff0c;其他网段基于192.168.1.0/24进行合理划分&#xff1b;2.R1-R4每个路由器存在两个环回接口&#xff0c;模拟PC&#xff0c;地址也在192.168.1.0/24网络内&#xff1b;3.R1-R4不能直接编写到达5.5.5.0/24的…