ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python自动下载壁纸脚本:爬虫与定时任务实战

2026/9/8 5:20:38 拓冰建站 浏览量
Python自动下载壁纸脚本:爬虫与定时任务实战 这个脚本是我自己最早写的一批小工具之一起因简单得有点不好意思每天开机看到固定壁纸觉得腻又懒得去图站一张张手动挑。后来干脆写了这个Python自动下载壁纸的脚本抓图、去重、落盘一次搞定再挂个定时任务让它每天自己跑一遍。整个过程绕不开三块内容Python爬虫的基础写法、脚本运行环境的配置、以及Windows/命令行环境下经常踩的坑。如果你刚学Python想做个能真正落地用的自动化小工具又不想一开始就去啃Scrapy这类重型框架这份代码和思路可以直接抄作业。整件事适合三类人一是想练手爬虫但不知道从哪个案例入门的初学者二是想替自己或家里老人实现壁纸自动更换的懒人三是想搞明白脚本在命令行跑不起来pip装不上库这类问题到底怎么解决的日常办公族。下面从选型到实现再到报错排查完整拆开讲一遍。1. 整体思路与工具选型1.1 为什么用 requests BeautifulSoup而不是上 Scrapy做壁纸下载这种单机小脚本第一原则是够用就好。壁纸站的页面结构普遍不复杂无非是一个列表页里铺满缩略图点进去能看到原图或者干脆在页面源码里直接暴露了图片直链。这种量级的抓取任务用requests发HTTP请求、再用BeautifulSoup解析HTML几十行就能解决问题。Scrapy虽然功能强大但它是为大规模、分布式爬虫设计的框架有自己的项目结构、Spider类、Item管道和中间件体系。为了下载几百张壁纸去维护一套Scrapy工程就像为了切一根葱去开一整套厨房电器配置成本远超收益。另外requests的报错信息非常直观比如超时、403、SSL证书错误都能在控制台里直接看到新手排查起来比Scrapy那套日志体系好上不少。还有一点容易被忽略很多壁纸站并没有开放公开API接口可能要分析XHR请求、找签名参数难度反而比解析静态HTML更高。所以直接用页面解析是最稳妥的路径。用BeautifulSoup定位图片链接的核心就是CSS选择器凡是能在浏览器F12面板里看到的class、id、属性都能直接用select方法抓出来学习曲线非常平缓。1.2 脚本整体流程设计写脚本之前先在脑子里把完整流程捋一遍能省下大量调试时间。我设计的流程是四步请求目标壁纸页面带上浏览器User-Agent伪装身份。解析返回的HTML用CSS选择器定位图片直链。逐个下载图片以“_”加序号的方式命名落盘。用历史记录文件记住已经下载过的图片避免重复抓取。这一步的关键取舍在于不是每个壁纸站都是直接在列表页放原图直链的很多站点的列表页只给缩略图真正的高清大图要进详情页才能拿到。这种情况下就得先解析列表页拿详情页URL再二次请求详情页提取原图。我这版脚本按列表页直接拿原图的站点来写同时在代码注释里留了详情页方案的扩展逻辑后面扩展章节会细说。1.3 文件目录结构规划脚本虽然小但目录结构最好一开始就规划好不然跑两天就乱成一团。我的目录是这样wallpaper/ ├── download_wallpaper.py # 主脚本 ├── history.json # 已下载图片的记录文件 └── images/ # 图片保存目录history.json这个文件非常关键。没有它脚本每次运行都会重复下载整个页面的所有图片既浪费流量又会让磁盘里堆满重复文件。做法很简单每下载成功一张图就把图片的URL或者文件名写进history.json下次运行前先加载这个文件发现URL已经存在就直接跳过。第一次运行需要创建history.json和images目录我在代码里用os.makedirs和文件存在性判断做了兜底不管什么状态启动都不会报错。2. 环境准备Python、编辑器与依赖库2.1 Python环境用官网装PATH必须勾上看热搜词里大量出现python安装教程python不是内部或外部命令这类问题其实九成都是安装时没勾PATH导致的。强烈建议直接去python.org下载官方安装包版本选3.10以上的就行安装向导第一步里有一个Add Python to PATH的复选框一定记得勾上。为什么PATH这么重要因为Windows执行命令时会在系统环境变量指定的目录列表里挨个找可执行文件。python没加进PATH你在终端敲python系统根本不知道去哪找它就会报不是内部或外部命令或者PowerShell里的“无法识别为cmdlet”。勾选PATH之后python、pip这两个命令就能在任意路径下直接使用了。装完之后验证一下打开终端敲python --version正常会输出类似Python 3.12.1的信息。如果提示找不到先关掉终端重新开一个因为环境变量修改后要新开终端才会生效。2.2 虚拟环境别把所有库都装进全局虽然这个脚本只用两三个第三方库但我还是建议创建一个虚拟环境来隔离依赖。虚拟环境相当于给当前项目圈出一个独立的Python运行空间在里面pip安装的库不会污染全局环境换机器部署时也只需要导出一份requirements文件。创建和激活的命令如下cd wallpaper python -m venv .venv # Windows PowerShell .venv\Scripts\Activate.ps1 # macOS / Linux source .venv/bin/activate激活之后终端提示符前面会出现.venv字样说明现在正处于虚拟环境中。如果你用的是VSCode打开项目文件夹后右下角会自动检测到.venv直接选它作为解释器就行PyCharm则在设置里的Project Interpreter里选择。这一步做完后续的pip install都会自动装进虚拟环境不会再有乱七八糟的版本冲突。2.3 编辑器VSCode和PyCharm怎么选这个问题被问得很多我给个比较实际的建议对比项VSCodePyCharm安装体积轻量约几百MB社区版也要1GB以上Python支持需装Python扩展开箱即用调试体验顺手配launch.json功能全适合大项目对新手友好度简单直接功能多但有点复杂适合场景小脚本、日常自动化大型项目、Web开发我的看法如果是这个壁纸脚本这种几十行的工具VSCode完全够用装好官方Python扩展后按F5就能直行变量监视、断点调试都有。如果后续要往数据分析、Web开发方向发展再换PyCharm也不迟没必要一上来就用重型IDE把自己淹没在按钮海洋里。3. 核心代码实现一步步写透3.1 请求头伪装为什么服务器要看你浏览器的脸色很多初学者写爬虫第一步就卡在网站返回403原因很简单服务器识别出请求不是来自浏览器直接拒绝了。浏览器发请求时会带一堆请求头其中最重要的是User-Agent用来声明我是Chrome、Firefox还是Safari。直接拿requests默认的python-requests/2.x去访问很容易被识别并拦截。我习惯的做法是单独定义一个请求头字典把User-Agent换成当前主流浏览器的值headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://example.com/, }Referer这个头表示请求是从哪个页面跳转过来的不少图站会校验它少了Referer也可能返回403。有人喜欢用fake-useragent库自动随机UA但那个库偶尔会拉到失效的UA字符串反而增加排查成本不如手写一个浏览器版本稳当还少一个依赖。3.2 网页解析定位图片链接的两种套路拿到HTML响应后先判断图片链接是直接暴露在页面中还是藏在详情页里。判断方法很笨但有效在浏览器里打开壁纸站列表页按F12打开开发者工具用选择元素的箭头工具点一下页面上的缩略图右侧源码里面如果能看到以.jpg或.png结尾的链接说明直链就在眼前。以常见的结构为例假设图片链接嵌在div.img-item下面的img标签的>from bs4 import BeautifulSoup soup BeautifulSoup(html_text, html.parser) items soup.select(div.img-item img) for img in items: url img.get(data-src) or img.get(src) if url and url.startswith(http): print(url)这里用or做了个属性回退有些站点懒加载图片时会把真实地址放在>def download_image(url, save_path, timeout15): response requests.get(url, headersheaders, streamTrue, timeouttimeout) if response.status_code ! 200: return False with open(save_path, wb) as f: for chunk in response.iter_content(chunk_size8192): if chunk: f.write(chunk) return Trueiter_content(8192)表示一次读8KB数据循环写入文件。这样做的好处是内存占用恒定不管图片多大都不会撑爆内存而且下载过程中能看到进度出了问题也容易定位是哪个文件没下完。timeout15也很重要不加超时的话某个URL一旦卡住脚本会永久挂在那里不动。3.4 去重模块用JSON文件做记忆去重我用了一个极简方案history.json里面是一个数组保存了所有已下载图片的URL。脚本启动时先读取这个文件到内存去重判断就是一次集合查找而已import json import os HISTORY_FILE history.json def load_history(): if os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, r, encodingutf-8) as f: return set(json.load(f)) return set() def save_history(history): with open(HISTORY_FILE, w, encodingutf-8) as f: json.dump(list(history), f, ensure_asciiFalse, indent2) history load_history() # 下载前 if url in history: print(f跳过已下载: {url}) continue # 下载成功后 history.add(url) save_history(history)为什么不直接拿文件名判断重复因为不同站点可能出现同名文件比如很多壁纸站都叫wallpaper-1920x1080.jpg重名概率极高。用URL做去重可以精确区分来源而且实现起来比哈希比对简单得多。3.5 主函数调度按频率限速别把服务器打崩最后把模块串起来的main函数我加了一个可配置的延迟间隔import time def main(): url https://example.com/wallpapers html_text fetch_page(url) image_urls parse_image_urls(html_text) os.makedirs(images, exist_okTrue) history load_history() new_count 0 for i, img_url in enumerate(image_urls, 1): if img_url in history: continue save_path os.path.join(images, fwallpaper_{len(history) new_count 1}.jpg) ok download_image(img_url, save_path) if ok: history.add(img_url) save_history(history) new_count 1 print(f[{new_count}] 下载成功: {save_path}) time.sleep(1) # 礼貌限速避免请求太密集 print(f本次新下载 {new_count} 张壁纸历史总数 {len(history)} 张。) if __name__ __main__: main()这个time.sleep(1)不是随便加的。壁纸站的图片服务器通常有访问频率限制如果瞬间并发几十个请求轻则被拒绝重则被封IP。每秒最多一张的频率对个人使用完全够友好。3.6 一个可直接复制的完整脚本把上面所有模块拼在一起得到可以直接保存运行的完整版本import json import os import time import requests from bs4 import BeautifulSoup BASE_URL https://example.com/wallpapers DOWNLOAD_DIR images HISTORY_FILE history.json headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: BASE_URL, } def fetch_page(url): resp requests.get(url, headersheaders, timeout15) resp.raise_for_status() resp.encoding resp.apparent_encoding return resp.text def parse_image_urls(html_text): soup BeautifulSoup(html_text, html.parser) urls [] for img in soup.select(div.img-item img): url img.get(data-src) or img.get(src) if url and url.startswith(http): urls.append(url) return urls def download_image(url, save_path): resp requests.get(url, headersheaders, streamTrue, timeout15) if resp.status_code ! 200: print(f下载失败 [{resp.status_code}]: {url}) return False with open(save_path, wb) as f: for chunk in resp.iter_content(chunk_size8192): if chunk: f.write(chunk) return True def load_history(): if os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, r, encodingutf-8) as f: return set(json.load(f)) return set() def save_history(history): with open(HISTORY_FILE, w, encodingutf-8) as f: json.dump(list(history), f, ensure_asciiFalse, indent2) def main(): os.makedirs(DOWNLOAD_DIR, exist_okTrue) html_text fetch_page(BASE_URL) image_urls parse_image_urls(html_text) history load_history() print(f页面解析到 {len(image_urls)} 张图片历史已下载 {len(history)} 张。) new_count 0 for i, img_url in enumerate(image_urls, 1): if img_url in history: continue filename fwallpaper_{len(history) new_count 1:04d}.jpg save_path os.path.join(DOWNLOAD_DIR, filename) if download_image(img_url, save_path): history.add(img_url) save_history(history) new_count 1 print(f[{new_count}/{len(image_urls)}] {filename} 已保存) time.sleep(1) print(f全部完成新下载 {new_count} 张累计 {len(history)} 张。) if __name__ __main__: main()运行方式很简单在激活了虚拟环境的终端里执行python download_wallpaper.py运行日志会一步步显示解析到多少张图、下载成功哪张、跳过了哪些重复项整个执行过程一目了然。4. 常见问题与排查技巧实录4.1 终端报无法将xx项识别为cmdlet、函数、脚本文件或可运行程序的名称这个报错在热搜里频繁出现不只是pythongit、claude、npm都可能触发。本质都一样Windows在PATH环境变量里找不到你输入的命令对应的程序。具体到我们脚本排查顺序是这样先确认python装好之后有没有重启终端环境变量修改要新开终端才生效。在终端里敲where python看能不能输出python.exe的完整路径。如果输出为空说明根本没有加入PATH需要去系统属性-环境变量-Path里手动添加python的安装目录。如果装了多个Python版本建议用py -3.12这种带版本号的命令来启动py启动器一般都会被正确注册到PATH。4.2 VSCode里能跑但双击脚本不能运行VSCode里能跑是因为它自动激活了虚拟环境而双击.py文件时系统用的是全局Python虚拟环境中装的requests在那里面根本不存在。解决办法有两个最简单的办法是写一个run.bat批处理文件双击它就行echo off cd /d D:\wallpaper call .venv\Scripts\activate.bat python download_wallpaper.py pause第一行的cd /d会把当前目录切到脚本所在目录call激活虚拟环境后面正常运行脚本pause让窗口执行完不闪退方便看日志。这个批处理思路也可以解决热搜里Windows脚本命令闪退的问题闪退多半是脚本中间报错但窗口关闭太快没来得及看。4.3 请求返回403或空页面内容403基本都是请求头的问题。先试试把User-Agent换成最新版Chrome的UA再看Referer是否设置正确。很多站点要求Referer必须是站内地址如果你在代码里留了个空Referer就会被当成盗链拦截。空页面内容的常见原因是页面用了动态加载。有些壁纸站列表不是一次性渲染的是JS向某个API接口异步请求数据后再填充DOM。这种情况requests拿到的HTML只是空壳里面根本没有图片节点。解决思路有两种一是用浏览器开发者工具里的Network面板找到真实的数据接口直接请求那个接口拿JSON二是上Selenium或Playwright这类自动化浏览器工具让浏览器完整渲染后再抓取。对于壁纸下载这个需求优先推荐第一种毕竟为了下几张图去跑一个完整的Chromium还是有点杀鸡用牛刀。4.4 pip安装依赖慢或直接超时如果国内网络环境访问Python官方仓库很慢命令行里加一行镜像参数即可pip install requests beautifulsoup4 -i https://pypi.tuna.tsinghua.edu.cn/simple想永久生效就先执行一次pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple之后pip就默认走镜像源了。另外如果报错提示No module named requests先确认你激活了正确的虚拟环境这是新手时期最高频的报错原因说白了不是没装库而是没在你正在用的那个环境里装。4.5 下载的是占位图或缩略图这个问题我前面提过一嘴但值得单独拉出来强调因为它太隐蔽了。下载下来的图片能打开也能当壁纸但分辨率只有几百乘几百糊得没法看。原因就是解析时取错了属性取了懒加载的src占位图。真正的原图地址通常在>for img in soup.select(img): print(img.attrs)看到那些以-1920x1080.jpg、-origin.jpg结尾的URL基本就是原图直链再针对性地写选择器就靠谱了。5. 把它做成无人值守的日常工具5.1 Windows任务计划程序定时运行脚本本身只是手动执行但壁纸下载这种需求最舒服的状态是每天自动跑一次然后系统自动换壁纸。Windows下实现定时运行最简单的方法是用任务计划程序。打开任务计划程序右侧点创建基本任务名称填下载壁纸触发器选每天时间建议凌晨两三点这时网络通常比较空闲。操作选启动程序程序填你的批处理run.bat的路径或者直接填python.exe的完整路径然后在添加参数里填脚本路径。完成之后右键任务点运行测试一次。任务计划程序里还有一项如果任务运行时间超过X天则停止保持默认就行。最关键的一点任务计划默认只有在用户登录时才运行这个不要取消否则脚本需要有界面环境才能正常跑批处理。5.2 下载完成后自动切换壁纸定时下载只是第一步能不能自动换桌面才是最终目标。Windows系统提供了一个API接口SystemParametersInfoWPython的ctypes库可以直接调用它import ctypes import os import random def set_wallpaper(folder): images [f for f in os.listdir(folder) if f.endswith(.jpg)] if not images: return path os.path.abspath(os.path.join(folder, random.choice(images))) ctypes.windll.user32.SystemParametersInfoW(20, 0, path, 3)数字20对应的是SPI_SETDESKWALLPAPER这个参数最后一个参数3表示立即刷新桌面。把这个函数放在主下载流程后面整个自动化链路就闭环了抓图、下载、随机换壁纸全程不需要人管。macOS下可以用osascript调System Events设置壁纸Linux的GNOME桌面可以用gsettings命令桌面环境不同命令也不同这里就不展开细写了。5.3 往通用图片下载器扩展的路线脚本跑到这一步骨架已经很完整了再要扩展就是从下载壁纸升级成通用图片下载器的方向支持多个图源站点把站点配置抽象成字典每个站点写一个单独的解析函数main流程不变只新增解析规则。按分辨率筛选解析图片URL后通过正则或查询参数提取宽高只下载1080P以上的图。下载结果存数据库图片多了之后JSON文件的读写效率会下降可以换成SQLite一条CREATE TABLE IF NOT EXISTS就能搞定。加一个简单web界面用Flask或FastAPI包一层手机上也能远程触发下载再配个简单的图片浏览页。需要提醒的是无论怎么扩展都得注意对目标站点的基本尊重。抓取频率控制在对方能承受的范围优先尊重网站的robots协议下载的图片只做个人使用不要二次分发。写脚本是练技术但不意味着可以无节制地压榨别人的服务器。我自己这台电脑上这段脚本已经跑了半年多最初高频踩的坑几乎全集中在两个方面一个是图片链接取错属性导致下了一堆缩略图另一个是Windows环境变量和虚拟环境的问题导致脚本换个电脑就跑不起来。如果你也想复现这个项目给你一个来自实际经验的建议不要直接对着在线网页调代码先手动把一个列表页的HTML保存到本地在本地反复调试解析逻辑等选择器稳定了再放出去联网跑。这样既不会因为频繁请求被网站限制又能秒级看到每一次改动的结果开发效率完全是两个量级。剩下的坑就留给你自己在跑通之后慢慢体会了。