Python自动化搜索关键词,批量爬取百度图片(附完整代码)
一、知识点简介
你在开发项目的时候,是不是经常为找图片素材发愁?做深度学习训练需要大量标注图片、写PPT要配图、做自媒体要封面图……一张张手动下载效率低到令人崩溃。
本文教你用 Python 写一个百度图片爬虫:输入关键词,自动搜索、批量下载高清图片。涉及的知识点包括:
- 百度图片搜索的请求构造与参数解析
requests库发送 HTTP 请求- 正则表达式与 JSON 解析提取图片地址
- 多线程下载加速
- 反爬策略绕过(UA伪装、延时控制)
全部实战代码可复制运行,零基础也能跟得上。
二、适用场景
| 场景 | 你能解决什么问题 |
|---|---|
| 深度学习数据集 | 自动采集某类图片用于训练(猫狗分类、商品识别等) |
| 自媒体配图 | 批量下载封面、插图素材 |
| 前端开发 | 快速获取设计参考图或占位图 |
| UI/UX 设计 | 收集竞品截图或风格参考 |
| 资料整理 | 对某一主题图片批量归档 |
⚠️ 免责声明:本文仅用于学习和个人研究用途。请勿批量下载受版权保护的图片用于商业用途,遵守百度及相关网站的使用协议。
三、核心原理
3.1 百度图片搜索流程(一句话讲明白)
你在浏览器中打开百度图片 → 输入关键词 → 按回车 → 页面展示图片缩略图。爬虫做的就是自动化这个操作,并提取出图片的真实下载链接。
3.2 背后干了什么?
当你搜索时,浏览器向百度服务器发了一个 AJAX 请求(不用管 AJAX 是什么,你只需要知道这是浏览器在"后台偷偷"拉数据),请求的 URL 长这样:
https://image.baidu.com/search/acjson?
tn=resultjson_com&
word=猫&
pn=30&
rn=30
关键参数:
| 参数 | 含义 | 白话解释 |
|---|---|---|
word |
搜索关键词 | 你要搜什么图片 |
pn |
起始位置(分页偏移) | 从第几张开始取,30就是第31张开始 |
rn |
每页返回数量 | 一次拿多少张,最大30 |
返回的数据是 JSON 格式(一种结构化的数据格式,类似字典),里面包含了图片的 URL、宽高、标题等信息。
我们要做的核心就三步:
- 构造请求 URL:把关键词和分页参数拼好
- 解析返回数据:从 JSON 里提取图片的真实下载链接
- 下载保存:用链接把图片写入本地文件
四、完整实操步骤
环境准备
需要 Python 3.6+,依赖库清单:
pip install requests
只用这一个第三方库,其他全是 Python 自带模块。够简洁吧?
五、代码实战
5.1 完整代码(直接复制就能跑)
import requests
import json
import re
import os
import time
import random
from urllib.parse import urlencode
from concurrent.futures import ThreadPoolExecutor, as_completed
# ========== 配置区 ==========
KEYWORD = "日落" # 搜索关键词
SAVE_DIR = "./baidu_images" # 图片保存目录
MAX_COUNT = 100 # 想下载多少张
PAGE_SIZE = 30 # 每页返回数量(百度最大30)
# 请求头,伪装成正常浏览器
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 build_url(keyword, pn):
"""
构造百度图片搜索的请求URL
参数:
keyword: 搜索关键词
pn: 起始位置,0第一页,30第二页,60第三页...
返回:
完整的请求URL字符串
"""
params = {
"tn": "resultjson_com",
"logid": "", # 日志ID,留空也可
"ipn": "rj",
"ct": "201326592",
"is": "",
"fp": "result",
"queryWord": keyword, # 查询关键词
"cl": "2",
"lm": "-1",
"ie": "utf-8",
"oe": "utf-8",
"adpicid": "",
"st": "-1",
"z": "",
"ic": "0",
"hd": "",
"latest": "",
"copyright": "",
"word": keyword, # 实际搜索词
"s": "",
"se": "",
"tab": "",
"width": "",
"height": "",
"face": "0",
"istype": "2",
"qc": "",
"nc": "1",
"fr": "",
"expermode": "",
"force": "",
"pn": pn, # 分页偏移量
"rn": PAGE_SIZE, # 每页条数
"gsm": "1e",
}
return "https://image.baidu.com/search/acjson?" + urlencode(params)
def fetch_image_urls(keyword, total=60):
"""
采集图片URL列表
参数:
keyword: 搜索关键词
total: 需要采集的总图片数
返回:
图片真实下载地址列表
"""
all_urls = []
page_num = 0
while len(all_urls) < total:
pn = page_num * PAGE_SIZE
url = build_url(keyword, pn)
try:
# 发送HTTP请求,带上伪装头
resp = requests.get(url, headers=HEADERS, timeout=10)
resp.encoding = "utf-8"
# 看看返回的是不是JSON格式
data = resp.json()
# 提取每个图片的真实URL
# 返回数据中有一个"data"列表,每个元素包含图片信息
for item in data.get("data", []):
# 跳过空数据
if not item:
continue
# 优先用 thumbURL(缩略图),没有则用 middleURL
img_url = item.get("thumbURL") or item.get("middleURL") or ""
if img_url and img_url not in all_urls:
all_urls.append(img_url)
if len(all_urls) >= total:
break
print(f" ✓ 第{page_num + 1}页采集完成,已获取 {len(all_urls)} 张")
except Exception as e:
print(f" ✗ 第{page_num + 1}页请求失败: {e}")
page_num += 1
# 礼貌性延时,避免被反爬(每次等1~2秒)
time.sleep(random.uniform(1.0, 2.0))
return all_urls[:total]
def download_single(img_info):
"""
下载单张图片(用于多线程)
参数:
img_info: 元组 (序号, 图片URL)
返回:
(序号, 是否成功)
"""
idx, url = img_info
try:
# 再次伪装,部分CDN会检查Referer
headers = {
"User-Agent": HEADERS["User-Agent"],
"Referer": "https://image.baidu.com/"
}
resp = requests.get(url, headers=headers, timeout=15)
if resp.status_code == 200:
# 从URL中提取文件后缀
ext = os.path.splitext(url.split("?")[0])[1]
if not ext or len(ext) > 5:
ext = ".jpg" # 默认.jpg
filename = os.path.join(SAVE_DIR, f"{idx:04d}{ext}")
with open(filename, "wb") as f:
f.write(resp.content)
return idx, True
else:
return idx, False
except Exception as e:
print(f" ✗ 下载失败 #{idx}: {url[:60]}... - {e}")
return idx, False
def download_images_parallel(urls, max_workers=5):
"""
多线程批量下载图片
参数:
urls: 图片URL列表
max_workers: 同时下载的线程数(默认5个)
"""
os.makedirs(SAVE_DIR, exist_ok=True)
# 包装数据:给每个URL编个号
img_list = [(i + 1, url) for i, url in enumerate(urls)]
success = 0
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有下载任务
futures = {executor.submit(download_single, info): info[0] for info in img_list}
for future in as_completed(futures):
idx, ok = future.result()
if ok:
success += 1
print(f" ✓ 下载成功 #{idx}")
return success
def main():
print(f"🔍 开始搜索关键词: {KEYWORD}")
print(f"📥 目标下载数量: {MAX_COUNT} 张")
print("-" * 50)
# 第一步:采集图片URL
print("📡 正在采集图片链接...")
urls = fetch_image_urls(KEYWORD, MAX_COUNT)
print(f"\n✅ 共采集到 {len(urls)} 个图片链接\n")
if not urls:
print("❌ 没有获取到任何图片,请检查网络或关键词")
return
# 第二步:多线程下载
print("⬇️ 开始下载图片...")
success = download_images_parallel(urls, max_workers=5)
print("\n" + "=" * 50)
print(f"📊 下载完成: 成功 {success}/{len(urls)} 张")
print(f"📁 保存目录: {os.path.abspath(SAVE_DIR)}")
print("=" * 50)
if __name__ == "__main__":
main()
5.2 运行效果
把上面代码保存为 baidu_image_crawler.py,在终端运行:
python baidu_image_crawler.py
输出示例:
🔍 开始搜索关键词: 日落
📥 目标下载数量: 100 张
--------------------------------------------------
📡 正在采集图片链接...
✓ 第1页采集完成,已获取 30 张
✓ 第2页采集完成,已获取 57 张
✓ 第3页采集完成,已获取 87 张
✅ 共采集到 87 个图片链接
⬇️ 开始下载图片...
✓ 下载成功 #1
✓ 下载成功 #2
...
✓ 下载成功 #87
==================================================
📊 下载完成: 成功 82/87 张
📁 保存目录: /Users/xxx/project/baidu_images
==================================================
下载后的目录结构:
baidu_images/
├── 0001.jpg
├── 0002.jpg
├── 0003.jpg
├── 0004.webp
├── 0005.png
...
└── 0087.jpg
5.3 核心代码分步拆解
① 构造请求参数(build_url)
百度图片用的是 HTTP GET 请求,所有参数拼在 URL 后面。我们用 urlencode 把字典参数转成 key=value&key2=value2 的格式。
关键点:pn 参数控制翻页。第一页 pn=0,第二页 pn=30,第三页 pn=60……这就是分页的核心逻辑。
② 采集图片链接(fetch_image_urls)
用 requests.get 拿到服务器返回的数据后,调用 .json() 把字符串解析成字典。图片信息都在 data 字段里,每个元素是一个图片对象。
为什么取 thumbURL 而不是其他字段?
返回的 JSON 里有很多 URL 字段,我们用 thumbURL(缩略图链接)是因为它最稳定、最快。middleURL、hoverURL、objURL 这些字段可能为空或者格式不统一。
③ 多线程下载(download_images_parallel)
单张下载用的是 requests.get(url).content,拿到图片的二进制数据后直接写入文件。但是网络 I/O 非常慢,一张一张下太浪费时间了。
这里用 ThreadPoolExecutor 开了 5 个线程,同时下载 5 张图片。等待的时候 CPU 去做别的事,整体速度提升 3~5 倍。
5.4 进阶功能(一键换肤)
如果你想换个用法,比如从命令行传参:
# 在 main() 上面加这一段
if __name__ == "__main__":
import sys
if len(sys.argv) >= 2:
KEYWORD = sys.argv[1]
if len(sys.argv) >= 3:
MAX_COUNT = int(sys.argv[2])
main()
然后就可以这样用:
python baidu_image_crawler.py "猫咪" 200
python baidu_image_crawler.py "故宫" 50
python baidu_image_crawler.py "Python教程封面" 30
六、常见踩坑问题
❌ 问题1:返回空数据或报错 403
现象:fetch_image_urls 返回空列表,或被服务器拒绝。
原因:百度的反爬机制发现了你不是真人在浏览器操作。
解决方案:
- ✅ 检查 Headers 是否完整,
User-Agent一定要用最新的 Chrome 版本号 - ✅ 加上
Referer: https://image.baidu.com/头(很多CDN靠它判断) - ✅ 请求之间加
time.sleep(1~2),控制频率 - ✅ 如果仍然 403,可以试试用
requests.Session先访问首页获取 cookie
# 增强版:用 Session 获取 Cookie
session = requests.Session()
session.get("https://image.baidu.com/", headers=HEADERS, timeout=10)
# 后续请求都用这个 session
resp = session.get(url, headers=HEADERS, timeout=10)
❌ 问题2:下载的图片打不开
现象:文件大小是0KB或者只有几十字节。
原因:图片URL过期、防盗链、或者实际是网页而不是图片。
解决方案:
- 下载前检查
Content-Type是否包含image关键字 - 加个校验:文件大小 < 1KB 的直接删掉
# 在 download_single 函数里加校验
if len(resp.content) < 1024: # 小于1KB的跳过
print(f" ⚠ 图片太小,跳过 #{idx}")
return idx, False
❌ 问题3:下载到一半卡住不动
原因:某个图片服务器的连接超时或响应太慢。
解决方案:requests.get 加 timeout 参数,超时了就跳过这张,不要死等。
resp = requests.get(url, headers=headers, timeout=(5, 10))
# (连接超时, 读取超时)
❌ 问题4:运行一段时间后 IP 被封
现象:爬了几百张后,突然所有请求都返回 403 或 429(Too Many Requests)。
原因:请求频率太高,百度检测到爬虫行为后封了你的 IP。
解决方案:
- 降低频率:把 sleep 时间加到 2~3 秒
- 换IP:用代理池轮换(适合大规模采集)
- 控制总量:单次不要超过 500 张,分时段跑
# 代理池使用示例
proxies = {
"http": "http://127.0.0.1:7890",
"https": "http://127.0.0.1:7890",
}
resp = requests.get(url, headers=HEADERS, proxies=proxies, timeout=10)
七、总结
核心要点回顾
- 百度图片搜索本质是一个 AJAX 接口,构造合适的 URL 参数即可拿到 JSON 格式的图片数据
thumbURL是最稳定的图片地址字段,优先用它- 伪装 User-Agent 和 Referer 是最低成本的反爬绕过关
- 多线程下载用
concurrent.futures.ThreadPoolExecutor,5 个线程是性价比比较高的配置 - 礼貌性延时(1~2秒)+ 超时控制 是长期稳定运行的关键
给你的学习建议
- 先跑通,再优化:复制代码先跑一次,看到图片下载下来,比看十遍原理都有用
- 改改参数玩玩:换关键词、改下载数量、调线程数,感受参数对结果的影响
- 结合项目用:试着把这个脚本集成到一个自动化工作流里(比如每天定时爬一批图片做训练数据)
- 延伸学习:理解了百度图片的采集原理,你可以用同样的思路去采集谷歌图片、必应图片、甚至是电商平台商品图
做技术就是这样——先模仿、再理解、最后创造。代码在手,跑起来就是胜利。
本文代码在 Python 3.10 / 3.12 环境下测试通过。如有问题欢迎评论区交流。
浙公网安备 33010602011771号