小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

coder-pig發表於2019-03-04

引言

關於爬小姐姐的指令碼示例,在我的Gayhub倉庫:ReptileSomething 裡已經有好幾個了,基本都是沒什麼技術含量的,直接解析HTML拿到 圖片的URL,然後下載,特別開一篇寫爬取花瓣網的小姐姐的實戰教程, 是因為爬這個網站的時候會遇到好幾個問題,第一感受到了反爬蟲的套路, (折騰了我將近2天):

  • 1.圖片是瀑布流佈局通過Ajax動態載入資料的
  • 2.在處理圖片詳情頁的時候才發現了圖片連結規則,前面做 了很多無謂的操作;
  • 3.最後獲得了圖片的正確url,但是根本下載不下來,不知道 是做了防盜鏈還是什麼?或者要登入之類的,瀏覽器開啟也無法下載, 開啟超連結是這樣的內容,但是當你右鍵儲存的時候發現並不能下載:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

不信的話可以試試。 img.hb.aicdn.com/36b521f7177…

我覺得算是爬圖片裡稍微有點難度的站點了,強烈建議跟著我 一起回顧這個過程!


1.問題初現:瀑布流和Ajax動態載入資料

Chrome抓包的時候,抓到的資料和Elements的內容不一樣,

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

js動態載入資料,前面已經見識過這種反爬蟲的套路了, 有Selenium在手,根本不虛,模擬一波瀏覽器請求 載入下就能得到和Elements一樣的內容了。


兩個問題:瀑布流和Ajax動態載入資料。 怎麼說?且聽我一一道來:

沒事喜歡練手的我意外發現了花瓣網,F12 Chrome抓一波包:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

隨手寫個程式碼看看:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

Elements看下我們想扒的是什麼:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

這裡儘管有個img,但是明顯是個小圖,應該是要點開a那個 /pins/1433175317連結裡才有大圖,點開: huaban.com/pins/143317…

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

看下Elements,這個就是我們想要的圖片url:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

恩,一如既往的簡單套路,搞到批量的列表url,然後下載圖片。 看回我們的利用Selenium得到的網頁程式碼,可以很穩。

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

接下來的事情本該就水到渠成的了,然後這時候發生了一件 令人猝不及防的事情:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

在網頁那裡滾到底部發現會載入更多的圖片,越滾越多,但是 我們的Selenium只抓到了30個,咦,這種在之前抓某個網站 的時候就遇到過了,寫個簡單的滾動到底部的js,然後Selenium 迴圈執行這個指令碼圖片數/30次就好了,中途可以休眠1s給他載入, 執行完畢後,再去呼叫page_source得到最終的頁面程式碼,然後走波 BeautifulSoup把我們1000多個小姐姐扒出來就好。

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

但是實際執行的結果卻出乎我們的意料,最後得到的小姐姐列表 還是30個,臥槽,什麼鬼,接著開啟我們的瀏覽器,滾動的時候 發現,列表內容竟然是動態變化的,開啟圖片列表節點,滾動網 頁,不禁又發出一句,臥槽,什麼鬼,列表是動態變化的???

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

這些剛還不在的,突然就蹦出來了,就好像你刷著即刻刷著 刷著就蹦出一個x子。動態載入?想想野路子,要不我們自己 量化下滾動偏移量,比如滾動100,我們抓一波頁面,存一下, 最後做下去重?這野路子不是一般的野:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

單不說怎麼量化這個偏移量了,瀏覽器寬度不一樣時載入的 數目還不一定是30,然後那麼多畫板,你每個畫板這樣玩? 效率巨低。

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

苦苦尋覓後,發現了兩個關鍵詞:瀑布流和Ajax動態載入資料


2.解決問題

瀑布流參差不齊的多欄佈局以及到達底部自動載入的方式 Ajax動態載入資料在不重新載入整個網頁的情況下,對網頁的某 部分進行更新

簡單點說就是:

圖片通過JS載入成瀑布流的形式,當到達底部後,會請求後臺拿到更多 的資料,解析後通過Ajax,可以在不關閉不轉跳不重新整理瀏覽器的情況下 部分更新頁面內容。

So,我們我們重新抓抓包,在滾動到底部的時候看下抓到的資料, 點選篩選XHR(XMLHttpRequest),這個是瀏覽器後臺與服務之間 交換資料的檔案,一般為json格式:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

點開,右側看看Headers,果然是json格式的:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

發現有這樣的請求頭,先放著,等下再研究規律:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

點開右側Previews,發現了傳回來的一大串Json,這裡的 pins應該就是新增加的妹子圖的相關資料了。

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐


3.問題再現:猜測與試驗,一步步解密規則

為了方便研究,我又滾動了幾下,載入更多的資料,以方便 找出規律:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

1.發起請求的規則

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

從上面我們可以得到一些這樣的資訊: 首先是Get請求,固定的基地址:http://huaban.com/boards/18907029/ 這個18907029是畫板id,後面的引數,jcx1ki7y和wfl=1應該是固定的, limit這個是載入數量,一般是分頁用的,就是一次載入20條新資料, 最後這個max:1348131400 暫時不知道是什麼?不過應該是某個什麼id 吧,看下第二個的max是1263961742,複製到第一個返回的json裡搜搜:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

