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

引言python

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

  • 1.圖片是瀑布流佈局經過Ajax動態加載數據的
  • 2.在處理圖片詳情頁的時候才發現了圖片連接規則,前面作 了不少無謂的操做;
  • 3.最後得到了圖片的正確url,可是根本下載不下來,不知道 是作了防盜鏈仍是什麼?或者要登陸之類的,瀏覽器打開也沒法下載, 打開超連接是這樣的內容,可是當你右鍵保存的時候發現並不能下載:

不信的話能夠試試。 img.hb.aicdn.com/36b521f7177…github

我以爲算是爬圖片裏稍微有點難度的站點了,強烈建議跟着我 一塊兒回顧這個過程!ajax


1.問題初現:瀑布流和Ajax動態加載數據

Chrome抓包的時候,抓到的數據和Elements的內容不同,正則表達式

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


兩個問題:瀑布流和Ajax動態加載數據。 怎麼說?且聽我一一道來:瀏覽器

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

隨手寫個代碼看看:app

Elements看下咱們想扒的是什麼:工具

這裏儘管有個img,可是明顯是個小圖,應該是要點開a那個 /pins/1433175317連接裏纔有大圖,點開: huaban.com/pins/143317…

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

恩,一如既往的簡單套路,搞到批量的列表url,而後下載圖片。 看回咱們的利用Selenium獲得的網頁代碼,能夠很穩。

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

在網頁那裏滾到底部發現會加載更多的圖片,越滾越多,可是 咱們的Selenium只抓到了30個,咦,這種在以前抓某個網站 的時候就遇到過了,寫個簡單的滾動到底部的js,而後Selenium 循環執行這個腳本圖片數/30次就行了,中途能夠休眠1s給他加載, 執行完畢後,再去調用page_source獲得最終的頁面代碼,而後走波 BeautifulSoup把咱們1000多個小姐姐扒出來就好。

可是實際運行的結果卻出乎咱們的意料,最後獲得的小姐姐列表 仍是30個,臥槽,什麼鬼,接着打開咱們的瀏覽器,滾動的時候 發現,列表內容居然是動態變化的,打開圖片列表節點,滾動網 頁,不由又發出一句,臥槽,什麼鬼,列表是動態變化的???

這些剛還不在的,忽然就蹦出來了,就好像你刷着即刻刷着 刷着就蹦出一個x子。動態加載?想一想野路子,要不咱們本身 量化下滾動偏移量,好比滾動100,咱們抓一波頁面,存一下, 最後作下去重?這野路子不是通常的野:

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

苦苦尋覓後,發現了兩個關鍵詞:瀑布流和Ajax動態加載數據


2.解決問題

瀑布流良莠不齊的多欄佈局以及到達底部自動加載的方式 Ajax動態加載數據在不從新加載整個網頁的狀況下,對網頁的某 部分進行更新

簡單點說就是:

圖片經過JS加載成瀑布流的形式,當到達底部後,會請求後臺拿到更多 的數據,解析後經過Ajax,能夠在不關閉不轉跳不刷新瀏覽器的狀況下 部分更新頁面內容。

So,咱們咱們從新抓抓包,在滾動到底部的時候看下抓到的數據, 點擊篩選XHR(XMLHttpRequest),這個是瀏覽器後臺與服務之間 交換數據的文件,通常爲json格式:

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

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

點開右側Previews,發現了傳回來的一大串Json,這裏的 pins應該就是新增長的妹子圖的相關數據了。


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

爲了方便研究,我又滾動了幾下,加載更多的數據,以方便 找出規律:

1.發起請求的規則

從上面咱們能夠獲得一些這樣的信息: 首先是Get請求,固定的基地址:http://huaban.com/boards/18907029/ 這個18907029是畫板id,後面的參數,jcx1ki7y和wfl=1應該是固定的, limit這個是加載數量,通常是分頁用的,就是一次加載20條新數據, 最後這個max:1348131400 暫時不知道是什麼?不過應該是某個什麼id 吧,看下第二個的max是1263961742,複製到第一個返回的json裏搜搜:

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

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

在我準備用Selenium模擬請求主頁的時候,我發現了用urllib 模擬請求,裏面就能拿到最後一個pin_id,只不過他是寫在js裏 的,咱們能夠經過正則表達式拿到咱們想要的pin_id們:

還有點開一個具體的大圖頁,發現他的url規則居然是: huaban.com/pins/926502… 就是http://huaban.com/pins/ + pin_id,因此咱們只要得到pin_id就能夠了!


2.一步步寫代碼

規則清楚了,接下來一步步寫代碼來獲取咱們想要的數據吧!

1)首頁數據的獲取

進來的時候會加載一次,不是經過Ajax加載,默認是30個,須要處理 一波網頁得到這個30個數據,而後30個數據的最後一個用於請求Ajax。 經過正則拿到pins這段json。

代碼以下

測試下代碼

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

能夠正確,接着就來處理json數據了~ (這要注意正則匹配用的是search,我一開始沒留意用的是match, 用一些真這個校驗工具測試本身的正則一直是對的,可是丟程序 裏缺一直不匹配,返回None,要注意!!!)

2)處理Ajax數據

首先是請求頭的設置,和咱們普通的請求不同,若是你直接 用瀏覽器打開ajax加載數據的那個url發現返回的並非json!!!

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

簡單測試下

運行結果

能夠,拿到數據了,接下來要優化下點東西,若是每次都要去拼接: 這串東西不就很麻煩了,能夠經過正則來進行替換:

這裏用到前面沒有細講的re.sub()替換方法和向前和向後界定 這兩個東西何時用到,當前這個場景就能用到,好比咱們想替換 pin_id,用一個括號括着想替換的部分,感受應該就能替換了:

結果

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

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

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

huaban.com/pins/127298…

查看頁面結構,獲得圖片url:

img.hb.aicdn.com/36b521f7177…

而後寫個簡單的模擬請求下這個網址,看下返回的數據有沒有這個圖片Url相匹配 的內容,所有搜沒有,而後把後面的分紅三段,一段段搜: 先搜:36b521f717741a4e3e024fd29606f61b8f960318f3763,秒找到, 有六處匹配的,這後面跟着-WzUoLC,就剩下最後的fw658,全局搜搜不到

難道是固定的?打開另外一個詳情頁看看,果真,fw658是固定的:

img.hb.aicdn.com/7dc8cfc5e00…

臥槽,key???前面拿json的數據就能拿到key,臥槽,該不會就 這樣拼接就能夠了吧,找到前面一個:

...使人沒法接受,折騰了那麼舊,原來拿到key就能夠拼接得出圖片url了


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

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

而後,在那個圖片的詳情頁卻是能夠下載,個人天,難不成要我每一個 圖片都用Selenium加載,而後處理頁面數據,在這裏下? 講真,我是很是抗拒用Selenium的,慢不說,還耗內存, 代理也不怎麼好設置,並且別人會說我low,百度谷歌搜了 一大堆,基本都是說了等於沒說...正在我萬念俱灰,想用回 Selenium這種Low比方式的時候,我萌生了一個想法: 會不會是須要登陸後才能下載,因而乎我把連接發給我組 的UI,而後她默認瀏覽器居然是ie,而後奇蹟發現了,沒有 登陸,直接用ie瀏覽器打開了,而後他麼的,能夠右鍵保存 到本地?接着我又把連接發給我隔壁的後臺小哥,一樣用ie 打開,能夠。此時熟悉的BGM響起:

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

因此,模擬ie瀏覽器的User-Agent就能夠下載圖片了!!!

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

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

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


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("下載完成~")
複製代碼

輸出結果

參見吾王~


5.小結:

磕磕碰碰,總算是把代碼給擼出來了,成功又收穫了一大波小姐姐, 爬蟲技能點+1,建議仍是少用無腦Selenium吧,另外剛發現, Chrome直接支持右鍵導出XPath,就不用本身慢慢扣了(若是你用lxml的話)。

好的,就說那麼多~


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

  • 關於圖片沒法下載的問題,留言區 China卷柏 告知了緣由: 請求頭裏的Referer,Chrome下載的時候自動填寫爲當前頁面的URL 因此只需在請求頭裏設置Referer爲空或者 http://huaban.com/ 就能夠了,實測能夠,gayhub代碼已更新,感謝~ 閱讀中遇到問題或者有寫錯的,歡迎提出一塊兒討論!

本節源碼下載

github.com/coder-pig/R…


來啊,Py交易啊

想加羣一塊兒學習Py的能夠加下,智障機器人小Pig,驗證信息裏包含: PythonpythonpyPy加羣交易屁眼 中的一個關鍵詞便可經過;

驗證經過後回覆 加羣 便可得到加羣連接(不要把機器人玩壞了!!!)~~~ 歡迎各類像我同樣的Py初學者,Py大神加入,一塊兒愉快地交流學♂習,van♂轉py。

相關文章
相關標籤/搜索