Python3爬蟲(chóng)圖片抓取

2022-04-06 11:46 更新

在上一章中,我們已經(jīng)學(xué)會(huì)了如何使用Python3爬蟲(chóng)抓取文字,那么在本章教程中,將通過(guò)實(shí)例來(lái)教大家如何使用Python3爬蟲(chóng)批量抓取圖片。

 注:該網(wǎng)站目前已經(jīng)更換了圖片的請(qǐng)求方式,以下爬蟲(chóng)方法只能作為思路參考,已經(jīng)無(wú)法運(yùn)行成功,望周知!

(1)實(shí)戰(zhàn)背景

1

上圖的網(wǎng)站的名字叫做Unsplash,免費(fèi)高清壁紙分享網(wǎng)是一個(gè)堅(jiān)持每天分享高清的攝影圖片的站點(diǎn),每天更新一張高質(zhì)量的圖片素材,全是生活中的景象作品,清新的生活氣息圖片可以作為桌面壁紙也可以應(yīng)用于各種需要的環(huán)境。

看到這么優(yōu)美的圖片,是不是很想下載啊。每張圖片我都很喜歡,批量下載吧,不多爬,就下載50張好了。

2)實(shí)戰(zhàn)進(jìn)階

我們已經(jīng)知道了每個(gè)html標(biāo)簽都有各自的功能。<a>標(biāo)簽存放一下超鏈接,圖片存放在哪個(gè)標(biāo)簽里呢?html規(guī)定,圖片統(tǒng)統(tǒng)給我放到<img>標(biāo)簽中!既然這樣,我們截取就Unsplash網(wǎng)站中的一個(gè)<img>標(biāo)簽,分析一下:

<img alt="Snow-capped mountain slopes under blue sky" src="https://images.unsplash.com/photo-1428509774491-cfac96e12253?dpr=1&

可以看到,<img>標(biāo)簽有很多屬性,有alt、src、class、style屬性,其中src屬性存放的就是我們需要的圖片保存地址,我們根據(jù)這個(gè)地址就可以進(jìn)行圖片的下載。

那么,讓我們先捋一捋這個(gè)過(guò)程:

  • 使用requeusts獲取整個(gè)網(wǎng)頁(yè)的HTML信息;
  • 使用Beautiful Soup解析HTML信息,找到所有<img>標(biāo)簽,提取src屬性,獲取圖片存放地址;
  • 根據(jù)圖片存放地址,下載圖片。

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

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

按照我們的設(shè)想,我們應(yīng)該能找到很多<img>標(biāo)簽。但是我們發(fā)現(xiàn),除了一些<script>標(biāo)簽和一些看不懂的代碼之外,我們一無(wú)所獲,一個(gè)<img>標(biāo)簽都沒(méi)有!跟我們?cè)诰W(wǎng)站審查元素的結(jié)果完全不一樣,這是為什么?

答案就是,這個(gè)網(wǎng)站的所有圖片都是動(dòng)態(tài)加載的!網(wǎng)站有靜態(tài)網(wǎng)站和動(dòng)態(tài)網(wǎng)站之分,上一個(gè)實(shí)戰(zhàn)爬取的網(wǎng)站是靜態(tài)網(wǎng)站,而這個(gè)網(wǎng)站是動(dòng)態(tài)網(wǎng)站,動(dòng)態(tài)加載有一部分的目的就是為了反爬蟲(chóng)。

對(duì)于什么是動(dòng)態(tài)加載,你可以這樣理解:我們知道化妝術(shù)學(xué)的好,賊厲害,可以改變一個(gè)人的容貌。相應(yīng)的,動(dòng)態(tài)加載用的好,也賊厲害,可以改變一個(gè)網(wǎng)站的容貌。

動(dòng)態(tài)網(wǎng)站使用動(dòng)態(tài)加載常用的手段就是通過(guò)調(diào)用JavaScript來(lái)實(shí)現(xiàn)的。怎么實(shí)現(xiàn)JavaScript動(dòng)態(tài)加載,我們不必深究,我們只要知道,動(dòng)態(tài)加載的JavaScript腳本,就像化妝術(shù)需要用的化妝品,五花八門(mén)。有粉底、口紅、睫毛膏等等,它們都有各自的用途。動(dòng)態(tài)加載的JavaScript腳本也一樣,一個(gè)動(dòng)態(tài)加載的網(wǎng)站可能使用很多JavaScript腳本,我們只要找到負(fù)責(zé)動(dòng)態(tài)加載圖片的JavaScript腳本,不就找到我們需要的鏈接了嗎?

對(duì)于初學(xué)者,我們不必看懂JavaScript執(zhí)行的內(nèi)容是什么,做了哪些事情,因?yàn)槲覀冇袕?qiáng)大的抓包工具,它自然會(huì)幫我們分析。這個(gè)強(qiáng)大的抓包工具就是Fiddler:http://www.telerik.com/fiddler

