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

Windows11+PyCharm利用MMSegmentation训练自己的数据集保姆级教程

系统版本:Windows 11

依赖环境:Anaconda3

运行软件:PyCharm

一.环境配置

  1. 通过Anaconda Prompt(anaconda)打开终端
  2. 创建一个虚拟环境
conda create --name mmseg python=3.9

3.激活虚拟环境

conda activate mmseg

4.安装pytorch和cuda

torch版本要求是1.12或者1.13,这里选择安装1.12,安装命令从pytorch官网找,地址

pip install torch==1.12.1+cu116 torchvision==0.13.1+cu116 torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cu116

5.安装mmcv 

安装命令生成地址:地址

pip install mmcv==2.0.0rc4 -f https://download.openmmlab.com/mmcv/dist/cu116/torch1.12/index.html

6.下载源码

这里可以去github上下载1.1.1版本代码,也可以下载我准备好的代码:地址

7.用pycharm打开mmsegmentation-1.1.1

选择好配置的环境之后,打开终端,运行如下命令:

pip install -v -e .

至此环境配置完成。

二.准备自己的数据集

数据集的准备请查看:数据集制作教程

上面提供的源码中包含可训练的数据集,可以直接下载!

三.开始训练

在pycharm中打开上面下载的源码。

1.在mmseg/datasets文件夹下新建mysegDataset.py

from mmseg.registry import DATASETS
from .basesegdataset import BaseSegDataset@DATASETS.register_module()
class mysegDataset(BaseSegDataset):# 类别和对应的 RGB配色METAINFO = {'classes':['background', 'red', 'green', 'white', 'seed-black', 'seed-white'],'palette':[[127,127,127], [200,0,0], [0,200,0], [144,238,144], [30,30,30], [251,189,8]]}# 指定图像扩展名、标注扩展名def __init__(self,seg_map_suffix='.png',   # 标注mask图像的格式reduce_zero_label=False, # 类别ID为0的类别是否需要除去**kwargs) -> None:super().__init__(seg_map_suffix=seg_map_suffix,reduce_zero_label=reduce_zero_label,**kwargs)

2.注册数据集

在`mmseg/datasets/__init__.py`中注册刚刚定义的`mysegDataset`数据集类,如下图所示,在最后添加即可

3.pipeline配置文件

在configs/_base_/datasets文件夹下新建mysegDataset_pipeline.py,并添加如下代码。

