十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python写一个自动下载壁纸的脚本:从环境配置到定时任务全解析

用Python写一个自动下载壁纸的脚本:从环境配置到定时任务全解析 几个月前我换了台2K显示器从此“找壁纸”成了每天最烦的事。网上标着高清的图下载下来一放大全是噪点合眼缘的图又常常带水印有些壁纸站还非要注册登录才能下载原图。折腾了几天之后我放弃了手动方案直接写了个Python脚本自动下载壁纸把我从这套重复劳动里彻底解放出来。这个脚本不仅能定时去壁纸源拉取图片还能按分辨率筛选、自动去重、多线程加速甚至下完还能直接设置成桌面壁纸。如果你也经常为了找壁纸浪费时间或者想学习怎么用Python写一个带完整流程的小工具这篇文章可以当一份直接抄作业的参考。需要说明的是这里说的“壁纸下载”不是搞什么灰产爬虫就是很普通的自动化需求从一个允许访问的图片站或开放API里把图片资源按规则保存到本地。整篇代码量不大核心逻辑拆开看也就四步拿数据、解析地址、下载文件、保存并查重。下面我把整个项目从零到能日常使用的过程逐步拆开包括环境配置、代码设计、常见坑和排查思路看完你能直接复现一版属于自己邏辑的壁纸下载器。1. 项目拆解先看清这个脚本到底在做什么1.1 核心需求拆成三个动作任何下载类脚本本质都是“数据搬运”。拿壁纸下载来说拆开看就是三个动作第一找到图片地址。这一步可能是从API返回的JSON里取链接也可能是从HTML页面源码里解析出img标签的src还可能来自一个静态资源清单。第二按一定规则筛选。壁纸不比普通图片我们需要分辨率够高的、比例合适的、主题匹配的所以筛选这一步是让脚本“好用”的关键。第三下载并保存。保存时要解决文件名重名、格式识别、断点续传等问题不然同一批图跑两遍磁盘里全是重复文件。我在最开始写脚本时犯过一个典型错误一上来就写代码抓了个网页就正则抠图片链接。结果跑完才发现下载了一堆图标、按钮、小缩略图根本没法当壁纸用。后来我重新梳理流程把“筛选”单独抽成一个环节才算真正解决需求。1.2 为什么选择Python而不是Shell脚本或Node.js如果只是“下载一批固定图片”用Shell脚本写个循环加wget也能实现网上关于“shell脚本入门”的教程也不少。那为什么选Python我给你一个很实在的对比维度Python脚本Shell脚本Node.js脚本解析复杂页面正则/BeautifulSoup都很成熟基本只能靠管道组合难维护需要搭npm包环境图片二进制处理requests几行搞定wget/curl能下但重命名麻烦需要node-fetch等依赖跨平台能力Windows/macOS/Linux统一每个平台命令有差异跨平台可以但环境准备更重定时任务、发通知等扩展生态丰富代码写起来直观偏系统运维向能做但工程感太重另外很多读者在“python安装”“npm无法识别”“git无法识别”这些关键词上踩过坑。用Python只要装好一个解释器不需要额外包管理器折腾而Node.js环境一旦出现npm路径问题排查起来比写代码本身还麻烦。Python的requests库两三行就能下载文件解析HTML用BeautifulSoup或正则都行这也是我最终选它的原因。1.3 脚本运行的整体流程把整个项目画成文字流程大概是这样指定壁纸来源URL或API— 发送请求并拿到响应数据 — 从响应中提取图片原图地址 — 对地址做过滤域名、分辨率、格式— 逐个下载到本地目录 — 下载前查重避免重复文件 — 记录日志。这个流程不单单适用于壁纸你可以把“图片地址”换成“文档链接”“音视频链接”脚本的核心骨架完全不用改。2. 开工前先把环境收拾利索2.1 Python安装这一步别再跳进坑里在运行任何python脚本之前你都需要一个能正常执行python命令的终端环境。很多读者的第一个坎就在这里热搜里“python安装详细步骤”“linux系统安装python”这类词一直居高不下说明这不是个冷门问题。Windows环境下我建议直接去Python官网下载安装包安装的时候有一个 checkbox叫“Add Python to PATH”一定记得勾上。这一步会帮你把Python解释器和pip都注册到系统环境变量裡后面在cmd或PowerShell里敲python才不会提示“不是内部或外部命令”。装完后打开终端输入python --version能正常输出版本号说明基础环境OK了。macOS和Linux上也类似只是多用Homebrew或系统包管理器安装。我见过太多人花大量时间在配环境上其实核心就一句话保证打开终端后python这个命令能找到真实存在的解释器。如果装完之后发现python不行、但python3可以说明系统里默认指向的版本不一致后续要么用python3执行要么把PATH顺序调整好。2.2 pip无法识别的快速解决思路pip : 无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称这句话我想所有Python新手都见过。网上一搜一大把相关词说明这个报错出现频率非常高。为什么会出现原因通常是Python已经安装了但Scripts目录没有加入PATH或者pip模块本身没有安装完整。最快的解决方式不是去配环境变量而是用Python模块方式调用pippython -m pip --version python -m pip install requests beautifulsoup4这样写的好处是它绕过了“pip是否在PATH里”的问题直接让Python解释器帮你去找pip模块。日常开发里我也习惯这么用本质上更严谨。如果连python -m pip都提示找不到模块那就说明这个Python环境本身不完整建议直接卸载后重装并且安装时勾选“pip”组件。另外一个建议是给这个项目创建一个虚拟环境这样不会把包装到全局环境里也不会出现“装了个requests导致另一个项目冲突”的连锁问题。python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate激活虚拟环境后再执行pip install requests beautifulsoup4。虚拟环境是很多新手的盲区但它对你的项目隔离非常有用尤其是后面想打包成exe或换机器跑的时候。3. 数据来源设计选API还是解析HTML页面3.1 API接口和页面解析的差异写壁纸下载脚本首要的问题不是“下载”而是“从哪里拿图”。我做了两版一版走API一版走HTML解析两者差异挺明显。走API接口的好处是数据格式稳定。比如一些壁纸类开放平台注册后拿到访问令牌请求一次URL就能拿到JSON里面清晰地列出了图片地址、宽度、高度、摄影师等信息。这个方案逻辑非常干净代码里根本不用写正则只要解析字典字段就行下载地址也能直接拿到原图。缺点是很多平台需要注册账号、申请密钥有时还有每日调用限制。走HTML解析的好处是门槛低、通用性强。任何壁纸站只要有列表页或标签页你就能通过请求HTML再用正则或解析库把图片地址提取出来。但缺点是站点改版后代码可能失效而且很多站对频繁请求有反爬策略需要在请求头、访问间隔上做不少优化。对比项API接口HTML页面解析数据稳定性高字段语义明确低页面结构变了就崩拿原图难度容易需要处理缩略图规则使用门槛需要注册申请Key只需要一个公开URL反爬压力主要看限额容易触发频率限制我给新手的建议是如果只是自己用优先选一个有公开API的图片接口把基础流程跑通如果你想爬的网站没有接口再用页面解析方案。两者技术栈99%是一样的换数据源时只需要改动解析那一块代码。3.2 常见的图片地址提取策略页面解析时不要一上来就想匹配所有图片。壁纸站普遍存在缩略图和原图两套地址缩略图经常几百像素宽根本不满足桌面壁纸需求。所以我在解析时通常做两层过滤。第一层限定图片后缀。只匹配.jpg、.jpeg、.png、.webp这些常见格式忽略data:image和base64内容。第二层对URL里的尺寸参数做规则处理。有些平台会在地址里带上宽高参数比如?w400h300这种需要把参数去掉或改成“原图标识”才能拿到真正的高清图。import re from urllib.parse import urljoin html div classwallpaperimg src/uploads/thumb/nature-001.jpg?w300 //div # 提取图片路径 matches re.findall(rimg[^]src[\]([^\])[\], html) print(matches) base_url https://example.com for m in matches: full_url urljoin(base_url, m) # 去掉缩略图参数改成原图规则 clean_url re.sub(r\?w\dh\d, , full_url) print(clean_url)还有一种情况是图片地址藏在JSON变量或JS数据里比如backgroundImage: url(...)这时需要额外匹配url(...)这种模式。总之先观察页面的真实结构再写正则这是页面解析的铁律。3.3 请求头的伪装和频率控制不管走API还是页面解析都要有一个基本的“请求礼貌”。我建议请求头里带上User-Agent让对方服务器认出你是个浏览器而不是脚本每次请求之间加一个随机延时避免高频访问被限制。import time import random HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def safe_request(url, retries3): for attempt in range(retries): try: resp requests.get(url, headersHEADERS, timeout10) time.sleep(random.uniform(0.5, 1.5)) return resp except requests.RequestException as e: print(f第{attempt 1}次请求失败{e}) return None这个safe_request函数后来成了我所有小项目里的标配组件不只是壁纸脚本稍微改改就能复用到别的下载场景。4. 核心代码实现从请求到落盘一次讲清楚4.1 请求页面并解析图片地址先从一个最简单的版本开始。我们假设目标页面是静态HTML图片地址都在img标签里。下面这段代码负责拉取页面并做初步解析import requests import re from urllib.parse import urljoin HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def get_html(url): resp requests.get(url, headersHEADERS, timeout10) resp.encoding utf-8 return resp.text def extract_image_urls(html, page_url): # 拉出所有 img 标签的 src src_list re.findall(rimg[^]src[\]([^\])[\], html) urls [] for src in src_list: full_url urljoin(page_url, src) if re.search(r\.(jpg|jpeg|png|webp)(\?|$), full_url, re.I): urls.append(full_url) return list(set(urls)) if __name__ __main__: page https://example.com/wallpapers/nature html_text get_html(page) image_urls extract_image_urls(html_text, page) print(f共找到 {len(image_urls)} 张图片) for u in image_urls[:10]: print(u)为什么用urljoin因为页面里的图片路径经常是相对路径比如/uploads/1.jpg。直接拼字符串很容易出错尤其当页面URL带子路径时。urljoin会按标准解析规则补全地址这一行省了很多麻烦。4.2 按分辨率等规则过滤找到了图片地址不代表就能当壁纸。你在列表页拿到的缩略图宽度可能只有400像素放到2K屏上直接糊。如果页面URL或文件名里带有尺寸信息可以提取出来做判断如果没有就需要发一次HEAD请求去读响应头或者直接下载后通过PIL库读取尺寸。这里我给出一个基于尺寸参数的简单过滤示例def filter_wallpapers(urls, min_width1920, min_height1080): good [] for url in urls: # 假设地址里包含 400x300 这类信息 size_match re.search(r/(\d{3,5})x(\d{3,5})/, url) if size_match: w, h int(size_match.group(1)), int(size_match.group(2)) if w min_width and h min_height: good.append(url) else: # 没有尺寸信息的自行决定是否保留 clean_url re.sub(r\?.*$, , url) if clean_url.lower().endswith((.jpg, .jpeg, .png, .webp)): good.append(clean_url) return good如果你的壁纸源是通过API返回数据的那就更简单了直接判断width和height字段就行。筛选逻辑是脚本是否“好用”的分水岭建议做得越细越好。4.3 下载图片并处理文件名冲突把图片二进制保存到本地时最容易忽略的是文件名。直接从URL尾部截取文件名极易遇到两个问题文件名里有特殊字符或者不同目录下有同名文件。我在计算机本地创建一个按日期分类的目录然后对文件名做一次清洗再检查是否已存在存在就跳过。这样脚本就算重复运行也只会下载新图。import os import datetime def clean_filename(name): # 去掉URL编码和特殊字符 name os.path.basename(name) name re.sub(r%[0-9A-Fa-f]{2}, _, name) name re.sub(r[\\/*?:|\s], _, name) return name[:120] def download_image(url, save_dir): os.makedirs(save_dir, exist_okTrue) filename clean_filename(url) if not filename: filename fwallpaper_{len(os.listdir(save_dir)) 1}.jpg save_path os.path.join(save_dir, filename) if os.path.exists(save_path): print(f跳过重复文件{save_path}) return False resp requests.get(url, headersHEADERS, timeout15) if resp.status_code ! 200: print(f下载失败{url} - {resp.status_code}) return False with open(save_path, wb) as f: f.write(resp.content) print(f已保存{save_path}) return True def today_dir(): return os.path.join(wallpapers, datetime.date.today().strftime(%Y-%m-%d))这里用“是否存在”来做查重很简单但如果同一个文件名下内容不同就会有隐患。更进一步的做法是记录文件哈希用哈希值判断重复。哈希方案对壁纸这种大文件来说性能略差不过胜在精确。我一般对图片数量不多的场景直接用文件名加大小双重判断。4.4 一个能直接跑通的主流程把所有函数串起来就成了一个最小可用的完整脚本。你只需要把page_url换成你自己的壁纸站地址import requests import re import os import datetime from urllib.parse import urljoin HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def get_html(url): resp requests.get(url, headersHEADERS, timeout10) resp.encoding utf-8 return resp.text def extract_image_urls(html, page_url): src_list re.findall(rimg[^]src[\]([^\])[\], html) urls [] for src in src_list: full_url urljoin(page_url, src) if re.search(r\.(jpg|jpeg|png|webp)(\?|$), full_url, re.I): urls.append(full_url) return list(set(urls)) def clean_filename(name): name os.path.basename(name) name re.sub(r%[0-9A-Fa-f]{2}, _, name) name re.sub(r[\\/*?:|\s], _, name) return name[:120] def download_image(url, save_dir): os.makedirs(save_dir, exist_okTrue) filename clean_filename(url) if not filename: filename fwallpaper_{len(os.listdir(save_dir)) 1}.jpg save_path os.path.join(save_dir, filename) if os.path.exists(save_path): print(f跳过重复文件{save_path}) return False resp requests.get(url, headersHEADERS, timeout15) if resp.status_code ! 200: print(f下载失败{url} - {resp.status_code}) return False with open(save_path, wb) as f: f.write(resp.content) print(f已保存{save_path}) return True def main(): page_url https://example.com/wallpapers html get_html(page_url) urls extract_image_urls(html, page_url) save_dir os.path.join(wallpapers, datetime.date.today().strftime(%Y-%m-%d)) ok_count 0 for index, url in enumerate(urls, 1): if download_image(url, save_dir): ok_count 1 print(f进度{index}/{len(urls)}) print(f完成共成功下载 {ok_count} 张) if __name__ __main__: main()这个版本没有任何多余装饰逻辑清晰适合新手对照理解。你如果用API接口只需要把get_html换成requests.get(api_url, headers{Authorization: ...})把extract_image_urls换成解析JSON字段。5. 把一次性脚本变成日常工具5.1 给下载任务加上定时执行脚本能跑只是一个开始真正省事的是让它定时自动跑。Windows上用“任务计划程序”即可也可以在命令行用schtasks注册schtasks /create /tn WallpaperDownloader /tr python D:\projects\wallpaper\download.py /sc daily /st 08:00macOS或Linux上写crontab任务即可0 8 * * * cd /path/to/wallpaper /usr/bin/python3 download.py不过直接跑任务计划有个小坑如果脚本中途报错你根本不会知道。后来我加了一个简单的通知机制比如脚本结束之后调用一个发送通知的接口或者至少把运行日志写入文件。日志是做这类自动化任务最重要的调试依据别省。5.2 多线程下载速度能快好几倍单个API调用来下载多张图耗时基本是累加的尤其壁纸文件动辄几MB几十张图下载下来要等很久。多线程下载是最直接的性能优化手段。Python的concurrent.futures用起来很方便from concurrent.futures import ThreadPoolExecutor def download_with_threads(urls, save_dir, workers5): with ThreadPoolExecutor(max_workersworkers) as executor: results list(executor.map( lambda url: download_image(url, save_dir), urls )) return sum(results)这里需要注意download_image要保证线程安全。我在实现时会把“检查是否存在”和“写文件”封装在一个函数内部不做共享状态理论上就没有数据竞争问题。线程数不宜过大壁纸站往往有保护机制我一般控制在3到5个线程。如果追求更快的速度可以用asyncio加aiohttp实现异步并发但代码复杂度会高一档初期不建议。5.3 下载完自动设置为桌面壁纸脚本下完图之后如果还要手动打开文件夹右键设置壁纸那就只自动化了一半。Windows上可以直接用系统API来设置壁纸。我写过一个简单函数通过ctypes调用SystemParametersInfoWimport ctypes def set_wallpaper(image_path): abs_path os.path.abspath(image_path) # 24表示 SPI_SETDESKWALLPAPER20是更新参数 ctypes.windll.user32.SystemParametersInfoW(24, 0, abs_path, 3)只需要把新下载的第一张图传进去就能自动换桌面。配合每日定时任务就是一套完整的“每天自动换壁纸”方案。macOS上可以通过osascript调用来实现Linux桌面环境则大多支持gsettings命令。6. 常见问题与排查实录6.1 页面解析出来是空的怎么办这个坑我踩过不止一次。有些壁纸站的图片列表是动态加载的页面HTML里根本没有img标签数据是通过JavaScript异步请求JSON再渲染出来的。你用requests拿到的HTML其实只是个空壳。遇到这种情况有两个方向第一打开浏览器的开发者工具切到Network面板看页面加载时发出的XHR请求找到真正返回图片数据的那一个接口直接请求它第二用selenium或playwright这类浏览器自动化工具渲染页面后再抓取但这种方式资源占用大不够轻量。我还遇到过编码问题页面是GBK编码直接用resp.encoding utf-8会乱码导致正则匹配不到正确地址。正确的做法是用resp.apparent_encoding去自动检测编码或者根据响应头里的charset来设置。6.2 下载时遇到403 Forbidden最常见的403原因是请求头不完整。很多图片服务器会校验Referer如果发现请求不是从自己的页面发出的就拒绝访问。解决办法是在下载图片时带上页面所在域名作为RefererHEADERS { User-Agent: Mozilla/5.0 ..., Referer: https://example.com/ }如果还是403考虑是不是图片有防盗链需要对Cookie做处理。此时可以先在浏览器里登录并复制Cookie到脚本中但注意Cookie有有效期长期脚本里建议维护登录态或改用官方API。6.3 requests下载大图片时卡死requests.get默认情况下会把响应内容全部读进内存图片大、网络差的时候容易卡死。可以加streamTrue按流式写入文件with requests.get(url, headersHEADERS, streamTrue, timeout30) as resp: resp.raise_for_status() with open(save_path, wb) as f: for chunk in resp.iter_content(chunk_size8192): if chunk: f.write(chunk)这样既能控制内存占用也能随时中断下载。我在下载百兆级别的大图时基本都走流式方案。6.4 报错“No module named requests”或pip无法识别这类问题几乎都在环境配置阶段出现。对策我已经在第二章说过优先用python -m pip install requests并且检查当前终端里python和pip是否指向同一个解释器。还有一个常见情况是用IDE运行脚本时正常但命令行一运行就提示缺库。这说明IDE用的是虚拟环境命令行用的是全局环境。解决办法是统一环境要么都在命令行激活同一个虚拟环境要么在IDE中指定相同的解释器。对初学者来说理解“解释器和包必须一一对应”这个点能避免大量莫名其妙的报错。6.5 文件名乱码和特殊字符壁纸站文件名经常带中文、空格、括号甚至URL编码。直接保存到本地会出现乱码或者保存失败。我在clean_filename里做了清洗把非法字符替换成下划线同时限制长度。需要注意的是Windows文件名最长为255个字符所以做截断是必要的。另外如果页面URL本身带了中文建议先做unquote解码再清洗否则保存下来的文件名是一长串%E4%BD%A0%E5%A5%BD可读性很差。7. 这个项目还能怎么扩展脚本做到这里已经能满足“自动下载壁纸”这个原始需求了。但我自己的使用过程中又加了不少功能同样是这个思路你完全可以按自己需求改。第一个扩展是“配置化”。把壁纸来源、保存目录、最小分辨率、下载线程数都放到一个config.json里这样换来源时不用改代码。第二个扩展是“多分类下载”。比如自然、城市、太空各自建一个目录下载时按关键词分流。第三个扩展是“打包成exe”用PyInstaller把脚本打包扔到Windows任务计划里直接运行别人用的时候也不需要先装Python。我还试过接入桌面通知下载完自动弹一条消息告诉我今天新增了多少张壁纸体验一下就变得完整了。更高级一点的玩法是给图片打标签或做质量过滤。比如用图像算法判断图片模糊程度太糊的直接丢弃或者按颜色主色调分类放在不同文件夹里。这个方向会引入Pillow库甚至OpenCV但壁纸这个小场景足够当练手项目。我个人在实际操作中的最大体会是脚本的“下载”功能只占整体代码的20%剩下80%都在处理异常——重试、查重、日志、伪装请求头。正是因为有了这些细节它才能从“能跑”变成“好用”。如果你也想动手写一个建议从最小版本开始跑通再逐步加功能不要一上来就写一个几百行的“大而全”脚本那样出了问题反而很难定位。最后再分享一个使用技巧跑完下载任务之后别急着删日志先翻一遍里面有没有失败链接顺手就能优化筛选规则。这种小工具本来就是越用越顺手的。
返回列表