news 2026/9/7 22:43:28

Python爬虫实战:Requests和BeautifulSoup批量下载壁纸

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬虫实战:Requests和BeautifulSoup批量下载壁纸

说个真实场景:我想给电脑换个新壁纸,打开壁纸网站翻了十几页,看到顺眼的就右键另存为,选文件夹、点保存,重复二十多次之后,基本就失去了换壁纸的热情。后来我干脆写了个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自带的ostimere等标准库。

工具就这么简单。真正要花心思的是理解“请求”这个过程:你写代码模拟浏览器,向服务器要东西,服务器返回数据,你再从数据里提取自己需要的部分。壁纸下载只是这个通用模型的第一个应用场景,理解了它,抢票脚本、自动签到、批量下载文档,本质上都是同一套思路。

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?q=nature&page=1" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } resp = requests.get(url, headers=headers, timeout=10) print(resp.status_code) print(resp.text[:200])

如果打印出200和一段JSON,说明网络完全通畅。如果卡住不动,或者超时,那就需要先解决网络问题,别急着往下写代码。我在实际项目里发现,这个验证步骤能帮你省下至少半小时的冤枉时间,因为它把“环境问题”和“代码问题”隔离开了。代码写错了你还能看报错,环境不通你连报错都看不懂。

3. 初版实现:基于JSON接口抓取壁纸原图

3.1 理解壁纸API的返回结构

先花一分钟看看接口返回的东西。用浏览器打开这个地址:

https://wallhaven.cc/api/v1/search?q=nature&page=1

你会在浏览器里看到一串JSON,里面有一个data数组,数组里每个元素就是一张壁纸的信息。最关键的字段是:

  • path:图片原图的直接下载地址,是我们真正要的东西
  • id:图片的唯一标识,适合用来做文件名
  • resolution:分辨率,比如1920x1080
  • tags:标签列表,可以用来做分类筛选

有些图片地址带?timestamp=xxx这样的查询参数,下载时最好先去掉或者保留都可以,只要后缀名是.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_pages=2): os.makedirs(OUTPUT_DIR, exist_ok=True) for page in range(1, num_pages + 1): params = { "q": keyword, "page": page, "atleast": "1920x1080" } try: resp = requests.get(API_URL, headers=HEADERS, params=params, timeout=15) 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, headers=HEADERS, timeout=30) 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_pages=2)

在你自己的电脑上,用python download_wallpaper.py运行它,你会看到终端一行一行地打印“已下载”,然后wallpapers文件夹里会多出一批壁纸。

3.3 关键代码段解读:请求头、分页参数、图片保存

这段代码里有两个细节值得展开讲。

第一个是HEADERS里的User-Agent。服务器收到请求时,会根据User-Agent判断你是浏览器还是爬虫脚本。Python的requests默认的用户代理是python-requests/x.y.z,很多壁纸站看到这个直接就拒绝。所以我们要把一个常见浏览器的UA字符串伪装上去。这不涉及任何违规,只是模拟真实用户访问。凡是做了UA校验的站点,不带上这个字段很容易收到403 Forbidden

第二个是参数里的atleast=1920x1080。这是wallhaven API自带的最小分辨率筛选参数,保证下载回来的图不会是一张很小的模糊缩略图。在类似接口上,这个“只取高清图”的思路是通用的:视频网站按码率筛选,图片站按分辨率筛选,文档站按文件类型筛选,核心都是把不需要的内容提前过滤掉。

图片保存时的文件名,我用了图片ID_关键词.扩展名这种组合。图片ID保证不重名,关键词方便知道这是哪一类壁纸。如果你不做这个组合,两个不同关键词的API可能会返回同一张图片,后者会把前者覆盖掉。

4. 升级到HTML解析:分析列表页并提取真正的高清图地址

4.1 为什么还要学HTML解析

既然API方案已经能用了,为什么还要学HTML解析?因为并不是所有壁纸站都有开放API,很多图库、素材站只有网页。学会用代码从网页里提取链接,才算把“爬虫”这条路彻底走通了。另外在面试和实际工作中,解析HTML的能力几乎天天用。

4.2 页面结构分析:以列表页为例

假设目标站点没有API,只有一个搜索结果列表页,页面里每个壁纸块长这样:

<figure class="thumb"> <img class="preview">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 property="og:image" content="https://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-LanguageCookie等字段。但我不建议一上来就全套伪装,因为字段越多越容易被规则匹配到。很多时候只需要一个User-Agent就能正常访问。先小步验证,再逐步加码,是排查请求被拒时的基本原则。

5. 让脚本真正自动化:翻页、去重、重试、定时执行

5.1 多页抓取与关键词组合

初版代码已经支持传入num_pages参数,但这里有个隐藏问题:如果某个关键词在第2页之后全是重复图片,脚本还是会傻乎乎地全部下载。解决办法是维护一个去重set:

seen = set() for item in items: image_id = item.get("id") if image_id in seen: continue seen.add(image_id)

如果你想把代码写得更灵活,可以让关键词也支持多个。比如用户传"nature,landscape",脚本就用q=nature,landscape去请求,这样一次能拿到两类图。

5.2 下载重试与超时控制

