Selenium自动化实战:百度识图图片上传全流程解析
2026/9/7 18:19:51 网站建设 项目流程

我最初接触 Selenium 的时候,看着满屏的 API 文档,说实话有点懵。直到有一天需要重复给百度识图传几十张图片做以图搜图,手动一张张点上传窗口点得人想砸键盘,我才下定决心把这个"传图"的动作彻底自动化。这篇笔记不是官方文档的翻译,是我从零到一跑通"百度识图上传图片"全流程的记录,里面有完整的代码、踩过的坑,以及我为啥这么写的思考过程。如果你正打算用 Selenium 做图片上传、批量识图,或者只是想搞懂文件上传类自动化到底怎么玩,这篇应该能帮你少走不少弯路。

1. 需求拆解:百度识图的"上传图片"到底在自动化什么

1.1 先搞清楚你面对的是不是真正的 file 文件框

做自动化第一件事不是写代码,而是打开目标页面,按 F12 看真实 DOM 结构。百度识图的上传入口在https://graph.baidu.com/,页面上有个醒目的"上传图片"按钮。点击它之后弹出的不是我们平时理解的文件选择对话框,而是一个由浏览器管理的原生文件选择窗口。

这个原生窗口是 Selenium 动不了的——它属于操作系统层面,浏览器自动化协议无法直接操作系统弹窗。那怎么办?答案是绕过这个弹窗,直接对接页面底层那个真正承载文件选择的 HTML 元素,也就是<input type="file">。这类元素有一个天然特性:Selenium 可以通过send_keys()方法直接往里面塞文件路径,不需要经过弹窗。

我最初也以为要先点击"上传图片"按钮、再处理弹窗,后来发现整个思路就错了。正确的逻辑是:定位到页面上隐藏的或者不显眼的file控件,直接给它传路径,等于替用户在文件选择框里按下了"确定"。

1.2 手动流程与自动化流程的对应关系

手动操作百度识图是这样一个流程:打开页面,点击上传入口,在系统弹窗里选择文件,点击确定,等待识别结果渲染。自动化流程其实是同构的,只是把中间"系统弹窗选择文件"这一环,替换成了向<input type="file">传路径。

这里有个非常关键的认知:百度识图的页面里,那个input元素可能是隐藏的。页面上你看到的上传按钮,可能只是某个<div>或者<span>的样式伪装,真正的file控件被隐藏了。既然是隐藏元素,普通用户看不见,但对 Selenium 来说完全不影响——只要能定位到它,就能传值。

所以这个自动化任务的核心技术挑战可以归结为三点:找得到、传得进、等得住。"找得到"是元素定位的问题,"传得进"是send_keys的问题,"等得住"是页面加载和识别结果渲染的等待问题。下面我会围绕这三件事逐步展开。

1.3 这个场景对应哪些实际需求

别小看"百度识图上传图片"这个小小的自动化场景,它背后能延伸出不少实际应用。

最常见的需求是批量以图搜图。比如手里有一批商品图、头像、风景图,想通过百度识图查看图片来源、相似图片、相关资讯,人工一张张上传显然不现实。用 Selenium 写个脚本,遍历本地文件夹的图片,逐张上传并抓取识别结果页面中的信息,就能实现半自动化的批量识图。

另一个需求是把识图结果对接后续流程。比如识图结束之后,页面会展示图片来源、相似图片等信息,通过 Selenium 抓取这些内容,再结合图片下载逻辑,就能搭建一个简单的图像溯源工具。

再说深一层,"上传图片"这个动作本身,是 Selenium 处理 file 上传控件的通用范式。不管是百度识图、各大邮箱附件、博客后台图片上传,只要是基于<input type="file">的上传场景,思路完全一致,代码几乎直接复用。学会这一个案例,等于掌握了 Selenium 文件上传类操作的通用解法。

2. 环境准备与浏览器驱动配置(最容易翻车的一步)

2.1 安装 Selenium 与依赖

我用的是 Python 版本,安装命令非常简单:

