天天看點

Python3爬蟲圖檔抓取

(1)實戰背景

Python3爬蟲圖檔抓取

上圖的網站的名字叫做Unsplash,免費高清桌面分享網是一個堅持每天分享高清的攝影圖檔的站點,每天更新一張高品質的圖檔素材,全是生活中的景象作品,清新的生活氣息圖檔可以作為桌面桌面也可以應用于各種需要的環境。

看到這麼優美的圖檔,是不是很想下載下傳啊。每張圖檔我都很喜歡,批量下載下傳吧,不多爬,就下載下傳50張好了。

2)實戰進階

我們已經知道了每個html标簽都有各自的功能。<a>标簽存放一下超連結,圖檔存放在哪個标簽裡呢?html規定,圖檔統統給我放到<img>标簽中!既然這樣,我們截取就Unsplash網站中的一個<img>标簽,分析一下:

可以看到,<img>标簽有很多屬性,有alt、src、class、style屬性,其中src屬性存放的就是我們需要的圖檔儲存位址,我們根據這個位址就可以進行圖檔的下載下傳。

那麼,讓我們先捋一捋這個過程:

  • 使用requeusts擷取整個網頁的HTML資訊;
  • 使用Beautiful Soup解析HTML資訊,找到所有<img>标簽,提取src屬性,擷取圖檔存放位址;
  • 根據圖檔存放位址,下載下傳圖檔。

我們信心滿滿地按照這個思路爬取Unsplash試一試,編寫代碼如下:

# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
     target = 'https://unsplash.com/'
     req = requests.get(url=target)
     print(req.text)
           

按照我們的設想,我們應該能找到很多

<img>

标簽。但是我們發現,除了一些

<script>

标簽和一些看不懂的代碼之外,我們一無所獲,一個

<img>

标簽都沒有!跟我們在網站審查元素的結果完全不一樣,這是為什麼? 

答案就是,這個網站的所有圖檔都是動态加載的!網站有靜态網站和動态網站之分,上一個實戰爬取的網站是靜态網站,而這個網站是動态網站,動态加載有一部分的目的就是為了反爬蟲。

對于什麼是動态加載,你可以這樣了解:我們知道化妝術學的好,賊厲害,可以改變一個人的容貌。相應的,動态加載用的好,也賊厲害,可以改變一個網站的容貌。

動态網站使用動态加載常用的手段就是通過調用JavaScript來實作的。怎麼實作JavaScript動态加載,我們不必深究,我們隻要知道,動态加載的JavaScript腳本,就像化妝術需要用的化妝品,五花八門。有粉底、口紅、睫毛膏等等,它們都有各自的用途。動态加載的JavaScript腳本也一樣,一個動态加載的網站可能使用很多JavaScript腳本,我們隻要找到負責動态加載圖檔的JavaScript腳本,不就找到我們需要的連結了嗎?

對于初學者,我們不必看懂JavaScript執行的内容是什麼,做了哪些事情,因為我們有強大的抓包工具,它自然會幫我們分析。這個強大的抓包工具就是Fiddler:http://www.telerik.com/fiddler

PS:也可以使用浏覽器自帶的Networks,但是我更推薦這個軟體,因為它操作起來更高效。

安裝方法很簡單,傻瓜式安裝,一直下一步即可,對于經常使用電腦的人來說,應該沒有任何難度。

這個軟體的使用方法也很簡單,打開軟體,然後用浏覽器打開我們的目标網站,以Unsplash為例,抓包結果如下:

Python3爬蟲圖檔抓取

我們可以看到,上圖左側紅框處是我們的GET請求的位址,就是網站的URL,右下角是伺服器傳回的資訊,我們可以看到,這些資訊也是我們上一個程式獲得的資訊。這個不是我們需要的連結,我們繼續往下看。

Python3爬蟲圖檔抓取

我們發現上圖所示的就是一個JavaScript請求,看右下側伺服器傳回的資訊是一個json格式的資料。這裡面,就有我們需要的内容。我們局部放大看一下:

Python3爬蟲圖檔抓取

