卡淘图片批量采集实战:从抓包到Requests下载全流程 平时喜欢研究爬虫的朋友应该不少人都遇到过类似需求看到某个收藏品平台上的图片挺好想批量保存下来整理成资料。今天就拿卡淘平台举个例子把从抓包到采集图片的完整链路走一遍。卡淘是专门做球星卡、收藏卡交易的平台页面和APP里展示的卡片图清晰度高、信息密度大很适合用来练习抓包和图片采集。这篇文章会从抓包工具配置讲起一直写到 Requests 脚本保存图片中间还会穿插我踩过的几个坑适合有一定 Python 基础、想搞懂动态请求和图片下载原理的同学。标题里看起来简单可真做起来有几个地方会卡人抓包工具配好了但看不到请求接口找到了但图片地址拼不出来图片能访问但代码下载时 403。这些问题我一个个拆开说。1. 为什么选“抓包采集图片”这套方案1.1 抓包才是万能的入口很多初学者习惯直接在网页上按 F12去 Elements 里找img src然后拿 requests 去抓 HTML。这个思路在小网站可以但遇到卡淘这种动态加载的站点很容易翻车。页面上的图片不是一开始就写在 HTML 里的而是网页加载完以后JavaScript 再去调接口拿到 JSON 数据把图片地址动态渲染出来的。你直接 requests 那个 HTML得到的只是一堆空壳 div图片地址一个都看不见。APP 就更不用说了你根本没法打开开发者工具去“审查元素”唯一能看到网络请求的方式就是抓包。抓包工具相当于一个中间人代理所有请求都会经过它你在工具里能看到客户端每次请求了哪个 URL、带了什么参数、服务器返回了什么内容。这个能力很关键因为它让你绕开了“页面长什么样”直接看到“数据从哪来”。用生活类比的话网页是成品菜抓包是进后厨你能看到厨师放了什么料、开多大火自然就知道菜是怎么做的。所以我做图片采集类项目时第一步永远是抓包而不是急着写爬虫代码。先把接口理清楚代码只是重复发送请求而已难度会降一截。1.2 卡淘平台的页面和数据特点卡淘平台定位是球星卡、收藏卡交易核心内容是卡片图片、价格、卖家信息。这类平台有个普遍特点图片多、详情字段多但接口设计相对规整。卡片列表一般是一个 JSON 接口每一条记录里包含了卡片 ID、标题、价格、缩略图地址、详情图地址。而且图片通常放在 CDN 或对象存储上地址会有比较明显的域名和路径规则。另外卡淘的图片加载是懒加载模式你滚动页面时才请求后面的图片。这个特点决定了你不能只抓 HTML因为 HTML 里只有前几屏的图片后面全是接口二次触发出来的。只有把列表接口整个抓下来按页码拿到所有数据才能完整采集。还要注意一点卡淘的网页端和 APP 端接口可能不一样。网页端是 AJAX 请求APP 端可能是 JSON 接口两者的字段结构也有差异。我建议入门阶段先抓网页端因为网页端请求头简单没有 APP 那么多加密参数适合先跑通流程。1.3 工具选型Fiddler、Charles、Requests 怎么配合抓包工具我常用两个Windows 上用 FiddlerMac 上用 Charles两者本质都是中间人代理只是界面风格不同。Fiddler 偏工具型Charles 偏图形化但核心功能都一样都能看到完整的请求和响应也都能导出 HAR 文件。工具角色定位工具用途Fiddler / Charles抓包、分析接口、查看响应结构Fiddler 的 Copy as cURL把接口请求直接复制成 curl 命令方便转换到 PythonPython Requests根据抓包得到的接口编写批量采集脚本json 模块 / XPath从 JSON 或 HTML 中提取图片地址把这些工具串起来就是完整流程先抓包确认接口再用 curl 转代码最后写循环批量下载。这套流程不只适用于卡淘其他动态网站、APP 甚至小程序都能用同样的套路。记住工具只是辅助核心是你要能看懂接口、找到图片地址的变化规律。2. 抓包环节找到图片接口是关键2.1 配置抓包环境Fiddler / Charles 手机与 PC很多人在抓包第一步就栽了工具开着APP 完全没网或者全是 CONNECT 请求看不到实际内容。原因通常是 HTTPS 证书没配好。现在绝大多数接口都是 HTTPS如果你不做中间人解密抓包工具只能看到域名看不到路径和参数。Fiddler 配置流程我过一遍打开 Fiddler菜单栏找到 Tools Options HTTPS勾选 Decrypt HTTPS traffic。如果提示安装证书直接同意并选择“受信任的根证书颁发机构”。手机和电脑连同一个 WiFi查看电脑的局域网 IP命令行 ipconfig。手机 WiFi 设置里打开 HTTP 代理填写电脑 IP 和端口 8888。手机浏览器访问http://电脑IP:8888在页面里点 FiddlerRoot certificate 下载证书。安装证书后进入手机的系统证书信任设置把刚刚安装的证书开启完全信任。Android 9 以上和 iOS 都需要这一步否则 HTTPS 请求依然看不到内容。这里有个细节Android 7 之后很多 APP 默认不信任用户证书即使安装证书也可能抓不到包。轻量解决方案是用 Charles 配合手机上的“安装到系统证书”操作或者直接用抓包工具自带的模拟器。如果你的目标就是卡淘网页版直接在电脑上用浏览器打开卡淘然后让 Fiddler 作为系统代理也可以比手机配置简单适合入门。Charles 的配置逻辑一样区别是默认端口为 8888证书下载地址是http://charlesproxy.com/getssl。手机和电脑在同一个局域网的前提下设置代理后访问这个地址下载证书即可。2.2 卡淘接口的分析思路配置好抓包工具后先在电脑浏览器里打开卡淘的卡片列表页随便往下滚动几屏。这时回到 Fiddler 里你会看到密密麻麻的请求。不要慌先按“Filter”或者按域名过滤把图片 CDN 域名排除掉只看主站接口。我习惯按这几个步骤筛看请求类型找GET请求大部分列表数据都是 GET。看响应类型找后缀带.json或者Content-Type: application/json的请求。看请求路径路径里包含list、search、card、goods这类关键词的优先看。在 Fiddler 右侧选中请求点击“JSON”选项卡预览响应如果能看到imageUrl、imgList这些字段那就是目标接口。定位到目标接口后右键复制Copy as cURL先保存到文本文件里。这个操作在后续构造 Python 请求时会非常省力因为 curl 命令里包含了全部请求头、Cookie 和参数你只需要稍作转换就能变成 requests 代码。2.3 从响应数据里定位图片真实地址拿到接口响应后通常是一长串嵌套 JSON。举个例子接口返回结构可能是{ code: 0, data: { page: 1, list: [ { goodsId: 12345, title: 2023 Panini Prizm Card, cover: https://img.katao-example.com/12345.jpg?x-oss-processimage/resize,w_300 } ] } }这个cover字段就是缩略图地址注意地址尾部可能带处理参数比如?x-oss-processimage/resize,w_300。这种参数会导致你拿到的不是原图图片尺寸被压缩过。想拿原图的话可以先把问号后面的部分去掉再请求。但有些 CDN 会校验参数去掉后可能返回超大图或者干脆 403这时候需要自己实测一下。有的接口里图片不是一个地址而是一个数组比如imgList里面包含多张高清图。这时要用循环提取不要只取cover否则你会丢掉详情页里的高清图。图片地址如果只写了路径没写域名比如/group1/M00/AA/123.jpg那还需要在前面拼接主域名。具体域名从哪里找很简单在响应里搜索http看看其他字段是不是都指向同一个域名把那个域名取出来拼上去。3. 爬虫代码实战从接口到图片文件3.1 构造请求与关键参数抓包搞定接口后就可以写 Python 代码了。这里我强烈建议先用抓包工具里复制出的 cURL 来验证接口直接在终端跑一遍 curl确认返回数据无误后再转换成 requests 代码。举个例子你复制出来的 curl 大概长这样curl https://api.katao-example.com/card/list?page1size20 \ -H User-Agent: Mozilla/5.0 ... \ -H Referer: https://www.katao-example.com/ \ -H Cookie: session_idabc123转换成 Pythonimport requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., Referer: https://www.katao-example.com/, Cookie: session_idabc123 } url https://api.katao-example.com/card/list params { page: 1, size: 20 } resp requests.get(url, headersheaders, paramsparams, timeout10) print(resp.status_code) print(resp.text[:500])这里有两个重点。第一headers里的Referer和User-Agent尽量不要省略很多图片接口和列表接口都会校验这两个字段少了可能拿不到数据。第二建议用requests.Session()而不是裸的requests.get()因为 session 会自动保存 Cookie后续请求同一个域名的图片时Cookie 也能带过去减少 403 概率。还有一个小细节如果响应内容有乱码先别急着改 encoding看一下resp.apparent_encoding或者响应头里的charset。大多数 JSON 接口是 UTF-8直接用resp.json()没问题但如果是网页 HTML 响应可能是 GBK就需要按实际编码转换。3.2 解析 JSON 并提取图片 URL拿到 JSON 后解析本身就是一层层地取字段。用resp.json()得到字典然后按结构取值data resp.json() card_list data[data][list] for item in card_list: goods_id item[goodsId] cover_url item[cover] img_list item.get(imgList, []) or [] # 只处理有效的图片地址 all_imgs [cover_url] img_list for img_url in all_imgs: if not img_url.startswith(http): img_url https://img.katao-example.com img_url print(goods_id, img_url)这一步逻辑不复杂但容易踩字段不存在的坑。很多卡片可能没有imgList直接item[imgList]会 KeyError。用.get()加默认值是最稳妥的写法拉。另外列表里某些商品可能下架了cover字段是空字符串也要跳过。实际项目里我不会只打印而是写一个生成器函数专门负责翻页并提取所有图片地址。def fetch_all_image_urls(max_page10): session requests.Session() session.headers.update(headers) seen set() for page in range(1, max_page 1): resp session.get( url, params{page: page, size: 20}, timeout10 ) if resp.status_code ! 200: continue data resp.json() card_list data.get(data, {}).get(list, []) if not card_list: break for item in card_list: cover item.get(cover, ) img_list item.get(imgList, []) or [] for img_url in [cover] img_list: if img_url and img_url not in seen: seen.add(img_url) yield img_url # 控制请求节奏 time.sleep(0.8)注意我用了seen集合去重因为翻页时可能同一张图出现多次比如热门卡片被推荐到多个页面。去重能大大减少重复下载节省时间。3.3 下载保存与并发优化图片地址收集好后进入下载环节。最简单的下载代码长这样def download_image(img_url, save_path): headers_for_img { User-Agent: headers[User-Agent], Referer: https://www.katao-example.com/ } resp requests.get(img_url, headersheaders_for_img, timeout15) if resp.status_code 200: with open(save_path, wb) as f: f.write(resp.content) else: print(f下载失败: {img_url} - {resp.status_code})但直接一把梭下载几千张图片会非常慢而且容易触发反爬。建议用线程池加一点并发但不要太大。import os from concurrent.futures import ThreadPoolExecutor import time save_dir katao_images os.makedirs(save_dir, exist_okTrue) def download_one(img_url): # 用图片地址的哈希做文件名避免特殊字符问题 file_name str(hash(img_url)) .jpg save_path os.path.join(save_dir, file_name) if os.path.exists(save_path): return try: download_image(img_url, save_path) except Exception as e: print(f异常: {img_url}, {e}) time.sleep(0.3) with ThreadPoolExecutor(max_workers5) as pool: pool.map(download_one, all_image_urls)max_workers5是我实测比较稳的值既能加速下载又不会对服务器造成太大压力。如果你想要更完整的文件名可以用goodsId 序号来命名。但需要先把业务 ID 传进函数逻辑上稍微复杂一点不过能避免 hash 命名导致后续找不到对应卡片的问题。3.4 反爬应对与频率控制采集图片这件事难点不在“下载”而在“别被封”。卡淘接口虽然不会像大厂一样有复杂风控但请求频率一旦过高照样会返回 403 或者验证码。我常用的几招每次请求之间加随机延迟用time.sleep(random.uniform(0.5, 1.5))不要固定延时。同一个 Session 复用减少握手次数。控制并发数5 个线程足够不要在 ThreadPoolExecutor 里开 50 个。设置重试机制。比如requests的urllib3 Retry或者简单循环捕获超时和 5xx 状态码后自动重试 2 到 3 次。简单重试代码def get_with_retry(session, url, headers, retries3): for i in range(retries): try: resp session.get(url, headersheaders, timeout15) if resp.status_code in (200, 302): return resp except requests.exceptions.RequestException: pass time.sleep(1) return None请求头不用每次换保持稳定有时候反而比随机换更不容易被识别。很多平台风控看的是你是否像“真人”固定 IP 固定 UA 固定频率反而更像一个正常用户。真正容易触发风控的是没有规律的大并发和高频请求。4. 常见问题与排查技巧实录4.1 抓不到包的几种典型原因抓包工具配置半天结果 APP 或浏览器像没走代理一样这是最常见的坑。我整理了一个速查表现象可能原因解决办法APP 显示网络异常代理配置错误或端口不对检查电脑 IP 和 Fiddler 端口 8888手机上重新设置代理只能看到 CONNECT 请求HTTPS 证书未安装或未信任重新安装证书iOS 还需要在“证书信任设置”里打开完全信任请求时有时无手机和电脑不在同一网段确认连接同一个 WiFi关掉电脑防火墙或者放行 8888 端口浏览器打开没有代理效果系统代理未开启在 Fiddler 菜单 Tools WinConfig 里开启 Windows 代理还有一类情况卡淘 APP 可能做了“禁止代理”检测一旦检测到 WiFi 代理就拒绝连接。这种场景下建议直接用电脑浏览器打开卡淘网页版来抓包流程简单而且入门阶段完全够用。4.2 图片 403 / 防盗链怎么处理接口能拉到数据图片地址也能复制到浏览器直接打开但一到代码下载就 403。这种情况十有八九是防盗链。防盗链的原理其实很简单网站检查 HTTPReferer字段如果不是来自自己的域名服务器就拒绝返回图片资源。你的浏览器能打开是因为它会自动带上卡淘的页面地址作为 Referer而 Python 默认是不带 Referer 的。处理方式img_headers { User-Agent: Mozilla/5.0 ..., Referer: https://www.katao-example.com/ }在下载图片的请求中主动加上Referer指向图片所在页面的域名一般就能解决。如果加了 Referer 还是 403再看一下图片地址是不是 302 跳转到另一个域名跳转后的域名可能要求不同的 Referer 规则。这就不太好一概而论需要实际抓包看响应头。4.3 动态参数 / 签名问题有时候列表接口里会出现sign、ts、nonce这类参数。这类参数一看就知道是签名校验目的就是防止有人直接构造请求。遇到这种情况先不要头铁去逆向 JS先看是不是真的必须带。有些平台的签名参数是可选的你手动把 URL 里的签名删掉照样能返回数据。有些平台则是必带的不带签名直接 401。区分方法很简单构造一个不带签名的请求试试返回正常就说明校验形同虚设返回错误就说明需要处理签名。如果必须带签名入门阶段最省力的方式是“复用真实请求”。什么意思就是你在卡淘网页上正常操作抓包工具会拿到一个带着真实sign的完整 URL你把这个 URL 存下来直接请求短期内有缓存时间可以用来小规模采集。但这种方式的局限很明显签名会过期而且你没法随意翻页。真要突破签名需要去看前端 JS 里怎么生成参数属于另一个更深的主题这里不展开。4.4 容易踩的坑编码、路径、去重编码问题不只在网页里有JSON 接口偶尔也会遇到。如果使用resp.json()抛异常可以先打印resp.text看看是不是被转义了。一般在响应头里会有charsetutf-8但有些老接口是gbk可以采用resp.content.decode(utf-8, errorsignore)兜底。路径问题主要在 Windows 上踩。图片地址里如果包含?、/、:这些字符不能直接作为文件名。所以我在上面代码里用了hash(img_url)作为文件名就是避免这个坑。你也可以用goodsId作为文件名但要注意 goodsId 可能是字符串也可能带空格先做一次strip()。去重问题的核心是避免重复下载。我建议在生成图片地址阶段就去重用set()收集所有 URL而不是下载阶段再判断。下载阶段判断os.path.exists虽然也可以但多几次磁盘访问影响效率。更好的做法是在代码里维护一个已经成功下载的 URL 列表进程重启后还能继续跑。5. 合规与个人经验结尾5.1 关于数据使用必须先说清楚采集图片这件事自己研究技术没问题但用的时候要克制。卡淘平台的图片属于平台用户和平台方版权归属不一定你下载了就属于你。批量抓取别人平台的资源无论目的是整理资料还是二次发布都可能违反平台用户协议甚至涉及版权问题。我写这篇内容的初衷是拆解“抓包 动态接口分析”这套通用技能。你在学习时可以把卡淘当成练习对象但不要大规模、长时间、高频率地采集更不要用采集到的图片去做商业用途。真实项目里如果需要卡淘数据建议先找官方开放接口或者和平台方沟通合作这才是正道。5.2 我的一些实操心得做了这么多采集项目图片类采集最值得记住的一句话是先找接口再写代码。很多人急着写爬虫结果被反爬卡得怀疑人生回头发现只是没找对数据源。抓包工具就是你找数据源的地图多用几遍就会形成本能反应。具体到卡淘这类平台我个人的经验是先用网页端抓包跑通整个流程再考虑 APP 端。网页端请求头简单没有 app 层的加解密入门效率高。跑通后再去抓 APP 端会容易很多因为你已经知道要关注哪些字段、哪个接口是核心。另外抓包得到的 HAR 文件建议保存下来。不要只存在 Fiddler 里可以右键导出为 HAR 文件。以后想重新写解析逻辑直接用 HAR 文件里的记录就能还原接口比重新抓包方便得多。我自己遇到接口字段变动时第一反应就是翻旧 HAR 对比差异。最后分享一个小技巧批量采集前一定要先用 1 页数据跑通全流程。比如先请求第 1 页只下载 5 张图试试水确认文件名、路径、下载成功率都符合预期再去循环所有页面。我就是吃过“下载到一半才发现图片全是缩略图”的亏当时 5000 多张图白下了白白浪费将近一个小时。这个小步骤只要花两分钟能省下后面一大半调试时间值得养成习惯。