Python3 大型網路爬蟲實戰 004 — scrapy 大型靜態商城網站爬蟲專案編寫及資料寫入資料庫實戰 — 實戰:爬取淘寶

AoboSir發表於2016-12-26

原博文連結:http://www.aobosir.com/blog/2016/12/26/python3-large-web-crawler-taobao-com-import-to-MySQL-database/


開發環境

  • Python第三方庫:lxml、Twisted、pywin32、scrapy
  • Python 版本:python-3.5.0-amd64
  • PyCharm軟體版本:pycharm-professional-2016.1.4
  • 電腦系統:Windows 10 64位

如果你還沒有搭建好開發環境,請到這篇部落格


本篇博文的重點內容:

  • 有一些資料,在原始碼上找不到的,這個時候需要使用 — 抓包。
  • Python呼叫MySQL資料庫

本爬蟲專案的目的是:某個關鍵字在淘寶上搜尋到的所有商品,獲取所有商品的: 商品名字、商品連結、商品價格、商品的評論。


開始實戰

建立一個爬蟲專案

scrapy startproject thirdDemo

Alt text

設定防反爬機制(settings.py 檔案)

請參考這篇部落格:給 Scrapy 爬蟲專案設定為防反爬

分析網站

  • 分析網頁介面
  • 分析網址結構
  • 分析網頁原始碼

1 . 分析網頁介面:

我們在淘寶網的搜尋欄裡面搜尋關鍵字,比如“小吃”。它一共會輸出100頁。

可見:100頁的搜尋結果是淘寶的上限。(最多100頁)

Alt text

2 . 分析網址結構:

當我們點選頁面進行瀏覽時,我們發現不同的頁面的網址有規律,並且下面是我們找到的規律:

  1. 紅色部分是一模一樣的。
  2. 刪除紅色部分,將剩下的組成網址,一樣可以正常的瀏覽原網頁。
  3. q= 後面是“小吃”的編碼形式。
  4. s= 後面的數值等於 44*(當前頁面-1)

Alt text

開始寫爬蟲程式(taobao.py 檔案)

建立一個爬蟲檔案(taobao.py 檔案)

cd thirdDemo
scrapy genspider -t basic taobao taobao.com

Alt text

使用PyCharm軟體開發,使用PyCharm軟體開啟 thirdDemo專案。

Alt text

新增需要使用的儲存容器物件(items.py檔案)

先到 items.py 檔案裡面的ThirddemoItem()函式裡面建立儲存用到容器(類的例項化物件)

class ThirddemoItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    link = scrapy.Field()
    price = scrapy.Field()
    comment = scrapy.Field()
    pass

Alt text

得到搜尋關鍵字對應的所有搜尋頁面(taobao.py檔案)

在回撥函式parse()中,建立一個變數(key)來儲存關鍵詞(零食)。然後在使用一個for迴圈來爬取所有的網頁。然後使用scrapy.http裡面的Request 來在parse()函式返回(返回一個生成器(yield))一個網頁原始碼:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request

class TaobaoSpider(scrapy.Spider):
    name = "taobao"
    allowed_domains = ["taobao.com"]
    start_urls = ['http://taobao.com/']

    def parse(self, response):
        key = '小吃'
        for i in range(0, 2):
            url = 'https://s.taobao.com/search?q=' + str(key) + '&s=' + str(44*i)
            print(url)
            yield Request(url=url, callback=self.page)
        pass

    def page(self, response):
        pass

Alt text

(注意:我們上面通過觀察網頁已經知道了,搜尋得到的頁面有100頁,但是我們現在是測試階段,不爬這麼多頁,上面的程式碼我們只爬了2頁)


執行一下:

Alt text

程式沒有問題。


得到所有商品的id

我們現在的目標是得到搜尋頁面中所有商品的連結。

Alt text

現在,我們觀察搜尋頁面的原始碼,我們找這些商品連結的規律,能否通過什麼商品id之類的資訊,然後通過商品id來構造出商品的連結網址。

幸運的是:確實可以這麼做。

我發現,不管是搜尋的商品,不管是在淘寶裡、天貓裡、天貓超市裡,商品的連結網址都可以用下面的格式去構造:

https://item.taobao.com/item.htm?id=商品的id

所以,現在我們要做的是:先提取商品的id:(使用正規表示式)

對搜尋結果的網頁隨便一個地方右鍵:檢視網頁原始碼(V)

(我發現:通過在瀏覽器中按F12 和 右鍵來 檢視網頁原始碼 這兩種檢視原始碼得到的原始碼不一樣,後者得到的原始碼和爬蟲爬取的原始碼一致,而前者和爬蟲爬取的不一致。)

所有我們不能使用Xpath表示式來用過標籤獲取商品id了。只能使用正規表示式了。

我想可能的原因是:搜尋頁面可能是動態構造出來的,所以使用Xpath表示式是不能對這種網址的原始碼進行提取資訊的。(我瞎想的,不知道是否正確。不過事實其實是:使用Xpath表示式是提取不了有效資訊的。)

Alt text

然後隨便點選進入一個商品的連結網頁,在這個網頁的網址裡面就可以找到這個商品的id:

Alt text

然後在剛剛開啟的原始碼中,查詢這個id:

Alt text

我們通過觀察發現,使用"nid":"就可以找到這個搜尋結果頁面裡面所有商品的id:

Alt text

這個頁面裡面一共是36個商品,沒錯。

Q: 你可能發現了,這個搜尋網頁裡面搜尋到結果是48個商品,我們得到的是36個,是不是少了?

A: 沒有少,這就是淘寶的營銷策略。一個搜尋頁面一共有48個商品,但是其中有10多個商品是重複的!其中甚至有個商品在這個搜尋頁面中重複出現了三次,不信,你可以仔細的找找。

所以,商品的id可以使用下面的正規表示式獲取:

'"nid":"(.*?)"'

我們在page()方法中得到爬取到的網頁原始碼的 body 標籤裡面的所有資訊:

先宣告一點:

爬取到的網頁原始碼是:以網頁原始碼中指定的編碼方式編碼得到的bytes資訊。

Alt text

我們需要得到對應的解碼資訊:

參考網站:Python3 bytes.decode()方法

        body = response.body.decode('utf-8')

response.body 它預設是二進位制格式,所以我們在使用它之前要給它解碼:decode('utf-8'),為了避免出錯,我給它傳第二個引數:ignore

page()函式中的程式碼現在是下面這個樣子的:

    def page(self, response):
        body = response.body.decode('utf-8','ignore')
        pattam_id = '"nid":"(.*?)"'
        all_id = re.compile(pattam_id).findall(body)
        print(all_id)
        pass

執行試試看:

Alt text


得到所有商品的連結網址

現在得到了所有商品的ip,現在通過這些ip,構造出所有商品的網址。得到了連結後,就可以去爬這個網頁的原始碼了:(下面的程式碼中,在next()方法中將商品的網頁網址列印了出來)

import re
    def page(self, response):
        body = response.body.decode('utf-8','ignore')
        pattam_id = '"nid":"(.*?)"'
        all_id = re.compile(pattam_id).findall(body)
        # print(all_id)
        for i in range(0, len(all_id)):
            this_id = all_id[i]
            url = 'https://item.taobao.com/item.htm?id=' + str(this_id)
            yield Request(url=url, callback=self.next)
            pass
        pass

    def next(self, response):
        print(response.url)
        pass

執行試試看:(自動的將網址調整到正確的網址上。比如天貓或者天貓超市之類的子域名)

Alt text


獲取商品的具體資訊(taobao.py 檔案)

獲取商品的名字

現在在next()回撥函式中例項化一個開始時在items.py檔案裡面建立的專案的儲存容器物件。然後,我們就可以直接使用它了。

所以現在在 taobao.py 檔案的上面新增這個檔案:

from thirdDemo.items import ThirddemoItem

現在我們要得到商品的標題。

我們儘量從原始碼中的資訊提取,如果原始碼中沒有的資訊,我們在使用抓包的凡是提取。

標題是可以直接在原始碼中提取的:(觀察網頁原始碼,直接中Xpath表示式)

天貓或者天貓超市的商品的標題可以使用下面的Xpath表示式提取:

title = response.xpath("//div[@class='tb-detail-hd']/h1/text()").extract()

淘寶的商品的標題可以使用下面的Xpath表示式提取:

title = response.xpath("//h3[@class='tb-main-title']/@data-title").extract()

所以,這裡提取標題,我們需要一個判斷語句,判斷這個商品的網址連結是天貓的還是淘寶的。

偽碼如下:

if 不是淘寶的網址:
    title = response.xpath("//div[@class='tb-detail-hd']/h1/text()").extract() # 天貓或者天貓超市
else:
    title = response.xpath("//h3[@class='tb-main-title']/@data-title").extract() # 淘寶

我們的判斷標準就是商品網址的子域名。子域名大致一共有三種:detail.tmall(天貓)、chaoshi.detail.tmall(天貓超市)、item.taobao(淘寶)

    def next(self, response):
        # print(response.url)
        url = response.url
        pattam_url = 'https://(.*?).com'
        subdomain = re.compile(pattam_url).findall(url)
        # print(subdomain)
        if subdomain[0] != 'item.taobao':
            title = response.xpath("//div[@class='tb-detail-hd']/h1/text()").extract()
            pass
        else:
            title = response.xpath("//h3[@class='tb-main-title']/@data-title").extract()
            pass
        self.num = self.num + 1;
        print(title)
        pass

執行試試看:

Alt text

有的時候,偶爾會得到幾個 [],這是因為,你爬的太快的,淘寶的伺服器沒有同意你爬取這個商品的網頁。(所以提高防反爬機制,效果會好一些。)

Alt text

獲取商品的連結網址(taobao.py 檔案)

(直接得到)

        item['link'] = response.url

獲取商品的價格資訊(原價)(taobao.py 檔案)

正常的價格可以在商品網頁的原始碼裡面獲取,但是淘寶價(促銷價)在商品原始碼裡面沒有,這時就需要通過抓包來獲取。

淘寶:

Alt text

天貓:

Alt text

我們先獲取正常的價格。這裡也需要分淘寶和天貓,它們獲取正常價格的Xpath表示式或者正規表示式不同。

注意:這裡總結表示式,通過對商品頁面右鍵 -> 檢視網頁原始碼 的方式檢視原始碼。

        if subdomain[0] != 'item.taobao':
            pattam_price = '"defaultItemPrice":"(.*?)"'
            price = re.compile(pattam_price).findall(response.body.decode('utf-8', 'ignore')) # 天貓
            pass
        else:
            price = response.xpath("//em[@class = 'tb-rmb-num']/text()").extract() # 淘寶
            pass
        print(price)

提取商品的累計評論數量:(使用抓包的方式)(taobao.py 檔案)

淘寶:

Alt text

天貓:

Alt text

可以使用 : Fiddler4抓包軟體 或者 瀏覽器按F12->Network->Name->Response檢視抓包資訊

這裡,我通過瀏覽器進行抓包,找到了評論數所在的包:(一個一個的找)

淘寶:

Alt text

觀察這個包的網址:

Alt text

這個網址,我們可以在瀏覽器中複製,再訪問以下:(是可以正常訪問的)

Alt text

https://rate.taobao.com/detailCommon.htm?auctionNumId=533237707421&userNumId=1990097437&ua=097UW5TcyMNYQwiAiwQRHhBfEF8QXtHcklnMWc%3D%7CUm5Ockt%2BQ3dDfkB8R35Eey0%3D%7CU2xMHDJ7G2AHYg8hAS8XKQcnCVU0Uj5ZJ11zJXM%3D%7CVGhXd1llXGlUYFRpV2tQaVFvWGVHekV8RHtBf0Z%2FQXRKdUx1T3VOYDY%3D%7CVWldfS0TMw8xBD8fIAAubQslcyU%3D%7CVmJCbEIU%7CV2lJGSQEORklGCMYOAI%2FADkZJREuEzMPMgc6GiYSLRAwDDEJNGI0%7CWGFcYUF8XGNDf0Z6WmRcZkZ8R2dZDw%3D%3D&callback=json_tbc_rate_summary

我發現上面的這個網址可以縮減為:

https://rate.taobao.com/detailCommon.htm?auctionNumId=533237707421
https://rate.taobao.com/detailCount.do?_ksTS=1480993623725_99&callback=jsonp100&itemId=533237707421

而這個533237707421就是商品的id。好了,找到這個網址的規律,現在可以> 手動構造這個評論數的網址了:

天貓:

Alt text

https://dsr-rate.tmall.com/list_dsr_info.htm?itemId=35338957824&spuId=235704813&sellerId=628189716&_ksTS=1480992656788_203&callback=jsonp204

可以縮減為:

https://dsr-rate.tmall.com/list_dsr_info.htm?itemId=35338957824

最後,我們發現:不管是淘寶還是天貓,都可以使用下面這個構造方式來得到含有正確評論數量的網址:

https://dsr-rate.tmall.com/list_dsr_info.htm?itemId=商品id

注意:使用https://rate.taobao.com/detailCommon.htm?auctionNumId=商品id 這種網址也可以,但是在對天貓商品得到評價數量和網頁裡面顯示的不同。所以我們不使用這個構造方法。

Alt text

Alt text

所以通過商品id就可以得到含有評論數量資訊的包的網址。現在在next()方法中需要通過商品的URL獲取商品的id。

Alt text

我們從上面的圖中看到:天貓和淘寶的網址不同,所以,從網址中獲取商品id的正規表示式也就不同。下面的程式碼的功能就是從商品的url中提取商品id:

        # 獲取商品的id(用於構造商品評論數量的抓包網址)
        if subdomain[0] != 'item.taobao':  # 如果不屬於淘寶子域名,執行if語句裡面的程式碼
            pattam_id = 'id=(.*?)&'
            this_id = re.compile(pattam_id).findall(url)[0]
            pass
        else:
            # 這種情況是不能使用正規表示式的,正規表示式不能獲取字串最末端的字串
            pattam_id = 'id=(.*?)$'
            this_id = re.compile(pattam_id).findall(url)[0]
            pass
        print(this_id)

注意:$ : 在正規表示式裡面的作用是:匹配字串末尾。

舉例:當url = 'https://item.taobao.com/item.htm?id=535023141744' 時,這是一個淘寶網站裡面的一個商品,現在我們想得到這個網址裡面的商品id。

如果你把正規表示式寫成這個樣子:pattam_id = 'id=(.*?)',是匹配不到結果的(商品id)。

正規表示式是通過字串上下文來匹配你需要的資訊的,如果只有“上文”,沒有“下文”時,對於使用正規表示式匹配字串末端字串,需要在正規表示式中使用$


執行試試看,一切都在掌控之中。

構造具有評論數量資訊的包的網址,並獲取商品的評論數量

得到目標抓包網址,獲取它的原始碼,然後提取評論數量:

import urllib
        # 構造具有評論數量資訊的包的網址
        comment_url = 'https://dsr-rate.tmall.com/list_dsr_info.htm?itemId=' + str(this_id)

        # 這個獲取網址原始碼的程式碼永遠也不會出現錯誤,因為這個URL的問題,就算URL是錯誤的,也可以獲取到對應錯誤網址的原始碼。
        # 所以不需要使用 try 和 except urllib.URLError as e 來包裝。
        comment_data = urllib.request.urlopen(comment_url).read().decode('utf-8', 'ignore')
        pattam_comment = '"rateTotal":(.*?),"'
        comment = re.compile(pattam_comment).findall(comment_data)
        # print(comment)
        item['comment'] = comment

現在返回item物件:

        yield item

Alt text


現在,我們就可以在pipline.py檔案裡面來對我們得到的這些商品資料進行一些操作了,比如列印到終端或者儲存到資料庫中。

但在這之前,我們需要設定一下settings.py檔案,將下面的程式碼的註釋去掉:

Alt text


pipline.py檔案中對taobao.py爬蟲檔案返回的item物件進行處理(比如列印到終端,或者儲存到資料庫中)

將得到的資訊列印到終端中:

class ThirddemoPipeline(object):
    def process_item(self, item, spider):
        title = item['title'][0]
        link = item['link']
        price = item['price'][0]
        comment = item['comment'][0]
        print('商品名字', title)
        print('商品連結', link)
        print('商品正常價格', price)
        print('商品評論數量', comment)
        print('------------------------------\n')
        return item

執行試試看:

Alt text


下面是將得到的資訊儲存到資料庫中的操作:

第一件事情就是 啟動資料庫,啟動資料庫的程式碼一般我們是將它寫到預設的__init__(self)函式中,這個方法就是最開始做的事情。

要想連線到資料庫,首先要有資料庫:使用MySQL資料庫

在python上要想使用MySqL資料庫,需要先安裝pymysql庫這個模組。

Alt text

有開啟資料庫的函式,就要有關閉資料庫的方法。

Alt text

現在,我們在process()函式中處理資料,將資料插入到資料庫裡面。並且加一個異常處理,因為我不希望程式執行的時候會出現錯誤而終止,並且我也不想

Alt text


請訪問:http://www.aobosir.com/

相關文章