1. 程式人生 > >使用ajax爬取網站圖片()

使用ajax爬取網站圖片()

以下內容轉載自:https://www.makcyun.top/web_scraping_withpython4.html

文章關於網站使用Ajaxj技術載入頁面資料,進行爬取講的很詳細

大致步驟如下:

(1)爬取索引頁資料

(2)解析索引頁面資料

(3)爬取詳情頁資料

(4)解析詳情頁資料

(5)儲存圖片

 

澎湃網文章的質量不錯,它的”美數課”欄目的資訊圖做得也很好。圖片乾貨多還能帶來ppt和圖表製作的技巧。為了更方便瀏覽所有文章圖片,通過分析Ajax爬取欄目至今所有資訊圖的圖片。

摘要: 上一篇文章介紹了單頁圖片的爬取,但是當爬取多頁時,難度會增加。同時,前幾篇爬蟲文章中的網站有一個明顯的特點是:可以通過點選滑鼠實現網頁的翻頁,並且url會發生相應的變化。除了此類網站以外,還有一類非常常見的網站特點是:沒有”下一頁”這樣的按鈕,而是”載入更多”或者會不斷自動重新整理從而呈現出更多的內容,同時網頁url也不發生變化。這種型別的網頁通常採用的是Ajax技術,要抓取其中的網頁內容需要採取一定的技巧。本文以資訊圖做得非常棒的澎湃”美數課”為例,抓取該欄目至今所有文章的圖片。


欄目網址:https://www.thepaper.cn/list_25635


本文知識點:

  • Ajax知識
  • 多頁圖片爬取

1. Ajax知識

在該主頁上嘗試不斷下拉,會發現網頁不斷地加載出新的文章內容來,而並不需要通過點選”下一頁”來實現,而且網址url也保持不變。也就是說在同一個網頁中通過下拉源源不斷地刷新出了網頁內容。這種形式的網頁在今天非常常見,它們普遍是採用了Ajax技術。

Ajax 全稱是 Asynchronous JavaScript and XML(非同步 JavaScript 和 XML)。
它不是一門程式語言,而是利用 JavaScript 在保證頁面不被重新整理、頁面連結不改變的情況下與伺服器交換資料並更新部分網頁的技術。

Ajax更多參考:
https://germey.gitbooks.io/python3webspider/content/6.2-Ajax%E5%88%86%E6%9E%90%E6%96%B9%E6%B3%95.html

採用了Ajax的網頁和普通的網頁有一定的區別,普通網頁的爬蟲程式碼放在這種型別的網頁上就行不通了,必須另闢出路。下面我們就來嘗試一下如何爬取網易”數讀”所有的文章。

主頁右鍵-檢查,然後按f5重新整理,會彈出很多連結檔案。滑鼠上拉回到第一個檔案:list_25635,在右側按ctrl+f搜尋一下第一篇文章的標題:”娃娃機生意經”,可以看到在html網頁中找到了對應的原始碼。

接著,我們拖動下拉滑鼠,顯示出更多文章。然後再次搜尋一篇文章的標題:”金磚峰會”,會發現搜不到相應的內容了。是不是感覺很奇怪?

其實,這裡就是用了Ajax的技術,和普通網頁翻頁是重新整理整個網頁不同,這種型別網頁可以再保持url不變的前提下只重新整理部分內容。這就為我們進行爬蟲帶來了麻煩。因為,我們通過解析網頁的url:https://www.thepaper.cn/list_25635只能爬取前面部分的內容而後面通過下拉刷新出來的內容是爬取不到的。這顯然不完美,那麼怎麼才能夠爬取到後面不斷刷新出來的網頁內容呢?

2. url分析

