☰
GG-CNN像素级抓取检测:从深度图到Gazebo/Pybullet仿真实践
2026/9/25 4:41:48 网站建设 项目流程

简介:一套基于GG-CNN的机器人抓取系统实现方案,面向机器人视觉与抓取方向的研究者、工程师及专业学生,用于解决从深度图输入到抓取位置与姿态预测的端到端建模问题,并覆盖仿真验证与算法调试验证流程。压缩包共5个文件,包含2个Python程序、1个Markdown说明、1个TXT说明文件及1个Word文档。Python脚本对应核心运行代码,可驱动抓取预测与Kinova机械臂的开环抓取流程;Markdown与TXT文档提供配置和操作指引,Word文档为附赠的补充资料。整体包体仅42KB,轻量但结构完整。已有215人学习该资源。资料整合了康奈尔与提花数据集的训练思路、Gazebo和Pybullet双仿真平台的可视化实现,以及Kinova机器人的集成案例,方便读者快速理解从网络训练到仿真部署的完整链路。

1. GG-CNN这套像素级抓取检测方案到底在做什么

把一张深度图丢给一个小卷积网络,它直接返回一张同尺寸的热图:每个像素都标注了“夹爪放在这里有多大把握抓起来”,再附上该点的转角与夹持宽度。机械臂沿着热图上最亮的位置伸手,就完成了一次抓取。这就是标题里GG-CNN(Grasp Quality CNN)这套像素级抓取检测方案的核心玩法。

整个链路并不复杂:康奈尔与提花数据集负责离线训练,让网络学会从深度图推断抓取质量与姿态;Gazebo与Pybullet负责在线仿真,一个偏系统联调、一个偏快速验证,让机械臂的每一次抓取都能实时可视化,还能自动记录失败样本。对于刚进入机器人操作领域、想在仿真里快速跑通抓取闭环的工程师和学生,这是一条性价比很高的路:模型轻量到能在CPU上实时推理,两个仿真平台各有分工,排查点集中。下面按“数据表达→训练→仿真→排错→闭环升级”的顺序拆开讲。

2. 从深度图到抓取质量图:GG-CNN的数据表达与网络结构

抓取检测有两条常见技术路线:一种是把抓取当成目标检测,输出若干个候选抓取矩形框;另一种就是GG-CNN这种像素级预测,对图像里的每个像素都估算一个抓取质量,再配上角度和宽度。后者的直接好处是单次前向就能得到全图的可抓性分布,后续规划直接在质量图上决策,不需要在候选框之间做非极大抑制之类的后处理。

2.1 抓取如何表示:从抓取矩形到逐像素标签

在抓取数据集里,一条标注通常是一个带方向的矩形:中心点(x, y)对应夹爪两指之间的中点,θ对应夹爪在图像平面内的旋转角,width对应两指张开距离,height对应夹爪开口深度。康奈尔和提花数据集的原始标注基本都是这个形式。落到GG-CNN里,要把一条矩形标注展开成三张与输入深度图同分辨率的标签图:Q图是每个像素的抓取质量分数,Φ图是每个像素对应的抓取角度,W图是每个像素对应的夹爪张开宽度。

这一步转换很机械,但容易写错,尤其是“矩形内部像素才有效”这个掩码,以及旋转矩形在离散图像坐标下的边界。下面这段代码可以作为参考实现:

import numpy as np def rect_to_pixel_labels(rect, h, w): """ 把一条抓取矩形标注展开成逐像素标签。 rect: (cx, cy, theta, half_w, half_h),theta为弧度 返回 Q、Phi、W 三张 h x w 的图 """ yy, xx = np.mgrid[0:h, 0:w].astype(np.float32) dx = xx - rect[0] dy = yy - rect[1] c, s = np.cos(rect[2]), np.sin(rect[2]) u = dx * c + dy * s # 像素在矩形局部坐标系下的横坐标 v = -dx * s + dy * c # 像素在矩形局部坐标系下的纵坐标 mask = (np.abs(u) <= rect[3]) & (np.abs(v) <= rect[4]) Q = np.zeros((h, w), dtype=np.float32) Phi = np.full((h, w), rect[2], dtype=np.float32) W = np.full((h, w), rect[3] * 2, dtype=np.float32) Q[mask] = 1.0 return Q, Phi, W