臥槽,剛好最後一個,不會那麼巧吧?然後把第三個max:1247629077複製 到第二個的Json裡看看:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

果然,好傢伙,這個max就是每次拿到的最後一個圖片的pin_id 知道規則了,模擬一波請求,解析一波json,每次拿到最後這個 pin_id,用作下次請求,當返回的pins裡沒東西,說明已經載入 完所有的了,來,寫一波程式碼,先要處理剛進來時載入的列表, 得到最後的一個圖的pin_id,然後才能開始執行上面那個拿 json的操作。

在我準備用Selenium模擬請求主頁的時候,我發現了用urllib 模擬請求,裡面就能拿到最後一個pin_id,只不過他是寫在js裡 的,我們可以通過正規表示式拿到我們想要的pin_id們:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

還有點開一個具體的大圖頁,發現他的url規則竟然是: huaban.com/pins/926502… 就是http://huaban.com/pins/ + pin_id,所以我們只要獲得pin_id就可以了!


2.一步步寫程式碼

規則清楚了,接下來一步步寫程式碼來獲取我們想要的資料吧!

1)首頁資料的獲取

進來的時候會載入一次,不是通過Ajax載入,預設是30個,需要處理 一波網頁獲得這個30個資料,然後30個資料的最後一個用於請求Ajax。 通過正則拿到pins這段json。

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

程式碼如下

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

測試下程式碼

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

開啟網頁載入更多確認下這個最後的pid是否正確:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

可以正確,接著就來處理json資料了~ (這要注意正則匹配用的是search,我一開始沒留意用的是match, 用一些真這個校驗工具測試自己的正則一直是對的,但是丟程式 裡缺一直不匹配,返回None,要注意!!!)

2)處理Ajax資料

首先是請求頭的設定,和我們普通的請求不一樣,如果你直接 用瀏覽器開啟ajax載入資料的那個url發現返回的並不是json!!!

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

隨手找個連結試試,就是解析json而已,

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

簡單測試下

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

執行結果

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

可以,拿到資料了,接下來要優化下點東西,如果每次都要去拼接: 這串東西不就很麻煩了,可以通過正則來進行替換:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

這裡用到前面沒有細講的re.sub()替換方法和向前和向後界定 這兩個東西什麼時候用到,當前這個場景就能用到,比如我們想替換 pin_id,用一個括號括著想替換的部分,感覺應該就能替換了:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

結果

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

是的,前面一大段東西沒了,如果你用了向前(?<=...)和向後界定(?=), 就可以讓正則只匹配和替換這兩個中間部分的字串了~

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

好的,替換成功,小小整理一下程式碼:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

好的,pin_id都能夠拿到了,接下來通過這些想辦法拿到圖片啦, 點開一個圖片的詳情頁,比如:

huaban.com/pins/127298…

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

檢視頁面結構,得到圖片url:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

img.hb.aicdn.com/36b521f7177…

然後寫個簡單的模擬請求下這個網址,看下返回的資料有沒有這個圖片Url相匹配 的內容,全部搜沒有,然後把後面的分成三段,一段段搜: 先搜:36b521f717741a4e3e024fd29606f61b8f960318f3763,秒找到, 有六處匹配的,這後面跟著-WzUoLC,就剩下最後的fw658,全域性搜搜不到

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

難道是固定的?開啟另一個詳情頁看看,果然,fw658是固定的:

img.hb.aicdn.com/7dc8cfc5e00…

臥槽,key???前面拿json的資料就能拿到key,臥槽,該不會就 這樣拼接就可以了吧,找到前面一個:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

...令人無法接受,折騰了那麼舊,原來拿到key就可以拼接得出圖片url了

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐


3.問題還現:我還是太naive了

嘖嘖,正確的圖片url拿到了,瀏覽器開啟也是沒問題的,接著就是無腦 拼url,然後一個個下載了,正當我以為一切已經結束的時候,才發現了 最後的隱藏關卡:"圖片並不能下載"???右鍵另存為儲存直接失敗, py程式碼直接崩潰。

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

然後,在那個圖片的詳情頁倒是可以下載,我的天,難不成要我每個 圖片都用Selenium載入,然後處理頁面資料,在這裡下? 講真,我是非常抗拒用Selenium的,慢不說,還耗記憶體, 代理也不怎麼好設定,而且別人會說我low,百度谷歌搜了 一大堆,基本都是說了等於沒說...正在我萬念俱灰,想用回 Selenium這種Low比方式的時候,我萌生了一個想法: 會不會是需要登入後才能下載,於是乎我把連結發給我組 的UI,然後她預設瀏覽器竟然是ie,然後奇蹟發現了,沒有 登入,直接用ie瀏覽器開啟了,然後他麼的,可以右鍵儲存 到本地?接著我又把連結發給我隔壁的後臺小哥,同樣用ie 開啟,可以。此時熟悉的BGM響起:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