我們把右側的選項卡從ALL切換到Network,然後按再次按f5重新整理,可以發現Name列有4個結果。選擇第3個連結開啟並點選Response,通過滑動可以看到一些文字內容和網頁中的文章標題是一一對應的。比如第一個是:娃娃機生意經|有沒有好奇過抓娃娃機怎麼又重新火起來了?,一直往下拖拽可以看到有很多篇文章。此時,再切換到headers選項卡,複製Request URL後面的連結並開啟,會顯示一部分文章的標題和圖片內容。數一下的話,可以發現一共有20個文章標題,也就是對應著20篇文章。

這個連結其實和上面的list_25635連結的內容是一致的。這樣看來,好像發現不了什麼東西,不過不要著急。

接下來,回到Name列,嘗試滾動下拉滑鼠,會發現彈出好幾個新的開頭為load_index的連結來。選中第一個load_index的連結,點選Response檢視一下html原始碼,嘗試在網頁中搜索一下:十年金磚峰這個文章的標題,驚奇地發現,在網頁中找到了對於的文章標題。而前面,我們搜尋這個詞時,是沒有搜尋到的。

這說明了什麼呢?說明十年金磚峰這篇文章的內容不在第一個list_25635連結中,而在這個load_index的連結裡。滑鼠點選headers,複製Request URL後面的連結並開啟,就可以再次看到包括這篇文章在內的新的20篇文章。

是不是發現了點了什麼?接著,我們繼續下拉,會發現彈出更多的load_index的連結。再搜尋一個標題:地圖湃|海外港口熱,可以發現在網頁中也同樣找到了文章標題。

回到我們的初衷:下載所有網頁的圖片內容。那麼現在就有解決辦法禮:一個個地把出現的這些url網址中圖片下載下來就大功告成了。

好,我們先來分析一下這些url,看看有沒有相似性,如果有很明顯的相似性,那麼就可以像普通網頁那樣,通過構造翻頁頁數的url,實現for迴圈就可以批量下載所有網頁的圖片了。複製前3個連結如下:

https://www.thepaper.cn/load_index.jsp?nodeids=25635&topCids=&pageidx=2&isList=true&lastTime=1533169319712  
https://www.thepaper.cn/load_index.jsp?nodeids=25635&topCids=&pageidx=3&isList=true&lastTime=1528625875167
https://www.thepaper.cn/load_index.jsp?nodeids=25635&topCids=&pageidx=4&isList=true&lastTime=1525499007926

發現pageidx鍵的值呈現規律的數字遞增變化,看起來是個好訊息。但同時發現後面的lastTime鍵的值看起來是隨機變化的,這個有沒有影響呢? 來測試一下,複製第一個連結,刪掉&lastTime=1533169319712這一串字元,會發現網頁一樣能夠正常開啟,就說明著一對引數不影響網頁內容,那就太好了。我們可以刪除掉,這樣所有url的區別只剩pageidx的值了,這時就可以構造url來實現for迴圈了。構造的url形式如下:

https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=2
https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=3
https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=4

同時,嘗試把數字2改成1並開啟連結看看會有什麼變化,發現呈現的內容就是第1頁的內容。這樣,我們就可以從第一頁開始構造url迴圈了。
https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=1

既然確定了首頁,那麼也要相應地確定一下尾頁。很簡單,我們把數字改大然後開啟連結看是否有內容即可。比如改為10 ,打開發現有內容顯示,很好。接著,再改為30,發現沒有內容了。說明該欄目的頁數介於這兩個數之間,嘗試幾次後,發現25是最後一個有內容的網頁,也意味著能夠爬取的頁數一共是25頁。

確定了首頁和尾頁後,下面我們就可以開始構造連結,先爬取第一篇文章網頁裡的圖片(這個爬取過程,我們上一篇爬取網易”數讀”已經嘗試過了),然後爬取這一整頁的圖片,最後迴圈25頁,爬取所有圖片,下面開始吧。

3. 程式程式碼

import os
import re
from multiprocessing import Pool
from urllib.parse import urlencode

import requests
from bs4 import BeautifulSoup
from requests.exceptions import RequestException

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
}


# 1 獲取索引介面網頁內容
def get_page_index(i):
    # 下載1頁
    # url = 'https://www.thepaper.cn/newsDetail_forward_2370041'

    # 2下載多頁,構造url
    paras = {
        'nodeids': 25635,
        'pageidx': i
    }
    url = 'https://www.thepaper.cn/load_index.jsp?' + urlencode(paras)

    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        return response.text
        # print(response.text)  # 測試網頁內容是否提取成功ok


# 2 解析索引介面網頁內容
def parse_page_index(html):
    soup = BeautifulSoup(html, 'lxml')

    # 獲取每頁文章數
    num = soup.find_all(name='div', class_='news_li')
    for i in range(len(num)):
        yield {
            # 獲取title
            'title': soup.select('h2 a')[i].get_text(),
            # 獲取圖片url,需加字首
            'url': 'https://www.thepaper.cn/' + soup.select('h2 a')[i].attrs['href']
            # print(url)  # 測試圖片連結
        }


# 3 獲取每條文章的詳情頁內容
def get_page_detail(item):
    url = item.get('url')
    # 增加異常捕獲語句
    try:
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            return response.text
            # print(response.text)  # 測試網頁內容是否提取成功
    except RequestException:
        print('網頁請求失敗')
        return None


# 4 解析每條文章的詳情頁內容
def parse_page_detail(html):
    soup = BeautifulSoup(html, 'lxml')
    # 獲取title
    if soup.h1:  # 有的網頁沒有h1節點,因此必須要增加判斷,否則會報錯
        title = soup.h1.string
        # 每個網頁只能擁有一個<H1>標籤,因此唯一
        items = soup.find_all(name='img', width=['100%', '600'])
        # 有的圖片節點用width='100%'表示,有的用600表示,因此用list合併選擇
        # https://blog.csdn.net/w_xuechun/article/details/76093950
        # print(items) # 測試返回的img節點ok
        for i in range(len(items)):
            pic = items[i].attrs['src']
            # print(pic) #測試圖片連結ok
            yield {
                'title': title,
                'pic': pic,
                'num': i  # 圖片新增編號順序
            }


# 5 下載圖片
def save_pic(pic):
    title = pic.get('title')
    # 標題規範命名:去掉符號非法字元| 等
    title = re.sub('[\/:*?"<>|]', '-', title).strip()

    url = pic.get('pic')
    # 設定圖片編號順序
    num = pic.get('num')

    if not os.path.exists(title):
        os.mkdir(title)

    # 獲取圖片url網頁資訊
    response = requests.get(url, headers=headers)
    try:
        # 建立圖片存放地址
        if response.status_code == 200:
            file_path = '{0}\{1}.{2}'.format(title, num, 'jpg')
            # 檔名採用編號方便按順序檢視,而未採用雜湊值md5(response.content).hexdigest()
            if not os.path.exists(file_path):
                # 開始下載圖片
                with open(file_path, 'wb') as f:
                    f.write(response.content)
                    print('文章"{0}"的第{1}張圖片下載完成'.format(title, num))
            else:
                print('該圖片%s 已下載' % title)
    except RequestException as e:
        print(e, '圖片獲取失敗')
        return None


def main(i):
    # get_page_index(i) # 測試索引介面網頁內容是否獲取成功ok

    html = get_page_index(i)
    data = parse_page_index(html)  # 測試索引介面url是否獲取成功ok
    for item in data:
        # print(item)  #測試返回的dict
        html = get_page_detail(item)
        data = parse_page_detail(html)
        for pic in data:
            save_pic(pic)


# 單程序
# if __name__ == '__main__':
#     for i in range(1, 26):
#         main(i)

# 多程序
if __name__ == '__main__':
    pool = Pool()
    pool.map(main, [i for i in range(1, 26)])
    pool.close()
    pool.join()

 

結果:

文章程式碼和欄目從2015年至今437篇文章共1509張圖片資源,可在下方連結中得到。
https://github.com/makcyun/web_scraping_with_python

本文完。