资讯详情 百度图片爬虫实战:从acjson接口解析到批量下载的完整指南
📅 2026/10/3 5:47:41
1. 动手之前先搞懂百度图片到底是怎么加载数据的很多人一说到“爬取百度图片”第一反应是用requests把页面HTML拉下来然后用正则或者BeautifulSoup去抠图片地址。这个思路放在十年前没问题放在今天大概率翻车。因为百度图片早就不是传统的服务端渲染页面了你在浏览器里看到的结果几乎全部是JavaScript动态请求后台接口拿到的数据渲染出来的。HTML源码里能看到的只是一堆框架代码和初始化的空壳真正的图片地址藏在Network面板的网络请求里。我最初踩过这个坑对着HTML翻了一下午连一张图片链接都没抠出来。后来打开开发者工具切到Network刷新页面看到几十个请求像流水一样滚过去其中有一个名为acjson的XHR请求返回内容是一大段JSON。从那一刻开始整个任务的思路就彻底变了要爬的不是页面而是这个接口。理解了这一层你就明白了为什么网络上大多数百度图片爬虫教程的核心都是围绕这个acjson接口展开的。这里要说清楚一个关键点acjson是一个标准的HTTP GET请求不是POST参数都写在URL里服务端返回的是JSON格式的数据。这意味着我们根本不用上Selenium、Playwright这类重型浏览器自动化工具用Python自带的requests就能轻松搞定。数据链路可以简单理解为浏览器前端每次滚动翻页向acjson接口发送一次带参数的GET请求接口返回30张图片的信息前端再渲染到页面上。我们做的爬虫本质上是把这个过程自动化自己构造请求、自己接收JSON、自己解析、自己下载保存。所以整篇文章的核心就是围绕“寻找接口、构造请求、解析JSON、下载图片”这四步展开。接下来我先带你完整拆解一次请求的参数结构再一步步写代码最后把常见的坑和排查思路都交代一遍。2. 拆解请求参数弄懂每个参数的含义比直接抄代码更重要2.1 核心接口和参数说明先用最简单的话概括百度图片的搜索结果接口基本格式是https://image.baidu.com/search/acjson后面跟着一大串query参数。我前端时间抓包了一个完整请求参数整理出来大概有三十多个但真正每次都起作用、必须关注的其实就那六七个。下面是核心参数的对照表参数名含义典型值/取值必填性tn请求类型标识resultjson_com必填word搜索关键词需要URL编码如猫→%E7%8C%AB必填pn起始位置页码偏移0、30、60、90...必填rn每次返回数量建议30过大容易被限制必填ie输入编码utf-8通常固定oe输出编码utf-8通常固定z图片尺寸过滤0为不过滤可指定选填width宽度过滤配合z使用选填height高度过滤配合z使用选填gsm页码相关参数通常等于pn值选填但建议带上这个表格里最容易让人迷惑的是pn和rn的关系。rn表示每页返回多少条结果pn表示从第几条开始取。比如你想取第1到30张pn0, rn30想取第31到60张pn30, rn30。本质上这就是一个“偏移量条数”的经典分页模型理解了这一点翻页逻辑就通了。有朋友可能会问为什么不用page这种参数这就是百度的接口风格它不用页码而是用偏移量。你仔细想一下pn30, rn30其实就相当于“从第31条开始取30条”这个逻辑比页码更灵活比如你想从第45条开始取pn45就行如果用纯页码就做不到这么细。理解了这个小设计后面构造翻页循环的时候就不会犯迷糊。2.2 请求头和总结请求“长得像浏览器”参数搞明白之后还有一个更容易被忽略但至关重要的事情请求头。你在爬虫里发一个GET请求如果啥headers都不带直接裸奔访问百度服务器返回的可能不是JSON数据而是一个安全验证页面或者直接拒绝。这不是百度独有的套路几乎所有平台都会校验请求的来源身份。我实测下来最关键的请求头只有两个User-Agent和Referer。User-Agent告诉服务器“我是一个浏览器”推荐用Chrome或者Edge的完整UA字符串Referer告诉服务器“我是从百度图片搜索页面跳转过来的”一般填https://image.baidu.com/。如果不加Referer你在下载图片的时候会遇到403 Forbidden这个后面单独讲。这里顺带提一个细节有些教程会让你把Cookie也带上说这样更稳定。我在实测中发现如果不涉及翻页太深比如只爬前五六百张不带Cookie也能正常工作但如果想要爬大量数据带上一次浏览器里复制的完整Cookie确实能降低被拦截的概率。不过要提醒一句Cookie是会话凭证长时间使用有失效问题如果某天请求突然不好使了先检查是不是Cookie过期了。Cookie属于“锦上添花”不是“雪中送炭”不要过度依赖。做一个完整请求的流程图简单描述一下就是拼接URL → 带上headers → 发起GET请求 → 判断状态码 → 拿到JSON → 解析。别急着写代码先在浏览器里验证一遍这个链路能避免很多无意义的尝试。3. 手把手写一个能跑的百度图片爬虫3.1 第一步构造搜索接口请求拿到JSON数据环境方面只需要Python 3.6以上版本第三方库用requests就够了。安装命令一行pip install requests。其他库都用标准库比如json、time、os不需要额外安装。可能有朋友会问解析JSON用不用jsonpath我的建议是纯Python字典操作就够了返回的JSON结构虽然嵌套深但核心数据字段很稳定没必要引入额外的依赖。下面直接给出一段可以运行的代码用来获取搜索关键词“猫”的第一页数据30条import requests import json # 搜索关键词 keyword 猫 # 起始位置第一页从0开始 pn 0 # 每页返回数量 rn 30 # 基础URL url https://image.baidu.com/search/acjson # 请求参数 params { tn: resultjson_com, word: keyword, pn: pn, rn: rn, ie: utf-8, oe: utf-8, z: 0, gsm: str(pn), } # 请求头 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://image.baidu.com/ } # 发起请求 response requests.get(url, paramsparams, headersheaders, timeout10) print(状态码:, response.status_code) # 解析JSON data response.json() # 核心数据在data[data]列表里 items data[data] print(返回条数:, len(items))代码本身很简单但我建议你先跑一次观察返回的结果。如果状态码是200items长度是30说明请求成功了。如果返回的不是JSON或者items是空的大概率是请求头或者参数有问题可以先把代码复制到浏览器地址栏里手动访问一次看看返回内容是什么这样排查起来非常直观。一个小提示直接打印整个data看不方便因为内容太长了。建议打印items[0].keys()看一下单条数据里有哪几个字段你会看到thumbURL、middleURL、objURL、fromURL等等别的不用管我们的目标就是图片直链。3.2 第二步解析JSON提取图片地址先弄清楚一个概念百度图片返回的数据里每条图片信息包含多个图片地址字段。我实际观察下来常用的有三个thumbURL缩略图地址体积小加载快但清晰度有限。middleURL中等尺寸地址大部分场景下清晰度够用。objURL原图地址体积最大但有时候是加密或转义过的不能直接下载。很多新手一上来就取objURL以为原图一定最好。但实测发现objURL存在“真假”的问题有些objURL是经过JS加密处理的直接requests请求会报错或者拿到的根本不是图片。更稳妥的做法是优先用middleURL清晰度基本能满足大多数桌面壁纸、数据集构建的需求如果确实需要原图可以尝试用objURL但需要在代码里加一个异常处理下载失败时自动回退到middleURL。解析代码非常简单# 提取所有图片地址 image_urls [] for item in items: # 个别item可能是空字典需要跳过 if not item: continue # 优先取中图没有就取缩略图 url item.get(middleURL) or item.get(thumbURL) if url: image_urls.append(url) print(有效图片地址数量:, len(image_urls))这就有意思了。你可能会发现有时候返回了30条数据但拿到的有效图片地址少于30个。这是正常现象原因是部分搜索结果会混入一些无效条目比如广告推荐位或者已经被下架的图片。不用纠结多爬一页就能补回来。3.3 第三步下载图片到本地地址拿到了剩下的就是按部就班地下载。下载的细节其实不少最核心的一个坑就是大批量下载时遇到的403 Forbidden。原因很简单图片一般存储在百度自己的CDN域名下比如img0.baidu.com当你用requests去请求这个图片地址时如果不带Referer服务器会认为这是一个“盗链”请求直接拒绝。解决方案就是下载图片时也带上Referer问题立刻就消失了。另外两个实用技巧一是要处理超时因为有些图片地址可能已经失效请求会一直挂着必须设置timeout二是要控制下载速度别一次性几十个请求并发冲过去很容易被服务器限流最好加个短暂的随机延时。我常用的下载代码是这样的import os import time import random # 创建保存目录 save_dir fbaidu_images/{keyword} os.makedirs(save_dir, exist_okTrue) download_headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://image.baidu.com/ } for idx, img_url in enumerate(image_urls): try: # 下载图片 resp requests.get(img_url, headersdownload_headers, timeout10) # 判断请求是否成功 if resp.status_code 200 and resp.content: # 根据URL后缀提取扩展名没有的话默认.jpg ext os.path.splitext(img_url)[1] if ext.lower() not in [.jpg, .jpeg, .png, .gif, .webp]: ext .jpg file_name f{idx:03d}{ext} file_path os.path.join(save_dir, file_name) # 写入文件 with open(file_path, wb) as f: f.write(resp.content) print(f已下载: {file_name}) else: print(f下载失败状态码: {resp.status_code}地址: {img_url}) except Exception as e: print(f下载异常: {e}地址: {img_url}) # 随机延时避免请求过快 time.sleep(random.uniform(0.3, 0.8))跑完这段代码你本地应该就有三十张左右的“猫”图片了。到这里一个最小可用的百度图片爬虫就算跑通了。整个过程没有用到任何高级技术核心就是requests发请求、json解析、文件写入这三板斧。3.4 完整版封装成函数支持批量翻页下载第一版代码跑通之后下一步自然是批量翻页。把上面的逻辑封装成一个函数然后循环调用pn0,30,60...就能连续抓取多页。这里我额外加了一个去重功能和总数控制避免重复下载也避免无限循环改不停停不下来。def download_baidu_images(keyword, total_num100, save_dirimages): 爬取百度图片并保存到本地 :param keyword: 搜索关键词 :param total_num: 期望下载的图片总数 :param save_dir: 保存目录 # 创建目录 final_dir os.path.join(save_dir, keyword) os.makedirs(final_dir, exist_okTrue) # 请求参数 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://image.baidu.com/ } base_url https://image.baidu.com/search/acjson # 已下载的图片地址集合用于去重 downloaded_urls set() # 当前已下载的数量 count 0 # 翻页起始位置 pn 0 while count total_num: params { tn: resultjson_com, word: keyword, pn: pn, rn: 30, ie: utf-8, oe: utf-8, z: 0, gsm: str(pn), } try: resp requests.get(base_url, paramsparams, headersheaders, timeout10) resp.raise_for_status() data resp.json() items data.get(data, []) except Exception as e: print(f请求第{pn}页失败: {e}) break # 本页是否包含有效数据 page_has_valid False for item in items: if count total_num: break if not item: continue img_url item.get(middleURL) or item.get(thumbURL) if not img_url: continue # 去重 if img_url in downloaded_urls: continue downloaded_urls.add(img_url) # 下载图片 try: img_resp requests.get(img_url, headersheaders, timeout10) if img_resp.status_code 200 and img_resp.content: ext os.path.splitext(img_url)[1] if ext.lower() not in [.jpg, .jpeg, .png, .gif, .webp]: ext .jpg file_name f{count:04d}{ext} file_path os.path.join(final_dir, file_name) with open(file_path, wb) as f: f.write(img_resp.content) count 1 print(f[{count}/{total_num}] 已下载: {file_name}) page_has_valid True # 每下载一张随机延时不要太快 time.sleep(random.uniform(0.2, 0.5)) except Exception as e: print(f下载图片失败: {e}) # 如果没有新的有效数据说明翻到底了结束循环 if not page_has_valid: print(没有更多数据了提前结束) break # 翻页 pn 30 # 翻页之间稍作休息 time.sleep(random.uniform(1, 2)) print(f下载完成共保存{count}张图片到 {final_dir}) # 调用示例爬取100张“猫”的图片 if __name__ __main__: download_baidu_images(猫, total_num100, save_dirbaidu_images)这段代码我实测跑下来比较稳100张图的下载时间大概在三到五分钟具体看网络情况。核心的改进点有两个一是用downloaded_urls集合做去重因为翻页过程中百度偶尔会返回重复结果去重能避免同一张图下载两次二是用page_has_valid判断是否还有有效数据如果连续翻页都没有新图片就自动终止不会无限循环下去。4. 爬取过程中的常见问题与排查思路4.1 状态码200但拿不到数据全是空字典这是我遇到最多的问题没有之一。明明请求返回了200接口也返回了JSON但data[data]这个数组里的元素全是空字典或者整条信息字段缺失。原因通常是请求参数里的某个字段缺失或者被服务器判定为异常请求。排查思路很简单先在浏览器里手动访问一次同样的URL如果浏览器能正常返回数据而代码不行对比浏览器和代码的请求头差异通常就是少带了某个Header。另一个常见原因是搜索词过于生僻百度图片确实没搜到相关内容这时候返回的数据本身就是空的换一个热门关键词再试就行。4.2 图片下载时遇到403 Forbidden403这个问题我在前面已经反复强调过核心就是防盗链机制。图片所在的CDN服务器会检查请求头里的Referer字段如果发现不是从百度页面过来的就直接拒绝。解决办法是在下载图片的请求中带上Referer: https://image.baidu.com/。如果你带了Referer还是403检查一下你的User-Agent是不是被识别为爬虫了——有些UA很容易被CDN策略拦截建议直接用完整、最新版的Chrome UA。顺便多说一句如果你是从objURL字段拿到的原图地址遇到403的概率会明显增高。原因是部分原图地址并不是百度自家CDN存储的而是跳转到原始网站的图片地址那些网站往往有更强的防盗链策略。所以我的建议是优先用middleURL稳定第一。4.3 下载到了图片但文件打不开或者全是乱码文件打不开通常只有一种可能写文件时拿到的不是图片数据而是HTML错误页面。这种情况的根因是图片地址失效服务器返回了一个404错误页但你的下载逻辑只判断了状态码200没有判断返回内容的类型。更完善的做法是检查响应头的Content-Type字段确认是以image/开头的类型再保存。我自己在实际操作中还会额外检查resp.content的长度如果小于5KB就大概率不是正常图片直接跳过。4.4 高频请求触发安全验证请求被封如果一口气爬几千张图脚本很容易被百度识别为异常流量然后触发安全验证表现为请求返回的JSON里不再有图片数据而是一个验证页面的HTML。这个问题的解决思路就一个词克制。控制请求频率每页请求之间至少间隔1到2秒每张图片下载之间间隔0.2到0.5秒别贪。另外可以用随机延时替代固定延时让请求间隔看起来更接近人类行为。说到底爬虫的本质是模拟正常用户行为越像真人被识别的概率就越低。5. 几个能少踩坑的实操经验在最后我把自己反复折腾过几次之后沉淀下来的经验按重要程度排个序每一条都是实打实的泪和教训。第一先小规模试跑再放开量。别一上来就设置total_num10000先把total_num设置成30跑通全流程之后观察一下图片质量、有无重复、有无下载失败的确认没问题了再放大数量。我见过太多人第一次跑大数据量结果跑到一半发现存的图有一半是打不开的白白浪费时间和带宽。第二保存图片的格式不要完全依赖URL后缀。百度图片返回的URL后缀经常是_500.jpg、.jpeg这种有时候甚至没有后缀。我建议下载成功后用文件头内容来判断真实格式或者简单一点统一保存成.jpg。绝大多数情况下即使原图是PNG格式保存成.jpg后缀也能正常打开因为图片内容的实际格式是写在文件头里的后缀只影响部分看图软件的首选解码器。第三定期更换搜索关键词。如果你需要构建一个较大的数据集不要盯着一个关键词死磕。百度图片对于同一个关键词的搜索结果翻到后面会出现大量重复图片或低相关图片。我的习惯是每个关键词只爬200到300张然后换个更细化的关键词继续比如“猫”爬完换“猫 壁纸”再爬效果远好于一个词硬爬2000张。第四对于下载到的图片建议做一个二次筛选。脚本能保证你下载到的是图片但不能保证图片内容和关键词完全匹配。百度图片的搜索结果里会混入一些近似图甚至无关图如果需要高质量的数据集建议下载完成后快速翻一遍目录把明显不对的删掉。最后再说一个扩展方向。这套“找到接口、构造请求、解析JSON”的思路本质上可以迁移到很多类似网站。科技社区里经常有人讨论懂车帝二手车信息爬取、得物数据爬取、BOSS直聘职位数据爬取虽然每个平台接口的参数、加密方式、反爬策略都不一样但排查流程是共通的打开开发者工具找到XHR请求分析参数模拟请求。遇到一个陌生的平台先从Network面板下手永远是最快的破局方式。当然不同的平台有不同的数据使用条款爬取时要注意遵守平台的robots协议和相关规定控制频率只把技术用于学习研究。