【爬蟲】【教程向】爬取壁紙網站Wallhaven收藏夾中的壁紙


開頭:

Wallhaven是一個壁紙網站,注冊賬號后可以將喜歡的壁紙添加至收藏夾中。

里面有許多質量非常高的壁紙,為了方便下載,於是花了兩天多時間寫了一個爬壁紙的腳本(蒟蒻落淚)


需求:

首先明確自己的需求:

代碼的功能應包含:

  • 訪問各個收藏夾,按收藏夾的分類下載圖片
  • 下載的圖片保存至對應收藏夾名的文件夾中
  • 下載圖片應按照網站中給出的編號來命名
  • 含有記錄下載的功能,避免未全部下載完后關閉程序,第二次打開時從頭開始下載的悲劇
  • 下載過程中有適當的提示,例如:正在下載xx收藏夾中的圖片;第x張圖片已下載完成等等

准備工作:

接着做一些准備工作:

最開始抱着試一試的心態找找看有沒有api,居然發現這個網站提供了詳細的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.......

以上


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM