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、宽高、标题等信息。

我们要做的核心就三步:

  1. 构造请求 URL:把关键词和分页参数拼好
  2. 解析返回数据:从 JSON 里提取图片的真实下载链接
  3. 下载保存:用链接把图片写入本地文件

四、完整实操步骤

环境准备

需要 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(缩略图链接)是因为它最稳定、最快middleURLhoverURLobjURL 这些字段可能为空或者格式不统一。

③ 多线程下载(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.gettimeout 参数,超时了就跳过这张,不要死等。

resp = requests.get(url, headers=headers, timeout=(5, 10))
# (连接超时, 读取超时)

❌ 问题4:运行一段时间后 IP 被封

现象:爬了几百张后,突然所有请求都返回 403 或 429(Too Many Requests)。

原因:请求频率太高,百度检测到爬虫行为后封了你的 IP。

解决方案

  1. 降低频率:把 sleep 时间加到 2~3 秒
  2. 换IP:用代理池轮换(适合大规模采集)
  3. 控制总量:单次不要超过 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)

七、总结

核心要点回顾

  1. 百度图片搜索本质是一个 AJAX 接口,构造合适的 URL 参数即可拿到 JSON 格式的图片数据
  2. thumbURL 是最稳定的图片地址字段,优先用它
  3. 伪装 User-Agent 和 Referer 是最低成本的反爬绕过关
  4. 多线程下载concurrent.futures.ThreadPoolExecutor,5 个线程是性价比比较高的配置
  5. 礼貌性延时(1~2秒)+ 超时控制 是长期稳定运行的关键

给你的学习建议

  1. 先跑通,再优化:复制代码先跑一次,看到图片下载下来,比看十遍原理都有用
  2. 改改参数玩玩:换关键词、改下载数量、调线程数,感受参数对结果的影响
  3. 结合项目用:试着把这个脚本集成到一个自动化工作流里(比如每天定时爬一批图片做训练数据)
  4. 延伸学习:理解了百度图片的采集原理,你可以用同样的思路去采集谷歌图片、必应图片、甚至是电商平台商品图

做技术就是这样——先模仿、再理解、最后创造。代码在手,跑起来就是胜利。


本文代码在 Python 3.10 / 3.12 环境下测试通过。如有问题欢迎评论区交流。

posted @ 2026-06-12 11:12  快乐西西  阅读(12)  评论(0)    收藏  举报