PS:也可以使用瀏覽器自帶的Networks,但是我更推薦這個(gè)軟件,因?yàn)樗僮髌饋?lái)更高效。

安裝方法很簡(jiǎn)單,傻瓜式安裝,一直下一步即可,對(duì)于經(jīng)常使用電腦的人來(lái)說(shuō),應(yīng)該沒(méi)有任何難度。

這個(gè)軟件的使用方法也很簡(jiǎn)單,打開(kāi)軟件,然后用瀏覽器打開(kāi)我們的目標(biāo)網(wǎng)站,以Unsplash為例,抓包結(jié)果如下:

3

我們可以看到,上圖左側(cè)紅框處是我們的GET請(qǐng)求的地址,就是網(wǎng)站的URL,右下角是服務(wù)器返回的信息,我們可以看到,這些信息也是我們上一個(gè)程序獲得的信息。這個(gè)不是我們需要的鏈接,我們繼續(xù)往下看。

4

我們發(fā)現(xiàn)上圖所示的就是一個(gè)JavaScript請(qǐng)求,看右下側(cè)服務(wù)器返回的信息是一個(gè)json格式的數(shù)據(jù)。這里面,就有我們需要的內(nèi)容。我們局部放大看一下:

5

這是Fiddler右側(cè)的信息,上面是請(qǐng)求的Headers信息,包括這個(gè)Javascript的請(qǐng)求地址:http://unsplash.com/napi/feeds/home,其他信息我們先不管,我們看看下面的內(nèi)容。里面有很多圖片的信息,包括圖片的id,圖片的大小,圖片的鏈接,還有下一頁(yè)的地址。這個(gè)腳本以json格式存儲(chǔ)傳輸?shù)臄?shù)據(jù),json格式是一種輕量級(jí)的數(shù)據(jù)交換格式,起到封裝數(shù)據(jù)的作用,易于人閱讀和編寫(xiě),同時(shí)也易于機(jī)器解析和生成。這么多鏈接,可以看到圖片的鏈接有很多,根據(jù)哪個(gè)鏈接下載圖片呢?先別急,讓我們繼續(xù)分析:

QQ截圖20180517155307

在這個(gè)網(wǎng)站,我們可以按這個(gè)按鈕進(jìn)行圖片下載。我們抓包分下下這個(gè)動(dòng)作,看看發(fā)送了哪些請(qǐng)求。

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

通過(guò)Fiddler抓包,我們發(fā)現(xiàn),點(diǎn)擊不同圖片的下載按鈕,GET請(qǐng)求的地址都是不同的。但是它們很有規(guī)律,就是中間有一段代碼是不一樣的,其他地方都一樣。中間那段代碼是不是很熟悉?沒(méi)錯(cuò),它就是我們之前抓包分析得到j(luò)son數(shù)據(jù)中的照片的id。我們只要解析出每個(gè)照片的id,就可以獲得圖片下載的請(qǐng)求地址,然后根據(jù)這個(gè)請(qǐng)求地址,我們就可以下載圖片了。那么,現(xiàn)在的首要任務(wù)就是解析json數(shù)據(jù)了。

json格式的數(shù)據(jù)也是分層的??梢钥吹絥ext_page里存放的是下一頁(yè)的請(qǐng)求地址,很顯然Unsplash下一頁(yè)的內(nèi)容,也是動(dòng)態(tài)加載的。在photos下面的id里,存放著圖片的id,這個(gè)就是我們需要獲得的圖片id號(hào)。

怎么編程提取這些json數(shù)據(jù)呢?我們也是分步完成:

  • 獲取整個(gè)json數(shù)據(jù)
  • 解析json數(shù)據(jù)

編寫(xiě)代碼,嘗試獲取json數(shù)據(jù):

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

很遺憾,程序報(bào)錯(cuò)了,問(wèn)題出在哪里?通過(guò)錯(cuò)誤信息,我們可以看到SSL認(rèn)證錯(cuò)誤,SSL認(rèn)證是指客戶端到服務(wù)器端的認(rèn)證。一個(gè)非常簡(jiǎn)單的解決這個(gè)認(rèn)證錯(cuò)誤的方法就是設(shè)置requests.get()方法的verify參數(shù)。這個(gè)參數(shù)默認(rèn)設(shè)置為T(mén)rue,也就是執(zhí)行認(rèn)證。我們將其設(shè)置為False,繞過(guò)認(rèn)證不就可以了?

6

有想法就要嘗試,編寫(xiě)代碼如下:

# -*- 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)

認(rèn)證問(wèn)題解決了,又有新問(wèn)題了:

7

可以看到,我們GET請(qǐng)求又失敗了,這是為什么?這個(gè)網(wǎng)站反爬蟲(chóng)的手段除了動(dòng)態(tài)加載,還有一個(gè)反爬蟲(chóng)手段,那就是驗(yàn)證Request Headers。接下來(lái),讓我們分析下這個(gè)Requests Headers:

8

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

專業(yè)的解釋能說(shuō)的太多,我挑重點(diǎn):

  • User-Agent:這里面存放瀏覽器的信息??梢钥吹缴蠄D的參數(shù)值,它表示我是通過(guò)Windows的Chrome瀏覽器,訪問(wèn)的這個(gè)服務(wù)器。如果我們不設(shè)置這個(gè)參數(shù),用Python程序直接發(fā)送GET請(qǐng)求,服務(wù)器接受到的User-Agent信息就會(huì)是一個(gè)包含python字樣的User-Agent。如果后臺(tái)設(shè)計(jì)者驗(yàn)證這個(gè)User-Agent參數(shù)是否合法,不讓帶Python字樣的User-Agent訪問(wèn),這樣就起到了反爬蟲(chóng)的作用。這是一個(gè)最簡(jiǎn)單的,最常用的反爬蟲(chóng)手段。
  • Referer:這個(gè)參數(shù)也可以用于反爬蟲(chóng),它表示這個(gè)請(qǐng)求是從哪發(fā)出的??梢钥吹轿覀兺ㄟ^(guò)瀏覽器訪問(wèn)網(wǎng)站,這個(gè)請(qǐng)求是從https://unsplash.com/,這個(gè)地址發(fā)出的。如果后臺(tái)設(shè)計(jì)者,驗(yàn)證這個(gè)參數(shù),對(duì)于不是從這個(gè)地址跳轉(zhuǎn)過(guò)來(lái)的請(qǐng)求一律禁止訪問(wèn),這樣就也起到了反爬蟲(chóng)的作用。
  • authorization:這個(gè)參數(shù)是基于AAA模型中的身份驗(yàn)證信息允許訪問(wèn)一種資源的行為。在我們用瀏覽器訪問(wèn)的時(shí)候,服務(wù)器會(huì)為訪問(wèn)者分配這個(gè)用戶ID。如果后臺(tái)設(shè)計(jì)者,驗(yàn)證這個(gè)參數(shù),對(duì)于沒(méi)有用戶ID的請(qǐng)求一律禁止訪問(wèn),這樣就又起到了反爬蟲(chóng)的作用。

Unsplash是根據(jù)哪個(gè)參數(shù)反爬蟲(chóng)的呢?根據(jù)我的測(cè)試,是authorization。我們只要通過(guò)程序手動(dòng)添加這個(gè)參數(shù),然后再發(fā)送GET請(qǐng)求,就可以順利訪問(wèn)了。怎么什么設(shè)置呢?還是requests.get()方法,我們只需要添加headers參數(shù)即可。編寫(xiě)代碼如下:

# -*- 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參數(shù)值是通過(guò)字典傳入的。記得將上述代碼中your Client-ID換成諸位自己抓包獲得的信息。代碼運(yùn)行結(jié)果如下:

9

皇天不負(fù)有心人,可以看到我們已經(jīng)順利獲得json數(shù)據(jù)了,里面有next_page和照片的id。接下來(lái)就是解析json數(shù)據(jù)。根據(jù)我們之前分析可知,next_page放在了json數(shù)據(jù)的最外側(cè),照片的id放在了photos->id里。我們使用json.load()方法解析數(shù)據(jù),編寫(xiě)代碼如下:

# -*- 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('下一頁(yè)地址:',next_page)
     for each in html['photos']:
          print('圖片ID:',each['id'])

解析json數(shù)據(jù)很簡(jiǎn)單,跟字典操作一樣,就是字典套字典。json.load()里面的參數(shù)是原始的json格式的數(shù)據(jù)。程序運(yùn)行結(jié)果如下:

10

圖片的ID已經(jīng)獲得了,再通過(guò)字符串處理一下,就生成了我們需要的圖片下載請(qǐng)求地址。根據(jù)這個(gè)地址,我們就可以下載圖片了。下載方式,使用直接寫(xiě)入文件的方法。

(3)整合代碼

每次獲取鏈接加一個(gè)1s延時(shí),因?yàn)槿嗽跒g覽頁(yè)面的時(shí)候,翻頁(yè)的動(dòng)作不可能太快。我們要讓我們的爬蟲(chóng)盡量友好一些。

# -*- 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'}

    """
    函數(shù)說(shuō)明:獲取圖片ID
    Parameters:
        無(wú)
    Returns:
        無(wú)
    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(1)
        for i in range(5):
            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(1)


    """
    函數(shù)說(shuō)明:圖片下載
    Parameters:
        無(wú)
    Returns:
        無(wú)
    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 = 1024):
                    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+1))
        gp.download(gp.photos_id[i], (i+1))

下載速度還行,有的圖片下載慢是因?yàn)閳D片太大。可以看到右側(cè)也打印了一些警報(bào)信息,這是因?yàn)槲覀儧](méi)有進(jìn)行SSL驗(yàn)證。

20170930130019403


原文作者:Jack-Cui

原文地址:https://blog.csdn.net/c406495762/article/details/78123502


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)