1. 程式人生 > >爬蟲技術(05)神箭手爬蟲回撥函式

爬蟲技術(05)神箭手爬蟲回撥函式

回撥函式是在神箭手應用爬取並處理網頁的過程中設定的一些系統鉤子, 通過這些鉤子可以完成一些特殊的處理邏輯.

回撥函式需要設定到configs物件中才起作用
下圖是採集爬蟲爬取並處理網頁的流程圖, 矩形方框中標識了採集爬蟲執行過程中所使用的重要回調函式:
這裡寫圖片描述

(1)beforeCrawl(site)

神箭手應用初始化時呼叫, 用來進行一些爬取前的操作, 慄如, 給所有HTTP請求新增Headers等

@param site site物件. site表示當前正在爬取的目標網站物件

在beforeCrawl回撥函式中可呼叫addHeader等site物件的方法.

在神箭手應用的程式碼中均可使用.

通用栗子:

在採集爬蟲初始化時給其所有的HTTP請求新增一個Header

configs.beforeCrawl = function(site) {
    site.addHeader("Referer", "http://www.demo.cn/");
};

(2)beforeDownloadPage(page, site)

在一個網頁下載或JS渲染開始之前呼叫, 主要用來處理網頁url

@param page 網頁物件. page.raw值為空, page表示當前正在爬取的網頁物件
@param site site物件. 
@return 處理後的網頁物件

在神箭手應用的程式碼中均可使用

通用栗子:

下載某個網頁之前, 如果網頁url中未包含&page=, 則給網頁url新增該字串, 處理過程如下:

configs.beforeDownloadPage = function(page, site) {
    if (page.url.indexOf("&page=") == -1) {
        page.url = page.url + "&page=1";
    }
    return page;
};

(3)onChangeProxy(site, page)

在神箭手應用切換代理IP後呼叫, 主要用來給HTTP請求新增Header和Cookie等資料

@param site site物件. 
@param page 網頁物件.

必備條件: 開啟代理IP切換

切換代理IP後, 先前HTTP請求新增的Cookie會被清除, 若打算繼續使用Cookie, 強烈建議在onChangeProxy回撥函式中新增Cookie

在神箭手應用的程式碼中均可使用

通用栗子:

預設已經開啟代理IP切換, 在神箭手應用切換代理IP後, 呼叫site.requestUrl後請求網頁返回的Cookie會新增到網頁域名中, 供後續符合該網頁域名的HTTP請求使用.

var configs = {
    // configs的成員
    ...
};

configs.onChangeProxy = function(site, page) {
    site.requestUrl("http://www.demo.cn/");
};

(4)isAntiSpider(url, content, page)

判斷訪問網頁時是否被目標網站遮蔽, 如果判斷被遮蔽了, 神箭手會切換一次代理IP後自動重新爬取(前提: 開啟代理IP切換)

@param url 網頁url, String型別
@param content 返回的網頁內容, String型別
@param page 網頁物件. 點此檢視page物件詳解
@return 判斷是否被目標網站遮蔽的標識, 布林型別. 如果判斷被目標網站遮蔽, 返回true; 如果判斷未被目標網站遮蔽, 返回false

在神箭手應用的程式碼中均可使用

通用栗子:

var configs = {
    // configs的成員
    ...
};

configs.isAntiSpider = function(url, content, page) {
    // 判斷返回的網頁內容是否包含"404頁面不存在"
    if (content.indexOf("404頁面不存在") !== -1) {
        // 返回"true",
        // 表示判斷此時被目標網站遮蔽了,
        // 神箭手會切換一次代理IP後自動重新爬取
        return true;
    }
    // 預設返回"false",
    // 判斷未被目標網站遮蔽
    return false;
};

注意:

  1. 預設情況下, 如果傳送HTTP請求返回的狀態碼是403, 神箭手會自動判斷為被目標網站遮蔽;

(5)afterDownloadPage(page, site)

在一個網頁下載或JS渲染完成之後呼叫, 主要用來處理網頁

@param page 網頁物件.
@param site site物件.
@return 處理後的網頁物件

在神箭手應用的程式碼中均可使用

通用栗子:

下載了某個網頁, 希望向網頁的body中新增HTML程式碼, 處理過程如下:

configs.afterDownloadPage = function(page, site) {
    var pageHtml = '<div id="num"><span>5</span></div>';
    var index = page.raw.indexOf("</body>");
    page.raw = page.raw.substring(0, index) + pageHtml 
        + page.raw.substring(index);
    return page;
};

(6)onProcessScanPage(page, content, site)

在下載入口頁內容之後, 發現並新增新url到待爬佇列之前呼叫. 主要用來手動新增需要爬取的新url到待爬佇列中

@param page 入口頁物件. 
@param content 入口頁的內容, String型別
@param site site物件. 
@return 是否需要在入口頁內容中自動發現新url, 布林型別. 預設返回true, 表示需要, 返回false, 表示不需要