這是Fiddler右側的資訊,上面是請求的Headers資訊,包括這個Javascript的請求位址:http://unsplash.com/napi/feeds/home,其他資訊我們先不管,我們看看下面的内容。裡面有很多圖檔的資訊,包括圖檔的id,圖檔的大小,圖檔的連結,還有下一頁的位址。這個腳本以json格式存儲傳輸的資料,json格式是一種輕量級的資料交換格式,起到封裝資料的作用,易于人閱讀和編寫,同時也易于機器解析和生成。這麼多連結,可以看到圖檔的連結有很多,根據哪個連結下載下傳圖檔呢?先别急,讓我們繼續分析:

Python3爬蟲圖檔抓取

在這個網站,我們可以按這個按鈕進行圖檔下載下傳。我們抓包分下下這個動作,看看發送了哪些請求。

https://unsplash.com/photos/1PrQ2mHW-Fo/download?force=true
https://unsplash.com/photos/JX7nDtafBcU/download?force=true
https://unsplash.com/photos/HCVbP3zqX4k/download?force=true
           

通過Fiddler抓包,我們發現,點選不同圖檔的下載下傳按鈕,GET請求的位址都是不同的。但是它們很有規律,就是中間有一段代碼是不一樣的,其他地方都一樣。中間那段代碼是不是很熟悉?沒錯,它就是我們之前抓包分析得到json資料中的照片的id。我們隻要解析出每個照片的id,就可以獲得圖檔下載下傳的請求位址,然後根據這個請求位址,我們就可以下載下傳圖檔了。那麼,現在的首要任務就是解析json資料了。

json格式的資料也是分層的。可以看到next_page裡存放的是下一頁的請求位址,很顯然Unsplash下一頁的内容,也是動态加載的。在photos下面的id裡,存放着圖檔的id,這個就是我們需要獲得的圖檔id号。

怎麼程式設計提取這些json資料呢?我們也是分步完成:

  • 擷取整個json資料
  • 解析json資料

編寫代碼,嘗試擷取json資料:

# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
     target = 'http://unsplash.com/napi/feeds/home'
     req = requests.get(url=target) print(req.text)
           

很遺憾,程式報錯了,問題出在哪裡?通過錯誤資訊,我們可以看到SSL認證錯誤,SSL認證是指用戶端到伺服器端的認證。一個非常簡單的解決這個認證錯誤的方法就是設定requests.get()方法的verify參數。這個參數預設設定為True,也就是執行認證。我們将其設定為False,繞過認證不就可以了?

Python3爬蟲圖檔抓取

有想法就要嘗試,編寫代碼如下:

# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
     target = 'http://unsplash.com/napi/feeds/home'
     req = requests.get(url=target, verify=False)
     print(req.text)
           

認證問題解決了,又有新問題了:

Python3爬蟲圖檔抓取

可以看到,我們GET請求又失敗了,這是為什麼?這個網站反爬蟲的手段除了動态加載,還有一個反爬蟲手段,那就是驗證Request Headers。接下來,讓我們分析下這個Requests Headers:

Python3爬蟲圖檔抓取

我截取了Fiddler的抓包資訊,可以看到Requests Headers裡又很多參數,有Accept、Accept-Encoding、Accept-Language、DPR、User-Agent、Viewport-Width、accept-version、Referer、x-unsplash-client、authorization、Connection、Host。它們都是什麼意思呢?

專業的解釋能說的太多,我挑重點:

  • User-Agent:這裡面存放浏覽器的資訊。可以看到上圖的參數值,它表示我是通過Windows的Chrome浏覽器,通路的這個伺服器。如果我們不設定這個參數,用Python程式直接發送GET請求,伺服器接受到的User-Agent資訊就會是一個包含python字樣的User-Agent。如果背景設計者驗證這個User-Agent參數是否合法,不讓帶Python字樣的User-Agent通路,這樣就起到了反爬蟲的作用。這是一個最簡單的,最常用的反爬蟲手段。
  • Referer:這個參數也可以用于反爬蟲,它表示這個請求是從哪發出的。可以看到我們通過浏覽器通路網站,這個請求是從https://unsplash.com/,這個位址發出的。如果背景設計者,驗證這個參數,對于不是從這個位址跳轉過來的請求一律禁止通路,這樣就也起到了反爬蟲的作用。
  • authorization:這個參數是基于AAA模型中的身份驗證資訊允許通路一種資源的行為。在我們用浏覽器通路的時候,伺服器會為通路者配置設定這個使用者ID。如果背景設計者,驗證這個參數,對于沒有使用者ID的請求一律禁止通路,這樣就又起到了反爬蟲的作用。

