Python爬蟲實戰 | 利用多執行緒爬取 LOL 高清桌布

語言: CN / TW / HK

來源:公眾號【傑哥的IT之旅】

作者:阿拉斯加

ID:Jake_Internet

一、背景介紹

隨著移動端的普及出現了很多的移動 APP,應用軟體也隨之流行起來。最近看到英雄聯盟的手游上線了,感覺還行,PC 端英雄聯盟可謂是爆火的遊戲,不知道移動端的英雄聯盟前途如何,那今天我們使用到多執行緒的方式爬取 LOL 官網英雄高清桌布。

二、頁面分析

目標網站:https://lol.qq.com/data/info-heros.shtml#Navi

image.png

官網介面如圖所示,顯而易見,一個小圖表示一個英雄,我們的目的是爬取每一個英雄的所有面板圖片,全部下載下來並儲存到本地。

次級頁面

上面的頁面我們稱為主頁面,次級頁面也就是每一個英雄對應的頁面,就以黑暗之女為例,它的次級頁面如下所示:

image.png

我們可以看到有很多的小圖,每一張小圖對應一個面板,通過 network 檢視面板資料介面,如下圖所示:

image.png

我們知道了面板資訊是一個 json 格式的字串進行傳輸的,那麼我們只要找到每個英雄對應的 id,找到對應的 json 檔案,提取需要的資料就能得到高清面板桌布。

然後這裡黑暗之女的 json 的檔案地址是:

hero_one = 'https://game.gtimg.cn/images/lol/act/img/js/hero/1.js'

這裡其實規律也非常簡單,每個英雄的面板資料的地址是這樣的:

url = 'https://game.gtimg.cn/images/lol/act/img/js/hero/{}.js'.format(id)

那麼問題來了 id 的規律是怎麼樣的呢?這裡英雄的 id 需要在首頁檢視,如下所示:

image.png

我們可以看到兩個列表[0,99], [100,156] ,即 156 個英雄,但是 heroId 卻一直到了 240….,由此可見,它是有一定的變化規律的,並不是依次加一,所以要爬取全部英雄面板圖片,需要先拿到全部的heroId。

三、抓取思路

為什麼使用多執行緒,這裡解釋一下,我們在爬取圖片,視訊這種資料的時候,因為需要儲存到本地,所以會使用大量的檔案的讀取和寫入操作,也就是 IO 操作,試想一下如果我們進行同步請求操作;

那麼在第一次請求完成一直到檔案儲存到本地,才會進行第二次請求,那麼這樣效率非常低下,如果使用多執行緒進行非同步操作,效率會大大提升。

所以必然要使用多執行緒或者是多程序,然後把這麼多的資料佇列丟給執行緒池或者程序池去處理;

在 Python 中,multiprocessing Pool 程序池,multiprocessing.dummy 非常好用。

  • multiprocessing.dummy模組:dummy模組是多執行緒;
  • multiprocessing模組:multiprocessing是多程序;

multiprocessing.dummy模組與multiprocessing模組兩者的 api 都是通用的,程式碼的切換使用上比較靈活;

我們首先在一個測試的 demo.py 檔案抓取英雄 id,這裡的程式碼我已經寫好了,得到一個儲存英雄 id 的列表,直接在主檔案裡使用即可;

demo.py

url = 'https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js' res = requests.get(url,headers=headers) res = res.content.decode('utf-8') res_dict = json.loads(res) heros = res_dict["hero"] # 156個hero資訊 idList = [] for hero in heros:     hero_id = hero["heroId"]     idList.append(hero_id) print(idList)

得到 idList 如下所示:

idlist = [1,2,3,….,875,876,877] # 中間的英雄 id 這裡不做展示

構建的 url:page = 'http://www.bizhi88.com/s/470/{}.html'.format(i)

這裡的 i 表示 id,進行 url 的動態構建;

那麼我們定製兩個函式一個用於爬取並且解析頁面(spider),一個用於下載資料  (download),開啟執行緒池,使用 for 迴圈構建儲存英雄面板 json 資料的 url,儲存在列表中,作為 url 佇列,使用 pool.map()  方法執行 spider (爬蟲)函式;