在onProcessScanPage回撥函式中可呼叫site物件的addScanUrl函式將新入口頁url新增到待爬佇列中

在採集爬蟲程式碼中可使用

採集爬蟲栗子1:

實現這個回撥函式並返回false, 表示採集爬蟲在處理這個入口頁url的時候, 不會從網頁中自動發現新url

configs.onProcessScanPage = function(page, content, site) {
    return false;
};

採集爬蟲栗子2:

根據入口頁的內容生成需爬取的列表頁url, 新增到待爬佇列中, 並通知採集爬蟲不再從當前網頁中自動發現新url

configs.onProcessScanPage = function(page, content, site) {
    var jsonObj = JSON.parse(page.raw);
    for (var i = 0, n = jsonObj.data.length; i < n; i++) {
        var item = jsonObj.data[i];
        var lastid = item._id;
        // 生成待爬取的第一個列表頁url
        var url = page.url + lastid;
        // 將新url新增到待爬佇列中
        site.addUrl(url);
    }
    // 不再從當前網頁中自動發現新url
    return false;
};

(7)onProcessHelperPage(page, content, site)

在下載列表頁內容之後, 發現並新增新url到待爬佇列之前呼叫. 主要用來手動新增需要爬取的新url到待爬佇列中

@param page 列表頁物件. 點此檢視page物件詳解
@param content 列表頁的內容, String型別
@param site site物件. 點此檢視site物件詳解
@return 是否需要在列表頁內容中自動發現新url, 布林型別. 預設返回true, 表示需要, 返回false, 表示不需要

在onProcessHelperPage回撥函式中可呼叫site物件的addUrl函式將新列表頁和內容頁url新增到待爬佇列中

在採集爬蟲程式碼中可使用

採集爬蟲栗子1:

實現這個回撥函式並返回false, 表示採集爬蟲在處理這個列表頁url的時候, 不會從網頁中自動發現新url

configs.onProcessHelperPage = function(page, content, site) {
    return false;
};

採集爬蟲栗子2:

根據列表頁的內容生成需爬取的內容頁url, 新增到待爬佇列中, 並通知採集爬蟲不再從當前網頁中自動發現新url

configs.onProcessHelperPage = function(page, content, site) {
    for (var i = 1; i <= 100; i++) {
        // 將拼出的新url新增到待爬佇列中
        site.addUrl("http://www.demo.com/pageNum=" + i);
    }
    // 不再從當前網頁中自動發現新url
    return false;
};

(8)onProcessContentPage(page, content, site)

在下載內容頁內容之後, 發現並新增新url到待爬佇列之前呼叫. 主要用來手動新增需要爬取的新url到待爬佇列中

@param page 內容頁物件. 點此檢視page物件詳解
@param content 內容頁的內容, String型別
@param site site物件. 點此檢視site物件詳解
@return 是否需要在內容頁內容中自動發現新url, 布林型別. 預設返回true, 表示需要, 返回false, 表示不需要

在onProcessContentPage回撥函式中可呼叫site物件的addUrl函式將新列表頁和內容頁url新增到待爬佇列中

在採集爬蟲程式碼中可使用

採集爬蟲栗子1:

實現這個回撥函式並返回false, 表示採集爬蟲在處理這個內容頁url的時候, 不會從網頁中自動發現新url

configs.onProcessContentPage = function(page, content, site) {
    return false;
};

採集爬蟲栗子2:

在onProcessContentPage回撥函式中, 如果發現內容頁中包含404 ERROR!, 則跳過當前內容頁url, 並通知採集爬蟲不再從當前網頁中自動發現新url

configs.onProcessContentPage = function(page, content, site) {
    // 判斷page.raw中是否包含"404 ERROR!"
    if (page.raw.indexOf("404 ERROR!") !== -1) {
        // 跳過當前爬取的內容頁
        page.skip();
    }
    // 不再從當前網頁中自動發現新url
    return false;
};

(9)afterDownloadAttachedPage(page, site)

在一個attachedUrl對應的網頁下載或JS渲染完之後呼叫, 主要用來處理網頁

@param page 網頁物件. 點此檢視page物件詳解
@param site site物件. 點此檢視site物件詳解
@return 處理後的網頁物件

在神箭手應用的程式碼中均可使用

通用栗子:

下載的網頁需去掉前後兩邊的中括號, 並將處理後的網頁物件返回, 處理過程如下:

configs.afterDownloadAttachedPage = function(page, site) {
    var data = page.raw;
    var start = data.indexOf("[") + 1;
    var end = data.lastIndexOf("]");
    page.raw = data.substring(start, end);
    return page;
};

(10)beforeHandleImg(fieldName, img)

從內容頁中抽取到一個抽取項的值之後呼叫, 對其中包含的img標籤進行處理

