Python 爬蟲進階 - 前后端分離,過程超詳細!


本文的文字及圖片來源於網絡,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯系我們以作處理

本文章來自騰訊雲 作者:python學習教程

想要學習Python?有問題得不到第一時間解決?來看看這里“1039649593”滿足你的需求,資料都已經上傳至文件中,可以自行下載!還有海量最新2020python學習資料。
點擊查看
在這里插入圖片描述

我們要抓取下面這個網站上的所有圖書列表:

https://www.epubit.com/books

 

在這里插入圖片描述

  1. 探索研究
    創建一個新的python文件,寫入如下代碼:
import requests
url = 'https://www.epubit.com/books'
res = requests.get(url)
print(res.text)

 

運行發現打印結果如下:
在這里插入圖片描述
這里面根本沒有圖書的信息。但使用瀏覽器檢查器可以看到圖書的信息:
在這里插入圖片描述
我們碰到了一個基於前后端分離的網站,或者說一個用JavaScript獲取數據的網站。這種網站的數據流程是這樣的:

初次請求只返回了網頁的基本框架,並沒有數據。就是前面截圖看到那樣。
但網頁的基本框架中包含JavaScript的代碼,這段代碼會再發起一次或者多次請求獲取數據。我們稱為后續請求。
為了抓取這樣的網站,有兩個辦法:

分析出后續請求的地址和參數,寫代碼發起同樣的后續請求。
使用模擬瀏覽器技術,比如selenium。這種技術可以自動發起后續請求獲取數據。
2) 分析后續請求
打開谷歌瀏覽器的檢查器,按圖中的指示操作:
在這里插入圖片描述
點擊Network,這里可以查看瀏覽器發送的所有網絡請求。
選XHR,查看瀏覽器用JavaScript發送的請求。
下面可以看到很多請求。我們要一個個看過去找到包含商品列表的請求。
再來理解一下瀏覽器打開一個網頁的過程,一般並不是一個請求返回了所有的內容,而是包含多個步驟:

第一個請求獲得HTML文件,里面可能包含文字,數據,圖片的地址,樣式表地址等。HTML文件中並沒有直接包含圖片。
瀏覽器根據HTML中的鏈接,再次發送請求,讀取圖片,樣式表,基於JavaScript的數據等。
所以我們看到有這么不同類型的請求:XHR, JS,CSS,Img,Font, Doc等。

我們爬取的網站發送了很多個XHR請求,分別用來請求圖書列表,網頁的菜單,廣告信息,頁腳信息等。我們要從這些請求中找出圖書的請求。

具體操作步驟如圖:
在這里插入圖片描述
在左邊選中請求
在右邊選擇Response
下面可以看到這個請求返回的數據,從數據可以判斷是否包含圖書信息。
Javascript請求返回的格式通常是JSON格式,這是一種JavaScript的數據格式,里面包含用冒號隔開的一對對數據,比較容易看懂。JSON很像Python中的字典。

在眾多的請求中,可以根據請求的名字大致判斷,提高效率。比如上圖中getUBookList看起來就像是獲取圖書列表。點開查看,返回的果然是圖書列表。

請記住這個鏈接的地址和格式,后面要用到:
在這里插入圖片描述
https://www.epubit.com/pubcloud/content/front/portal/getUbookList?page=1&row=20&=&startPrice=&endPrice=&tagId= 分析一下,可以看到:

網址是:https://www.epubit.com/pubcloud/content/front/portal/getUbookList
page=1表示第1頁,我們可以依次傳入2,3,4等等。
row=20表示每一頁有20本書
startPrice和endPrice表示價格條件,他們的值都是空,表示不設定價格限制。
3) 使用postman測試猜想
為了驗證這個設想打開谷歌瀏覽器,在地址欄中輸入以下網址:

https://www.epubit.com/pubcloud/content/front/portal/getUbookList?page=1&row=20&=&startPrice=&endPrice=&tagId=

 

可是得到了如下的返回結果:

{
    "code": "-7",
    "data": null,
    "msg": "系統臨時開小差,請稍后再試~",
    "success": false
}

 

這並不是系統出了問題,而是系統檢測到我們是非正常的請求,拒絕給我們返回數據。

這說明除了發送這個URL,還需要給服務器傳送額外的信息,這些信息叫做Header,翻譯成中文是請求頭的意思。

