Wallhaven是一个壁纸网站,注册账号后可以将喜欢的壁纸添加至收藏夹中。
里面有许多质量非常高的壁纸,为了方便下载,于是花了两天多时间写了一个爬壁纸的脚本(蒟蒻落泪)
需求:
首先明确自己的需求:
代码的功能应包含:
- 访问各个收藏夹,按收藏夹的分类下载图片
- 下载的图片保存至对应收藏夹名的文件夹中
- 下载图片应按照网站中给出的编号来命名
- 含有记录下载的功能,避免未全部下载完后关闭程序,第二次打开时从头开始下载的悲剧
- 下载过程中有适当的提示,例如:正在下载xx收藏夹中的图片;第x张图片已下载完成等等
准备工作:
接着做一些准备工作:
最开始抱着试一试的心态找找看有没有api,居然发现这个网站提供了详细的api说明
良心,感动
在api说明页面的最后面我们可以了解到:
通过这个链接:https://wallhaven.cc/api/v1/collections?apikey=<API KEY>(下文中称为1号链接)可以访问自己的所有收藏夹信息
再通过这个链接:https://wallhaven.cc/api/v1/collections/USERNAME/ID(下文中称为2号链接)可以访问某个收藏夹中所收藏的全部壁纸的详细信息
注:若某个收藏夹被你设置为私密,访问详情时只需要在后面加上?apikey=<API KEY>便可以了。API KEY可以通过查看个人信息的设置页面找到。
思路:
以上是爬图片前做的准备工作,下面便开始构思思路了:
1号链接:
打开1号链接可以看见:
{ "data": [ {"id":9xxxx8,"label":"Nxx","views":0,"public":0,"count":0}, {"id":9xxxx4,"label":"Xxxxx","views":4,"public":0,"count":126}, {"id":9xxxx7,"label":"Xxxxxxng","views":0,"public":0,"count":22}, {"id":9xxxx6,"label":"Xixxxxxxon","views":0,"public":0,"count":9}, {"id":9xxxx8,"label":"Nxxxxo","views":0,"public":1,"count":10}, {"id":9xxxx9,"label":"Nxxxxxian","views":0,"public":1,"count":33}, {"id":9xxxx7,"label":"Nxxxxx","views":2,"public":1,"count":47}, {"id":9xxxx0,"label":"Sxxxxn","views":2,"public":1,"count":24}, {"id":9xxxx2,"label":"Sxxxxxe","views":0,"public":1,"count":7} ] }
注:直接访问得到的内容是没有换行了,为了方便展示我加了换行符,下面也是一样
得到的是一个只有一个键值对的字典,键名是data,值是一个列表,列表里又套了多个字典,字典中包含了各个收藏夹的详细信息,包括:收藏夹的id、名称等等,重要的就是id和名称这两项内容了。
2号链接:
接下来根据id访问2号链接:
{ "data": [ {"id":"9mz31w", --snip-- "path":"https:\/\/w.wallhaven.cc\/full\/9m\/wallhaven-9mz31w.png", --snip-- }}, --snip-- {"id":"lqzp62", --snip-- "path":"https:\/\/w.wallhaven.cc\/full\/lq\/wallhaven-lqzp62.jpg", }} ], "meta":{"current_page":1,"last_page":2,"per_page":24,"total":47} }
得到的同样也是一个字典,有两个键,一个是data,另一个是meta,主要用的是data。第一个键data的值是一个列表,列表中又套了多个字典,分别包含了各张图片的详细信息,我们只需要里面path键的值就可以了。
看完了上面两个页面,再根据下载的需求便有了一个大致的思路:
首先访问1号链接,得到各个收藏夹的id与名称即label,接着根据得到的id,分别访问2号链接,得到图片的链接即path,访问path即可得到原图
代码详情:
有了思路便开始正式写代码:
首先导入所需要的运行库:
import requests
import os
import json
import re
from time import sleep
再定义一个Wallhaven_master类,再定义若干个属性:
def __init__(self):
super(Wallhaven_master, self).__init__()
self.url_user = 'https://wallhaven.cc/api/v1/collections?apikey=<API KEY>'#用的时候需要填写自己的api key
self.headers = {
"User-Agent": "",#内容可通过自己的浏览器查看,这里我删了
}
self.name = [] # 列表,储存每个收藏夹的label
self.colle_id = [] # 列表,储存每个收藏夹的id
self.pic_id = {} # 字典,键名为收藏夹id,键值为列表,储存每个收藏夹下所有图片path
self.DLed = [] # 列表,储存此次运行过程中已下载的图片path
self.DLread = [] # 列表,储存从json文件从读取的已下载的图片path,用于排重
self.DLtmp = []#列表,储存从json中读取的已下载链接
接下来写若干个函数(方法):
def get_data(self, a):
'''
打开api ,返回 data 字典
'''
r = self.get(a, headers=self.headers)
req = r.json()
data = req['data']
return data
#备注:通过访问前面的两个链接可以发现有个巧合:都是个字典,且都有键data,里面都保存了我们需要的信息
# 因为这个巧合,为了方面我们的访问,减少代码量便可以写这个函数
def get_colle_name(self):
'''
获得每个收藏夹的名字 label
'''
data = self.get_data(self.url_user)
for x in data:
name = x['label']
self.name.append(name)
#备注:将收藏夹的名称添加至列表self.name[]中
def get_colle_id(self):
"""
获取每个收藏夹的id
在列表 pic_id 中添加 列表元素 colle_id
"""
data = self.get_data(self.url_user)
for x in data:
colle = x['id']
self.colle_id.append(colle)
def get_pic_id(self):
'''
获得每个图片的 path
将 path 添加至字典 self.pic_id 各个键的值(列表)中
'''
for x in self.colle_id:
url = f"https://wallhaven.cc/api/v1/collections/NAME/{x}?apikey=<API KEY>"
#备注:NAME是自己的用户名,api key前面有介绍
# 这里我把自己的信息删了
data = self.get_data(url)
self.pic_id[x] = []
for y in data:
path = y['path']
self.pic_id[x].append(path)
def cret_floder(self):
'''
根据收藏夹名称创建文件夹
'''
if not os.path.exists('WH-pics'):
os.mkdir("WH-pics")
os.chdir('WH-pics')
for x in self.name:
if not os.path.exists(x):
os.mkdir(x)
def run(self):
'''
主程序:下载模块
'''
num = 0
for x in self.pic_id.values():
print(f'正在下载收藏夹 {self.name[num]} 中的图片')
rest = len(self.pic_id[self.colle_id[num]])
if not rest:
print('此收藏夹中图片已全部下载完成')
th = 1
for y in x:
name = y.split('/')[-1]
r = self.get(y)
#备注:此处删除了下载图片的代码
self.DLed.append(y)
self.loadjson()
print(f"第 {th} 张图片已下载完成!")
th += 1
sleep(2.5)
print('\n')
num += 1
print('所有收藏夹中的所有图片已全部下载完成!')
def readjson(self):
'''
读取json文件中的已下载链接,若无则创建
'''
with open('DLed.json', 'a+') as dl:
xyz = 0
try:
with open('DLed.json', 'r') as dl:
self.DLread = json.load(dl)
except ValueError:
self.DLread = []
def loadjson(self):
'''
将运行过程中下载的图片链接和从json文件中读取的已下载链接合并至一个列表中
每下载一张图片,更新一次json文件,确保保存了下载记录,防止重复下载
'''
self.DLtmp = self.DLed + self.DLread
with open('DLed.json', 'w+') as dl:
json.dump(self.DLtmp, dl)
def delrepeat(self):
''''
删除重复的下载链接,避免重复下载
'''
for x in self.pic_id.values():
for y in self.DLread:
if y in x:
x.remove(y)
以上便是程序的主要内容了,运行的话只需要创建一个类Wallhaven_master的实例,再依次使用上面写了的函数(方法)便可以了。
运行:
self.get_colle_id()
self.get_colle_name()
self.cret_floder()
self.get_pic_id()
self.readjson()
self.delrepeat()
self.run()
self.loadjson()
排重模块思路:
最后再说一下如何避免重复下载的思路:
创建一个json文件,里面用于存储之前已下载的图片的链接即path,
每次运行程序前读取里面的内容,在已获得的全部下载链接中,遍历删除读取的内容,之后再开始下载,
接着,每下载一张图片,便将该图片的链接添加至列表DLed中,
紧接着,合并列表DLed和列表DLread(即从json文件中读取的列表),接着在json文件中覆盖写入新列表,这样便确保了每下一张图片就可以把下载好的链接写入至json文件中来记录。
特别注意:
从官方的api介绍文档中得知:
API calls are currently limited to 45 per minute. If you do hit this limit, you will receive a 429 - Too many requests error.
Attempting to access a NSFW wallpaper without an API Key or with an invalid one will result in a 401 - Unauthorized error.
Any other attempts to use an invalid API key will result in a 401 - Unauthorized error.
每分钟请求数不得超过45次,所以使用sleep函数每下载一张图片便休息2s左右便可以完全满足需要
写在最后:
代码缺陷:
运行完代码后发现怎么每个收藏夹中下载的图片最多只有24张?!又看了api介绍文档后才发现,访问收藏夹详情只能得到前24张图片的详情。按理说在2号链接上加上?page=x是应该会解决的,但是我尝试了没有用,在网上查资料也没有找到解决办法,这个问题至今未解决。所以只能手动把收藏夹拆分成多个收藏夹,下载完后再手动合并,将就着用吧....蒟蒻落泪
其他:
自己一开始是想在网上找找有没有现成的直接拿来用,但是看了一圈发现都和我想要的没有半毛钱关系.....遂自己动手写了一个。
u1s1,在网上找现成的过程中,发现他们用的都是分析网页的方法,我就没看见一个利用官方api的,迷惑....
说实话写这个是我第一次写的一个“大” 程序,参考了一本python的入门介绍书,然后对着一个爬B站相册的代码照猫画虎的写出来了。
在写的过程中掉进了好多坑,比如说在使用列表元素的值的时候没有加索引,报错提示说:TypeError: unhashable type: 'list',看不懂,花了一晚上时间死活没想通.....蒟蒻落泪
还有,得到收藏夹名称时,访问的键应该是label,写的时候写成了lable.......
以上