这段代码的逻辑是先把每个像素坐标投影到矩形局部坐标系,落在矩形内部的像素才被标记为有效抓取点,Phi和W在有效区域内填上同一个矩形参数。注意rect传入的是半宽半高,W要恢复成完整宽度,否则夹爪宽度会凭空缩一半。原始标注常是从3D点云投影到图像得到的,坐标是浮点值,不要在转换前提前取整,取整会让矩形边缘偏移几个像素,质量图峰值也会跟着偏。

如果一张图有多条标注,常见做法是取质量最大者合并,或者让质量从矩形中心向外按高斯衰减。衰减的好处是网络输出的质量图有自然梯度,argmax不容易在边缘抖动;但衰减半径设太大,会把两个靠近物体之间的空隙也糊成可抓点,我一般把sigma设为矩形宽度的四分之一左右。

角度周期性是另一个必须处理的问题。θ=0°和θ=180°在物理上是同一个抓取方向,如果网络直接回归角度数值,这两个等价标签在数值上却差180,训练会持续冲突。常见对策是把角度编码成两个通道cos(2θ)和sin(2θ),网络输出这两通道,后处理再用atan2还原。

2.2 GG-CNN的结构为什么能实时

GG-CNN的网络主体是编码器-解码器的全卷积结构。编码器用步长卷积把深度图逐层压缩,解码器用上采样恢复到原分辨率,中间没有全连接层。输入224×224的深度图,输出就是224×224的三张图。参数总量在几十k量级,相比U-Net动辄几百万参数,它轻得多;在普通CPU上单帧推理可以到每秒几十帧,放进机器人控制回路完全来得及。

轻量带来的限制也要清楚:由于网络容量有限,模型对物体形状的泛化全靠训练数据,训练集里没见过的物品,它更常见的是给一个“似是而非”的高质量分数,而不是明确拒绝。因此训练数据的规模和多样性非常关键,这也是标题里同时出现康奈尔与提花两个数据集的原因。

推理端的后处理同样重要。拿到质量图后,常见做法是直接取全局最大值点作为抓取中心,再从角度图和宽度图里取该点的值:

q_map, phi_map, w_map = model(depth_tensor) # 三张图,shape: (B, 1, H, W) q = q_map[0, 0].cpu().numpy() phi = phi_map[0, 0].cpu().numpy() w = w_map[0, 0].cpu().numpy() best = np.unravel_index(np.argmax(q), q.shape) # 返回 (y, x) grasp = (best[1], best[0], phi[best], w[best])

如果网络用的是双通道角度编码,后处理要用atan2还原,示例里写的是单通道角度回归的简化写法。质量图峰值如果又宽又平,说明模型对当前场景置信度不高;峰值又尖又高,则抓取比较有把握。这个信号可以做成抓取自检:比如峰值低于0.4就放弃这次抓取,换个视角或重新放置物体。

2.3 为什么输入用深度图而不是RGB

把RGB换掉,是抓取检测里非常关键的一个选择。深度图有三个实打实的优点。第一,对光照不敏感。工业场景里金属件、反光塑料件常常让RGB特征彻底失效,而深度图只关心几何距离,反光和阴影不构成干扰。第二,自带真实尺度。有了深度和相机内参,抓取点的三维坐标可以直接算出来,夹爪宽度也能从像素换算成实际米数。第三,仿真到真实的迁移损耗小。在Gazebo和Pybullet里渲染RGB,纹理域差非常大;深度图的域差主要体现在传感器噪声分布,比RGB小得多。

现实中也有工程师习惯先把深度图转点云,再算点云法线,做表面几何分析,最后才决定抓取点。这是工业视觉里很成熟的路线,但步骤多、参数敏感,一个噪声点就能让法线估计偏掉。GG-CNN这条路直接跳过点云和法线,把深度值交给网络自己去学几何关系,在仿真环境里相当于少了一层预处理,实时性也更稳。

仿真出来的深度图经常混有空洞,训练和推理两端的填充策略必须一致。我常用的做法是先用中值滤波或邻域插值填掉小洞,大块空洞用深度量程最大值填充,归一化时把填充值当作“最远距离”。不要直接把NaN喂给网络,NaN只要出现一次,梯度就会乱掉。

3. 用康奈尔与提花数据集训练GG-CNN:预处理、超参和评估口径