# 数据集路径
dataset_type = 'mysegDataset' # 数据集类名
data_root = 'Watermelon87_Semantic_Seg_Mask/' # 数据集路径(相对于mmsegmentation主目录)# 输入模型的图像裁剪尺寸,一般是 128 的倍数,越小显存开销越少
crop_size = (512, 512)# 训练预处理
train_pipeline = [dict(type='LoadImageFromFile'),dict(type='LoadAnnotations'),dict(type='RandomResize',scale=(2048, 1024),ratio_range=(0.5, 2.0),keep_ratio=True),dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),dict(type='RandomFlip', prob=0.5),dict(type='PhotoMetricDistortion'),dict(type='PackSegInputs')
]# 测试预处理
test_pipeline = [dict(type='LoadImageFromFile'),dict(type='Resize', scale=(2048, 1024), keep_ratio=True),dict(type='LoadAnnotations'),dict(type='PackSegInputs')
]# TTA后处理
img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75]
tta_pipeline = [dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')),dict(type='TestTimeAug',transforms=[[dict(type='Resize', scale_factor=r, keep_ratio=True)for r in img_ratios],[dict(type='RandomFlip', prob=0., direction='horizontal'),dict(type='RandomFlip', prob=1., direction='horizontal')], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')]])
]# 训练 Dataloader
train_dataloader = dict(batch_size=2,num_workers=0,persistent_workers=True,sampler=dict(type='InfiniteSampler', shuffle=True),dataset=dict(type=dataset_type,data_root=data_root,data_prefix=dict(img_path='img_dir/train', seg_map_path='ann_dir/train'),pipeline=train_pipeline))# 验证 Dataloader
val_dataloader = dict(batch_size=1,num_workers=0,persistent_workers=True,sampler=dict(type='DefaultSampler', shuffle=False),dataset=dict(type=dataset_type,data_root=data_root,data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'),pipeline=test_pipeline))# 测试 Dataloader
test_dataloader = val_dataloader# 验证 Evaluator
val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice', 'mFscore'])# 测试 Evaluator
test_evaluator = val_evaluator

4.配置生成

在主目录下新建configset.py,并添加如下代码。

改代码中主要用于配置训练参数,右键运行生成配置文件。

from mmengine import Config
cfg = Config.fromfile('./configs/unet/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py') ##选择训练模型
dataset_cfg = Config.fromfile('./configs/_base_/datasets/mysegDataset_pipeline.py')   ## 选择pipeline
cfg.merge_from_dict(dataset_cfg)# 类别个数
NUM_CLASS = 6cfg.crop_size = (256, 256)
cfg.model.data_preprocessor.size = cfg.crop_size
cfg.model.data_preprocessor.test_cfg = dict(size_divisor=128)# 单卡训练时,需要把 SyncBN 改成 BN
cfg.norm_cfg = dict(type='BN', requires_grad=True) # 只使用GPU时,BN取代SyncBN
cfg.model.backbone.norm_cfg = cfg.norm_cfg
cfg.model.decode_head.norm_cfg = cfg.norm_cfg
cfg.model.auxiliary_head.norm_cfg = cfg.norm_cfg# 模型 decode/auxiliary 输出头,指定为类别个数
cfg.model.decode_head.num_classes = NUM_CLASS
cfg.model.auxiliary_head.num_classes = NUM_CLASS# 训练 Batch Size
cfg.train_dataloader.batch_size = 2# 结果保存目录
cfg.work_dir = './work_dirs/mysegDataset-UNet'# 模型保存与日志记录
cfg.train_cfg.max_iters = 10000 # 训练迭代次数
cfg.train_cfg.val_interval = 500 # 评估模型间隔
cfg.default_hooks.logger.interval = 100 # 日志记录间隔
cfg.default_hooks.checkpoint.interval = 2500 # 模型权重保存间隔
cfg.default_hooks.checkpoint.max_keep_ckpts = 1 # 最多保留几个模型权重
cfg.default_hooks.checkpoint.save_best = 'mIoU' # 保留指标最高的模型权重# 随机数种子
cfg['randomness'] = dict(seed=0)cfg.dump('myconfigs/mysegDataset_UNet.py')

5.修改num_workers=0

使用Windows系统训练,将上一步生成的配置文件中所有的num_workers修改成0。

crop_size = (256,256,
)
data_preprocessor = dict(bgr_to_rgb=True,mean=[123.675,116.28,103.53,],pad_val=0,seg_pad_val=255,size=(512,1024,),std=[58.395,57.12,57.375,],type='SegDataPreProcessor')
data_root = 'Watermelon87_Semantic_Seg_Mask/'
dataset_type = 'mysegDataset'
default_hooks = dict(checkpoint=dict(by_epoch=False,interval=2500,max_keep_ckpts=1,save_best='mIoU',type='CheckpointHook'),logger=dict(interval=100, log_metric_by_epoch=False, type='LoggerHook'),param_scheduler=dict(type='ParamSchedulerHook'),sampler_seed=dict(type='DistSamplerSeedHook'),timer=dict(type='IterTimerHook'),visualization=dict(type='SegVisualizationHook'))
default_scope = 'mmseg'
env_cfg = dict(cudnn_benchmark=True,dist_cfg=dict(backend='nccl'),mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0))
img_ratios = [0.5,0.75,1.0,1.25,1.5,1.75,
]
load_from = None
log_level = 'INFO'
log_processor = dict(by_epoch=False)
model = dict(auxiliary_head=dict(align_corners=False,channels=64,concat_input=False,dropout_ratio=0.1,in_channels=128,in_index=3,loss_decode=dict(loss_weight=0.4, type='CrossEntropyLoss', use_sigmoid=False),norm_cfg=dict(requires_grad=True, type='BN'),num_classes=6,num_convs=1,type='FCNHead'),backbone=dict(act_cfg=dict(type='ReLU'),base_channels=64,conv_cfg=None,dec_dilations=(1,1,1,1,),dec_num_convs=(2,2,2,2,),downsamples=(True,True,True,True,),enc_dilations=(1,1,1,1,1,),enc_num_convs=(2,2,2,2,2,),in_channels=3,norm_cfg=dict(requires_grad=True, type='BN'),norm_eval=False,num_stages=5,strides=(1,1,1,1,1,),type='UNet',upsample_cfg=dict(type='InterpConv'),with_cp=False),data_preprocessor=dict(bgr_to_rgb=True,mean=[123.675,116.28,103.53,],pad_val=0,seg_pad_val=255,size=(256,256,),std=[58.395,57.12,57.375,],test_cfg=dict(size_divisor=128),type='SegDataPreProcessor'),decode_head=dict(align_corners=False,channels=64,concat_input=False,dropout_ratio=0.1,in_channels=64,in_index=4,loss_decode=dict(loss_weight=1.0, type='CrossEntropyLoss', use_sigmoid=False),norm_cfg=dict(requires_grad=True, type='BN'),num_classes=6,num_convs=1,type='FCNHead'),pretrained=None,test_cfg=dict(crop_size=256, mode='whole', stride=170),train_cfg=dict(),type='EncoderDecoder')
norm_cfg = dict(requires_grad=True, type='BN')
optim_wrapper = dict(clip_grad=None,optimizer=dict(lr=0.01, momentum=0.9, type='SGD', weight_decay=0.0005),type='OptimWrapper')
optimizer = dict(lr=0.01, momentum=0.9, type='SGD', weight_decay=0.0005)
param_scheduler = [dict(begin=0,by_epoch=False,end=160000,eta_min=0.0001,power=0.9,type='PolyLR'),
]
randomness = dict(seed=0)
resume = False
test_cfg = dict(type='TestLoop')
test_dataloader = dict(batch_size=1,dataset=dict(data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'),data_root='Watermelon87_Semantic_Seg_Mask/',pipeline=[dict(type='LoadImageFromFile'),dict(keep_ratio=True, scale=(2048,1024,), type='Resize'),dict(type='LoadAnnotations'),dict(type='PackSegInputs'),],type='mysegDataset'),num_workers=0,persistent_workers=False,sampler=dict(shuffle=False, type='DefaultSampler'))
test_evaluator = dict(iou_metrics=['mIoU','mDice','mFscore',], type='IoUMetric')
test_pipeline = [dict(type='LoadImageFromFile'),dict(keep_ratio=True, scale=(2048,1024,), type='Resize'),dict(type='LoadAnnotations'),dict(type='PackSegInputs'),
]
train_cfg = dict(max_iters=10000, type='IterBasedTrainLoop', val_interval=500)
train_dataloader = dict(batch_size=2,dataset=dict(data_prefix=dict(img_path='img_dir/train', seg_map_path='ann_dir/train'),data_root='Watermelon87_Semantic_Seg_Mask/',pipeline=[dict(type='LoadImageFromFile'),dict(type='LoadAnnotations'),dict(keep_ratio=True,ratio_range=(0.5,2.0,),scale=(2048,1024,),type='RandomResize'),dict(cat_max_ratio=0.75, crop_size=(512,512,), type='RandomCrop'),dict(prob=0.5, type='RandomFlip'),dict(type='PhotoMetricDistortion'),dict(type='PackSegInputs'),],type='mysegDataset'),num_workers=0,persistent_workers=False,sampler=dict(shuffle=True, type='InfiniteSampler'))
train_pipeline = [dict(type='LoadImageFromFile'),dict(type='LoadAnnotations'),dict(keep_ratio=True,ratio_range=(0.5,2.0,),scale=(2048,1024,),type='RandomResize'),dict(cat_max_ratio=0.75, crop_size=(512,512,), type='RandomCrop'),dict(prob=0.5, type='RandomFlip'),dict(type='PhotoMetricDistortion'),dict(type='PackSegInputs'),
]
tta_model = dict(type='SegTTAModel')
tta_pipeline = [dict(file_client_args=dict(backend='disk'), type='LoadImageFromFile'),dict(transforms=[[dict(keep_ratio=True, scale_factor=0.5, type='Resize'),dict(keep_ratio=True, scale_factor=0.75, type='Resize'),dict(keep_ratio=True, scale_factor=1.0, type='Resize'),dict(keep_ratio=True, scale_factor=1.25, type='Resize'),dict(keep_ratio=True, scale_factor=1.5, type='Resize'),dict(keep_ratio=True, scale_factor=1.75, type='Resize'),],[dict(direction='horizontal', prob=0.0, type='RandomFlip'),dict(direction='horizontal', prob=1.0, type='RandomFlip'),],[dict(type='LoadAnnotations'),],[dict(type='PackSegInputs'),],],type='TestTimeAug'),
]
val_cfg = dict(type='ValLoop')
val_dataloader = dict(batch_size=1,dataset=dict(data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'),data_root='Watermelon87_Semantic_Seg_Mask/',pipeline=[dict(type='LoadImageFromFile'),dict(keep_ratio=True, scale=(2048,1024,), type='Resize'),dict(type='LoadAnnotations'),dict(type='PackSegInputs'),],type='mysegDataset'),num_workers=0,persistent_workers=False,sampler=dict(shuffle=False, type='DefaultSampler'))
val_evaluator = dict(iou_metrics=['mIoU','mDice','mFscore',], type='IoUMetric')
vis_backends = [dict(type='LocalVisBackend'),
]
visualizer = dict(name='visualizer',type='SegLocalVisualizer',vis_backends=[dict(type='LocalVisBackend'),])
work_dir = './work_dirs/mysegDataset-UNet'

6.训练模型

在终端中运行以下命令:

 python tools/train.py myconfigs/mysegDataset_UNet.py

相关文章:

Windows11+PyCharm利用MMSegmentation训练自己的数据集保姆级教程

系统版本:Windows 11 依赖环境:Anaconda3 运行软件:PyCharm 一.环境配置 通过Anaconda Prompt(anaconda)打开终端创建一个虚拟环境 conda create --name mmseg python3.93.激活虚拟环境 conda activate mmseg 4.安装pytorch和cuda tor…...

WPS计算机二级•文档的文本样式与编号

听说这是目录哦 标题级别❤️新建文本样式 快速套用格式🩷设置标题样式 自定义设置多级编号🧡使用自动编号💛取消自动编号💚设置 页面边框💙添加水印🩵排版技巧怎么分栏💜添加空白下划线&#x…...

Word中Ctrl+V粘贴报错问题

Word中CtrlV粘贴时显示“文件未找到:MathPage.WLL”的问题 Word的功能栏中有MathType,但无法使用,显示灰色。 解决方法如下: 首先找到MathType安装目录下MathPage.wll文件以及MathType Commands 2016.dotm文件,分别复…...

python-leetcode 24.回文链表

题目: 给定单链表的头节点head,判断该链表是否为回文链表,如果是,返回True,否则,返回False 输入:head[1,2,2,1] 输出:true 方法一:将值复制到数组中后用双指针法 有两种常用的列表实现&#…...

数据治理双证通关经验分享 | CDGA/CDGP备考全指南

历经1个月多的系统准备,本人于2024年顺利通过DAMA China的CDGA(数据治理工程师)和CDGP(数据治理专家)双认证。现将备考经验与资源体系化整理,助力从业者高效通关。 🌟 认证价值与政策背景 根据…...

3.4 学习UVM中的uvm_monitor类分为几步?

文章目录 前言1. 定义2. 核心功能3. 适用场景4. 使用方法5. 完整代码示例5.1 事务类定义5.2 Monitor 类定义5.3 Scoreboard 类定义5.4 测试平台 6. 代码说明7. 总结 前言 以下是关于 UVM 中 uvm_monitor 的详细解释、核心功能、适用场景、使用方法以及一个完整的代码示例&…...

Java在大数据处理中的应用:从MapReduce到Spark

Java在大数据处理中的应用:从MapReduce到Spark 大数据时代的到来让数据的存储、处理和分析变得前所未有的重要。随着数据量的剧增,传统的单机计算方式已经无法满足处理需求。为了解决这个问题,许多分布式计算框架应运而生,其中Ma…...

日常吐槽。

一、写在前面 stereopy日常出bug(github issue里得有一半的问题是我提的,当然也有可能是因为我菜),stereopy自己生成的anndata自己不能计算空间共现关系,还是靠squidpy才能计算。另外还要一些函数一开并行计算就报错,这里留一些s…...

2025最新版Node.js下载安装~保姆级教程

1. node中文官网地址:http://nodejs.cn/download/ 2.打开node官网下载压缩包: 根据操作系统不同选择不同版本(win7系统建议安装v12.x) 我这里选择最新版win 64位 3.安装node ①点击对话框中的“Next”,勾选同意后点…...

机器学习:学习记录(二)

1. 机器学习中的常用函数 logistic函数(sigmoid函数):非线性激活函数,将R区间映射到(0,1)区间 ReLU函数:非线性激活函数,简单可以写作max(0,x),在0处不可导,但是可以人为定义其导数…...

迁移学习 Transfer Learning

迁移学习(Transfer Learning)是什么? 迁移学习是一种机器学习方法,它的核心思想是利用已有模型的知识来帮助新的任务或数据集进行学习,从而减少训练数据的需求、加快训练速度,并提升模型性能。 &#x1f…...

实现:多活的基础中间件

APIRouter : 路由分发服务 API Router 是一个 HTTP 反向代理和负载均衡器,部署在公有云中作为 HTTP API 流量的入口,它能识别 出流量的归属 shard ,并根据 shard 将流量转发到对应的 ezone 。 API Router 支持多种路由键&am…...

Mybatis源码01 - 总体框架设计

Mybatis总体框架设计 文章目录 Mybatis总体框架设计一:MyBatis架构概览1:接口层1.1:使用传统的MyBatis提供的API1.2:使用Mapper接口 2:数据处理层【核心】2.1:参数映射和动态SQL语句生成2.2:SQL…...

在大型语言模型(LLM)框架内Transformer架构与混合专家(MoE)策略的概念整合

文章目录 传统的神经网络框架存在的问题一. Transformer架构综述1.1 transformer的输入1.1.1 词向量1.1.2 位置编码(Positional Encoding)1.1.3 编码器与解码器结构1.1.4 多头自注意力机制 二.Transformer分步详解2.1 传统词向量存在的问题2.2 详解编解码…...

Selenium WebDriver自动化测试(扩展篇)--Jenkins持续集成

文章目录 一、引言二、Jenkins简介三、安装部署Jenkins安装部署 四、集成Git与Maven安装必要的插件配置Git配置Maven 五、创建Job创建自由风格的项目配置源码管理配置构建触发器配置构建环境配置构建步骤配置Post-build Actions 六、触发构建示例:GitHub Webhook触发…...

Wiki文档转换为Word技术

一、技术背景与目标 Wiki系统导出的文档通常以HTML格式存在,且内容分散在多个文件中,每个页面对应一个HTML文件。然而,Microsoft Word(Word)在处理HTML文件时,仅支持单个HTML文件的导入。因此,为了将Wiki导出的内容转换为Word可识别的格式,必须将分散的HTML文件整合为一…...

1.【线性代数】——方程组的几何解释

一 方程组的几何解释 概述举例举例一1. matrix2.row picture3.column picture 概述 三种表示方法 matrixrow picturecolumn picture 举例 举例一 { 2 x − y 0 − x 2 y 3 \begin{cases} 2x - y 0 \\ -x 2y 3 \end{cases} {2x−y0−x2y3​ 1. matrix [ 2 − 1 − 1 …...

力扣1448. 统计二叉树中好节点的数目

Problem: 1448. 统计二叉树中好节点的数目 文章目录 题目描述思路复杂度Code 题目描述 思路 对二叉树进行先序遍历,边遍历边对比并更新当前路径上的最大值pathMax,若当pathMax小于等于当前节点值,则好节点的数目加一 复杂度 时间复杂度: O (…...

【C#零基础从入门到精通】(二)——C#注释和命名法详解

【C#零基础从入门到精通】(二)——C#注释和命名法详解 C# 中的注释 定义 在 C# 里,注释是一种特殊的代码文本,它不会被编译器执行,主要用于对代码进行解释、说明,帮助开发者更好地理解代码的功能、用途、实现思路以及注意事项等,提升代码的可读性和可维护性。 注释类型…...

SQLServer的创建,表创建,主键,约束,模糊查询

设置 注意: 设置完成之后 重新启动 创建数据库 注意: 这个目标路径必须要有该文件名的文件夹 -- 指向 master 数据库,告诉它我们要创建一个新的数据库操作了 use master go-- 创建数据库 create database StudentManageDB on primary (-- 以下四个组成部分缺一不可…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)

目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 ​编辑​编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...

Vue 3 + WebSocket 实战:公司通知实时推送功能详解

📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...

算法—栈系列

一&#xff1a;删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {stack<char> st;for(int i 0; i < s.size(); i){char target s[i];if(!st.empty() && target st.top())st.pop();elsest.push(s[i]);}string ret…...

深入浅出JavaScript中的ArrayBuffer:二进制数据的“瑞士军刀”

深入浅出JavaScript中的ArrayBuffer&#xff1a;二进制数据的“瑞士军刀” 在JavaScript中&#xff0c;我们经常需要处理文本、数组、对象等数据类型。但当我们需要处理文件上传、图像处理、网络通信等场景时&#xff0c;单纯依赖字符串或数组就显得力不从心了。这时&#xff…...

Python爬虫(52)Scrapy-Redis分布式爬虫架构实战:IP代理池深度集成与跨地域数据采集

目录 一、引言&#xff1a;当爬虫遭遇"地域封锁"二、背景解析&#xff1a;分布式爬虫的两大技术挑战1. 传统Scrapy架构的局限性2. 地域限制的三种典型表现 三、架构设计&#xff1a;Scrapy-Redis 代理池的协同机制1. 分布式架构拓扑图2. 核心组件协同流程 四、技术实…...