Python爬虫实战:用requests与BeautifulSoup批量下载网页图片

Python爬虫实战:用requests与BeautifulSoup批量下载网页图片 简介这是一份面向工业视觉开发者和Python初学者的工业相机图像采集脚本基于开源计算机视觉库实现相机连接、实时抓帧、曝光与增益等参数设置以及图像保存与资源释放的完整流程可帮助读者绕开繁杂的硬件文档快速掌握工业相机与Python的通信方法解决设备图像采集场景中的常见编程问题。资源包采用RAR压缩格式压缩包内仅包含一个Python源文件整体大小只有四KB代码量精简到接近最小可运行示例非常适合直接阅读、调试和二次修改也可以作为自动化检测项目中图像采集模块的起点。脚本通过摄像头对象完成设备初始化使用读取帧方法获取图像并判断返回状态再通过属性设置方法调节曝光、增益等关键参数最后使用图像保存函数写出图片并释放相机资源完整覆盖了从相机开启到资源回收的典型操作链路同时给出了参数配置示例与异常判断逻辑帮助开发者在实际项目中快速上手。这一资源目前已有两千七百六十三人浏览学习学习热度较高内容实用且轻量不管是入门了解工业相机的工作原理还是直接嵌入到实时监控与质量检测系统中都能提供不错的参考价值。 平时不管是做内容采集、整理素材还是给朋友从网页上扒一批产品图总会碰到同一个需求把一个页面里所有图片一次性抓下来。以前我总写临时脚本每次都要重新查一遍 requests 和 BeautifulSoup 的用法后来干脆把这些逻辑整理成一个独立脚本也就是今天分享的 GrabImage.py。它做的事情很直接输入一个网页 URL自动解析页面里所有的图片地址并发下载到本地目录支持自定义保存路径和线程数不需要数据库、不需要框架一个文件加几个第三方库就能跑。适合刚开始学 Python 爬虫的同学拿来练手也适合有批量整理图片需求的朋友直接抄作业。GrabImage.py 的价值在于把网页到本地图片这条链路完整打通里面包含 URL 拼接、懒加载地址处理、文件名清理、并发下载这些容易被忽略但必须处理的细节。这篇文章会把设计思路、完整代码、运行演示和踩坑记录一次讲清楚代码可以直接复制使用。1. 整体设计先想清楚要解决哪些问题1.1 技术选型为什么是 requests 加 BeautifulSoup很多刚接触爬虫的朋友上来就想上 Selenium觉得能渲染 JavaScript 就一定更强。但 GrabImage.py 从一开始就定位成轻量、快速、零配置的工具所以核心用的是 requests 加 BeautifulSoup。这两个组合处理纯静态页面的图片抓取场景已经绰绰有余启动快、内存占用低、依赖少requests 的会话复用机制在批量请求时也能明显减少 TCP 握手开销。对一台普通笔记本来说跑一个几十张图片的页面资源和时间成本都可以忽略不计。那什么时候才需要 Selenium我实测下来只有当页面图片是通过 JavaScript 异步加载、地址藏在接口返回里的时候才需要上无头浏览器去渲染。这种情况我会单独写一个分支处理而不是让整个脚本一直背着一个笨重的浏览器引擎。工具类脚本最重要的原则就是按需加载复杂度能用简单方案解决的就别把架构搞复杂。这也是我一开始坚持不用 Scrapy 的原因——框架本身没问题但对一个单文件小工具来说它的调度器和中间件机制反而成了多余的负担。1.2 脚本的整体处理流程GrabImage.py 的执行流程不复杂核心就四步请求页面拿 HTML、解析 HTML 提取图片地址、去重和拼接完整链接、并发下载到指定目录。每一步看着简单实际写的时候都有不少细节。比如第二步img 标签的 src 可能是相对路径也可能是>def extract_image_urls(soup, base_url): urls set() # 先处理 img 标签优先取>with ThreadPoolExecutor(max_workersargs.threads) as executor: futures {executor.submit(download_image, url, args.output): url for url in urls} for future in as_completed(futures): filepath, size future.result() if filepath: success 1 total_size size print(f[成功] {filepath} ({size/1024:.1f} KB)) else: print(f[失败] {futures[future]})as_completed 的好处在于哪个任务先完成就先处理哪个不用等最慢的那个。实际跑批量下载时这个差别非常明显——有些图片服务器响应要好几秒如果强行要求全部结束再统一处理中间出现任何卡顿都会拖慢整批任务。另外我在每个下载任务里设置了 15 秒超时避免个别死链把线程池拖死。如果你下载的图片体积普遍较大可以把这个值调大到 30 秒但不要再高了否则遇到无响应的地址会非常难受。2.3 文件名的安全处理文件名这块我踩过不少坑。直接从 URL 里取文件名经常会遇到三件事一是 URL 编码的中文名二是带 query 参数的地址三是包含/:*?|这类非法字符。所以 download_image 里我加了一个 safe_filename 函数做两层防护先用 urlparse 把 query 参数去掉再通过正则把 Windows 和 Linux 下都不合法的字符替换成下划线。如果解析出来的文件名为空就用时间戳加固定后缀兜底。def safe_filename(url): path urlparse(url).path name os.path.basename(path) if not name: name fimg_{int(time.time())}.jpg name re.sub(r[\\/:*?|], _, name) return name这里特别提醒一点如果图片地址里包含 URL 编码的中文或者空格建议用urllib.parse.unquote先解码一次再落盘否则文件名会是一堆%E4%BD%A0%E5%A5%BD这样的字符。我自己在整理素材时就被这个坑过几十张图下载完文件名全是乱码排查了半天才发现是编码问题。虽然不影响图片本身但后续管理起来非常痛苦。3. 完整代码与实操演示3.1 完整代码把上面几个函数串起来再加上命令行参数解析就是完整的 GrabImage.py。整体代码一百行左右主要包含四个部分参数解析、页面请求与解析、图片地址提取、并发下载。所有功能都通过 argparse 暴露成命令行参数这样不仅能自己用后续打包给别人用也方便不需要改一行代码。#!/usr/bin/env python3 # -*- coding: utf-8 -*- GrabImage.py - 网页图片批量抓取工具 用法: python GrabImage.py https://example.com python GrabImage.py https://example.com -o ./download -t 8 import os import re import time import argparse import requests from urllib.parse import urljoin, urlparse from bs4 import BeautifulSoup from concurrent.futures import ThreadPoolExecutor, as_completed HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, } def get_soup(url): resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding return BeautifulSoup(resp.text, html.parser) def extract_image_urls(soup, base_url): urls set() for img in soup.find_all(img): src img.get(data-src) or img.get(src) or img.get(data-original) if src: urls.add(urljoin(base_url, src)) for a in soup.find_all(a): href a.get(href) if href and re.search(r\.(jpg|jpeg|png|gif|webp|bmp)$, href, re.I): urls.add(urljoin(base_url, href)) return urls def safe_filename(url): path urlparse(url).path name os.path.basename(path) if not name: name fimg_{int(time.time())}.jpg name re.sub(r[\\/:*?|], _, name) return name def download_image(url, save_dir): try: resp requests.get(url, headersHEADERS, timeout15) resp.raise_for_status() filename safe_filename(url) filepath os.path.join(save_dir, filename) with open(filepath, wb) as f: f.write(resp.content) return filepath, len(resp.content) except Exception: return None, 0 def main(): parser argparse.ArgumentParser(description网页图片批量抓取工具) parser.add_argument(url, help目标网页URL) parser.add_argument(-o, --output, defaultimages, help图片保存目录默认 images) parser.add_argument(-t, --threads, typeint, default5, help并发线程数默认 5) args parser.parse_args() os.makedirs(args.output, exist_okTrue) soup get_soup(args.url) urls extract_image_urls(soup, args.url) if not urls: print(未发现任何图片地址请确认页面是否使用了 JS 渲染) return print(f共发现 {len(urls)} 张图片开始下载...) success 0 total_size 0 with ThreadPoolExecutor(max_workersargs.threads) as executor: futures {executor.submit(download_image, url, args.output): url for url in urls} for future in as_completed(futures): filepath, size future.result() if filepath: success 1 total_size size print(f[成功] {filepath} ({size/1024:.1f} KB)) else: print(f[失败] {futures[future]}) print(f下载完成成功 {success}/{len(urls)}共 {total_size/1024/1024:.2f} MB保存到 {args.output}/) if __name__ __main__: main()3.2 实际运行效果演示我拿一个普通的图片展示页做了测试命令就一行python GrabImage.py https://example.com/gallery -o ./gallery -t 6运行后脚本会先打印发现的图片总数然后每个任务完成时实时输出文件路径和体积。我实测一个包含 32 张图片的页面6 个线程大概 12 秒跑完其中有一张因为原图链接失效失败了其余 31 张全部下载成功成功率 96%。这个结果对大多数场景来说是可接受的。如果你对成功率有更高要求可以在 download_image 里加一个失败重试的循环比如失败后间隔两秒重试一次通常能救回来一部分因为网络波动导致的失败。3.3 打包成 exe 给不会 Python 的人用很多朋友写脚本只自己用其实这工具完全可以打包成 exe 发给同事或客户。命令行下执行一行 PyInstaller 命令就行pip install pyinstaller pyinstaller -F -n GrabImage GrabImage.py打包完在 dist 目录下会生成一个 GrabImage.exe对方电脑上不需要装 Python 环境双击或者带上参数就能跑。这里提醒一句PyInstaller 打包时建议用 -F 参数生成单文件虽然启动时会稍微慢一点但分发最省事如果电脑上装了多个 Python 版本记得先确认当前激活的环境是你要打包的那个。我之前因为没注意虚拟环境把依赖装到了全局环境里打包出来的 exe 在别人机器上运行直接提示缺模块重新打包才解决。4. 常见问题与排查技巧实录4.1 高频报错对照表用了一段时间之后我把最常遇到的几类问题整理成了表格遇到报错直接对号入座比翻文档快很多。报错信息原因解决办法ConnectionError: Max retries exceeded目标站点无法访问或拒绝连接检查 URL 是否正确补全 headers尝试更换 http/https 协议SSLError / 证书验证失败站点证书异常或过期请求时加 verifyFalse仅限信任的站点UnicodeDecodeError页面编码识别失败使用 resp.apparent_encoding 而不是固定 utf-8HTTP 403 Forbidden被服务器反爬拦截换成完整的浏览器 UA必要时补 Referer 和 Cookie下载下来全是同一张占位图懒加载地址没取到检查 style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />