手把手教你寫電商爬蟲-第三課 實戰尚妝網AJAX請求處理和內容提取

系列教材:

手把手教你寫電商爬蟲-第一課 找個軟柿子捏捏

手把手教你寫電商爬蟲-第二課 實戰尚妝網分頁商品採集爬蟲

 

看完兩篇,相信大家已經從開始的小菜鳥晉升爲中級菜鳥了,好了,那我們就繼續我們的爬蟲課程。

上一課呢一定是因爲對手太強,導致我們並沒有完整的完成尚妝網的爬蟲。

吭吭~,我們這一課繼續,爭取徹底搞定尚妝網,不留任何遺憾。

我們先回顧一下,上一課主要遺留了兩個問題,兩個問題都和ajax有關。

1、由於是ajax加載下一頁,導致下一頁url並不會被系統自動發現。

2、商品頁面的價格是通過ajax加載的,我們直接從網頁中獲取不到信息本身。

好了,我們先解決第一個問題:

第一個問題實際上是一個爬蟲中比較常見的問題,即url的發現,默認情況下,URL的發現是神箭手框架自動處理的,但是如果在ajax的情況下,框 架則無從發現url,這個時候就需要我們自己手動處理url的發現,這裏,神箭手給我們提供了一個很方便的回調函數讓我們來自己處理url的發現:

onProcessHelperUrl(url, content, site)

這個回調函數有兩個參數,分別是當前處理的頁面對象和整個爬取站的對象,我們可以通過獲取頁面對象的內容來分析是否有我們需要的新一頁的url,通 過 site.addUrl()方法加入到url隊列中既可。這裏我們可以看到,當超出頁數的時候,尚妝網會給我們返回一個這樣的頁面,我們就知道頁數超過 了,不需要在加入新的頁url:

 

 

這個頁面我們很好判斷,只需要看內容中是否有"無匹配商品"關鍵字既可。

這裏我們需要一些基礎的js能力,代碼如下:

    configs.onProcessHelperUrl = function(url, content, site){  
        if(!content.indexOf("無匹配商品")){  
            //如果沒有到最後一頁,則將頁數加1  
            var currentPage = parseInt(url.substring(url.indexOf("&page=") + 6));  
            var page = currentPage + 1;  
            var nextUrl = url.replace("&page=" + currentPage, "&page=" + page);  
            site.addUrl(nextUrl);  
        }  
    }

 

原理很簡單,如果內容中沒有無匹配商品這個關鍵詞的時候,則把當前頁面的下一頁加入的待爬隊列中。
好了,ajax分頁問題徹底解決,下面來看這個最棘手的ajax內容加載的問題,也就是如何獲取到商品頁面中的價格信息

 

首先,遇到這類問題,我們通常有兩個思路:

1、通過js引擎將整個頁面渲染出來之後,在去做內容抽取,這個方案對於一些複雜js頁面是唯一解決方案,用神箭手框架來處理也很簡單,不過由於需要執行js,導致抓取速度很慢,不到不得已情況,我們先不使用這個核武器

2、通過剛剛處理分頁的經驗,我們可以預先分析ajax請求,然後將這一步多出來的請求和原來的頁面請求做一個關聯。這種方案適合比較簡單的js頁面中。

 

OK,介紹完思路,根據經驗,我們感覺尚妝網的ajax加載並沒有十分複雜,所以我們選擇方案二來處理這種ajax頁面加載。

同樣的,首頁我們通過chrome開發者工具,抓取到這個ajax請求,這裏教大家一個小竅門,開發者工具中,可以篩選請求對象未xhr,這種就是異步請求,我們就很容易發現我們的嫌疑url:

http://item.showjoy.com/product/getPrice?skuId=22912

我們在頁面中找一下這個22912怎麼提取最方便,我們很快就發現了一個標籤:

    <input type="hidden" value="22912" id="J_UItemId" />

 

這個標籤很乾淨,獲取的xpath也很簡單:

//input[@id="J_UItemId"]/@value

 

這樣就好辦了,我們再看下這個頁面請求的結果是什麼:

{"count":0,"data":{"discount":"6.2","discountMoney":"43.00","originalPrice":112,"price":"69.00","showjoyPrice":"69.00"},"isRedirect":0,"isSuccess":0,"login":0}

 

可以看出來,是一個典型的json對象,這個就好辦了,神箭手框架中給我們提供了通過jsonpath提取內容的方式,可以很簡單的提取到價格對象,即price對應的值。

 

那最後我們怎麼才能關聯這個請求呢?這裏也是框架中提供的一個方案,叫做attachedUrl,專門用來解決關聯請求的請求的問題,也就是某一個字段的值可以通過一個關聯請求的內容中抽取出來。語法我就不介紹了,直接上代碼吧:

    {  
      name: "skuid",  
      selector: "//input[@id='J_UItemId']/@value",  
    },  
    {  
      name: "price",  
      sourceType: SourceType.AttachedUrl,  
      attachedUrl: "http://item.showjoy.com/product/getPrice?skuId={skuid}",  
      selectorType: SelectorType.JsonPath,  
      selector: "$.data.price",  
    }

 

簡 單介紹一下attachedUrl的用法,首先我們要設置sourceType爲attachedUrl,同時我們要設置一個attachedUrl,即 爲關聯請求的地址,其中由於有一個值是動態的,所以我們需要在這個抽取項之前先抽取一下這個動態的值,所以我們增加了一個抽取項的名字叫做skuid,在 attachedUrl中的調用方法爲{skuid},真實請求時,該項就會被自動替換成我們上一個skuid抽取項抽取到的值。接着,由於我們獲取到的 是json返回,因此我們抽取的方式應該是通過jsonpath,最後,寫一個抽取規則既可,jsonpath比xpath更加簡單,相信大家一看就懂 了。

 