在下圖中可以看到正常的請求中包含了多個請求頭:
在這里插入圖片描述
選中要查看的請求
在右邊選Headers
往下翻,可以看到Request Headers,下面就是一項項數據:
Accept: application/json, text/plain, /
Accept-Encoding:gzip, deflate, br

為了讓服務器正常處理請求,我們要模擬正常的請求,也添加相應的header。如果給的Header也都一樣,服務器根本不可能識別出我們是爬蟲。后面我們會學習如何在發送請求時添加header。

但通常服務器並不會檢查所有的Header,可能只要添加一兩個關鍵Header就可以騙服務器給我們數據了。但我們要一個個測試那些Header是必須的。

在瀏覽器中無法添加Header,為了發送帶Header的HTTP請求,我們要使用另一個軟件叫做Postman。這是一個API開發者和爬蟲工程師最常使用的工具之一。

首先在postman的官網下載:www.postman.com。根據指示一步步安裝軟件,中間沒有額外的設置。

打開postman后可以看到如下界面:
在這里插入圖片描述
在最上面點擊加號,可以添加一個新的請求
中間填寫請求的URL
點Headers進入Headers的設置界面,添加Header。
這些Header的名字和值可以在檢查器中復制過來。如果自己拼寫,注意千萬不要寫錯。

我們來了解一下幾個常見的header:

User-Agent: 這個Header表示請求者是誰,一般是一個包括詳細版本信息的瀏覽器的名字,比如:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
如果爬蟲不添加這個Header,服務器一下就能識別出這是不正常請求,可以予以拒絕。當然,是否拒絕取決於程序員的代碼邏輯。
Cookie: 如果一個網站需要登錄,登錄的信息就保存在Cookie中。服務器通過這個Header判定是否登陸了,登陸的是誰。
假設我們要自動在京東商城下單,我們可以先人工登錄,復制Cookie的值,用Python發送請求並包含這個Cookie,這樣服務器就認為我們已經登陸過了,允許我們下單或做其他操作。如果在程序中加上計時的功能,指定具體下單的時間點,這就是秒殺程序。這是爬取需要登錄的網站的一種常用方法。
Accept:指瀏覽器接受什么格式的數據,比如**application/json, text/plain, */***是指接受JSON,文本數據,或者任何數據。
Origin-Domain: 是指請求者來自那個域名,這個例子中是:www.epubit.com
關於更多的HTTP的Header,可以在網上搜索HTTP Headers學習。

我一個個添加常用的Header,但服務器一直不返回數據,直到添加了Origin-Domain這個Header。這說明這個Header是必備條件。

網頁的后台程序有可能不檢查Header,也有可能檢查一個Header,也有可能檢查多個Header,這都需要我們嘗試才能知道。

既然Origin-Domain是關鍵,也許后台程序只檢查這一個Header,我們通過左邊的選擇框去掉其他的Header,只保留Origin-Domain,請求仍然成功,這說明后台只檢查了這一個Header:

在這里插入圖片描述
然后修改地址欄中的page參數,獲取其他的頁,比如截圖中修改成了3,再發送請求,發現服務器返回了新的數據(其他的20本書)。這樣我們的請求過程就成功了。

  1. 寫抓取程序
    開發爬蟲,主要的時間是分析,一旦分析清楚了,爬取代碼並不復雜:
import requests

def get_page(page=1):
    '''抓取指定頁的數據,默認是第1頁'''
    # 使用page動態拼接URL
    url = f'https://www.epubit.com/pubcloud/content/front/portal/getUbookList?page={page}&row=20&=&startPrice=&endPrice=&tagId='
    headers = {'Origin-Domain': 'www.epubit.com'}
    # 請求的時候同時傳入headers
    res = requests.get(url, headers=headers) 
    print(res.text)

get_page(5)

 

這里我們測試了抓取第5頁的數據,比對打印出的JSON數據和網頁上的第5頁數據,結果是匹配的。

現在我們去分析JSON的數據結構,再來完善這個程序。

  1. 分析JSON數據
    JSON就像Python中的字典,用大括號存放數據,用冒號分割鍵和值。下面是省略的JSON數據:
{
    "code": "0",
    "data": {
        "current": 1, //第一頁
        "pages": 144, //一共幾頁
        "records": [  //很多本書的信息放在方括號中
            {
                "authors": "[美] 史蒂芬·普拉達(Stephen Prata)",  //作者
                "code": "UB7209840d845c9", //代碼
                "collectCount": 416, //喜歡數
                "commentCount": 64, //評論數
                "discountPrice": 0, //折扣價
                "downebookFlag": "N",
                "fileType": "",
                ...
            },
            {
                "authors": "笨叔",
                "code": "UB7263761464b35",
                "collectCount": 21,
                "commentCount": 3,
                "discountPrice": 0,
                "downebookFlag": "N",
                "fileType": "",
                ...
            },
            ...
        ],
        "size": 20,
        "total": 2871
    },
    "msg": "成功",
    "success": true
}

 

我們來學習一下這個JSON格式:

最外面是一個大括號,里面包含了code, data, msg, success四塊信息。這個格式是開發這個網頁的程序員自己設計的,不同的網頁可能不同。
其中code, msg和sucess表示請求的狀態碼,請求返回的提示,請求是否成功。而真正的數據都在data中。
data的冒號后面是一個大括號,表示一個數據對象。里面包含了當前頁數(current),總頁數(pages),書的信息(records)等。
records表示很多本書,所以它用一個方括號表示,方括號里面又有很多大括號包起來的數據對象,每個大括號表示一本書。

{
    "authors": "[美] 史蒂芬·普拉達(Stephen Prata)", //書名
    "code": "UB7209840d845c9", //代碼
    "collectCount": 416, //喜歡數
    "commentCount": 64,  //評論數
    "discountPrice": 0,  //折扣0,表示沒有折扣
    ...
    "forSaleCount": 3,  //在售數量
    ...
    "logo": "https://cdn.ptpress.cn/pubcloud/bookImg/A20190961/20200701F892C57D.jpg",
    "name": "C++ Primer Plus 第6版 中文版", //書名
    ...
    "price": 100.30,  //價格
    ...
}

 

每本書的信息有很多個字段,這里省略掉了很多字段,給重要的信息添加了注釋。

  1. 完成程序
    現在來完善上面的程序,從JSON中解析出我們要的數據,為了簡化,我們只抓取:書名,作者,編號和價格。

程序框架:

import requests
import json
import time 
class Book:
    # --省略--
def get_page(page=1):
    # --省略--
    books = parse_book(res.text)
    return books
def parse_book(json_text):
    #--省略--

all_books = []
for i in range(1, 10):
    print(f'======抓取第{i}頁======')
    books = get_page(i)
    for b in books:
        print(b)
    all_books.extend(books)
    print('抓完一頁,休息5秒鍾...')
    time.sleep(5)

 

定義了Book類來表示一本書
添加了parse_book函數負責解析數據,返回包含當前頁的20本書的list
最下面使用for循環抓取數據,並放到一個大的列表中,range中添加要抓取的頁數。通過前面的分析可以知道一共有幾頁。
抓取完一頁后,一定要sleep幾秒,一是防止給網站帶來太大壓力,二是防止網站會封鎖你的IP,是為他好,也是為了自己好。
把抓來的信息保存到文件中的代碼,請自行完成。
下面來看看,被省略掉的部分:

Book類:

class Book:
    def __init__(self, name, code, author, price):
        self.name = name
        self.code = code
        self.author = author
        self.price = price

    def __str__(self):

        return f'書名:{self.name},作者:{self.author},價格:{self.price},編號:{self.code}'

 

下面是__str__函數是一個魔法函數,當我們使用print打印一個Book對象的時候,Python會自動調用這個函數。

parse_book函數:

import json

def parse_book(json_text):
    '''根據返回的JSON字符串,解析書的列表'''
    books = []
    # 把JSON字符串轉成一個字典dict類
    book_json = json.loads(json_text)
    records = book_json['data']['records']
    for r in records:
        author = r['authors']
        name = r['name']
        code = r['code']
        price = r['price']
        book = Book(name, code, author, price)
        books.append(book)
    return books

 

在最上面import了json模塊,這是Python自帶的,不用安裝
關鍵的代碼就是使用json把抓來的JSON字符串轉成字典,剩下的是對字典的操作,就很容易理解了。
抓取基於 JavaScript 的網頁,復雜主要在於分析過程,一旦分析完成了,抓取的代碼比 HTML 的頁面還要更簡單清爽!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM