当前位置: 首页 > news >正文

学习爬虫第四天:多任务爬虫

多任务爬虫

  • 进程 (`multiprocessing`)
  • 线程(`threading`)
  • 协程 (`asyncio`)
  • 案例:获取LoL所有皮肤图片(https://101.qq.com/#/hero)


进程 (multiprocessing)

作用

  • 并行执行任务
    • 每个进程有独立的 Python 解释器和内存空间
    • 可以同时利用多核 CPU,真正做到并行
    • 特别适合 CPU 密集型任务(大计算量、数据处理、图像处理等)
  • 绕过 GIL 限制
    • Python 的多线程受 GIL(全局解释器锁) 限制,CPU 密集型任务不能同时执行
    • 多进程每个进程独立运行,不受 GIL 限制
  • 进程间通信(IPC)
    • 提供 QueuePipe、共享内存 ValueArray 等方式,方便进程之间传递数据
  • 创建进程
def index1(num):  print(f'index1:{num}')
# 创建子进程  
# 方法一
t2=Process(target=index1,args=(1,))  
# 方法二
t3=Process(target=index1,kwargs={'num':2})
  • 启动进程
t2.start()  
  • 等待进程(执行完t2才执行后面代码)
t2.join()

守护进程就是一种 在后台运行、随主进程存在的子进程
它有两个特点:

  1. 独立后台运行:通常用来执行辅助任务,不需要用户交互。
  2. 随主进程退出而结束:当主进程结束时,所有守护进程会被自动终止。
  • 设置为守护进程
t2.daemon=True

进程队列工作机制
- 当你往 JoinableQueueput(item) 一个任务时,内部会将 “未完成的任务计数(unfinished tasks count)” 加一。
- 消费者(worker)进程/线程从队列 get() 任务,做处理。处理完成后,调用 queue.task_done(),这样未完成任务计数减一。
- 如果有代码调用 queue.join(),那么这个调用会阻塞,直到 未完成的任务计数降到 0 为止,也就是所有放入队列的任务都被取出并且调用过 task_done()。然后 join() 会解除阻塞。
- 如果调用 task_done() 的次数比 put() 的次数多,就会抛出 ValueError,因为这表示多次标记完成,和任务实际不符。

  • 创建进程队列
from multiprocessing import JoinableQueue
q = JoinableQueue()
  • 进程队列等待
q.join()
  • 进程队列继续执行
q.task_done()

线程(threading

作用:能让你的程序同时执行多个任务

  • 创建线程
from threading import Thread
def index(num):  print(f'index:{num}')  
t1=Thread(target=index,args=(1,))  
  • 启动线程
t1.start()

守护线程就是一种后台运行的线程,当主线程结束时,它会自动退出,不会阻止程序结束。

  • 设置为守护线程
t1.daemon=True
  • 多线程队列
import queueq=queue.Queue()

线程和进程使用方法是差不多一样的

协程 (asyncio)

协程(Coroutine)是一种 用户态的轻量级“线程”,它可以在一个线程内执行多个任务,通过主动让出控制权来实现并发,而不是依赖操作系统调度线程。
协程作用

  1. 减少线程开销:不需要频繁创建/切换线程。
  2. 提高 I/O 并发:在网络爬虫、爬取网页、下载文件等场景特别有效。
  3. 简化异步编程:比传统回调方式更直观。
import asyncio  async def task(n):  print(f"开始任务 {n}")  await asyncio.sleep(1)  # 模拟 I/O 操作  print(f"结束任务 {n}")  return n*2  async def main():  # 并发执行多个协程  results = await asyncio.gather(task(1), task(2), task(3))  print(results)  asyncio.run(main())
  • async def 定义协程函数。
  • await 用于等待耗时操作,不阻塞其他协程。
  • asyncio.gather 可以并发执行多个协程。

进程、线程与协程的区别

进程(Process)线程(Thread)协程(Coroutine)
资源拥有拥有独立地址空间和系统资源共享进程的内存空间和资源运行在单个线程内,共享线程资源
从属关系独立存在必须属于某个进程必须运行在某个线程之上
调度单位操作系统资源分配的基本单位操作系统调度的基本单位程序员在用户态控制的调度单位
切换方式由操作系统内核完成,上下文切换成本高由操作系统调度,上下文切换成本中等由用户代码控制(如 await / yield),切换成本最低
切换开销大(需要切换内存地址空间、页表、寄存器等)中(只切换寄存器、栈等线程上下文)小(不涉及系统调用,只在用户态切换栈)
通信方式使用 IPC(如管道、信号、共享内存、消息队列)通过共享内存通信(需加锁同步)共享内存、协作式执行,一般不需要锁
并发与并行可实现并行(多个进程可在多核上同时运行)可实现并行(多个线程可在多核上运行)通常为并发(单线程异步切换),非真正并行
创建与销毁创建/销毁开销最大创建/销毁比进程小创建极快(纯用户态),几乎无开销
异常影响一个进程崩溃不会影响其他进程一个线程崩溃可能导致整个进程崩溃单个协程出错不会影响其他协程(除非未捕获异常)
可靠性高,进程隔离好中,共享资源导致风险高,共享少且协作式执行
实现层级内核态内核态用户态
适用场景多进程架构、服务隔离、安全要求高多任务并行、I/O 密集型任务异步 I/O、高并发(如网络爬虫、异步服务器等)
代表技术multiprocessing、系统守护进程threadingJava Threadasynciogeventgo routine

进程池和线程池和协程池的区别(了解)

项目进程池线程池协程池
运行层级操作系统(多进程)操作系统(单进程多线程)用户态(单线程多协程)
调度者操作系统内核操作系统内核程序员/事件循环
并发模式并行(多核可同时执行)并发(多线程共享内存)并发(单线程异步切换)
适用任务CPU 密集型I/O 密集型高并发 I/O、异步任务
代表模块(Python)multiprocessing.PoolThreadPoolExecutorasyncio.Semaphore / aiomultiprocess
资源消耗极低
管理复杂度稍高(需事件循环)

案例:获取LoL所有皮肤图片(https://101.qq.com/#/hero)

通过分析发现英雄数据都在这个请求里
请添加图片描述

点击英雄,然后点“皮肤详情”
请添加图片描述

通过抓包发现皮肤图片请求
请添加图片描述

模拟上述请求,获取所有英雄皮肤代码

import json  
import os  
from textwrap import indent  import chardet  
from requests_html import HTMLSession  session=HTMLSession()  
hero_list_url='https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js'  
headers={  'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0',  }  hero_list_response=session.get(url=hero_list_url,headers=headers)  if hero_list_response.status_code==200:  print("获取英雄列表成功\n")  # # 自动检测编码  # detected=chardet.detect(hero_list_response.content)  # encoding=detected['encoding']    # print(encoding)  # 解码  hero_list_content = hero_list_response.content.decode()  hero_list_content = json.loads(hero_list_content)  # print(json.dumps(hero_list_content,ensure_ascii=False,indent=4))  if not os.path.exists('LOL皮肤'):  os.mkdir('LOL皮肤')  for hero in hero_list_content['hero']:  print(f'英雄名字:{hero["name"]}_{hero["title"]}_{hero["heroId"]}')  # 获取英雄皮肤图片  hero_skin_url = f'https://game.gtimg.cn/images/lol/act/img/js/hero/{hero["heroId"]}.js'  hero_skin_response = session.get(url=hero_skin_url)  if hero_skin_response.status_code==200:  hero_skin_content = hero_skin_response.content.decode()  hero_skin_content = json.loads(hero_skin_content)  # print(f"英雄皮肤数据:{json.dumps(hero_skin_content,ensure_ascii=False,indent=4)}")  if not os.path.exists(f'LOL皮肤/{hero["name"]}'):  os.mkdir(f'LOL皮肤/{hero["name"]}')  if not os.path.exists(f'LOL皮肤/{hero["name"]}/phone'):  os.mkdir(f'LOL皮肤/{hero["name"]}/phone')  if not os.path.exists(f'LOL皮肤/{hero["name"]}/pc'):  os.mkdir(f'LOL皮肤/{hero["name"]}/pc')  for hero_skin in hero_skin_content['skins']:  # 皮肤名字  name = hero_skin['name'].replace('/',' ')  # 手机端皮肤url  phone_url = hero_skin['loadingImg']  # 电脑端皮肤url  pc_url = hero_skin['centerImg']  # 过滤数据中缺少数据的,防止干扰数据  if phone_url!='' and pc_url!='' :  # 下载图片  phone_response = session.get(url=phone_url)  with open(f'LOL皮肤/{hero["name"]}/phone/{name}.png', 'wb') as f:  f.write(phone_response.content)  pc_response = session.get(url=pc_url)  with open(f'LOL皮肤/{hero["name"]}/pc/{name}.png', 'wb') as f:  f.write(pc_response.content)  if phone_response.status_code==200 and pc_response.status_code==200:  print(f'下载{hero["name"]} 的 {name} 皮肤成功')  else:  print(f'下载{hero["name"]} 的 {name} 皮肤失败 phone: {phone_response.status_code}  pc:{pc_response.status_code}')  print(f'下载{hero["name"]} 皮肤完毕\n')  else:  print(f'获取 {hero["name"]} 英雄皮肤失败')  print("\n下载完毕")  
else:  print("获取英雄列表失败",hero_list_response.status_code)

请添加图片描述

使用进程更改代码(加快执行时间)

import json  
import os  
import time  
from multiprocessing import Process  
from textwrap import indent  import chardet  
from requests_html import HTMLSession  def task(session,hero):  # 获取英雄皮肤图片  hero_skin_url = f'https://game.gtimg.cn/images/lol/act/img/js/hero/{hero["heroId"]}.js'  hero_skin_response = session.get(url=hero_skin_url)  if hero_skin_response.status_code == 200:  hero_skin_content = hero_skin_response.content.decode()  hero_skin_content = json.loads(hero_skin_content)  # print(f"英雄皮肤数据:{json.dumps(hero_skin_content,ensure_ascii=False,indent=4)}")  if not os.path.exists(f'LOL皮肤/{hero["name"]}'):  os.mkdir(f'LOL皮肤/{hero["name"]}')  if not os.path.exists(f'LOL皮肤/{hero["name"]}/phone'):  os.mkdir(f'LOL皮肤/{hero["name"]}/phone')  if not os.path.exists(f'LOL皮肤/{hero["name"]}/pc'):  os.mkdir(f'LOL皮肤/{hero["name"]}/pc')  for hero_skin in hero_skin_content['skins']:  # 皮肤名字  name = hero_skin['name'].replace('/', ' ')  # 手机端皮肤url  phone_url = hero_skin['loadingImg']  # 电脑端皮肤url  pc_url = hero_skin['centerImg']  # 过滤数据中缺少数据的,防止干扰数据  if phone_url != '' and pc_url != '':  # 下载图片  phone_response = session.get(url=phone_url)  with open(f'LOL皮肤/{hero["name"]}/phone/{name}.png', 'wb') as f:  f.write(phone_response.content)  pc_response = session.get(url=pc_url)  with open(f'LOL皮肤/{hero["name"]}/pc/{name}.png', 'wb') as f:  f.write(pc_response.content)  if phone_response.status_code == 200 and pc_response.status_code == 200:  print(f'下载{hero["name"]} 的 {name} 皮肤成功')  else:  print(  f'下载{hero["name"]} 的 {name} 皮肤失败 phone: {phone_response.status_code}  pc:{pc_response.status_code}')  print(f'下载{hero["name"]} 皮肤完毕\n')  else:  print(f'获取 {hero["name"]} 英雄皮肤失败')  if __name__=='__main__':  session = HTMLSession()  hero_list_url = 'https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js'  headers = {  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0',  }  hero_list_response = session.get(url=hero_list_url, headers=headers)  if hero_list_response.status_code == 200:  print("获取英雄列表成功\n")  # # 自动检测编码  # detected=chardet.detect(hero_list_response.content)  # encoding=detected['encoding']        # print(encoding)  # 解码  hero_list_content = hero_list_response.content.decode()  hero_list_content = json.loads(hero_list_content)  # print(json.dumps(hero_list_content,ensure_ascii=False,indent=4))  if not os.path.exists('LOL皮肤'):  os.mkdir('LOL皮肤')  for hero in hero_list_content['hero']:  print(f'英雄名字:{hero["name"]}_{hero["title"]}_{hero["heroId"]}')  t1=Process(target=task,args=(session,hero))  t1.start()  end_time=time.time()  print("\n下载完毕")  else:  print("获取英雄列表失败", hero_list_response.status_code)

直接速度提升一大截
请添加图片描述


在这里插入图片描述

如果你在阅读过程中也有新的见解,或者遇到类似问题,🥰不妨留言分享你的经验,让大家一起学习。

喜欢本篇内容的朋友,记得点个 👍点赞,收藏 并 关注我,这样你就不会错过后续的更多实用技巧和深度干货了!

期待在评论区看到你的声音,我们一起成长、共同进步!😊

http://www.dtcms.com/a/465219.html

相关文章:

  • 专注大连网站建设长沙网站设计咨询电话
  • 网站备案编号查询做网站的语言版本
  • 预训练基础模型简介
  • 【笔记】WPF中如何的动态设置DataGridTextColumn是否显示
  • 告别手动复制,API助您完成电商数据获取数据分析店铺搬家
  • 软件工程的核心原理与实践
  • LeetCode 394. 字符串解码(Decode String)
  • Spring Bean耗时分析工具
  • 济南可信网站网站开发命名规范
  • 应用案例丨3D工业相机如何实现「焊接全工序守护」
  • 网站接广告网站可以叫做系统吗
  • 应用层协议之Telnet协议
  • 科技赋能成长,小康AI家庭医生守护童真
  • 芯谷科技--D7005高效高压降压型DC-DC转换器
  • 玻尿酸:从天然分子到科技美学的全面解析
  • # 3.1.8<3.2.0<3.3.1,Apache DolphinScheduler集群升级避坑指南
  • 微算法科技(NASDAQ:MLGO)基于任务迁移的弹性框架重塑动态扩缩容,赋能边缘智能计算
  • 卡盟网站怎么做图片wordpress换网址插件
  • 【汽车篇】基于深度学习的门盖自动装配系统:汽车制造装配的革新力量
  • 乐迪信息:基于AI算法的煤矿作业人员安全规范智能监测与预警系统
  • 英文电商网站建设泛微oa办公系统教程
  • Windows环境搭建:PostGreSQL+PostGIS安装教程
  • SQL COUNT() 函数详解
  • 中山网站设计收费标准wordpress 右边栏
  • 坦桑尼亚网站域名后缀一个虚拟主机可以放几个网站
  • 从大模型到轻量级部署:知识蒸馏优化技术
  • 速通ACM省铜第二十一天(补) 赋源码(共现的数)
  • 自用,正点Linux虚拟机系统文件概况
  • 从“用框架”到“控系统”——业务模型和技术模型之间的映射
  • 洛谷 / 一本通 - dp 题目详解 7(超详细版)