1. 项目概述
1.1 这个项目到底在解决什么问题
先说结论:所谓“爬取百度图片”,核心要解决的并不是“怎么下载一张图”,而是“怎么把搜索引擎里的批量图片结果,变成你能自由使用的本地素材库”。
我最早接触到这个需求,是在做一次电商详情页素材整理的时候。运营那边给的图东一张西一张,分辨率参差不齐,风格也不统一,整理到崩溃之后,我第一反应就是:与其手动翻百度图片一篇一篇存,不如直接写个脚本,把搜索关键词的图片结果批量拉下来,再按尺寸、格式筛一遍。于是就有了这个项目。
这个项目适合谁?三类人比较刚需:
- 自媒体/新媒体运营:需要大量配图素材,但不想一张张手动下载;
- 数据爱好者/入门爬虫学习者:想找一个“不涉及复杂登录、反爬门槛低、但又有真实工程细节”的练手项目;
- 设计师/电商从业者:需要快速收集某个风格的参考图或同类竞品图,做视觉调研。
它解决的问题很朴素:把“人工搜索→逐张保存→手动重命名”这条链路,替换成“输入关键词→自动抓取→自动筛选→批量落地”。看起来只是省了鼠标点击,实际上省掉的是你反复切换页面、整理文件、核对图片是否可用的隐性时间成本。
1.2 技术栈与整体思路
先给一个最常用的技术组合,适合绝大多数场景:
| 组件 | 选型 | 说明 |
|---|---|---|
| 语言 | Python 3.8+ | 生态成熟,爬虫库和解析库最全 |
| 请求库 | requests | 简单直接,配合模拟UA即可 |
| 解析库 | re / BeautifulSoup4 | 百度图片反爬力度较弱,正则提取很直接 |
| 数据存储 | 本地文件系统 | 按关键词建目录,文件有序命名 |
| 并发控制 | ThreadPoolExecutor | 提升下载速度,但要控制线程数 |
整体思路也不复杂:百度图片的页面本身是一个动态加载的网页,真实的图片URL藏在它内部JavaScript变量里。我们不需要模拟浏览器去等它渲染,直接请求页面源码,然后用正则把那个变量里的URL列表抠出来,再逐个下载。
这个思路听起来简单,但真正动手时,你会发现里面有大量细节值得抠:请求头少了Referer图片可能403、URL里的转义字符需要解码、返回的图片格式可能是webp需要转jpg……这些坑我在后面会一一展开。先往下看流程,再讲实操。
2. 核心原理拆解:百度图片是如何加载图片的
2.1 页面源码里的“藏宝库”
很多人第一次写爬虫,习惯用Selenium去模拟浏览器,然后等图片懒加载完成后,再去截取网页里的img标签。这样做不是说不行,而是又慢又脆——每张图都要等浏览器渲染,反爬稍微加强一点就会失效,而且内存占用特别大。
我实际测试后发现,百度图片的搜索结果页,在HTML源码里就有一段类似这样结构的数据:
<img class="main_img" src="...">pip install requests beautifulsoup4 tqdm如果你严格跟我走正则路线,BeautifulSoup其实可以不用,但我还是建议装上,因为后面你做数据清洗或者扩展别的功能时会用得上。tqdm是用来显示下载进度的,批量下载几百上千张图时,看着进度条慢慢往前走,心里有底。
3.2 第一版实现:从网页提取图片URL
直接上核心代码。我简化了一些细节,保留最重要的逻辑:
import requests import re import os from urllib.parse import unquote def get_image_urls(keyword, page_num=1): """从百度图片搜索页提取高清图片URL""" 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/" } # 百度新版搜索页URL url = f"https://image.baidu.com/search/index?tn=baiduimage&word={keyword}" resp = requests.get(url, headers=headers, timeout=10) resp.encoding = "utf-8" html = resp.text # 提取data-imgurl字段中的图片地址 pattern = re.compile(r'"data-imgurl":"(.*?)"') img_urls = pattern.findall(html) # 解码unicode转义和url编码 img_urls = [unquote(url.replace("\\/", "/")) for url in img_urls] # 去重、过滤不完整链接 img_urls = list(set([url for url in img_urls if url.startswith("http")])) return img_urls这段代码的核心就三件事:带Headers发起请求 → 正则匹配data-imgurl字段 → 解码并去重。为什么Headers里要带Referer?因为百度图片的部分图源做了防盗链,不带Referer直接请求图片服务器,返回的会是一张“403禁止访问”的默认图,这是我用第一版踩过最大的坑。
3.3 实现批量下载与文件管理
拿到URL列表之后,下载就相对简单了。但这里也需要讲几个细节:
def download_images(img_urls, save_dir, keyword): """批量下载图片到本地""" if not os.path.exists(save_dir): os.makedirs(save_dir) success_count = 0 for idx, img_url in enumerate(img_urls): try: headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Referer": "https://image.baidu.com/" } img_resp = requests.get(img_url, headers=headers, timeout=15) # 判断响应是否为图片 if "image" not in img_resp.headers.get("Content-Type", ""): continue # 根据content-type动态生成扩展名 ext = img_resp.headers.get("Content-Type").split("/")[-1] if ext == "jpeg": ext = "jpg" elif ext == "webp": ext = "jpg" # 统一转为jpg,方便后续使用 file_name = f"{keyword}_{idx+1}.{ext}" file_path = os.path.join(save_dir, file_name) with open(file_path, "wb") as f: f.write(img_resp.content) success_count += 1 except Exception as e: print(f"下载失败: {img_url},错误: {e}") continue return success_count下载器的逻辑同样不复杂,但有三个容易被忽略的小细节值得单独拎出来说:
第一,Content-Type校验。有些时候抓到的URL虽然以.jpg结尾,但服务器实际上给你返回了一个HTML错误页。用Content-Type判断是“image”开头才保存,可以避免一堆乱码文件落地。
第二,webp转jpg。百度图片里大量压缩图是webp格式,如果你下载下来直接给PS或PR用,很多老版本软件打不开。我的做法是直接改名,虽然本质上没有转码,但扩展名是.jpg后,绝大多数看图软件都能正常识别(webp本身兼容性已经很好,这里主要是为了统一)。
第三,文件命名。我用的是“关键词_序号.扩展名”的格式。这个格式丑是丑,但做批量素材时,关键词前置可以让你在文件夹里按名称排序时,同一主题的图自然聚在一起。
3.4 进阶版:翻页与多线程并发
上面两段代码属于“开箱即用”的版本,但真正面对几百上千张图的需求,单线程下载速度会让你怀疑人生。所以我在后续迭代里加了两个优化:分页接口拉取 + 多线程下载。
分页接口的核心代码如下:
def get_image_urls_by_api(keyword, total=60): """通过后端接口分页获取更多图片""" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Referer": "https://image.baidu.com/" } all_urls = [] for pn in range(0, total, 30): api_url = "https://image.baidu.com/search/acjson" params = { "tn": "resultjson_com", "word": keyword, "pn": pn, "rn": 30, } try: resp = requests.get(api_url, params=params, headers=headers, timeout=10) data = resp.json() # 解析data字段,获取每张图的thumbURL for item in data.get("data", []): if item.get("thumbURL"): all_urls.append(item["thumbURL"]) except Exception as e: print(f"第{pn}条请求失败: {e}") continue return list(set(all_urls))多线程下载就更简单了,Python的ThreadPoolExecutor一行就能实现:
from concurrent.futures import ThreadPoolExecutor def multithread_download(url_list, save_dir, keyword, max_workers=10): with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = [] for i, url in enumerate(url_list): future = executor.submit(download_single_image, url, i, save_dir, keyword) futures.append(future) for future in futures: future.result()这里提醒一句:线程数不要贪多。我试过50个线程并发下载,结果被百度图片服务器限流,IP临时被封了几个小时。10个线程足够,下载速度也已经远超手动操作,完全是量级上的差异。
3.5 主流程入口
写完上面的函数,主入口就非常清爽了:
if __name__ == "__main__": keyword = "特斯拉 Model Y" # 替换成你想要的关键词 save_dir = f"./images/{keyword}" print(f"开始爬取关键词: {keyword}") urls = get_image_urls_by_api(keyword, total=120) print(f"共获取到 {len(urls)} 张图片URL") if urls: multithread_download(urls, save_dir, keyword, max_workers=10) print("下载完成")整个项目跑起来后,你会看到控制台刷出一行行下载记录,进度条推进,然后本地文件夹里出现一批以关键词为前缀的图片文件。说实话,第一次完整跑通这个脚本,那种“搜索引擎变成了我的素材库”的感觉,比写很多复杂业务代码都爽。
4. 常见问题与排查技巧实录
4.1 爬取结果为空,一张图都拿不到
这是新手最容易遇到的问题。我把排查顺序列成一个速查表:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 返回的URL列表为空 | 页面结构变化,正则匹配失效 | 打开浏览器开发者工具,查看搜索页HTML源码,确认>def download_single_image(img_url, idx, save_dir, keyword, retries=3): for attempt in range(retries): try: headers = { "User-Agent": "Mozilla/5.0", "Referer": "https://image.baidu.com/" } resp = requests.get(img_url, headers=headers, timeout=15) if "image" not in resp.headers.get("Content-Type", ""): return False # 保存逻辑... return True except Exception: time.sleep(2) # 间隔一下,避免连续失败 continue return False4.3 URL里全是转义字符,下载出来文件损坏这个问题主要出在百度搜索页的数据上,有些图片地址是经过JS编码的,比如 如果你在解析时发现URL里还带其他转义符,比如 注意,这行代码放在替换反斜杠之前还是之后,顺序不同结果会有差异,我推荐顺序是“先替换 4.4 下载速度太慢怎么办如果你已经用了多线程还是觉得慢,可以考虑换思路:只下载缩略图而非原图。百度图片的缩略图地址(thumbURL)通常带特定的尺寸参数,比如 5. 延伸场景与规范化建议5.1 爬下来的图片怎么归类管理我见过很多人爬完图就扔在文件夹里不管,几个月后要用时根本找不到。这里分享一个我自己的管理习惯:
CSV清单可以用下面这个方式顺手生成: 这样做的价值在于,图片如果有版权纠纷或者需要溯源时,你随时可以找到原始来源。爬取公开图片做个人学习参考是常见操作,但如果用于商业项目,务必逐张确认版权许可,这是职业习惯,也是底线。 5.2 能不能扩展成通用图片爬虫这个项目的核心逻辑完全适用于其他同类搜索引擎或图库网站,区别主要在于URL结构和数据字段的解析。比如有的网站图片数据存在 在Linux下用命令行的人,大多对Shell既熟悉又陌生。熟悉的是每天敲ls、cd、grep这些指令,陌生的是这个“命令行解释器”到底是怎么把一行字符变成一个个进程的。我自己动手做了一个自定义Shell之后,才真正把这条链路看清楚:从键盘读…
1. 项目概述与选题拆解1.1 这套系统到底是什么简单说,这就是一个典型的 Java 全栈毕业设计项目,后端用 Spring Boot 提供接口服务,前端用 Vue 写页面交互,数据全部存在 MySQL 里,整体是一个面向律师事务所的日常业务管…
简介:对应周志华《机器学习》(西瓜书)第四章决策树的学习需求,这份代码压缩包将信息熵与基尼指数两种划分选择算法完整落地为可运行脚本。包内共9个文件,包含5个csv数据文件(西瓜数据集2.0、3.0以及iris、a…
简介:基于Neo4j的水浒传人物关系可视化及问答系统,是一份面向计算机、通信、人工智能、自动化相关专业学生与从业者的毕业设计源码及答辩资料。项目以Python实现后端逻辑,结合Neo4j图数据库构建人物关系图谱,并提供Web端可视化与问…
1. 项目概述:RLX不是又一个“玩具编译器”,而是为真实AI基础设施而生的Rust原生引擎如果你最近在关注AI底层系统栈的演进,大概率已经注意到一个名字开始频繁出现在论文预印本、开源社区讨论和高性能计算团队的内部技术选型会上——RLX。它不像…
搞农业视觉项目这几年,我越来越觉得一个核心问题:YOLOv8这类目标检测模型,单独跑命令行验证精度是一回事,真正交付给用户做检测是另一回事。很多同学模型训练完,mAP50都到0.95了,但用户拿不到手,…
|