
说个真实场景我想给电脑换个新壁纸打开壁纸网站翻了十几页看到顺眼的就右键另存为选文件夹、点保存重复二十多次之后基本就失去了换壁纸的热情。后来我干脆写了个Python脚本让它自己把壁纸下载回来我只需要提前定好关键词和数量。这个“Python脚本自动下载壁纸”的项目本质上是一个入门级但很完整的网络请求、内容解析、文件读写的小工具适合所有刚开始学爬虫、想把Python用到真实生活里的人。它能帮你把“一张张手动保存”变成“一键批量下载”还能顺便学会请求头、JSON解析、翻页、重试这些今后写任何自动化脚本都会用到的底层技能。1. 这个脚本到底要做什么需求拆解与方案取舍1.1 先想清楚自动化的边界很多人一上来就写代码结果写着写着发现需求没想清楚。自动下载壁纸听起来简单但里面至少有这几个子问题需要先定义壁纸从哪个来源下载每次下载多少张、什么清晰度是只下载一次还是希望定期自动更新下载后是丢在文件夹里还是直接设成桌面壁纸以我个人的经验第一版不要贪多先把“手动执行一次能下载N张壁纸”跑通再考虑定时任务。否则既要处理代理、又要处理登录态、还要挂在后台常驻出问题的时候根本分不清是哪一环崩了。我建议把第一版目标定成给定一个关键词和数量脚本自动从壁纸站抓取图片地址下载到本地wallpapers目录。1.2 壁纸源怎么选API优先其次再考虑HTML解析技术选型上最大的分岔路口是目标站点有没有提供API。有API的站点比如wallhaven它有一个公开的JSON接口你往https://wallhaven.cc/api/v1/search这个地址传关键词、页数、分辨率它会直接返回图片的标题、标签、缩略图地址和原图地址。拿到path字段之后requests直接下载就行整个过程连解析HTML都不需要。没有API的站点就需要写HTML解析逻辑先用requests.get抓取页面源码再用BeautifulSoup定位图片节点提取链接再拼接出原图地址。这条路能走通但凡是稍微有点反爬意识的站点都会在页面里塞一堆干扰字段、懒加载属性、加密字体新手在这里被劝退的概率很大。所以我的建议非常明确第一版优先选带API的壁纸源。这不叫偷懒而是把精力集中在核心逻辑上。等API方式跑通理解了请求、响应、下载这套流程再去看HTML解析你会感觉轻松很多。1.3 你需要准备的知识与工具这个项目需要你对Python有最基本的了解知道import、函数、for循环、字符串拼接就够了不用会面向对象也不需要用装饰器。具体依赖只有两个requests用来发HTTP请求BeautifulSoup4用来解析HTML。其余全是Python自带的os、time、re等标准库。工具就这么简单。真正要花心思的是理解“请求”这个过程你写代码模拟浏览器向服务器要东西服务器返回数据你再从数据里提取自己需要的部分。壁纸下载只是这个通用模型的第一个应用场景理解了它抢票脚本、自动签到、批量下载文档本质上都是同一套思路。2. 环境准备Python、依赖包和最容易卡住的“cmdlet报错”2.1 安装Python以及确认命令行环境如果你Mac或Linux一般自带Python 3直接在终端输入python3 --version就能确认。如果在Windows上最常见的麻烦是下载了Python安装包却装不上或者装完了在命令行里输入python提示python 不是内部或外部命令。这种问题和Python本身无关而是安装时没勾选“Add Python to PATH”导致命令行找不到python.exe。2.2 安装requests与BeautifulSoup4环境没问题之后打开终端执行pip install requests beautifulsoup4如果你电脑上同时装了多个Python版本可能要换成pip3或者python -m pip install requests beautifulsoup4。装完之后验证一下python -c import requests; print(requests.__version__)能打印出版本号就说明环境没问题。这里有个很多新手会卡住的点执行任何命令都提示“无法将...识别为cmdlet、函数、脚本文件或可运行程序的名称”。出现这个提示要么是命令对应的程序没安装要么是程序装了但不在当前命令行能搜索到的路径里。刚配好Python就遇到这种报错九成是PATH没生效最简单的排查办法是重开终端或者重启电脑让环境变量重新加载。还不行就直接把python.exe的完整路径写到命令里比如C:\Users\你的用户名\AppData\Local\Programs\Python\Python311\python.exe --version能运行就说明程序本身没问题纯粹是PATH的锅。2.3 写第一个验证脚本排除网络与SSL问题在跑正式的壁纸代码之前我强烈建议先写一个小验证脚本确认你的网络环境可以正常访问目标站点import requests url https://wallhaven.cc/api/v1/search?qnaturepage1 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(url, headersheaders, timeout10) print(resp.status_code) print(resp.text[:200])如果打印出200和一段JSON说明网络完全通畅。如果卡住不动或者超时那就需要先解决网络问题别急着往下写代码。我在实际项目里发现这个验证步骤能帮你省下至少半小时的冤枉时间因为它把“环境问题”和“代码问题”隔离开了。代码写错了你还能看报错环境不通你连报错都看不懂。3. 初版实现基于JSON接口抓取壁纸原图3.1 理解壁纸API的返回结构先花一分钟看看接口返回的东西。用浏览器打开这个地址https://wallhaven.cc/api/v1/search?qnaturepage1你会在浏览器里看到一串JSON里面有一个data数组数组里每个元素就是一张壁纸的信息。最关键的字段是path图片原图的直接下载地址是我们真正要的东西id图片的唯一标识适合用来做文件名resolution分辨率比如1920x1080tags标签列表可以用来做分类筛选有些图片地址带?timestampxxx这样的查询参数下载时最好先去掉或者保留都可以只要后缀名是.jpg、.png就行。3.2 完整代码按关键词下载N张图下面这个脚本是完整可运行的我建议你直接复制到一个download_wallpaper.py文件里import os import re import time import requests API_URL https://wallhaven.cc/api/v1/search OUTPUT_DIR wallpapers HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } def download_wallpapers(keyword, num_pages2): os.makedirs(OUTPUT_DIR, exist_okTrue) for page in range(1, num_pages 1): params { q: keyword, page: page, atleast: 1920x1080 } try: resp requests.get(API_URL, headersHEADERS, paramsparams, timeout15) resp.raise_for_status() data resp.json() except Exception as e: print(f[第{page}页] 请求失败: {e}) continue items data.get(data, []) if not items: print(f[第{page}页] 没有数据结束) break for item in items: image_url item.get(path) image_id item.get(id, str(int(time.time() * 1000))) if not image_url: continue ext os.path.splitext(image_url.split(?)[0])[1] if ext.lower() not in (.jpg, .jpeg, .png, .webp): ext .jpg filepath os.path.join(OUTPUT_DIR, f{image_id}_{keyword}{ext}) try: img_resp requests.get(image_url, headersHEADERS, timeout30) img_resp.raise_for_status() with open(filepath, wb) as f: f.write(img_resp.content) print(f已下载: {image_id} {item.get(resolution, )} - {filepath}) except Exception as e: print(f下载失败: {image_url} - {e}) time.sleep(0.5) time.sleep(2) if __name__ __main__: download_wallpapers(nature, num_pages2)在你自己的电脑上用python download_wallpaper.py运行它你会看到终端一行一行地打印“已下载”然后wallpapers文件夹里会多出一批壁纸。3.3 关键代码段解读请求头、分页参数、图片保存这段代码里有两个细节值得展开讲。第一个是HEADERS里的User-Agent。服务器收到请求时会根据User-Agent判断你是浏览器还是爬虫脚本。Python的requests默认的用户代理是python-requests/x.y.z很多壁纸站看到这个直接就拒绝。所以我们要把一个常见浏览器的UA字符串伪装上去。这不涉及任何违规只是模拟真实用户访问。凡是做了UA校验的站点不带上这个字段很容易收到403 Forbidden。第二个是参数里的atleast1920x1080。这是wallhaven API自带的最小分辨率筛选参数保证下载回来的图不会是一张很小的模糊缩略图。在类似接口上这个“只取高清图”的思路是通用的视频网站按码率筛选图片站按分辨率筛选文档站按文件类型筛选核心都是把不需要的内容提前过滤掉。图片保存时的文件名我用了图片ID_关键词.扩展名这种组合。图片ID保证不重名关键词方便知道这是哪一类壁纸。如果你不做这个组合两个不同关键词的API可能会返回同一张图片后者会把前者覆盖掉。4. 升级到HTML解析分析列表页并提取真正的高清图地址4.1 为什么还要学HTML解析既然API方案已经能用了为什么还要学HTML解析因为并不是所有壁纸站都有开放API很多图库、素材站只有网页。学会用代码从网页里提取链接才算把“爬虫”这条路彻底走通了。另外在面试和实际工作中解析HTML的能力几乎天天用。4.2 页面结构分析以列表页为例假设目标站点没有API只有一个搜索结果列表页页面里每个壁纸块长这样figure classthumb img classpreview>from bs4 import BeautifulSoup soup BeautifulSoup(page_text, html.parser) for figure in soup.select(figure.thumb): img figure.find(img) data_src img.get(data-src) print(data_src)4.3 把缩略图地址改写成原图地址很多站点为了省流量列表页只展示缩略图缩略图地址里会带small这样的路径段而原图通常在同一目录级别下换成full。比如wallhaven的缩略图地址是https://w.wallhaven.cc/small/3l/3l1mzk.jpg它的原图地址是https://w.wallhaven.cc/full/3l/3l1mzk.jpg所以可以用一个字符串替换把/small/直接替换成/full/。这里有个经验如果一个字段你可能用到多次先把它打印出来看一眼再写替换逻辑别闭着眼替换。我在实际做解析时至少有三四次以为“路径规律猜对了”结果下载回来一堆404错误页。另一种更稳妥的方式是进入详情页找og:image这样的meta标签。几乎所有图库站为了让图片能在社交平台分享都会在详情页的HTML里放一个og:image里面就是高清原图的完整地址meta propertyog:image contenthttps://example.com/full/3l/3l1mzk.jpg /用BeautifulSoup提取这条meta比猜路径规律可靠得多。4.4 补充请求头为什么没有User-Agent会被拒绝HTML解析版比API版更需要完整的请求头因为API通常对自动化比较宽容而网页端的反爬往往更严。除了User-Agent至少还要补上Referer让服务器看到请求是从它自己站内的页面跳转过来的而不是凭空出现的HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://example.com/ }如果发现加了这个还是被拒可以再增加Accept-Language、Cookie等字段。但我不建议一上来就全套伪装因为字段越多越容易被规则匹配到。很多时候只需要一个User-Agent就能正常访问。先小步验证再逐步加码是排查请求被拒时的基本原则。5. 让脚本真正自动化翻页、去重、重试、定时执行5.1 多页抓取与关键词组合初版代码已经支持传入num_pages参数但这里有个隐藏问题如果某个关键词在第2页之后全是重复图片脚本还是会傻乎乎地全部下载。解决办法是维护一个去重setseen set() for item in items: image_id item.get(id) if image_id in seen: continue seen.add(image_id)如果你想把代码写得更灵活可以让关键词也支持多个。比如用户传nature,landscape脚本就用qnature,landscape去请求这样一次能拿到两类图。5.2 下载重试与超时控制网络请求不可能每次都成功尤其是批量下载图片时偶尔几次超时属于正常现象。初版代码遇到一次超时就把整张图跳过实际使用下来会发现总会有那么几张漏网之鱼。我给下载逻辑加一个简单的重试机制每张图最多尝试3次每次失败后等待时间递增比如第1次等1秒第2次等3秒第3次等6秒def download_with_retry(url, filepath, max_retries3): for attempt in range(max_retries): try: resp requests.get(url, headersHEADERS, timeout30) resp.raise_for_status() with open(filepath, wb) as f: f.write(resp.content) return True except Exception as e: print(f第{attempt 1}次失败: {e}) time.sleep(2 * (attempt 1)) return False这种退避策略叫线性退避比固定间隔更科学它既给服务器留了恢复时间也不会因为一直重试把自己的网络出口堵死。5.3 定时执行Windows任务计划与Linux crontab脚本能跑通之后下一步就是让它定时自动跑。Windows上最直接的方式是“任务计划程序”打开任务计划程序创建一个基本任务触发器选“每天”操作里填python.exe的完整路径参数填脚本的完整路径工作目录填脚本所在文件夹。建完之后可以右键“运行”测试一下看脚本是否真的能被系统拉起来。Linux和macOS上更简单用crontab -e加一行0 9 * * * cd /home/user/wallpaper /usr/bin/python3 download_wallpaper.py run.log 21意思是每天早上9点先进入项目目录再执行下载脚本把日志输出到run.log。这个cd的写法很关键因为脚本里用了相对路径wallpapers目录如果crontab启动时工作目录不对你会看到脚本在跑但文件存到了莫名其妙的位置。你可能觉得每天固定时间只能下载一次不够灵活也可以用[Tool]或者计划任务设置“每隔N小时重复”这样可以把下载频率控制在你自己想要的节奏上。5.4 下载完成后直接换壁纸只下载到文件夹离真实体验还差一步。我希望运行完脚本桌面壁纸直接换成最新下载的那张。Windows下可以用Python调用系统APIimport ctypes def set_wallpaper_windows(image_path): ctypes.windll.user32.SystemParametersInfoW(20, 0, image_path, 3)Linux桌面环境可以用gsettings命令gsettings set org.gnome.desktop.background picture-uri file:///path/to/image封装好这个函数后下载完图片不要急着结束从最新文件里挑一张调用set_wallpaper这样每天的自动化流程就完整了定时下载并换好壁纸你坐到电脑前它已经是一个崭新的桌面了。5.5 日志记录让脚本出问题时你能知道原因加一个简单日志不依赖logging模块也行直接在脚本里把时间戳和状态写到一个文本文件def log(msg): with open(download.log, a, encodingutf-8) as f: f.write(f{time.strftime(%Y-%m-%d %H:%M:%S)} {msg}\n)所有关键节点都调一下log()。定时任务里的崩溃很讨厌因为它不像你手动运行那样能看到终端里的报错。有了日志第二天起床看到壁纸没换打开download.log就能知道是请求失败了、还是图片下载到一半网络断了、还是目标站点改版导致解析规则失效了。6. 实测中你会遇到的坑和对应排查思路6.1 SSL证书验证失败怎么办我第一次在某个旧服务器上跑脚本时报错信息里带着SSL: CERTIFICATE_VERIFY_FAILED。当时第一反应是代码有问题排查了半天才发现是服务器上的Python环境没有安装根证书。正常情况下代码里应该保留证书验证requests默认就是开启的。如果遇到这个报错优先看本机系统时间是否准确时间不准会导致证书过期判定其次考虑更新certifi库pip install --upgrade certifi。最后才考虑在请求里加上verifyFalse但必须带上这个参数时建议同时加一个urllib3.disable_warnings()不然每次请求都会刷一条警告信息。6.2 返回内容乱码、JSON解析失败下载壁纸时收到乱码十有八九是编码问题。HTML默认编码是UTF-8但也有些站是GBK或者GB2312。requests虽然会根据响应头猜测编码但如果你手动指定了错误的编码就会得到乱码。建议处理HTML时先判断一下resp.encoding resp.apparent_encodingapparent_encoding是requests根据字节内容推测出来的编码比响应头里的charset更靠谱。JSON解析失败大多是接口压根没返回JSON而是返回了一个403错误页或者验证码页面。遇到这种情况先打印resp.text[:500]看看实际内容是什么再决定是补请求头还是降低请求频率。6.3 图片下载了但打不开最经典的坑文件下载成功硬盘里也看到文件了但双击打开提示“文件已损坏”或“格式不支持”。问题几乎都出在保存内容上你以为下载的是图片实际上响应体是HTML错误页。比如图片地址被服务器要求带特定Cookie你直接requests.get得到的其实是一个跳转页把这个HTML存成了.jpg自然打不开。排查方法是下载后看一眼文件大小正常壁纸至少几百KB如果只有几KB甚至几百字节那大概率是错误页。再稳妥一点可以检查文件头JPEG以FF D8开头PNG以89 50 4E 47开头with open(filepath, rb) as f: head f.read(4) if head in (b\xff\xd8\xff\xe0, b\xff\xd8\xff\xe1): print(JPEG) elif head b\x89PNG: print(PNG)这个方法在批量处理文件时非常好用能自动把脏文件挑出来。6.4 被网站限流降频、重试与合规提醒批量下载最容易触发网站的反爬。表现通常是前几十张下载很顺利突然开始大量超时、403最后IP直接进小黑屋。这并不代表网站小气而是你的行为已经明显超出正常用户频率了。解决思路是先降频把图片之间的sleep(0.5)调到sleep(2)以上翻页间隔调到3~5秒。其次加一个随机延时让请求时间不是固定间隔否则容易被请求频率特征给识别出来time.sleep(random.uniform(1.5, 3.0))更重要的一点是合规使用的边界。你这个脚本只能用于个人学习交流用来下载自己有权限获取的内容。动笔写代码之前建议看一眼目标站点的robots.txt和服务条款。像wallhaven这类站是允许个人用API做自动化下载的但很多图库网站并不允许抓取其全站内容。保持低频、个人小批量使用既是对站方负责也是对你自己的保障。6.5 我的个人经验与后续扩展方向这套脚本我实际跑了小半年最大的感受是自动化脚本的价值不在于它省了多少时间而在于它逼着你把需求想清楚。比如最初我只是想“下载壁纸”但用了几天后发现数量太多了筛选是个麻烦于是加了分辨率筛选和标签过滤后来发现在公司电脑上还要用代理又加了一层代理配置。每加一个功能你对Python的理解就深一层。后续扩展方向其实非常多加一个argparse参数解析让脚本能接收命令行参数而不是改代码加一个图片去重功能用文件MD5哈希判断是否已经下载过甚至可以用Pillow库把下载的图片统一裁剪成当前屏幕分辨率。这些方向不需要我这个脚本全都实现但下一次你再想写别的自动化脚本时这些代码可以直接复用。写代码解决实际问题最有成就感的地方就是这最后一环脚本在后台安静地跑着你只需要在二次元风景、极简几何、高清摄影这些关键词里挑一个剩下的全交给它。