真相花瓣沒有做ie系列瀏覽器的相容

所以,模擬ie瀏覽器的User-Agent就可以下載圖片了!!!

'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)'
複製程式碼

立馬試試,當看到第一張圖片下載到了我的倉庫裡的時候, 我就知道我猜對了:

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

剩下的就是組織一波程式碼,批量下載了~


4.完整程式碼

import urllib.request
import urllib.error
import re
import json
import coderpig
import os

# 圖片拼接url後,分別是字首字尾
img_start_url = 'http://img.hb.aicdn.com/'
img_end = '_fw658'
# 獲取pins的正則
boards_pattern = re.compile(r'pins":(.*)};')
# 修改pin_id的正則
max_pattern = re.compile(r'(?<=max=)\d*(?=&limit)')
# 圖片輸出檔案
pin_ids_file = 'pin_ids.txt'
# 圖片輸出路徑
pic_download_dir = 'output/Picture/HuaBan/'

json_headers = {
    'Host': 'huaban.com',
    'Accept': 'application/json',
    'X-Request': 'JSON',
    'X-Requested-With': 'XMLHttpRequest'
}


# 獲得borads頁資料,提取key列表寫入到檔案裡,並返回最後一個pid用於後續查詢
def get_boards_index_data(url):
    print(url)
    proxy_ip = coderpig.get_proxy_ip()
    resp = coderpig.get_resp(url, proxy=proxy_ip).decode('utf-8')
    result = boards_pattern.search(resp)
    json_dict = json.loads(result.group(1))
    for item in json_dict:
        coderpig.write_str_data(item['file']['key'], pin_ids_file)
    # 返回最後一個pin_id
    pin_id = json_dict[-1]['pin_id']
    return pin_id


# 模擬Ajax請求更多資料
def get_json_list(url):
    proxy_ip = coderpig.get_proxy_ip()
    print("獲取json:" + url)
    resp = coderpig.get_resp(url, headers=json_headers, proxy=proxy_ip).decode('utf-8')
    if resp is None:
        return None
    else:
        json_dict = json.loads(resp)
        pins = json_dict['board']['pins']
        if len(pins) == 0:
            return None
        else:
            for item in pins:
                coderpig.write_str_data(item['file']['key'], pin_ids_file)
            return pins[-1]['pin_id']


# 下載圖片的方法
def download_pic(key):
    proxy_ip = coderpig.get_proxy_ip()
    coderpig.is_dir_existed(pic_download_dir)
    url = img_start_url + key + img_end
    resp = coderpig.get_resp(url, proxy=proxy_ip, ie_header=True)
    try:
        print("下載圖片:" + url)
        pic_name = key + ".jpg"
        with open(pic_download_dir + pic_name, "wb+") as f:
            f.write(resp)
    except (OSError, urllib.error.HTTPError, urllib.error.URLError, Exception) as reason:
        print(str(reason))


if __name__ == '__main__':
    coderpig.init_https()
    if os.path.exists(pin_ids_file):
        os.remove(pin_ids_file)
    # 一個畫板連結,可自行替換
    boards_url = 'http://huaban.com/boards/27399228/'
    board_last_pin_id = get_boards_index_data(boards_url)
    board_json_url = boards_url + '?jcx38c3h&max=354569642&limit=20&wfl=1'
    while True:
        board_last_pin_id = get_json_list(max_pattern.sub(str(board_last_pin_id), board_json_url))
        if board_last_pin_id is None:
            break
    pic_url_list = coderpig.load_data(pin_ids_file)
    for key in pic_url_list:
        download_pic(key)
    print("下載完成~")
複製程式碼

輸出結果

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

參見吾王~


5.小結:

磕磕碰碰,總算是把程式碼給擼出來了,成功又收穫了一大波小姐姐, 爬蟲技能點+1,建議還是少用無腦Selenium吧,另外剛發現, Chrome直接支援右鍵匯出XPath,就不用自己慢慢扣了(如果你用lxml的話)。

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

好的,就說那麼多~


====== 修正 ======

  • 關於圖片無法下載的問題,留言區 China卷柏 告知了原因: 請求頭裡的Referer,Chrome下載的時候自動填寫為當前頁面的URL 所以只需在請求頭裡設定Referer為空或者 http://huaban.com/ 就可以了,實測可以,gayhub程式碼已更新,感謝~ 閱讀中遇到問題或者有寫錯的,歡迎提出一起討論!

本節原始碼下載

github.com/coder-pig/R…


來啊,Py交易啊

想加群一起學習Py的可以加下,智障機器人小Pig,驗證資訊裡包含: PythonpythonpyPy加群交易屁眼 中的一個關鍵詞即可通過;

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐

驗證通過後回覆 加群 即可獲得加群連結(不要把機器人玩壞了!!!)~~~ 歡迎各種像我一樣的Py初學者,Py大神加入,一起愉快地交流學♂習,van♂轉py。

小豬的Python學習之旅 —— 9.爬蟲實戰:爬取花瓣網的小姐姐


相關文章