Unsplash是根據哪個參數反爬蟲的呢?根據我的測試,是authorization。我們隻要通過程式手動添加這個參數,然後再發送GET請求,就可以順利通路了。怎麼什麼設定呢?還是requests.get()方法,我們隻需要添加headers參數即可。編寫代碼如下:

# -*- coding:UTF-8 -*-
import requests
if __name__ == '__main__':
     target = 'http://unsplash.com/napi/feeds/home'
     headers = {'authorization':'your Client-ID'}
     req = requests.get(url=target, headers=headers, verify=False)
     print(req.text)
           

headers參數值是通過字典傳入的。記得将上述代碼中your Client-ID換成諸位自己抓包獲得的資訊。代碼運作結果如下:

Python3爬蟲圖檔抓取

皇天不負有心人,可以看到我們已經順利獲得json資料了,裡面有next_page和照片的id。接下來就是解析json資料。根據我們之前分析可知,next_page放在了json資料的最外側,照片的id放在了photos->id裡。我們使用json.load()方法解析資料,編寫代碼如下:

# -*- coding:UTF-8 -*-
import requests, json
if __name__ == '__main__':
     target = 'http://unsplash.com/napi/feeds/home'
     headers = {'authorization':'your Client-ID'}
     req = requests.get(url=target, headers=headers, verify=False)
     html = json.loads(req.text)
     next_page = html['next_page']
     print('下一頁位址:',next_page)
     for each in html['photos']:
          print('圖檔ID:',each['id'])
           

解析json資料很簡單,跟字典操作一樣,就是字典套字典。json.load()裡面的參數是原始的json格式的資料。程式運作結果如下:

Python3爬蟲圖檔抓取

圖檔的ID已經獲得了,再通過字元串處理一下,就生成了我們需要的圖檔下載下傳請求位址。根據這個位址,我們就可以下載下傳圖檔了。下載下傳方式,使用直接寫入檔案的方法。

(3)整合代碼

每次擷取連結加一個1s延時,因為人在浏覽頁面的時候,翻頁的動作不可能太快。我們要讓我們的爬蟲盡量友好一些。

# -*- coding:UTF-8 -*-
import requests, json, time, sys
from contextlib import closing

class get_photos(object):

    def __init__(self):
        self.photos_id = []
        self.download_server = 'https://unsplash.com/photos/xxx/download?force=trues'
        self.target = 'http://unsplash.com/napi/feeds/home'
        self.headers = {'authorization':'Client-ID c94869b36aa272dd62dfaeefed769d4115fb3189a9d1ec88ed457207747be626'}

    """
    函數說明:擷取圖檔ID
    Parameters:
        無
    Returns:
        無
    Modify:
        2017-09-13
    """   
    def get_ids(self):
        req = requests.get(url=self.target, headers=self.headers, verify=False)
        html = json.loads(req.text)
        next_page = html['next_page']
        for each in html['photos']:
            self.photos_id.append(each['id'])
        time.sleep()
        for i in range():
            req = requests.get(url=next_page, headers=self.headers, verify=False)
            html = json.loads(req.text)
            next_page = html['next_page']
            for each in html['photos']:
                self.photos_id.append(each['id'])
            time.sleep()


    """
    函數說明:圖檔下載下傳
    Parameters:
        無
    Returns:
        無
    Modify:
        2017-09-13
    """   
    def download(self, photo_id, filename):
        headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36'}
        target = self.download_server.replace('xxx', photo_id)
        with closing(requests.get(url=target, stream=True, verify = False, headers = self.headers)) as r:
            with open('%d.jpg' % filename, 'ab+') as f:
                for chunk in r.iter_content(chunk_size = ):
                    if chunk:
                        f.write(chunk)
                        f.flush()

if __name__ == '__main__':
    gp = get_photos()
    print('擷取圖檔連接配接中:')
    gp.get_ids()
    print('圖檔下載下傳中:')
    for i in range(len(gp.photos_id)):
        print('  正在下載下傳第%d張圖檔' % (i+))
        gp.download(gp.photos_id[i], (i+))
           

下載下傳速度還行,有的圖檔下載下傳慢是因為圖檔太大。可以看到右側也列印了一些警報資訊,這是因為我們沒有進行SSL驗證。

Python3爬蟲圖檔抓取

原文作者:Jack-Cui

繼續閱讀