网络请求不可能每次都成功,尤其是批量下载图片时,偶尔几次超时属于正常现象。初版代码遇到一次超时就把整张图跳过,实际使用下来会发现总会有那么几张漏网之鱼。

我给下载逻辑加一个简单的重试机制:每张图最多尝试3次,每次失败后等待时间递增,比如第1次等1秒,第2次等3秒,第3次等6秒:

def download_with_retry(url, filepath, max_retries=3): for attempt in range(max_retries): try: resp = requests.get(url, headers=HEADERS, timeout=30) 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 2>&1

意思是每天早上9点,先进入项目目录,再执行下载脚本,把日志输出到run.log。这个cd的写法很关键,因为脚本里用了相对路径wallpapers目录,如果crontab启动时工作目录不对,你会看到脚本在跑,但文件存到了莫名其妙的位置。

你可能觉得每天固定时间只能下载一次不够灵活,也可以用[Tool]或者计划任务设置“每隔N小时重复”,这样可以把下载频率控制在你自己想要的节奏上。

5.4 下载完成后直接换壁纸

只下载到文件夹离真实体验还差一步。我希望运行完脚本,桌面壁纸直接换成最新下载的那张。Windows下可以用Python调用系统API:

import 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", encoding="utf-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。最后才考虑在请求里加上verify=False,但必须带上这个参数时,建议同时加一个urllib3.disable_warnings(),不然每次请求都会刷一条警告信息。

6.2 返回内容乱码、JSON解析失败

下载壁纸时收到乱码,十有八九是编码问题。HTML默认编码是UTF-8,但也有些站是GBK或者GB2312。requests虽然会根据响应头猜测编码,但如果你手动指定了错误的编码,就会得到乱码。

建议处理HTML时先判断一下:

resp.encoding = resp.apparent_encoding

apparent_encodingrequests根据字节内容推测出来的编码,比响应头里的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库把下载的图片统一裁剪成当前屏幕分辨率。这些方向不需要我这个脚本全都实现,但下一次你再想写别的自动化脚本时,这些代码可以直接复用。

写代码解决实际问题,最有成就感的地方就是这最后一环:脚本在后台安静地跑着,你只需要在二次元风景、极简几何、高清摄影这些关键词里挑一个,剩下的全交给它。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 22:42:21

WSO优化LSSVM时间序列预测模型实战

1. 项目背景与核心价值 时间序列预测在金融、气象、工业等领域具有广泛应用&#xff0c;传统统计方法如ARIMA在处理非线性关系时表现有限。机器学习方法中&#xff0c;支持向量机(SVM)因其出色的泛化能力受到青睐&#xff0c;但标准SVM存在计算复杂度高的问题。最小二乘支持向量…

作者头像 李华
网站建设 2026/9/7 22:40:45

易飞ERP与钉钉审批集成方案解析

1. 项目背景与核心价值易飞ERP与钉钉审批单据的集成方案&#xff0c;是目前中小企业数字化转型中最具性价比的解决方案之一。作为从业10年的ERP实施顾问&#xff0c;我亲历过数十家企业从传统纸质审批向移动化审批的转型过程。这种集成模式最大的优势在于&#xff1a;利用钉钉超…

作者头像 李华
网站建设 2026/9/7 22:40:43

uniapp地图海量点位聚合方案:性能优化与实战踩坑指南

地图业务在uniapp里做到中后期&#xff0c;十有八九会碰到同一个坎&#xff1a;点位太多&#xff0c;地图扛不住。我第一次接到门店分布需求的时候&#xff0c;后台一次性返回了800多个坐标点&#xff0c;直接全量塞进map组件的markers数组里。结果小程序端还能勉强拖拽&#x…

作者头像 李华
网站建设 2026/9/7 22:39:21

前端实战:从零手写京东风格电商页面,布局交互性能全解析

做开发这些年&#xff0c;我面试过不少前端新人&#xff0c;发现一个挺普遍的现象&#xff1a;简历里写着“电商项目经验”&#xff0c;真让对方打开页面讲讲模块怎么拆、布局为什么这样写、轮播图卡顿怎么排查&#xff0c;很多人就卡壳了。如果你也是刚学完HTML、CSS和JavaScr…

作者头像 李华
网站建设 2026/9/7 22:39:02

Overleaf 6.x私有化部署升级实践:告别编译超时与数据隐私焦虑

进六月之后&#xff0c;我手上几篇论文的返修时间都很紧&#xff0c;结果正是从那个时候开始&#xff0c;公共版 Overleaf 的编译排队变得让人血压飙升。下午三四点&#xff0c;一个文档改完点“Recompile”&#xff0c;少则等二十秒&#xff0c;多则直接给你一个“Timed Out”…

作者头像 李华
网站建设 2026/9/7 22:36:03

OpenGL核心模式三剑客:VBO、VAO、EBO原理与实战

如果你正在学现代OpenGL&#xff0c;VBO、VAO、EBO这三个缩写大概率会同时出现在你面前。它们是核心模式&#xff08;Core Profile&#xff09;下最基础的三个缓冲对象&#xff0c;也是从“会调API”到“真正理解GPU怎么干活”之间必须跨过的一道坎。这篇笔记会把三者的职责、配…

作者头像 李华