``` def map(self, fn, *iterables, timeout=None, chunksize=1):     """Returns an iterator equivalent to map(fn, iter)”“”

這裡我們的使用是:pool.map(spider,page) # spider:爬蟲函式;page:url佇列

```

作用:將列表中的每個元素提取出來當作函式的引數,建立一個個程序,放進程序池中;

引數1:要執行的函式;

引數2:迭代器,將迭代器中的數字作為引數依次傳入函式中;

json資料解析

image.png

這裡我們就以黑暗之女的面板的 json 檔案做展示進行解析,我們需要獲取的內容有 1.name2.skin_name3.mainImg,因為我們發現 heroName 是一樣的,所以把英雄名作為該英雄的面板資料夾名,這樣便於檢視儲存;

item = {} item['name'] = hero["heroName"] item['skin_name'] = hero["name"] if hero["mainImg"] == '':    continue item['imgLink'] = hero["mainImg"]

有一個注意點:

有的 mainImg 標籤是空的,所以我們需要跳過,否則如果是空的連結,請求時會報錯;

四、資料採集

匯入相關第三方庫

import requests # 請求 from multiprocessing.dummy import Pool as ThreadPool # 併發 import time # 效率 import os # 檔案操作 import json # 解析

頁面資料解析

``` def spider(url):     res = requests.get(url, headers=headers)     result = res.content.decode('utf-8')     res_dict = json.loads(result)

skins = res_dict["skins"]  # 15個hero資訊     print(len(skins))

for index,hero in enumerate(skins): # 這裡使用到enumerate獲取下標,以便檔案圖片命名;         item = {} # 字典物件         item['name'] = hero["heroName"]         item['skin_name'] = hero["name"]

if hero["mainImg"] == '':             continue         item['imgLink'] = hero["mainImg"]         print(item)

download(index+1,item) ```

download 下載圖片

def download(index,contdict):     name = contdict['name']     path = "面板/" + name     if not os.path.exists(path):         os.makedirs(path)     content = requests.get(contdict['imgLink'], headers=headers).content     with open('./面板/' + name + '/' + contdict['skin_name'] + str(index) + '.jpg', 'wb') as f:         f.write(content)

這裡我們使用 OS 模組建立資料夾,前面我們有說到,每個英雄的 heroName 的值是一樣的,藉此建立資料夾並命名,方便面板的儲存(歸類),然後就是這裡圖片檔案的路徑需要仔細,少一個斜槓就會報錯。

main() 主函式

def main():      pool = ThreadPool(6)     page = []     for i in range(1,21):         newpage = 'https://game.gtimg.cn/images/lol/act/img/js/hero/{}.js'.format(i)         print(newpage)         page.append(newpage)     result = pool.map(spider, page)     pool.close()     pool.join()     end = time.time()

說明:

  • 在主函式裡我們首選建立了六個執行緒池;

  • 通過 for 迴圈動態構建 20 條 url,我們小試牛刀一下,20 個英雄面板,如果爬取全部可以對之前的 idList 遍歷,再動態構建 url;

  • 使用 map() 函式對執行緒池中的 url 進行資料解析儲存操作;

  • 當執行緒池 close 的時候並未關閉執行緒池,只是會把狀態改為不可再插入元素的狀態;

五、程式執行

if __name__ == '__main__':     main()

結果如下:

image.png

當然了這裡只是截取了部分影象,總共爬取了 200+ 張圖片,總體來說還是可以。

六、總結

本次我們使用了多執行緒爬取了英雄聯盟官網英雄面板高清桌布,因為圖片涉及到 IO 操作,我們使用併發方式進行,大大提高了程式的執行效率。

當然爬蟲淺嘗輒止,此次小試牛刀,爬取了 20 個英雄的面板圖片,感興趣的小夥伴可以把面板全部爬取下來,只需要改變遍歷的元素為之前的 idlist 即可。

本文完。


原創不易,如果你覺得這篇文章對你有點用的話,麻煩你為本文點個贊、評論或轉發一下,因為這將是我輸出更多優質文章的動力,感謝!

對了,掘友們記得給我點個免費的關注喲!防止你迷路下次就找不到我了。

我們下期再見!