
写这个自动下载壁纸的脚本最初纯属被逼出来的。我平时喜欢囤高清壁纸可壁纸站翻半天找到一张对味的点下载之前先给你弹两个广告好不容易存下来发现是带水印的缩略图心态直接炸裂。后来我决定自己写个Python脚本让程序替我搞定翻页、定位图片、下载存盘这套重复劳动。思路其实不复杂用requests请求壁纸站页面用BeautifulSoup解析出图片真实地址再循环把图片存到本地文件夹。整个项目只依赖一个第三方库不涉及框架非常适合刚入门Python、想拿真实项目练手的朋友也适合不想在下载壁纸上继续浪费生命的人。读完这篇你能拿到一份完整可运行的脚本并且知道每一步为什么要这么写。1. 项目拆解自动下载壁纸到底要解决什么问题1.1 手动存图的真实痛点不只是费时间很多人觉得下载壁纸嘛点几张图能花多久我一开始也这么想。可真当你按列表页-详情页-原图-另存为这个流程走一遍就知道有多反人类了壁纸站一页有几十个缩略图点进详情页又要加载半天右键保存下来的文件名还往往是乱码编号存完根本不知道是哪张。这个项目要解决的核心问题就是把人肉浏览替换成程序遍历。你只需要给脚本一个列表页地址它会自动找出这一页所有壁纸的详情链接再逐个进入详情页提取原图地址最后批量下载到本地。整个过程里人只负责启动脚本和事后挑图。往深一层看这种需求本质上是网页抓取里最典型的列表页 详情页模型。搞懂这个模型你之后再去写批量下载文档、备份商品信息、抓取数据报表都是同一套逻辑迁移。所以别看它是个下载壁纸的小脚本它是一节很实在的爬虫入门课。1.2 为什么选Python生态、成本、上限用其他语言写这个脚本行不行行但没必要。我见过有人用Node.js写爬虫也能跑但代码量和对新手的友好度都差点意思。还有人想用shell脚本处理可shell对HTML解析基本是残废遇到稍微复杂的DOM结构就直接抓瞎。Python在这个场景下的优势非常直白生态成熟requests处理HTTP请求BeautifulSoup处理HTML解析两个库加起来就是爬虫的标准答案。写起来不到一百行可读性也高代码放在那里半年后再看还能一眼看懂。另外说句实在话Python这个选择的天花板很高。你今天用它写下载壁纸的脚本明天想爬数据、做量化分析、写自动化测试语言基础和库的使用习惯都能复用。热词里那些Python量化交易策略代码自动化脚本接口脚本之类的东西底层能力都是从这里起步的。这门语言的投入产出比是明显高于其他脚本方案的。2. 动手前先搞懂页面结构、请求伪装与数据提取2.1 打开F12先摸清壁纸站的HTML骨架写任何爬虫脚本第一件事不是写代码是打开浏览器按F12把目标网站的HTML结构看明白。这一步省不得。我见过太多人上来就写正则写一半发现页面改版了或者选错了标签白忙活一个小时。拿一个典型的壁纸站举例它的列表页长这样一个class为wallpaper-item的div包裹着一个a标签和一个img标签a标签的href指向详情页img的src是缩略图。详情页里则是一个id为wallpaper的img标签src才是原图地址结构大概是这个样子div classwallpaper-item a href/detail/12345.html img src/thumb/12345.jpg alt壁纸标题 /a /div详情页里则是img idwallpaper src/uploads/2024/12345.jpg为什么要分列表页和详情页两步走因为列表页上的图片大多数是压缩过的缩略图直接拿下来当壁纸放大全是马赛克。真正的高清原图藏在详情页里。所以脚本要做的事情是先抓列表页里的详情链接再进详情页找原图地址。这个两步跳转的逻辑就是前面说的列表页 详情页模型。用BeautifulSoup提取列表链接对应代码非常短from bs4 import BeautifulSoup soup BeautifulSoup(html, html.parser) items soup.select(div.wallpaper-item a) links [a[href] for a in items]这里要注意soup.select()用的是CSS选择器语法如果你不熟悉CSS也可以换成soup.find_all(a)再过滤。选择器只是个工具关键是你要先搞明白页面结构再动手。2.2 请求头伪装让服务器以为你是真人很多初学者写爬虫遇到的第一个拦路虎是这个报错403 Forbidden。页面在浏览器里打开得好好的换成requests一请求就被拒。原因很简单服务器知道你不是真人。浏览器发起请求时会带上User-Agent、Referer、Accept等一堆头信息。而requests默认的User-Agent是类似python-requests/2.31这样的字符串服务器一看就知道是脚本在访问直接拒绝。所以我们需要伪造一个浏览器的请求头让服务器以为访问者是个普通用户headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Referer: https://example.com/ }User-Agent是基础Referer告诉服务器你从哪个页面跳转过来的。某些壁纸站对图片防盗链有检查光有User-Agent还不够Referer填对了才能拿到图。遇到403的时候优先检查这两个字段八成能解决。2.3 BeautifulSoup和正则怎么选更省心提取图片地址有两条常见路线正则表达式和BeautifulSoup。我先说结论能结构化解析就别用正则硬抠。正则的优势是轻量Python标准库自带re模块不需要装任何东西。但它的劣势也很明显面对嵌套的HTML正则写起来又长又脆稍微改个属性顺序就可能匹配失败。我之前为了验证效果用正则从同一段HTML里提取链接写出来的模式串又臭又长换了个页面结构直接崩。BeautifulSoup则是把HTML解析成DOM树你可以按标签、属性、CSS选择器去取内容写出来的代码可读性强得多。同样是提取所有壁纸详情链接BeautifulSoup的写法一眼就能看懂detail_links [] for a in soup.select(div.wallpaper-item a): href a.get(href) if href: detail_links.append(href)不过正则也不是完全没用。后面清洗文件名、截取URL参数这类纯字符串处理的活儿用正则反而顺手。所以我的建议是解析HTML用BeautifulSoup清洗字符串用正则各司其职。3. 完整实操从零写一个能自动下载壁纸的Python脚本3.1 环境准备Python、pip、依赖库一步到位开始写代码之前先把环境收拾利索。你需要在电脑上装好Python 3.8以上版本装好之后打开终端验证一下python --version如果系统提示找不到python命令可能是安装时没勾选Add Python to PATH。这个坑在很多新手电脑上经常出现解决方法是重新运行安装包把环境变量选项勾上或者直接用完整路径调用Python。接下来安装第三方库requests和beautifulsoup4这两样就够了pip install requests beautifulsoup4如果你在Windows上遇到pip无法将pip项识别为 cmdlet之类的报错说明pip命令没进PATH。最简单的办法是改用模块方式运行python -m pip install requests beautifulsoup4这个场景在搜索热词里出现频率特别高所以单独提一句python -m pip是pip命令失效时的通用后手记牢它。3.2 第一版脚本先让列表页的壁纸地址全部露出来写脚本我习惯用渐进式先把最核心的链路跑通再逐步加功能。第一版的目标只有一个输入一个列表页地址输出当前页所有壁纸详情链接。import requests from bs4 import BeautifulSoup from urllib.parse import urljoin headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, } def get_detail_urls(list_url): resp requests.get(list_url, headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) detail_urls [] for a in soup.select(div.wallpaper-item a): href a.get(href) if href: detail_urls.append(urljoin(list_url, href)) return detail_urls list_url https://example.com/wallpaper/list_1.html urls get_detail_urls(list_url) for url in urls: print(url)这里有几个细节要解释一下。timeout10是设置请求超时时间防止某个请求卡死整个脚本挂住不动。resp.encoding utf-8是手动指定页面编码避免中文乱码。urljoin(list_url, href)则是把相对地址拼成完整的绝对地址因为很多网站的链接写的是/detail/12345.html这种相对路径不补充前缀的话下一步请求就会失败。跑完这个脚本如果终端里刷出了一串详情页地址说明第一步链路是通的。这时候再往下写心里就有底了。3.3 第二版脚本翻页、去重、下载合并成一条龙第一版只打印了链接接下来把它升级成能真正下载图片的完整脚本。我的做法是增加两个函数一个负责从详情页提取原图地址一个负责下载图片并保存到本地。import os import re import time import requests from bs4 import BeautifulSoup from urllib.parse import urljoin headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Referer: https://example.com/, } def get_detail_urls(list_url): resp requests.get(list_url, headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) detail_urls [] for a in soup.select(div.wallpaper-item a): href a.get(href) if href: detail_urls.append(urljoin(list_url, href)) return detail_urls def get_image_url(detail_url): resp requests.get(detail_url, headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) img soup.find(img, idwallpaper) if not img: return None return urljoin(detail_url, img.get(src)) def download_image(img_url, save_folder): resp requests.get(img_url, headersheaders, timeout10, streamTrue) if resp.status_code ! 200: return False filename img_url.split(/)[-1].split(?)[0] filename re.sub(r[\\/:*?|], _, filename) save_path os.path.join(save_folder, filename) with open(save_path, wb) as f: for chunk in resp.iter_content(chunk_size8192): if chunk: f.write(chunk) return True save_folder wallpapers os.makedirs(save_folder, exist_okTrue) downloaded set() for page in range(1, 6): list_url fhttps://example.com/wallpaper/list_{page}.html detail_urls get_detail_urls(list_url) print(f第 {page} 页找到 {len(detail_urls)} 个详情页) for detail_url in detail_urls: img_url get_image_url(detail_url) if not img_url: continue filename img_url.split(/)[-1].split(?)[0] if filename in downloaded: continue downloaded.add(filename) ok download_image(img_url, save_folder) print(f{成功 if ok else 失败} {filename}) time.sleep(1)几个关键设计的用意逐一说明。streamTrue加在下载图片的请求上是让requests以流式方式返回内容。图片文件动辄几兆甚至几十兆如果一次性用resp.content全部读入内存壁纸数量一多电脑内存容易被吃满。配合iter_content(chunk_size8192)每读8KB就往磁盘写一次内存占用始终很低。这个写法在下载大文件时是标准姿势也非常适合图片批量下载。re.sub(r[\\/:*?|], _, filename)是清洗文件名。Windows下的文件名不允许包含\/:*?|这些字符而网站给图片起的文件名不一定规整不处理直接保存轻则报错重则覆盖文件。这行正则看着不起眼实际上能帮你避开大量磁盘写入报错。downloaded这个集合是干去重用的。翻页遍历时经常遇到同一个壁纸在不同分类里重复出现或者分页顺序有交叉。不设置去重就会重复下载白耗流量。集合的查找是O(1)复杂度图片数量再大也不怕。最后那个time.sleep(1)是为了给目标网站留出请求间隔。很多人写爬虫喜欢把速度拉满结果就是被服务器封IP。你一分钟请求几百次服务器不封你封谁一次请求睡一秒下载几十张图不过多等一分钟换来的是稳定不封禁这笔账很划算。4. 常见问题与排查技巧实录4.1 高频报错速查表从403到乱码一次看完我在实跑这个脚本的过程中把最容易踩到的坑整理了张表按现象-原因-解法的格式列出来。你在跟着做的时候碰到问题直接来这里找答案。现象可能原因解决办法403 Forbidden请求头不够像浏览器伪造User-Agent和Referer必要时用requests.Session保持会话SSL证书报错CERTIFICATE_VERIFY_FAILED本地证书环境不完整临时用verifyFalse关闭验证或更新证书库中文乱码页面实际编码不是UTF-8尝试resp.encoding resp.apparent_encoding或按响应头charset指定下载下来的文件打不开扩展名和真实图片格式不符根据URL后缀或Content-Type动态生成文件名FileNotFoundError文件名包含非法字符用re.sub清洗/:*?请求长时间没反应目标服务器响应慢所有请求加timeout参数配合try/except重试提示pip找不到pip未加入PATH改用python -m pip install最后一个问题多说两句。Windows的PowerShell对新手的敌意是出了名的很多报错看起来吓人实际只是环境变量没配对。遇到提示某个命令无法被识别第一反应不要重装系统先检查命令是否真的装好了、路径是否写对了再用python -m这个万金油方式绕过去。4.2 实测踩坑记录这4个细节能帮你省一晚上调试时间第一相对路径必须用urljoin补全。我有一次偷懒直接用提取出来的href请求结果抛出一堆404。后来才发现网站的链接全是/detail/xxx.html这种相对路径少了域名前缀请求自然全打偏了。凡是拼接URL的地方记得用urljoin(基准地址, 相对路径)。第二不要一上来就上多线程。有热词提到脚本注入全自动脚本之类的东西很多人也被带偏觉得下载脚本必须写得高大上。实际上这个壁纸脚本用单线程完全够用。非得上并发你会立刻感受到什么叫反爬升级请求快一点IP就被重点盯上。先跑通单线程再加入请求间隔稳定运行了再考虑优化这才是正确顺序。第三下载失败要保证脚本能继续跑。图片请求偶尔会失败原因可能是网络抖动、超时、服务器临时抽风。代码里要对下载函数做异常托管失败的打印出来不要中断整体流程。专业一点的写法是给download_image加上try/except捕捉requests异常后返回False日志记录下来事后再统一重试。这个习惯在你写任何批量处理脚本时都受用。第四别忽略目标网站的访问条款。这算是个老生常谈但确实重要。写自动化脚本去访问别人的服务器本质上是在消费对方资源控制频率、设置合理的延时、不拿脚本去撞接口都是最基本的网络礼仪。很多脚本跑着跑着被封了的求助帖复盘下来大多是对目标服务器的访问压力太大。把代码写好之外学会做一个让人舒服的访客也是爬虫技术的一部分。最后说点我个人的体会。这个脚本我后来又改了几版给里面加了多线程下载、断点续传、按分辨率过滤功能是越来越像样了但回头看最值钱的反而是最朴素的那些基础函数先理解页面结构用requests拿到数据再靠BeautifulSoup把内容抽出来。这套思路放到任何抓取需求上都一样。还有一个建议是遇到报错先别急着抄代码花几分钟把报错信息读明白这个过程里学到的东西比脚本本身更值钱。