离线训练的目标很朴素:让网络学会从一张深度图推断哪里能抓、什么角度抓、夹爪张多宽。康奈尔数据集规模小、是真实拍摄,适合快速验证模型和调参;提花数据集是合成渲染的大规模数据集,适合给模型“见世面”。稳妥的路线是先在康奈尔上小成本调通训练流程,再用提花做预训练,最后回到仿真里评估物理抓取成功率。

3.1 康奈尔与提花:先搞清楚标注格式再动手

康奈尔抓取数据集(Cornell Grasping Dataset)是抓取检测老牌基准,包含真实场景的RGB-D图像和抓取矩形标注,正负样本都有,图像量不大,标准评估方式是5折交叉验证,衡量预测矩形与标注矩形之间的夹角和中心偏移。提花数据集(Jacquard)则是从CAD模型自动渲染的大规模合成数据,标注自动生成,图像量比康奈尔多一个量级,对GG-CNN这种轻量模型尤其重要——数据量越大,越能弥补网络容量小的短板。

我见过的项目里,不少人拿到数据包就急着写训练脚本,结果两个数据集的标注格式完全不同:康奈尔是文本文件里的一组组坐标点,提花是JSON或CSV描述,角度表示方式也不一致。最省事的做法是先写一个可视化脚本,把标注矩形画在深度图上,逐样本过一遍。这个习惯能挡住一半的预处理事故。

Dataset类建议统一包一层,把“读标注→转像素标签→深度预处理”固定下来:

# 伪代码:数据集的统一出口,模型只看到深度图和标签图 class GraspDataset(Dataset): def __init__(self, root, mode="cornell", train=True): # 根据mode读取cornell或jacquard的标注列表 self.samples = load_annotations(root, mode) def __getitem__(self, idx): depth, rects = self.samples[idx] depth = preprocess_depth(depth) # 补洞、归一化 Q, Phi, W = merge_rects_to_labels(rects) # 多条矩形合并成三张图 return depth, Q, Phi, W

返回的必须是归一化后的深度图和对应的三张标签图。不要在Dataset内部做了随机裁剪之后再去单独变换角度标签,尽量让标签和图像走同一个变换管线。

3.2 预处理:深度归一化、NaN填充与角度编码

深度图预处理顺序,我建议固定为四步。第一步,把深度裁剪到相机量程,比如[0.3, 1.5]米,量程外的值直接截断,去掉桌面边缘和背景的无效深度。第二步,处理NaN和无限值,填成量程最大值,让网络学会“这里太远,不抓”。第三步,归一化到[0,1]或[-1,1],保存同一组均值和尺度,训练与推理必须复用同一套参数,否则模型输出会整体漂移。第四步,根据相机内参把像素坐标换算成相机坐标系下的XYZ,这一步不一定要在训练前做,但实时抓取需要3D坐标时,建议在数据管道里留好接口。

角度编码单独强调一下。直接用角度数值做回归,最怕0°和180°的等价问题。用cos(2θ)、sin(2θ)双通道编码实现成本很低,却能让角度预测稳定得多:

# 标签里的theta转成网络输出头需要的编码 phi2 = np.stack([np.cos(2 * theta), np.sin(2 * theta)], axis=-1) # shape: H, W, 2

后处理时用np.arctan2(phi2[..., 1], phi2[..., 0]) / 2还原。角度单位全程统一,弧度就全是弧度,别在数据集A用角度、数据集B用弧度,这种单位混用是训练翻车的常见原因。

增广策略上,GG-CNN体量小,增广收益比大模型更明显。常用组合是:随机裁剪224×224区域、随机旋转±30度、随机平移±30像素、随机缩放。最容易写错的是旋转增广:图像旋转矩阵必须同步作用到角度图上,否则标签就错了。用warpAffine时很容易只转了图像忘记转角度图,建议把两个操作封装在同一个函数里。

3.3 训练超参与评估口径:一张可以直接抄的配置表

下面是一份我用下来比较稳的训练配置,康奈尔和提花通用,区别主要在epoch数和学习率调度。

配置项推荐值说明
优化器Adam初始学习率0.001,每5个epoch乘以0.5
Batch size16输入分辨率224×224
输入单通道深度图归一化到[-1,1]
损失Q用BCE,Phi/W用MSE背景像素不参与角度和宽度回归
正负样本平衡正样本权重1.5~2倍防止BCE被大面积背景带偏
早停监控质量图峰值与真实矩形中心距离不要只盯着训练loss
def ggcnn_loss(q_pred, phi_pred, w_pred, q_gt, phi_gt, w_gt, mask): q_loss = F.binary_cross_entropy(q_pred, q_gt, reduction="none") phi_loss = F.mse_loss(phi_pred, phi_gt, reduction="none") w_loss = F.mse_loss(w_pred, w_gt, reduction="none") # 背景像素不约束角度和宽度,只约束质量 loss = (q_loss + mask * (phi_loss + w_loss)).mean() return loss

mask来自标签生成阶段:矩形内部为1,外部为0。如果不加mask,网络会浪费大量容量去预测无关区域的角度和宽度,质量图会被垃圾约束带偏。如果发现BCE和MSE量级差太多,可以给Phi和W的损失加权到0.5左右,训练曲线会更干净。

评估要分成两个口径。第一个是康奈尔5折精度,把预测矩形与标注矩形比夹角差和中心距,统计命中率。这个数字代表“模型找框找得准不准”,高到90%以上并不稀奇。第二个才是物理抓取成功率:在Gazebo或Pybullet里让机械臂按预测结果抓物体,统计物体被抓离桌面且保持不掉的比例。这个才是最终KPI。建议从第一天起就把物理抓取成功率脚本写好,每训练一轮记录一次,光看康奈尔精度很容易自我感觉良好,放进仿真立刻打回原形。

4. 在Gazebo与Pybullet上跑通实时抓取:模型接入仿真的两条路

仿真阶段的核心目标是实时可视化:场景里有深度相机、机械臂、桌面物体,系统实时出深度图,GG-CNN实时预测,抓取点可视化成一个矩形,机械臂实时运动过去抓取。

4.1 为什么两套仿真都要用:Gazebo与Pybullet的分工差异

标题里同时出现Gazebo和Pybullet不是堆平台,它们各自擅长不同场景。Gazebo是ROS生态里的主流动力学仿真器,传感器插件丰富,深度相机的噪声、点云发布都现成,适合做整机系统联调,把机械臂的MoveIt规划、相机标定、抓取策略全串起来。Pybullet则是Python直接驱动的物理仿真库,启动快、体量轻、能直接拿到深度帧,适合算法快速迭代和单元验证。

常见的路线是:白天在Pybullet里调抓取策略、验证模型权重、跑数据闭环;稳定后在Gazebo里做带ROS通信的完整演示。Pybullet里半小时能跑完的抓取实验,在Gazebo里可能要花半天,因为还要处理URDF加载、话题同步、控制器名称这类系统问题。先快后慢的顺序能省下大量调试时间。

对比项PybulletGazebo
启动速度秒级十秒到几十秒级
深度图获取getCameraImage直接给depth buffer传感器插件发布话题,需等待就绪
逆运动学calculateInverseKinematics内置配合MoveIt或自写IK
适合环节策略调参、数据采集、快速验证系统集成、控制器验证、演示部署

另外提醒一句仿真环境版本问题:Gazebo在Ubuntu 22.04上的常见组合是ROS 2 Humble配Gazebo Classic,版本不匹配时会遇到模型加载一堆红字、传感器不出数的情况,先把ros环境变量和Gazebo路径打印出来核对一遍再往下查。

4.2 把Kinova模型接进仿真:URDF与深度相机的配置要点

Kinova机械臂有公开URDF模型,但直接加载经常出问题:mesh路径是相对路径、碰撞体太精细导致仿真变慢、关节力矩上限用真实值导致小负载也失败。我一般先做两手准备:一是把URDF里的mesh路径全部改成绝对路径,模型文件散到同一目录;二是把碰撞体简化成圆柱或盒体。低速抓取任务简化碰撞体不影响结果,但能显著减少物理引擎计算量。

Pybullet侧加载URDF很直接:

