ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

百度图片爬虫实战:突破动态加载,从抓包到Python接口直取

百度图片爬虫实战:突破动态加载,从抓包到Python接口直取 网上关于爬取百度图片的教程十篇里至少八篇照着跑第一天就报错。原因很简单百度图片改版过不止一两次那些教你去搜索页HTML里用正则硬抠img标签的老教程对应的是很多年前的静态页面结构——你现在打开百度图片搜索页查看网页源码一个真实图片链接都找不到全是JS变量和一段一段的异步请求逻辑。于是很多人以为自己代码写错了反复检查正则、换解析库其实问题根本不在这而是你没找对数据到底在哪儿。我的经验可以浓缩成一句话爬百度图片不要和页面死磕去找它背后的数据接口。页面只是壳图片数据是浏览器在后台向某个API发请求拿到的我们要做的就是把浏览器发出的这个真实请求原样复刻一遍。这篇文章我会从抓包开始把接口的参数拆清楚再给你一套可以直接跑通的Python代码最后把我实际运行过程中踩过的坑——403、验证码、字段加密、翻页重复——全部摊开讲明白。无论你是刚学爬虫的新手还是想快速搞一批图片素材做数据集这份内容都能直接用。1. 为什么你照着老教程写一搜就变“空页面”——动态加载的真相想理解百度图片的爬取思路第一步不是写代码而是搞清楚图片数据到底是怎么出现在你屏幕上的。百度图片搜索结果页属于典型的Ajax动态加载页面。你在浏览器里打开image.baidu.com输入关键词回车地址栏里的URL确实指向了一个搜索页地址但服务器返回给你的HTML只是一个空壳里面有搜索框、导航栏、部分页面结构的DOM骨架以及一大段负责“干活”的JavaScript代码。真正的图片信息——缩略图地址、原始图地址、图片尺寸、来源页面——全都不在HTML里而是等页面加载完之后由JS在后台触发一个HTTP请求从专门的图片数据接口拉回来再动态拼接渲染到页面上的。这种情况下你用requests.get(搜索页URL)拿到的HTML自然一个图片链接都找不到。这不是代码的问题也不是被反爬了而是浏览器和普通爬虫拿到的资源本来就不一样浏览器会执行JS爬虫不会。这也解释了为什么网上老教程里的正则表达式现在全部失效——正则能提取的前提是数据真实存在于HTML源码中现在数据不在那儿了你再精通正则也无济于事。理解这一层之后摆在你面前的有两条路。第一条路上Selenium或Playwright这类浏览器自动化工具让它开一个真实的浏览器去加载页面、执行JS、渲染完整结果然后再提取图片。这条路在新手教程里很常见因为它逻辑直观——“我在浏览器里能看到什么代码就能拿到什么”。但它有个致命缺点慢。每加载一页都要等图片全部渲染完一页30张图同时还要处理网络请求和资源加载翻个几十页就要等很久。而且浏览器自动化特征明显被识别后更容易触发验证码和风控策略。第二条路直接模拟浏览器在后台发出的那个Ajax接口请求。数据最终就是从那个接口返回的接口拿到的JSON里包含了图片的所有信息。我们只需要分析出接口地址、构造好参数、带上必要的请求头一个requests.get就能把浏览器JS执行半天才能拿到的数据快速取回来。速度快、代码简单、稳定性高唯一的技术门槛就是需要你会抓包分析。我强烈建议你选第二条路。这个思路不仅适用于百度图片放到其他动态加载的网站上几乎是通用的破局方法论。2. 打开抓包工具锁定真正的图片数据接口确定用接口方案之后第一步永远是抓包不要上来就瞎猜接口名。打开Chrome浏览器按F12进入开发者工具切到Network面板刷新一下百度图片搜索页然后在搜索框里输入一个你想爬的关键词点搜索。这时候Network面板里会刷出一堆请求不要被它们吓到重点只看XHRXMLHttpRequest或Fetch类型的请求因为图片数据的异步加载基本都是走这两类。回到搜索页翻到底部触发了下一页加载后你会发现一个名为acjson的请求冒出来这就是核心接口。我以它为例完整地址长得像这样https://image.baidu.com/search/acjson?tnresultjson_comipnrjct201326592is0fpresultqueryWord%E7%8C%ABcl2lm-1ieutf-8oeutf-8adpicidst-1zic0hdlatestcopyrightword%E7%8C%ABs0slidespq5sim_style0m1267601782appimg.browse.channeltn20n30pn30rn30gsm1e...参数非常多但不是每个参数都必需。我实际测试下来只要保留下面这一组核心参数接口就能稳定返回数据参数含义示例值说明tn接口类型标识resultjson_com固定值不要改ipn页面标识rj固定值word搜索关键词%E7%8C%AB需要URL编码queryWord原始搜索词%E7%8C%AB通常和word一致pn图片起始索引0、30、60第1页从0开始第2页30依次递加rn每页返回数量30一般传30gsm十六进制页码偏移0、1e、3c是pn的十六进制字符串这里有个细节值得多说一句。gsm参数在请求串里看起来怪怪的有经验的爬虫工程师一眼就能认出来它是十六进制pn30时gsm1e30的十六进制是1epn60时gsm3c60的十六进制是3cpn0时就是0。这个参数在早期接口校验严格的时候是必带的现在部分场景下丢掉也能正常返回但既然我们不差这一行代码建议还是按规则拼上兼容性最好。关于关键词参数的编码需要用urllib.parse.quote()对中文关键词做URL编码而不是直接把中文拼在URL里。比如“猫”编码后是%E7%8C%AB如果你直接把中文放进去服务端解析时很可能出现乱码或者直接拒绝请求。请求头里必须重点关注两个字段User-Agent和Referer。User-Agent用来模拟真实浏览器环境别用默认的python-requests目标服务器一眼就能识破你不是真人操作。Referer要设置成https://image.baidu.com/这个字段在反爬链路里很关键部分风控策略就是靠它来判断请求是不是从自己页面发出的。你可以在浏览器里直接访问下面这个URL测试一下把word参数换成其他关键词看看返回的JSON结构是什么样的https://image.baidu.com/search/acjson?tnresultjson_comipnrjword%E7%8C%ABqueryWord%E7%8C%ABpn0rn30gsm0返回结果是一个标准的JSON对象核心数据都存在data字段里。data是一个数组每个元素对应一张图片的信息里面有thumbURL缩略图地址、middleURL中等尺寸图地址、hoverURL、fromURL来源页面地址、fromPageTitle来源页面标题等字段。我们下载图片时优先取thumbURL或middleURL这两个字段是真实的图片CDN地址稳定性高而早期教程里常用的objURL现在经常会被反爬策略加密或者重写成无效字符串用起来容易踩坑。3. Python代码实战从URL到本地图片的完整流程接口分析清楚了代码就水到渠成。整个爬虫分成三个模块构造请求拉取图片URL列表、逐个下载图片、校验下载结果是否真的是图片。先准备环境。我用的Python版本是3.10第三方库只需要一个requests安装命令pip install requests不需要Selenium不需要Scrapy一个requests就能搞定全套流程这也是接口方案的巨大优势。第一段代码从百度图片接口获取指定关键词、指定页数的图片URL列表import requests import urllib.parse import time from pathlib import Path 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/ } def fetch_image_urls(keyword: str, pages: int 5, per_page: int 30) - list: api_url https://image.baidu.com/search/acjson image_urls [] encoded_keyword urllib.parse.quote(keyword) for page in range(pages): pn page * per_page params { tn: resultjson_com, ipn: rj, word: encoded_keyword, queryWord: encoded_keyword, pn: pn, rn: per_page, gsm: hex(pn)[2:] if pn else 0 } try: resp requests.get(api_url, paramsparams, headersHEADERS, timeout15) resp.raise_for_status() data resp.json() except Exception as e: print(f第{page 1}页请求失败: {e}) continue for item in data.get(data, []): if not item: continue img_url item.get(thumbURL) or item.get(middleURL) or item.get(hoverURL) if img_url: image_urls.append(img_url) print(f第{page 1}页获取到 {len(image_urls)} 个图片链接) time.sleep(1) return image_urls第二段代码负责把图片下载到本地并校验文件是不是真图片def download_image(url: str, save_path: Path) - bool: try: resp requests.get(url, headersHEADERS, timeout15) if resp.status_code ! 200: return False content_type resp.headers.get(Content-Type, ) if image not in content_type: return False save_path.write_bytes(resp.content) return True except Exception: return False def detect_suffix(file_path: Path) - str: with open(file_path, rb) as f: head f.read(12) if head[:2] b\xff\xd8: return .jpg if head[:8] b\x89PNG\r\n\x1a\n: return .png if head[:6] in (bGIF87a, bGIF89a): return .gif if head[:4] bRIFF and head[8:12] bWEBP: return .webp return .jpg第三段代码主流程把URL列表去重、下载、校验并重命名def main(): keyword 猫 pages 5 save_dir Path(f./baidu_images/{keyword}) save_dir.mkdir(parentsTrue, exist_okTrue) urls fetch_image_urls(keyword, pagespages, per_page30) seen set() count 0 for url in urls: if url in seen: continue seen.add(url) tmp_path save_dir / ftmp_{count} ok download_image(url, tmp_path) if not ok: print(f[FAIL] {url}) continue suffix detect_suffix(tmp_path) final_path save_dir / f{keyword}_{count}{suffix} tmp_path.rename(final_path) count 1 print(f[OK] {count}: {final_path.name}) time.sleep(0.5) print(f完成共下载 {count} 张图片保存目录: {save_dir})把三个代码块按顺序拼在一起修改main()里的关键词和页数直接运行就能把图片批量保存到本地。整个流程是串行的页与页间隔1秒、图片与图片间隔0.5秒这个节奏我个人实测下来比较安全既不会慢到让人抓狂也不容易触发限制。想更快可以自行把time.sleep调低但我不建议你为了那几分钟时间把IP搞进风控名单。有一点要提醒百度图片接口返回的data数组里偶尔会混进空字典或字段缺失的占位项。代码里if not item就是用来过滤这种脏数据的。如果漏掉这个判断后面item.get(thumbURL)虽然不会报错但会把大量无效空值加进列表影响去重逻辑的准确性。4. 跑起来之后翻车现场复盘403、验证码、加密字段的真实解法代码能跑通只是第一步真正让你增长经验的是跑起来之后遇到的各种问题。我把自己在实际运行中踩过的坑和排查过程完整记录下来这些问题在官方文档里永远查不到但几乎每个人都会遇到。4.1 下载图片时大面积403先别怀疑IP被封锁第一次跑完整流程我遇到的第一个问题就是接口请求一切正常图片URL也成功拿到了但调用下载函数时大量返回403。当时我第一反应是IP被百度封了差点就上代理。后来冷静下来一分析接口请求没问题说明IP没被封锁问题肯定出在图片下载那一步的请求头设置上。我检查了下载函数发现我只复制了接口请求的User-Agent忘记带上Referer。百度图片的CDN节点对Referer的检查非常严格图片请求到达CDN时如果没有Referer或者Referer不是百度图片域名CDN直接判定为盗链返回403。解决办法一目了然给下载请求也加上和接口请求一样的HEADERS保证Referer字段存在。我上面的download_image函数里已经把这个坑填上了如果你是自己写的代码务必检查这一行。4.2 返回的不是JSON而是HTML页面说明触发风控了相比403更隐蔽的一个坑是requests.get(接口地址)返回的状态码是200但resp.json()直接抛异常或者解析出来的数据是空的。我打印了一下响应内容发现返回的是一整段HTML甚至还带一个验证码页面。这个场景我排查了挺久。状态码200很容易让人误以为请求成功但百度图片的风控策略有时候不会直接拒绝你而是悄悄把该返回JSON数据的接口改成了返回一个验证页面。这通常是因为请求频率太高或者短时间内同一个关键词翻页翻得太快。判断方法很简单请求完先看Content-Type头如果响应头里写的不是application/json而是text/html那基本就是被风控拦截了。面对这种情况正确的做法不是加大并发硬刚而是主动降速把翻页间隔从1秒提高到3秒把下载间隔从0.5秒提高到1秒然后等待几分钟再继续。如果你只是个人爬点图片素材这个力度足够如果连续多个IP都被拦截说明目标站点的风控升级了这时候纠结请求头意义不大先停手调整策略再上。4.3 图片链接出现转义字符和反斜杠怎么处理我在用正则提取图片地址时还踩过一个低级但典型的坑。百度图片搜索页源码里有一部分图片地址是被转义过的形如https:\/\/img0.baidu.com\/it\/u12345,6789fm253fmtautoapp138fJPEG?w500h500注意那些\/和反斜杠如果直接当URL去请求requests会因为它不是规范URL而报错。但如果你用的是上面代码里的方式——resp.json()解析响应——就不会遇到这个问题因为JSON解析器会自动把转义字符还原成正常字符。如果你是从HTML源码里正则提取链接就必须要做一次清洗处理cleaned_url dirty_url.replace(\\/, /).replace(\\, )所以我的建议很明确能用JSON接口就用JSON接口少碰HTML源码正则提取。接口返回的数据结构清晰、字段稳定还能自动处理转义字符比正则法省心太多。4.4 翻页翻到最后全是重复图片别把锅甩给代码图片列表里出现重复URL这个情况我调试了很久才想明白。百度图片的接口虽然支持pn参数无限往后翻但搜索结果的海量图片资源并不是彻底分页存储的翻到一定深度之后返回的图片质量会下降且重复率急剧升高——你可能连续翻好几页返回的都是同一个结果集里的图。这不是代码逻辑问题是接口自身的特性。针对这种情况最实用的两个对策是第一控制合理的翻页深度。我自己抓普通关键词时通常只翻5到10页每页30张也就是150到300张图片够用且质量高。非要翻几百页你会发现大量时间浪费在下载重复图片上。第二做好URL去重。上面的代码里用了seen集合通过if url in seen来跳过重复链接这个看似简单的逻辑在数据量上去之后能省下大量无效下载时间。我用一张表格把上面几个坑汇总一下方便你对照排查现象根因对策下载大量403图片CDN防盗链缺少Referer请求头补全Referer字段返回HTML而非JSON请求频率过高触发风控降速、降低翻页频率、等待后再试URL带转义字符从HTML源码正则提取导致改用接口JSON解析或手动replace清洗翻页后面全是重复图接口深度结果集重复控制翻页深度URL去重data里大量空字典接口返回占位数据用if not item跳过干净5. 不只是百度图片这套“接口优先”思路怎么迁移到其他场景百度图片只是你爬虫练习的第一站。真正有价值的是你从这篇文章里学会的方法论——先找接口再构造请求最后解析数据。这套链路往任何一个网站迁移都是通的只是难度和细节不同。拿最近几类常见需求来说。有人问懂车帝的二手车信息怎么爬有人问得物的数据抓取怎么做还有人想爬淘宝商品评论、爬酷狗音乐做可视化。这些平台和百度图片有一个根本差异它们都有完善的账号体系和复杂的签名校验机制。百度图片你带上Referer和User-Agent就能畅通无阻但懂车帝、得物、淘宝这类平台接口请求里通常要带签名参数、加密token甚至需要先过滑块验证。原理是同一个但技术门槛直接从入门跳到了进阶。我不建议新手一上来就啃这种硬骨头更不推荐去研究突破风控的灰色手段先把百度图片这种公开数据源练熟再逐步接触带鉴权的接口路径会顺畅得多。另外这两年出现了一些新的工具库也值得关注。比如热词里提到的scrapling它对动态页面做了不少简化处理支持异步抓取和更智能的选择器和Selenium相比体量更轻。但我的观点不变工具只是辅助如果你连数据从哪个接口来都没搞明白换再新的库也是治标不治本。先把抓包和接口分析的基本功练扎实用什么库都是锦上添花。还有人拿爬图片来做大数据方向的事比如热词里的“大数据架构图 百度图片”。把抓下来的图片素材整理成数据集这就进入数据工程的范畴了按主题目录归档、用hashlib计算文件哈希去重、记录原始URL和时间戳生成索引。这些清洗步骤看着不起眼却是后续做模型训练、素材检索、可视化分析的地基。我给一个简单的去重思路下载后用hashlib.md5(file_path.read_bytes()).hexdigest()对文件做哈希相同哈希值就意味着图片内容完全相同直接删掉冗余副本。这种方式比单纯比对URL更可靠因为同一个URL下可能返回不同尺寸的图片。再往音频、视频场景扩展思路也是一致的。爬取酷狗音乐做可视化本质还是先找到音频文件的真实播放地址再处理盗链和格式问题。百度图片的CDN用Referer校验音频平台的防盗链可能还要拼时间戳、签名参数。套路不变复杂度递增仅此而已。我在实际使用中还有一个体会面对这类媒体资源爬虫给图片数据集建立清晰的命名规范比多抓一百张图更有价值。不要用一连串无意义数字做文件名直接把“关键词_序号”作为文件名图片信息一目了然。如果你的关键词里带中文注意在文件系统里做好兼容处理Windows系统下有些中文字符和特殊符号会出幺蛾子。这些小细节等你数据量攒到几万张图片时就会感谢当初的自己。
返回列表