写一个Python脚本自动下载壁纸这事儿听起来简单但真正上手跑一遍你会发现坑全藏在细节里。手动右键另存壁纸这件事做过的人都懂翻半天图库、选张合适的、还要手动改分辨率碰到想批量收藏的图集更是点到手酸。脚本自动下载就是把这套流程拆成“找图—下载—落盘”三步交给程序循环执行。这个需求非常适合Python入门到进阶的过渡阶段你能练到requests、正则/BS4解析、异常处理、并发下载、定时任务这些常用技能做完的脚本也能真正服务日常使用不是那种跑完就扔的玩具项目。1. 整体设计与思路拆解1.1 先拆需求下载壁纸的核心链路写脚本之前先把需求拆清楚。自动下载壁纸的完整链路就三环获取图片地址、发起下载请求、保存到本地。听起来不难但每一环都有选择要做。获取图片地址有两种常见方式。一是直接调用站点公开API比如Bing每日壁纸接口返回JSON解析一下就有原图URL二是写爬虫解析网页HTML从img标签或懒加载的data-src属性里提取壁纸链接。API方式稳定、省事适合新手HTML解析更通用但要看目标站点的结构处理翻页、懒加载、反爬这些问题。下载环节要考虑用什么库、要不要带请求头、要不要设超时、是否支持断点续传。保存环节则要处理好文件名冲突、目录创建、日志记录。整个脚本的设计思路就是围绕这三环做防御性编码——网络请求和文件操作是最容易出意外的两处宁可多写几行容错也不要裸奔跑。1.2 壁纸源选型为什么我推荐Bing每日壁纸写这类脚本图片源决定了你后续代码的复杂度。我比较推荐Bing每日壁纸作为首选练手源原因很简单它提供公开接口返回规范的JSON数据图片由微软官方托管稳定性有保障每日更新不需要处理翻页壁纸本身分辨率高适合做桌面背景。更关键的是它不需要登录、不需要复杂反爬处理对新手非常友好。当然如果你想扩展也完全可以换用其他源比如壁纸源获取方式优点缺点Bing每日壁纸公开JSON接口结构稳定无需反爬每天只有一张壁纸更新Lorem Picsum公开图片服务按ID返回随机图片适合测试图片风格偏摄影未必每个人都喜欢Pexels API官方API Key图片质量高支持搜索关键词需要申请API Key有调用配额普通壁纸网站HTML解析图库丰富类型多样需要处理懒加载、翻页、反爬初学阶段建议从Bing每日壁纸切入先把“API解析—下载—保存”这条链路跑通。等技术熟练了再去挑战HTML解析、翻页、多线程最后可以自己封装一个适配多源的小框架把不同的壁纸源抽象成一个个解析函数。1.3 技术栈选择与取舍技术栈上没有太多花哨的东西Python 3.10配合requests发请求、BeautifulSoup解析HTML、tqdm做进度条、concurrent.futures做并发下载。为什么不选Scrapy杀鸡用牛刀。壁纸下载这种轻量任务Scrapy的中间件、管道、选择器体系全用上反而增加心智负担。为什么不选Selenium除非目标站点是重度JS渲染否则Selenium启动浏览器本身就属于性能浪费而且依赖浏览器驱动部署起来很麻烦。用requests就能完成绝大多数需求。图片是静态资源服务器返回的就是二进制文件流没有动态渲染的问题。如果你想更“现代”一点也可以用httpx它支持HTTP/2和异步但requests的生态更成熟遇到问题更容易搜到解决方案。就事论事这个项目用requests完全够用。2. 核心细节解析与实操要点2.1 请求头被403折磨的万能解药很多初学者第一次写爬虫遇到403 Forbidden第一反应是“被网站封了”其实大概率只是缺了个User-Agent。服务器识别客户端身份主要靠请求头里的User-Agent字段很多站点对非浏览器请求会直接拒绝。加一个常见的浏览器UA就能解决大部分问题。我自己习惯把请求头抽成一个常量字典放在脚本开头统一管理HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Accept: image/avif,image/webp,image/apng,image/*,*/*;q0.8, Referer: https://www.bing.com/, }其中的Referer不是每次都需要但如果你要爬的壁纸站有防盗链请求头里带上目标站点的域名作为Referer往往能过关。防盗链的原理是服务器检查请求来源如果不是本站页面发起的请求就拒绝。这是一种廉价但有效的反爬策略而携带Referer就是顺着它的规则玩。2.2 超时与重试别让脚本卡死requests默认不设置timeout请求就一直挂着直到系统超时。这在自动化脚本里是致命的——你可能半夜发现在爬一个不响应图片服务器整个进程卡死在那里。所以每个网络请求都必须显式设置超时。import time import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def create_session_with_retries(): session requests.Session() retry_strategy Retry( total3, backoff_factor1, status_forcelist[500, 502, 503, 504], ) adapter HTTPAdapter(max_retriesretry_strategy) session.mount(http://, adapter) session.mount(https://, adapter) return session这段代码里最有价值的其实是backoff_factor1。它的意思是重试时等待时间按指数递增第一次重试等1秒第二次等2秒第三次等4秒。这样做比固定间隔重试更科学因为服务器可能在短时间t内持续过载给它一点恢复时间反而更容易成功。2.3 流式下载与控制内存下载图片有两种方式一种直接用resp.content拿全部二进制另一种用resp.iter_content按块迭代写入文件。单张壁纸通常只有几百KB用resp.content直接写文件问题不大但如果你做批量下载一次性把多张图片全部读进内存内存占用就上去了。我习惯用流式下载边下载边落盘这样即便下载100张图内存占用也是恒定的。def download_image(url, save_path, headers): try: with requests.get(url, headersheaders, streamTrue, timeout15) as r: r.raise_for_status() with open(save_path, wb) as f: for chunk in r.iter_content(chunk_size8192): if chunk: f.write(chunk) return True except Exception as e: print(f下载失败: {url} - {e}) return Falsechunk_size设置成8192字节8KB是实践里的折中太小会导致频繁磁盘写入太大又会让内存占用升高。这个值不用精确计算8KB到64KB都行。2.4 文件命名与去重策略保存壁纸时文件名不能乱来。至少要考虑三点可读性、避免非法字符、去重。Bing每日壁纸的JSON里通常包含日期信息比如“20250203”用它命名就很合理SAVE_DIR wallpapers os.makedirs(SAVE_DIR, exist_okTrue) save_path os.path.join(SAVE_DIR, fbing_{date}.jpg)这里有个细节Windows下文件名不能包含/:*?|这些字符如果壁纸名字是从接口返回的title字段里取的记住要清洗一遍import re def sanitize_filename(name: str) - str: return re.sub(r[\\/*?:|], , name).strip()去重逻辑更简单下载前先判断目标文件是否已存在。如果存在直接跳过。这个操作在批量下载几十张图时能省下大量重复流量也避免把同一张壁纸下载两次。2.5 异常处理与错误日志自动化脚本最忌讳的就是静默失败。你要让脚本的出错信息可追踪、可复盘第一版脚本可以简单地把错误打印到控制台但更好的做法是把日志写到文件里。Python自带的logging模块就够用import logging logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, handlers[ logging.FileHandler(download.log, encodingutf-8), logging.StreamHandler(), ], )这样一个setup控制台和日志文件同时输出排查问题的时候不用靠肉眼盯屏幕。3. 实操过程与核心环节实现3.1 环境准备新建一个项目文件夹初始化虚拟环境。虚拟环境这一步骤容易被初学者忽略但强烈建议养成习惯避免不同项目之间的依赖冲突。mkdir bing_wallpaper cd bing_wallpaper python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate然后安装依赖pip install requests beautifulsoup4 tqdm如果你只做Bing接口下载不解析HTML那只需要requests和tqdm就够了。3.2 获取Bing每日壁纸并解析JSON先写一个最简版本把Bing每日壁纸的JSON拉到本地看看结构import requests HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 } URL https://www.bing.com/HPImageArchive.aspx?formatjsidx0n8mktzh-CN resp requests.get(URL, headersHEADERS, timeout10) data resp.json() for item in data[images]: print(item[startdate], item[url])这里的URL参数含义formatjs表示返回JSON格式idx0表示从今天开始想要往前的图片可以调大idxn8表示一次取8张mktzh-CN表示地区不同地区的每日壁纸不同。返回的JSON里images数组的每个元素包含url、startdate、title等字段。需要注意Bing返回的url是相对的比如/th?idOHR.SomePlace_ZH-CN1234567890_UHD.jpg拼接时要在前面加上https://www.bing.com。3.3 下载单张壁纸拿到URL后就可以下载了。这里补充一点Bing壁纸的特性URL里包含_UHD.jpg后缀表示该图片是超高清版本分辨率通常有1920x1080甚至更高。如果你想指定分辨率可以手动将_UHD.jpg替换成_1920x1080.jpg这在某些需要限制图片尺寸的场景下很有用。一个完整的下载函数如下import os import requests def download_single_wallpaper(item, save_dirwallpapers): os.makedirs(save_dir, exist_okTrue) base_url https://www.bing.com img_url base_url item[url] # 可选强制使用UHD版本 if _UHD.jpg in img_url: img_url img_url.replace(_UHD.jpg, _UHD.jpg) date item[startdate] save_path os.path.join(save_dir, fbing_{date}.jpg) if os.path.exists(save_path): logging.info(f已存在跳过: {save_path}) return False try: with requests.get(img_url, headersHEADERS, streamTrue, timeout20) as r: r.raise_for_status() with open(save_path, wb) as f: for chunk in r.iter_content(chunk_size8192): if chunk: f.write(chunk) logging.info(f下载成功: {save_path}) return True except Exception as e: logging.error(f下载失败: {img_url}, 错误: {e}) return False这个函数虽然简单但已经包含了目录创建、文件去重、流式下载、错误捕获四个关键点。3.4 批量下载最近N天壁纸基于上面的单张下载函数批量下载就只是循环加进度条from tqdm import tqdm def batch_download(n8): url fhttps://www.bing.com/HPImageArchive.aspx?formatjsidx0n{n}mktzh-CN resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() data resp.json() success 0 for item in tqdm(data[images], desc下载壁纸): if download_single_wallpaper(item): success 1 print(f完成成功 {success}/{len(data[images])} 张)到这一步脚本的核心功能已经能跑了。如果你想立刻看到效果直接在命令行里运行python main.py就会在当前目录的wallpapers文件夹里看到下载好的图片。3.5 进阶用BeautifulSoup解析普通壁纸网站会了API方式后挑战一下HTML解析是很有价值的。以某个静态壁纸网站为例解析思路如下from bs4 import BeautifulSoup def parse_image_urls(html: str): soup BeautifulSoup(html, html.parser) img_tags soup.find_all(img, class_wallpaper-img) urls [] for img in img_tags: # 懒加载时图片真实地址可能在data-src中 url img.get(data-src) or img.get(src) if url and url.startswith(http): urls.append(url) return urls这个示例里的选择器class_wallpaper-img是假设的实际情况以你要爬的网站HTML为准。常见坑有两个一是图片懒加载导致src是占位图真实地址在data-src或data-original属性里需要额外提取二是网页是JS动态渲染的requests拿不到真实内容这种时候才需要考虑Selenium或Playwright。3.6 用并发提升下载速度批量下载几十张壁纸时逐张下载确实慢瓶颈主要在I/O等待。用ThreadPoolExecutor实现多线程下载很简单但要注意控制并发数量不要开太多线程把目标服务器打压力太大也不要被网站封IP。from concurrent.futures import ThreadPoolExecutor, as_completed def concurrent_batch_download(items, max_workers4): success 0 with ThreadPoolExecutor(max_workersmax_workers) as executor: future_to_item { executor.submit(download_single_wallpaper, item): item for item in items } for future in as_completed(future_to_item): if future.result(): success 1 return successmax_workers4是比较保守的参数日常下载4到8个线程足够。多线程下载的核心是下载速度受限于网络和服务器线程开得再多也没意义反而可能触发反爬。3.7 定时自动化让脚本自己跑脚本写好后可以挂到系统定时任务里。Windows下用任务计划程序macOS/Linux下用crontab。以Linux为例每天上午10点执行一次下载今天壁纸的操作0 10 * * * cd /path/to/bing_wallpaper /path/to/venv/bin/python main.py download.log 21Windows的任务计划程序创建基本任务触发器设为“每天”操作设置为启动程序程序填Python解释器路径参数填脚本路径即可。注意一定要用虚拟环境里的Python解释器路径而不是系统Python否则可能找不到第三方库。如果你不想配置系统定时任务也可以写一个简单的循环再sleep到次日不过这种写法不推荐进程一旦被重启就失效了还是系统级定时任务更可靠。4. 常见问题与排查技巧实录4.1 下载连接被拒绝 / 403403最常见的原因是请求头里没有User-Agent或者被目标站点的防盗链拒绝。解决办法就是加上UA和Referer。如果还不行降低请求频率在请求之间加一个随机延时import random import time time.sleep(random.uniform(1, 3))4.2 SSL证书验证失败复现出来的报错一般是requests.exceptions.SSLError: HTTPSConnectionPool ... certificate verify failed。常见原因是操作系统根证书过期更新证书即可。应急办法是给请求加verifyFalse但要同时抑制警告import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) resp requests.get(url, headersHEADERS, timeout10, verifyFalse)需要警别的是关闭证书验证只是应急手段不要在产品化代码里常态使用否则容易遭到中间人攻击。4.3 JSON解析报错如果resp.json()抛JSONDecodeError大概率是服务器返回了HTML错误页面而不是JSON数据。写代码时可以先打印响应文本前200个字符看一眼再决定排查方向。通常可以把问题锁定在请求头、Cookies或IP风控上。4.4 下载的图片打不开或尺寸不对图片打不开一种可能是下载不完整文件大小为零或远小于预期。解决办法是用流式下载后校验文件大小比如file_size os.path.getsize(save_path) if file_size 1024 * 10: # 小于10KB视为异常 os.remove(save_path)另一种可能是URL本身就是缩略图而非原图比如Bing壁纸如果你不去掉缩略图参数下载的图分辨率可能只有几百像素。拿到URL后先打印观察确认是原图地址再保存。4.5 Windows下运行时因为编码报错运行脚本时如果出现UnicodeEncodeError通常是因为Windows终端默认GBK编码而打印内容包含中文。简单处理是在脚本开头import sys import io sys.stdout io.TextIOWrapper(sys.stdout.buffer, encodingutf-8)不过更好的做法是少在控制台打印中文内容或者直接用日志模块输出到文件既能避免编码问题也方便之后查阅。4.6 pip和Python版本混乱Windows上装了多个Python版本时命令行运行python -V和pip -V可能不是同一个。解决办法是始终使用虚拟环境或者在命令行中直接使用python -m pip install而不是pip install这样能确保装到当前python对应的环境里。5. 进阶优化与避坑心得5.1 断点续传和临时文件下载大文件或网络不稳定时如果下载到一半断开整个文件就废了。实现断点续传的思路先保存到临时文件下载完成后改名。更进一步用HTTP Range头做断点续传headers {**HEADERS, Range: bytes1024-}注意Range用法需要服务器支持断点续传Bing这类静态文件服务器通常支持但一些小站点不一定。先用Range下载到临时文件下载完成后os.rename替换原文件这套逻辑能搞定绝大多数场景。5.2 使用logging替换print如果你想长期使用这个脚本建议把所有print换成logging。这样时间戳、日志级别、输出位置都能自定义。之前的logging配置除了控制台输出还会写入download.log查问题时直接看日志文件比在终端翻历史记录高效得多。5.3 继续扩展的方向脚本本身已经是完整可用的工具了但如果想继续折腾这几个方向值得考虑接入参数解析argparse支持命令行指定下载数量、保存目录、是否启动多线程把下载列表和已下载文件存入SQLite数据库做更复杂的去重和统计写一个函数设置Windows壁纸下载完直接应用真正做到全自动换壁纸接入消息通知下载完成后通过邮件或推送服务通知你5.4 我踩过的一些坑说说我自己的实操体验。第一次写这个脚本时我以为只调用Bing接口就完事了结果下载下来的图片后半天打不开检查后发现是接口返回的URL里有一个缩略图参数去掉后才拿到原图。后来下载速度太慢折腾了半天多线程结果发现瓶颈根本不是代码而是服务器限速多线程并没有带来质的提升。还有一次我尝试爬一个图库网站怎么请求都被403换了好几个UA都不行最后发现是Cookie里有签名参数。这种反爬就需要先在浏览器里登录一次把Cookie复制下来带进请求或者用Selenium模拟真实浏览器行为。所以说爬虫这个领域“水很深”但基础思路无外乎模拟真实用户、控制频率、解析数据、容错重试这几件事。最后再分享一个小技巧。如果你不想每次跑脚本都盯着终端可以把脚本接到Windows的“任务计划程序”里每天固定时间执行一次。我自己的做法是下载完顺便调用系统API切换壁纸这样每天开机就是一张新图片完全不用手动干预。把自己的电脑变成“每日自动换肤”的状态这种自动化带来的愉悦感折腾过的人才懂。