好了,弄了這麼多,完整的代碼如下:

    var configs = {  
        domains: ["www.showjoy.com","list.showjoy.com","item.showjoy.com"],  
        scanUrls: ["http://list.showjoy.com/search/?q=cateIds%3A1,cateName%3A%E9%9D%A2%E8%86%9C"],  
        contentUrlRegexes: ["http://item\\.showjoy\\.com/sku/\\d+\\.html"],  
        helperUrlRegexes: ["http://list\\.showjoy\\.com/search/\\?q=cateIds%3A1,cateName%3A%E9%9D%A2%E8%86%9C(\\&page=\\d+)?"],//可留空  
        fields: [  
            {  
                // 第一個抽取項  
                name: "title",  
                selector: "//h3[contains(@class,'choose-hd')]",//默認使用XPath  
                required: true //是否不能爲空  
            },  
            {  
                // 第二個抽取項  
                name: "comment",  
                selector: "//div[contains(@class,'dtabs-hd')]/ul/li[2]",//使用正則的抽取規則  
                required: false //是否不能爲空  
            },  
            {  
                // 第三個抽取項  
                name: "sales",  
                selector: "//div[contains(@class,'dtabs-hd')]/ul/li[3]",//使用正則的抽取規則  
                required: false //是否不能爲空  
            },  
            {  
                name: "skuid",  
                selector: "//input[@id='J_UItemId']/@value",  
            },  
            {  
                name: "price",  
                sourceType: SourceType.AttachedUrl,  
                attachedUrl: "http://item.showjoy.com/product/getPrice?skuId={skuid}",  
                selectorType: SelectorType.JsonPath,  
                selector: "$.data.price",  
                  
            }  
        ]  
    };  
      
    configs.onProcessHelperUrl = function(url, content, site){  
        if(!content.indexOf("無匹配商品")){  
            //如果沒有到最後一頁,則將頁數加1  
            var currentPage = parseInt(url.substring(url.indexOf("&page=") + 6));  
            var page = currentPage + 1;  
            var nextUrl = url.replace("&page=" + currentPage, "&page=" + page);  
            site.addUrl(nextUrl);  
        }  
        return true;  
    }  
    var crawler = new Crawler(configs);  
    crawler.start();

 

 

終於搞定了,我們趕緊測試一下爬取的結果吧:

 

欣賞自己艱苦的勞動成果是不是很有成就感,不過現在的爬取結果依然有些美中不足,評論數和銷售額拿到的都是一個完整的句子,而我們希望得到的是具體的數字,這個怎麼操作呢?這個其實就是一個字段抽取到之後的進一步處理,框架中給我們提供了一個回調函數爲:

afterExtractField(fieldName, data)

函數會將抽取名和抽取到的數據傳進來,我們只需要通過js的字符串處理函數對數據進行進一步加工既可,直接上完整的修改過的代碼:

var configs = {  
    domains: ["www.showjoy.com","list.showjoy.com","item.showjoy.com"],  
    scanUrls: ["http://list.showjoy.com/search/?q=cateIds%3A1,cateName%3A%E9%9D%A2%E8%86%9C"],  
    contentUrlRegexes: ["http://item\\.showjoy\\.com/sku/\\d+\\.html"],  
    helperUrlRegexes: ["http://list\\.showjoy\\.com/search/\\?q=cateIds%3A1,cateName%3A%E9%9D%A2%E8%86%9C(\\&page=\\d+)?"],//可留空  
    fields: [  
        {  
            // 第一個抽取項  
            name: "title",  
            selector: "//h3[contains(@class,'choose-hd')]",//默認使用XPath  
            required: true //是否不能爲空  
        },  
        {  
            // 第二個抽取項  
            name: "comment",  
            selector: "//div[contains(@class,'dtabs-hd')]/ul/li[2]",//使用正則的抽取規則  
            required: false //是否不能爲空  
        },  
        {  
            // 第三個抽取項  
            name: "sales",  
            selector: "//div[contains(@class,'dtabs-hd')]/ul/li[3]",//使用正則的抽取規則  
            required: false //是否不能爲空  
        },  
        {  
            name: "skuid",  
            selector: "//input[@id='J_UItemId']/@value",  
        },  
        {  
            name: "price",  
            sourceType: SourceType.AttachedUrl,  
            attachedUrl: "http://item.showjoy.com/product/getPrice?skuId={skuid}",  
            selectorType: SelectorType.JsonPath,  
            selector: "$.data.price",  
              
        }  
    ]  
};  
  
configs.onProcessHelperUrl = function(url, content, site){  
    if(!content.indexOf("無匹配商品")){  
        //如果沒有到最後一頁,則將頁數加1  
        var currentPage = parseInt(url.substring(url.indexOf("&page=") + 6));  
        var page = currentPage + 1;  
        var nextUrl = url.replace("&page=" + currentPage, "&page=" + page);  
        site.addUrl(nextUrl);  
    }  
    return true;  
}  
configs.afterExtractField = function(fieldName, data){  
    if(fieldName == "comment" || fieldName == "sales"){  
        var regex = /.*((\d+)).*/;  
        return (data.match(regex))[1];  
    }  
    return data;  
}  
var crawler = new Crawler(configs);  
crawler.start();

 


我們判斷了如果是comment和sales抽取項時,通過正則直接匹配到括號裏的數字,這裏注意,網頁上的括號本來是全角的括號,所以千萬不要寫錯了。

 

這次終於可以開心的看着自己的爬蟲數據結果了:


對爬蟲感興趣的童鞋可以加qq羣討論:342953471。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章