import pybullet as p p.connect(p.GUI) p.setTimeStep(1 / 240.0) p.setGravity(0, 0, -9.8) kinova_id = p.loadURDF( "kinova_gen3.urdf", # 本地绝对路径 basePosition=[0, 0, 0.05], useFixedBase=True, # 底座固定,机械臂不会翻倒 ) obj_id = p.loadURDF("box.urdf", basePosition=[0.6, 0, 0.45])

useFixedBase=True这里必须设置,否则机械臂一动,整个模型就会翻倒。桌面模型同样要固定。加载后要过滤关节:Kinova是7自由度臂加夹爪,把非驱动关节过滤掉,只控制有关节的id。

深度相机配置要和真实相机尽量一致,才能复用推理代码。Pybullet相机API的参数包括视点、目标点、上向量、fov、分辨率,深度相机位置最好固定在一个支架关节上,这样相机位姿是常量,抓取时不用每次标定。Gazebo里的深度相机一般用传感器插件发布ROS话题,near和far直接决定深度图的有效距离范围,必须和训练时预处理一致。

在Gazebo里,深度相机话题的频率常被仿真步长限制。如果推理比话题快,不要强行提高话题频率,优先降低分辨率或调大far。画质对抓取的意义远小于量程,深度值范围搞对了,抓取才能对得上。

4.3 从深度图到机械臂执行:实时抓取的最小闭环

整个实时抓取的代码结构可以固定成三个模块:深度采集、模型推理、运动执行。下面是一段在Pybullet里跑通最小闭环的示例,节点上直接注释了每一步的作用:

def run_grasp_loop(arm_id, arm_joint_ids, cam_pose, model): # 1. 取深度图 view = p.computeViewMatrix(cam_eye, cam_target, [0, 0, 1]) proj = p.computeProjectionMatrixFov( fov=60, aspect=320 / 240, near=0.1, far=3.0 ) _, _, _, depth_buffer, _ = p.getCameraImage(320, 240, view, proj) depth = 0.1 + 2.9 * depth_buffer.reshape(240, 320) depth[depth_buffer == 1.0] = 0.0 # 背景置0,和训练预处理一致 # 2. GG-CNN推理 with torch.no_grad(): q, phi, w = model(normalize(depth).unsqueeze(0)) u, v = np.unravel_index(torch.argmax(q[0, 0]), q.shape) theta = float(phi[0, 0, v, u]) # 按你的输出定义还原 # 3. 像素点转相机坐标系,再转世界坐标系 zc = depth[v, u] xc = (u - 160) * zc / 320.0 yc = (v - 120) * zc / 320.0 pos_cam = [xc, yc, zc] pos_world = p.multiplyTransforms( cam_pose[0], cam_pose[1], pos_cam, [0, 0, 0, 1] ) # 4. 逆运动学并下发 orn = p.getQuaternionFromEuler([0, np.pi, theta]) # 末端朝下,绕Z转theta joint_poses = p.calculateInverseKinematics( arm_id, 7, pos_world[0], orn ) for i, jid in enumerate(arm_joint_ids): p.setJointMotorControl2( arm_id, jid, p.POSITION_CONTROL, joint_poses[i] )

相机的内参这里被硬编码成fx=fy=320、cx=cy=160,实际项目中不要把内参写死,要放到配置文件里,并且训练数据预处理用的内参和仿真相机内参必须一致。像素转3D的公式虽简单,但相机外参一变就要重算,不注意就会抓偏。

在Gazebo里,同样的链路只是把“取深度图”换成订阅ROS话题,把“逆运动学下发”换成MoveIt或trajectory controller。话题同步要特别注意:深度图和相机位姿来自不同话题时,时间戳要同步,否则抓取动作会打在物体刚才的位置上。Gazebo里最常翻车的事故之一,就是机械臂伸过去时物体已经被上一个控制周期的动作碰歪了。

5. 仿真抓取最容易翻车的5个坑:现象、原因和排查顺序

仿真环境的坑比训练还多,很多问题看起来像模型不行,实际上环境配置出错。下面按我遇到概率从高到低列几类典型的。

5.1 现象:深度图全黑或全是0,但话题有数据

原因:Gazebo相机插件的near/far设置不当,物体超出了量程;Pybullet里则常见是depth buffer没还原,直接把远平面值当成了深度。如果不看数值只看图像,会被黑屏误导,白白去调显示参数。 解决:先打印深度值的最小、最大和非零占比,确认物体确实在量程内。Gazebo里优先把far调到2.0以上;Pybullet里用公式还原depth buffer后再用,别直接用原始值。显示黑屏不一定是数据问题,可能是16bit深度被当成8bit显示,属于视觉误判。

5.2 现象:康奈尔5折精度90%以上,仿真里抓一个掉一个

原因:康奈尔精度衡量的是“预测矩形与标注矩形的对齐程度”,跟物理抓握完全是两回事。仿真里有重心、摩擦、夹爪几何这些训练时从未见过的约束;另外宽度图预测的是像素单位,没换算成机器人夹爪实际张开距离,两指就张不开。 解决:把仿真物理抓取成功率作为唯一最终指标。抓取前打印预测宽度和物体实际包围盒宽度,确认像素到米的换算系数。调试时把夹爪摩擦系数临时调大,排除“抓而不稳”的物理因素之后,再逐步恢复真实摩擦力。

5.3 现象:loss下降正常,质量图却一团糊,峰值又宽又平

原因:标签生成时Q图的高斯衰减半径设得太大;或正负样本失衡严重,BCE被大量背景像素带偏。 解决:可视化训练样本里的Q图,看峰值是否又尖又明显。整张图都在0.5左右漂移时,把衰减半径调小,正样本区域做到“中心亮、边缘暗”。同时检查正样本像素占比,低于5%就给正样本加权重到1.5至2倍。质量图必须要有清晰峰值,这是抓取闭环能否稳定选点的前提。

5.4 现象:换物体就抓不到,像是模型只在背桌子

原因:训练数据里物体总是落在图像中央,或形状太单一;深度图预处理时背景和物体距离混在一起,模型学到的是“图像中央可抓”,不是“物体几何可抓”。轻量模型泛化能力有限,没见过就是没见过。 解决:增加平移增广,把物体随机放在桌面不同坐标;用提花数据预训练后再微调;仿真测试时用形状差异大的物体集。如果问题出在背景,把超过量程的背景深度截掉,一般能缓解。

5.5 现象:仿真里推理卡顿,机械臂动作一顿一顿

原因:常见有三处:把质量图上多个候选点逐一画调试线且不清理,Pybullet GUI渲染与物理步进抢占同一线程,推理时数据在numpy和torch之间反复拷贝。 解决:只画Top-1抓取矩形,每次更新用replaceItemUniqueId参数,让调试线不累积;控制循环用setRealTimeSimulation(0)加固定步长sleep,避免物理步进与GUI刷新互相干扰;推理数据尽量一次性批处理,避免每个控制周期都做来回拷贝。

6. 把仿真失败帧回灌训练集:抓取系统闭环升级的一个实用技巧

训练集里加负样本是老生常谈,但仿真环境给了一个真实部署里很难得的条件:自动采集失败场景。夹爪闭合但没抓住、物体从夹爪里滑掉、末端撞到桌面——这些瞬间的深度图,都是训练样本里最缺的难例。把它们记下来,重新标注后回灌训练集,比单纯堆更多正常抓取数据有效得多。

Pybullet里判断抓取失败很简单:

def is_grasp_success(arm_id, obj_id, table_height): contacts = p.getContactPoints(arm_id, obj_id) if not contacts: return False pos, _ = p.getBasePositionAndOrientation(obj_id) return pos[2] > table_height + 0.01

失败时保存当前深度图、预测的Q/Phi/W和机械臂当时的位姿。回灌时建议把这条样本的Q标签整张置0,让模型在这个视角下不再冒进。换句话说,这些负样本是在告诉模型:这个角度看上去可能能抓,但实际抓不起来。

回灌微调时,学习率降到原来的十分之一,例如0.0001,混合原有正样本训练集,只跑10到20个epoch。关键点在于不要让网络忘记已学到的抓取经验,所以负样本占比要控制,不超过新增样本的20%,其余仍用原训练集。微调后再跑一轮仿真抓取成功率,把“失败→回灌→微调→再验证”作为循环不断推进,系统会越调越稳。

我有一个保持了很久的习惯:每次改数据管道、改增广、改网络结构,第一件事不是看训练曲线,而是在仿真里跑一轮50次抓取的冒烟测试,记录成功率与失败原因分布。早年我被康奈尔精度迷惑过,模型在训练集上表现完美,进到仿真却连续抓翻两个物体,最后定位到是宽度单位换算的bug。所以我的原则是:模型改动进仿真验证,仿真失败进数据集回灌,形成一个可持续自我改进的抓取闭环。这套流程希望对你也有帮助。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询