@param fieldName 抽取項的名字, String型別. 子抽取項的名字會帶著父抽取項的名字, 通過.連線, 如, images.image_url
@param img img標籤, String型別
@return 處理後的img標籤, String型別

很多網站對圖片設定了延遲載入, 這時就需要在beforeHandleImg回撥函式中處理

在神箭手應用的程式碼中均可使用

通用栗子:

configs.beforeHandleImg = function(fieldName, img) {
    if (fieldName == "detail") {
        // 通過正則匹配"img"中的"src9"屬性獲取真實圖片url
        var m = /src9=\"(https?[:\/]+.*?)\"/.exec(img);
        if (m) {
            // 預設圖片url
            var url = "http://x.autoimg.cn/club/lazyload.png";
            // 真實圖片url
            var newUrl = m[1];
            // 將預設圖片url替換成真實圖片url
            img = img.replace(url, newUrl);
        }
    }
    return img;
};

(11)beforeCacheImg(fieldName, url)

在對爬取到的圖片url進行託管處理之前呼叫, 主要對託管的圖片url進行處理

@param fieldName 抽取項的名字, String型別. 子抽取項的名字會帶著父抽取項的名字, 通過.連線, 如, images.image_url
@param url 圖片url, String型別
@return 處理後的圖片url, String型別

在神箭手應用的程式碼中均可使用

通用栗子:

configs = {
    // configs的其他成員
    ...
    fields: [
        // 其他"field"
        ...
        {
            name: "answers",
            selector: "//div[contains(@class,'answers')]",
            repeated: true,
            children: [
                {
                    name: "avatar",
                    selector: "//div[contains(@class,'answer-avatar')]"
                }
            ]
        }
    ]
};

configs.beforeCacheImage = function(fieldName, url) {
    if (fieldName == "answers.avatar") {
        // 對url進行字串替換, 得到較大圖片的url, 並返回
        return url.replace("_s.jpg", "_l.jpg");
    }
    // 返回未處理的圖片url
    return url;
};

(12)afterExtractField(fieldName, data, page, site)

從內容頁中抽取到一個抽取項的值後進行的回撥, 在此回撥中可以對該抽取項的值進行處理並返回

@param fieldName 抽取項的名字, String型別. 子抽取項的名字會帶著父抽取項的名字, 通過.連線, 如, images.image_url
@param data 抽取結果, 即抽取項的值
@param page 內容頁物件. 點此檢視page物件詳解
@param site site物件. 點此檢視site物件詳解
@return 處理後的抽取結果, 資料型別請和處理前”data”的資料型別保持一致; 否則, 神箭手會自動強制轉換成”data”的資料型別, 如果轉換失敗, 會返回空值

在神箭手應用的程式碼中均可使用

通用栗子:

configs = {
    // configs的其他成員
    ...
    fields: [
        {
            name: "interests",
            repeated: true
        },
        {
            name: "gender",
            selector: "XXX"
        },
        {
            name: "time"
        }
    ]
};

configs.afterExtractField = function(fieldName, data, page, site) {
    if (fieldName == "interests") {
        data = ["足球", "看書", "健身"];
        // 由於"interests"抽取項是陣列型別, 返回值是陣列型別
        return data;
    }
    if (fieldName == "gender") {
        // 由於"gender"抽取項是String型別, 返回值是String型別
        if (data == "male") return "男";
        else if (data == "female") return "女";
        else return "未知";
    }
    if (fieldName == "time") {
        // 獲取到當前時間的秒級時間戳並賦值給"data",
        // "data""float"型別,
        data = new Date().getTime()/1000;
        // 返回整型時間戳
        return parseInt(data);
    }
    return data;
};

(13)afterExtractPage(page, data, site)

在內容頁的所有抽取項抽取完成之後, 在此回撥函式中對抽取項再進行一次處理或進行其他操作

@param page 內容頁物件. 點此檢視page物件詳解
@param data 內容頁的抽取結果, JS物件
@param site site物件. 點此檢視site物件詳解
@return 處理後的data物件

在神箭手應用的程式碼中均可使用

通用栗子:

var configs = {
    // configs的其他成員
    ...
    fields: [
        {
            name: "title",
            selector: "XXX"
        },
        {
            name: "time",
            selector: "XXX"
        },
        {
            name: "tags",
            repeated: true
        },
        {
            name: "time"
        }
    ]
};

configs.afterExtractPage = function(page, data, site) {
    // 得到字串"t"
    var t = "[" + data.time + "] " + data.title;
    // 將"t"賦值給"title"抽取項
    // 由於"title"抽取項是String型別, "data.title"也必須是String型別
    data.title = t;
    // 由於"tags"抽取項是陣列型別, "data.tags"也必須是陣列型別
    data.tags = ["新聞", "人物"];
    // 將當前時間轉換成秒級時間戳賦值給"time"抽取項
    data.time = parseInt(new Date().getTime()/1000);
    // 返回處理後的"data"物件
    return data;
};