pip install selenium

如果安装速度慢,可以切换国内镜像源:

pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple

安装完之后,验证一下版本:

python -m pip show selenium

这一步本身没什么难度,真正的坑在浏览器驱动。

2.2 ChromeDriver 版本必须与浏览器版本匹配

Selenium 要操作浏览器,不能直接与浏览器对话,必须通过一个中间桥梁——WebDriver。Chrome 的 WebDriver 就叫 ChromeDriver。这里最大的规则是:ChromeDriver 的主版本号必须与本机 Chrome 浏览器的主版本号一致

比如你用的是 Chrome 120,那 ChromeDriver 也必须是 120 的对应版本。版本不匹配时,运行脚本会直接报SessionNotCreatedException,提示版本不兼容。

查看 Chrome 版本号的地方在:浏览器地址栏输入chrome://version,可以看到完整的版本号,比如120.0.6099.109。然后去 ChromeDriver 的下载地址找到对应的大版本下载。

下载 ChromeDriver 之后,有两种使用方式。最简单的是把解压出来的chromedriver.exe放在 Python 脚本同目录下,或者在系统 PATH 里添加它的路径。不过这种方式在项目迁移时容易出问题。我更推荐在代码里显式指定驱动路径:

from selenium import webdriver from selenium.webdriver.chrome.service import Service service = Service(r"D:\tools\chromedriver-win64\chromedriver.exe") driver = webdriver.Chrome(service=service)

这样写的好处是,换机器、换环境时只需改路径,不用重新配置系统环境变量。

2.3 用 webdriver-manager 自动管理驱动版本

如果觉得手动下载驱动太麻烦,还有一个更省心的方案:webdriver-manager这个库可以自动检测浏览器版本、自动下载对应驱动、自动完成配置。

pip install webdriver-manager

使用方式也很简洁:

from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service)

它会把驱动缓存在本地,之后启动时直接复用,不再重复下载。第一次运行会稍慢,因为要下载驱动,后续就很快了。我建议新手直接用这个方法,能省掉一大半环境配置的烦恼。

2.4 配置浏览器参数:隐藏自动化特征与无头模式

对于百度识图这类普通页面,Selenium 默认启动的 Chrome 窗口会带有"Chrome 正在受到自动软件的控制"提示条。某些页面检测到 WebDriver 特征后可能会增加验证难度。虽然百度识图通常不会因此拒绝访问,但为了避免不必要的干扰,我习惯在启动时增加一些参数:

from selenium.webdriver.chrome.options import Options options = Options() options.add_argument("--disable-blink-features=AutomationControlled") options.add_experimental_option("excludeSwitches", ["enable-automation"])

这两个参数的作用分别是:去掉 WebDriver 的标志位、去掉自动化提示条。它们不能百分百保证不被检测,但能降低被特殊对待的概率。

另一个常用参数是无头模式(headless),也就是浏览器在后台运行,不弹出窗口:

options.add_argument("--headless=new")

无头模式的优点是不占用桌面、运行速度更快,适合部署在服务器上做批量任务。缺点是有时候某些前端渲染行为与有头模式不完全一致,排错时不如有头模式直观。我的习惯是调试阶段用有头模式,跑正式批量任务时再切到无头模式,两者代码完全一致,只改参数。

3. 核心实现:定位文件上传入口并传输图片路径

3.1 分析百度识图页面的 DOM 结构

写代码之前,先花几分钟看页面结构。用 Chrome 打开百度识图页面,按 F12,在 Elements 面板里按Ctrl+F搜索input,会看到input[type="file"]这个控件。

百度识图的页面里,这个 file 控件通常长这样:

<input type="file" class="upload-input" accept="image/*">

注意它的type="file",这是 Selenium 唯一能直接传路径的控件类型。只要页面上存在它,无论显示与否,都可以直接定位。

这里有个小细节值得说:有些网站为了实现更好的样式,会把真正文件控件做成隐藏的,再用一个<label>标签的for属性指向它。这种设计对自动化其实更友好,因为<label>的存在与否不影响send_keys,我们只需要找到那个隐藏的 file 控件。

3.2 元素定位:XPath 与 CSS Selector 的选择

定位元素的方式有很多种:By.IDBy.NAMEBy.CLASS_NAMEBy.XPATHBy.CSS_SELECTOR等。百度识图这个场景里,我优先推荐用 CSS Selector:

from selenium.webdriver.common.by import By file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']")

这行代码的含义是:找到页面中第一个type属性等于fileinput元素。只要页面上只有一个文件上传控件,这个写法就足够精准。

如果页面非常复杂,有多个 file 控件,就需要更精确的定位。这时可以结合父级元素或者表单 id:

file_input = driver.find_element(By.XPATH, "//form[@id='uploadForm']//input[@type='file']")

不过就百度识图而言,input[type='file']已经够用了。实际项目里要注意的是,如果找不到元素,不要急着换定位方式,先确认自己是不是在正确的 iframe 里。

3.3 完整流程代码:从打开页面到上传成功

下面给出一个可以直接跑通的完整脚本:

import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def create_driver(): options = Options() options.add_argument("--disable-blink-features=AutomationControlled") options.add_experimental_option("excludeSwitches", ["enable-automation"]) service = Service(r"D:\tools\chromedriver-win64\chromedriver.exe") driver = webdriver.Chrome(service=service, options=options) return driver def upload_image(driver, image_path): driver.get("https://graph.baidu.com/") wait = WebDriverWait(driver, 10) file_input = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='file']")) ) file_input.send_keys(image_path) time.sleep(3) print("上传完成,当前页面标题:", driver.title) if __name__ == "__main__": driver = create_driver() try: upload_image(driver, r"C:\Users\test\Pictures\sample.jpg") time.sleep(5) finally: driver.quit()

这段代码做的事情是:启动浏览器、打开百度识图、等待 file 控件出现、把本地图片路径塞进去。send_keys执行之后,浏览器会自动触发文件上传逻辑,相当于用户选择了文件并确认,接下来百度识图就会自动开始识别。

有两点需要特别解释。

第一,为什么要WebDriverWait而不是直接find_element?因为页面加载需要时间,如果元素还没渲染完成就去找,会直接抛出NoSuchElementException。显式等待会轮询页面,直到元素出现或者超时,比固定time.sleep更可靠。

第二,send_keys(image_path)执行后,百度识图一般会在新窗口或者当前窗口展示识别结果。为了保留足够的渲染时间,我临时用了time.sleep(3)。更严谨的做法是等待某个结果特征元素出现,这个后面会说。

3.4 等结果出现而不是等固定时间

固定sleep虽然简单,但不够优雅,也不稳定。网络快的时候浪费了时间,网络慢的时候又可能因为等待不足而找不到结果。更专业的做法是显式等待某个结果元素出现。

比如百度识图上传成功后,页面会出现一个"识图"结果的区域,我们可以等待这个区域里的某个元素。由于百度识图页面结构可能调整,我会用一个通用的策略:等待页面 URL 变化,或者等待某个与"相似图片"相关的元素出现。

wait = WebDriverWait(driver, 15) wait.until(EC.url_changes("https://graph.baidu.com/"))

如果 URL 没有变化,也可以等待页面中出现的所有<img>标签数量增加:

wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "div[class*='result'] img")))

这种方式比固定sleep更稳定,因为它不依赖网络速度,而是依赖页面实际状态。这也是 Selenium 官方推荐的做法:Wait for the right thing.

3.5 上传后如何验证成功并截图

验证上传是否成功,最简单直观的方式是截图留证。Selenium 自带截图方法:

driver.save_screenshot("upload_result.png")

如果是无头模式,截图可以直接反映浏览器真实渲染结果,方便排查问题。完整的验证逻辑可以这样写:

# 等待一段时间后,判断页面中是否存在结果图片 time.sleep(2) screenshots = driver.find_elements(By.CSS_SELECTOR, "img") if len(screenshots) > 5: print("识别结果已加载") driver.save_screenshot("upload_result.png") else: print("可能上传失败,请检查截图") driver.save_screenshot("upload_error.png")

这个逻辑依赖"识图结果页面图片数量明显多于初始页面"这一假设,虽然不是特别精准,但作为验证手段足够了。更稳妥的是结合页面标题或其他特征元素综合判断。

3.6 完整的多流程示例:批量上传多张图片

如果只有一张图,手动点几下其实也能接受,批量才是自动化的真正价值。我写过一个简单版本,遍历指定文件夹下的图片,逐张上传并抓取页面信息:

import os import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def get_image_paths(folder): extensions = (".jpg", ".jpeg", ".png", ".bmp", ".webp") return [ os.path.join(folder, f) for f in os.listdir(folder) if f.lower().endswith(extensions) ] def main(): options = Options() options.add_argument("--disable-blink-features=AutomationControlled") options.add_experimental_option("excludeSwitches", ["enable-automation"]) driver = webdriver.Chrome(service=Service(r"D:\tools\chromedriver-win64\chromedriver.exe"), options=options) wait = WebDriverWait(driver, 10) image_folder = r"C:\Users\test\Pictures\to_search" image_paths = get_image_paths(image_folder) if not image_paths: print("文件夹下没有图片") driver.quit() return for idx, image_path in enumerate(image_paths, 1): print(f"正在处理第 {idx} 张:{image_path}") driver.get("https://graph.baidu.com/") file_input = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='file']")) ) file_input.send_keys(image_path) time.sleep(4) # 这里可以添加抓取结果的代码,比如提取相似图片链接等 driver.save_screenshot(f"result_{idx}.png") print(f"第 {idx} 张完成,已截图") driver.quit() if __name__ == "__main__": main()

这个脚本里,每处理一张图片之前都重新driver.get回到识图首页,避免连续上传时页面状态混乱。这是我在踩过几次坑之后总结的——如果同一页面连续调用send_keys,第二次不一定能触发上传逻辑,重新加载页面是最稳妥的方案。

4. 常见问题与排查技巧实录

4.1 驱动版本不匹配(出现频率最高的报错)

报错信息长这样:

selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 114 Current browser version is 120.0.6099.109 with binary path ...

意思很直白:你的 ChromeDriver 支持的是 Chrome 114,但当前浏览器是 120。解决办法是去下载与浏览器版本对应的 ChromeDriver,或者用webdriver-manager自动管理。这个坑几乎每个 Selenium 新手都会踩,因为 Chrome 会自动更新,而 ChromeDriver 不会跟着自动更新。每次 Chrome 自动升级之后,脚本突然跑不了,多半就是这个原因。

我的经验是:如果脚本之前能跑、突然不能跑了,第一反应就去检查浏览器版本和驱动版本。用webdriver-manager的话,能一定程度上缓解这个问题,因为启动时它会检查版本并自动下载匹配的驱动。

4.2 找不到 input 元素

报错:

selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"input[type='file']"}

排错思路按顺序来。先确认页面是否真的加载完成,有没有被验证码、登录弹窗等拦截;再确认是否在正确的 iframe 里;最后才考虑是不是前端框架渲染延迟。

iframe 的问题比较隐蔽。有些页面上传控件在子页面 iframe 里,直接定位是找不到的,必须先切换到 iframe:

driver.switch_to.frame("iframe_id_or_name") # 定位元素 # 操作完成后再切回主页面 driver.switch_to.default_content()

百度识图页面通常没有 iframe 问题,但其他网站不一定。排查时在 F12 里看看 file 控件是不是被 iframe 包裹,这一点很重要。

4.3 send_keys 传了路径但是没反应

这种情况很诡异,代码没报错,页面也没动静。可能的原因有几种。

第一,图片路径错误或文件不存在。send_keys不会验证文件是否存在,如果路径写错,浏览器可能直接忽略。所以排查第一步是确认路径正确,最好用os.path.exists()提前验证。

第二,文件类型不被接受。百度识图支持 jpg、png 等常见格式,如果你传的是 gif 或者特殊格式,页面可能不会触发上传。

第三,页面结构变化。百度识图的input[type='file']可能被改成其他属性,此时需要通过 F12 重新查看实际结构。

碰见这种问题,最有效的排查手段是截图——driver.save_screenshot()把当前页面截图保存下来,一眼就能看出页面状态。

4.4 上传之后页面跳转到了新标签页,找不到结果

百度识图有时候会在新标签页中打开识别结果。如果脚本里还在用旧标签页的 driver 实例,自然找不到结果内容。切换标签页的方式:

# 获取所有窗口句柄 handles = driver.window_handles # 切换到最新打开的标签页 driver.switch_to.window(handles[-1])

操作完后如果需要回到原页面,再切回handles[0]。在写批量脚本的时候要注意,每个循环结束后确保 driver 回到了预期的标签页,否则元素定位会越来越乱。

4.5 页面弹窗或登录引导干扰

实际运行中,百度识图偶尔会弹出登录引导、新功能引导等浮层。这些弹窗如果不处理,可能会遮挡结果区域,影响元素定位和点击。

最简单的处理方式是加一个统一的"关弹窗"逻辑:检测页面中是否存在关闭按钮(有些弹窗右上角有个 X),存在就点击。不过百度识图的弹窗频率不算高,我一般只在安装验证码滑块逻辑时统一处理。不建议一上来就写一大堆兜底逻辑,等真遇到了再加。

4.6 关于滑块验证的简单说明

百度识图在部分场景下会在上传后触发图片滑块验证,这是常见的安全机制。Selenium 处理滑块的常见思路是:先定位滑块和缺口位置,通过ActionChains模拟拖拽。不过这种操作涉及行为模拟的细节,成功率与页面实现方式、网络环境都有关系,没有一劳永逸的方案。

我个人的态度是:滑块验证属于反自动化机制,脚本层面能做的主要是降低触发概率——比如控制请求频率、增加随机等待时间、避免连续频繁操作。如果业务本身对识别频率要求很高,建议考虑官方 API 或者与平台合作,而不是硬怼验证逻辑。

5. 进阶应用:从"传一张图"到"自动化工作流"

5.1 识别结果的抓取与保存

百度识图的结果页会展示图片的尺寸、来源网站、相似图片等信息。如果需要把这些数据保存下来,可以定位结果区域的元素逐一提取。

# 假设结果区域包含多个图片链接 result_images = driver.find_elements(By.CSS_SELECTOR, "div[class*='result'] a img") for img in result_images: src = img.get_attribute("src") print("相似图片链接:", src)

这里要注意,页面中的图片地址可能是经过转义的,也有的是 base64 编码,需要根据实际情况做处理。我在实际抓取时遇到过两种常见情况:src是完整 URL 的直接保存;srcdata:image/...开头的那需要用 Python 的 base64 解码并写入文件。

5.2 结合 requests 下载识图结果图片

Selenium 负责操作页面,requests 负责高效下载图片,两者配合是常见套路。获取到图片 URL 之后,直接用 requests 下载:

import requests def download_image(url, save_path): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } resp = requests.get(url, headers=headers, timeout=10) if resp.status_code == 200: with open(save_path, "wb") as f: f.write(resp.content) print(f"下载成功:{save_path}") else: print(f"下载失败:HTTP {resp.status_code}")

用 requests 而不是 Selenium 下载,是因为 requests 更轻量、更快,不会占用浏览器资源。不过有些图片 URL 需要带 referer 才能访问,此时需要在 headers 里加上Referer字段,指向来源页面。

5.3 把上传封装成函数,方便其他项目复用

代码写多了之后,我习惯把"上传图片到百度识图"封装成独立函数,参数是图片路径,返回值是 driver 实例,这样后续其他脚本可以直接 import 使用。

def upload_to_baidu(driver, image_path): driver.get("https://graph.baidu.com/") wait = WebDriverWait(driver, 10) file_input = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='file']")) ) file_input.send_keys(image_path) time.sleep(2) return driver

封装的好处是让主流程代码变得清晰,也方便在多个脚本里复用同一套上传逻辑。遇到页面结构变化时,只需要改这一个函数,不用动其他代码。

5.4 文件下载完成后才继续下一步的通用思路

热搜词里有一条是"selenium怎样使文件下载完成之后才进行下一步",这其实是 Selenium 自动化里很经典的问题。对于图片上传这种场景,我们的"下一步"依赖于"上传成功"这个前置条件。

我常用的方案是:轮询判断目标文件是否已存在、大小是否稳定。比如脚本把识图结果保存到本地某个目录,那就用os.path.getsize()循环判断文件大小是否在增长,大小连续两次一致说明下载完成。

import os import time def wait_for_download_complete(directory, timeout=30): end_time = time.time() + timeout last_size = -1 while time.time() < end_time: files = os.listdir(directory) if not files: time.sleep(1) continue current_file = os.path.join(directory, files[0]) current_size = os.path.getsize(current_file) if current_size == last_size and current_size > 0: return os.path.join(directory, files[0]) last_size = current_size time.sleep(1) raise TimeoutError("下载超时")

这个方法的核心逻辑是:"文件大小不再变化"意味着下载完成。它比固定sleep更可靠,尤其在网络不稳定的情况下,能明显减少因等待不足导致的文件损坏。

5.5 常见的验证码和频率控制策略

自动化脚本跑时间长了,难免会遇到访问频率限制。百度识图虽然没有特别严格的限制,但如果每秒钟上传一次,很快可能被临时限制。我常用的策略是随机等待:

import random # 每次上传后随机等待 2-5 秒 time.sleep(random.uniform(2, 5))

随机等待的意义在于让请求节奏更接近人工操作,而不是机械的固定间隔。另外,不要开太多并发线程同时跑一个账号和 IP,这基本等于告诉服务器你在用脚本。做批量任务时,控制好速率比什么都重要。

6. 实操心得与几点补充建议

这套流程我前前后后用了大半年,从最初踩版本匹配的坑、找元素找不到、传路径没反应,到现在基本一次跑通。有几点体会特别想分享。

第一,Selenium 学习的核心抓手就是"先看 DOM,再写代码"。不要凭记忆或者想象去定位元素,每次动手前花两分钟按一下 F12,观察目标控件的真实属性和结构。很多定位失败的问题,根源在于页面已经改版或者元素嵌套层级变了,而你还在用旧的 CSS path。

第二,等待机制的优先级永远高于固定 sleep。WebDriverWait配合expected_conditions是处理动态页面最可靠的方式。固定 sleep 只是调试期的偷懒手段,上线长期跑的任务必须换成显式等待。

第三,文件上传这个动作,核心代码其实就一行send_keys。真正的难点在于周边配套:驱动的管理、页面的等待、结果的验证、异常的兜底。学的时候建议不要只看单一技术点,要站在整个流程的角度去思考,这样遇到问题才有全局的排查思路。

第四,百度识图页面处于活跃迭代状态,前端结构随时可能调整。这意味着你现在写好的定位方式,过段时间很可能需要微调。保持"F12 + 实时观察"的习惯,遇到报错先看元素还在不在,而不是急着改代码。我在实际使用中就遇到过三四次页面改版导致的选择器失效,每次都靠截图和 DOM 排查快速定位问题。

最后再分享一个实用技巧:跑批量任务前,先拿三张图片做冒烟测试,确认上传、等待、截图的逻辑都没问题,再开全量。这个习惯帮我避免了很多次跑了一半才发现问题的尴尬。

百度识图的上传自动化,某种意义上是一个缩影——它能让你真正理解 Selenium 处理文件上传的核心逻辑,也能让你体会等待策略和异常处理在自动化中的分量。把这套流程吃透,以后面对各类文件上传场景,都能举一反三。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询