淘寶爬蟲實戰(附程式碼和數據集)
- 2019 年 11 月 21 日
- 筆記
本文通過淘寶「防脫髮洗髮水」爬取和分析,來提供爬取海量淘寶商品資訊的思路,除了基礎爬蟲外,還應該思考拿到類似的商品數據之後如何清洗,以及作為一個分析者可以從什麼維度去分析。 完整程式碼和數據放在文末,如果單純需要數據集練手的同學可以在底部下載(4400條產品數據)
其實,這篇文章靈感源自一個賭局:
程式設計師朋友小A又在和小Z抱怨脫髮問題。
小A:「以這樣的掉發速度,我的髮際線1年後將退化到後腦勺」。
「我聽到身邊80%的人都在抱怨自己的脫髮問題」小Z摸了摸自己的髮際線心如止水。
小A:」有危機就有商機,防脫髮洗髮水最近真的是賣爆了,特別在線上,絕對佔了洗髮水整個行業的半壁江山以上!」
小Z總能GET到奇怪的點:「你這樣的說法不嚴謹,我覺得沒有50%」。
小A被奇葩的問題點給氣到了:「WOC!你的點怎麼那麼怪!不然咱們打個賭好嗎,我賭防脫髮佔了50%以上,誰輸誰是孫子(zei)!」
只用了3分鐘,小Z就擬定好分析思路,並得到了小A的認可:
以淘寶入手,爬到最近30天洗髮水關鍵詞的銷售情況,再篩選出防脫髮洗髮水,看一看佔比多少。(順便還可以分析分析其他的數據)
說干就干,打開淘寶,搜索「洗髮水」,出來的是自然排序的結果(綜合了銷量、價格、搜索權重等等),但我們想要相關商品按銷量來排序,點擊「按銷量排序」。

一、數據爬取
PART1 觀察並定位數據:

我們想要哪些數據呢?
商品的價格、月收貨(銷售)人數、產品名稱、店鋪名稱、店鋪地址這幾個比較直觀的欄位我們爬取哪幾個呢?
小孩子才做選擇,成年人必須全要!
雖然現在很多網址都是動態載入,需要審查元素來找相關地址,但我們在找之前,養成「先右鍵,選擇查看源程式碼,看一看想要的數據有沒有在靜態網頁」的習慣是極好的。
結果淘寶誠不欺我,所有我們想要的數據,都在源程式碼中,也就是說,我們用PYTHON直接訪問瀏覽器中的網址就可以得到目標數據。
認真看看源程式碼,找到更準確的定位

所有想要的數據都在一個類JSON(可以先理解為字典)的字元串中,而前面還有幾十行雜亂無章的字元,很亂,但不要緊,數據在總有辦法找到他們的。
PART2 請求嘗試
這裡引用上一篇文章的一段話來比喻PYTHON訪問前的偽裝
「你住在高檔小區,小P這個壞小伙想偽裝你進去做不可描述的事情。
他知道,門衛會根據身份象徵來模糊判斷是否是小區業主,所以小P先租了一套上檔次的衣服和一輛稱得上身份的豪車(可以理解為偽裝headers),果然混過了門衛。但是呢,小P進進出出太頻繁,而且每次停車區域都不一樣,引起了門衛的嚴重懷疑,在一個星期後,門衛升級檢驗系統,通過人臉識別來驗證,小P被拒絕在外,但很快,小P就通過毀容級別的化妝術(偽裝cookies),完全偽裝成你,竟然混過了人臉識別系統,隨意出入,為所欲為。」
導入相關的PYTHON庫

養成先修改headers的好習慣再訪問:

看看狀態碼(200表示正常訪問):

目前來說,還算正常,但堂堂的淘寶這麼簡單的一個偽裝就可以爬了???不科學!!不過先繼續吧,精確定位到我們需要的數據欄位。
上一步,我們發現所有的數據都在一個類JSON的字元串中,理應先精確定位他首尾的大括弧({}),嘗試用JSON來高效解析。
首:

尾:

通過嚴密的排查(同學們這一步真的需要耐心去找),我們發現所有目標數據都被包裹在以pageName開頭,shopcardOff的字元中,如果能夠完整截取這個大括弧和裡面的內容,就可以解析了:

結果。。。報錯啊報錯。。。

我們沒有通過字元串定位拿到想要的數據,通過系統排查,發現問題出在訪問,第一次訪問雖然狀態碼是200,但並沒有返回源程式碼看到的數據,喏:

到這裡,是時候祭出萬能的cookies了,操作方式,右鍵——審查元素——刷新網頁——按照下面紅框點選:

程式碼中進行偽裝:

再次按照剛才的步驟來定位和解析數據:

一樣的操作,沒有報錯,看來大功告半成!
PART3 精確定位目標數據:
經過前面兩步的鋪墊,我們已經拿到了目標數據並解析成JSON格式,現在直接可以按照訪問字典的方式來精確定位數據,非常暴力(至於內部的層級結構,需要大家耐心細緻的自我尋找規律):

PART4 循環爬取:
循環爬取的關鍵就在於找到網址規律,構建多個網頁,用上面的程式碼來循環訪問。
我們在網頁上點擊下一頁,再下一頁,再下下一頁,很容易發現,網站變化規律的核心就是最後面
s的值,第一頁是0,第二頁是44,第三頁是88,SO EASY~
構造一個自定義爬取頁數的函數,只需要輸入基礎網址和要爬取的頁數,要多靈活有多靈活:

接上一步的訪問獲取數據操作進行逐頁訪問,即實現了多頁面爬取,部分結果預覽如下:

至此,商品標題,價格,店鋪名稱,店鋪地址,收貨人數,商品的URL全部拿下,基於「防脫髮洗髮水」的基本數據爬取宣告完成。(完整程式碼在文章最後)
二、數據清洗
清洗之前,最好先明確分析的目的,小Z最核心的訴求是要知道脫髮洗髮水銷售占整個洗髮水大盤的比重,其次,想要進行一些其他分析,比如渠道(旗艦店、專營店、貓超等等分別佔比)分布。
1、數字相關欄位規整:

爬取數據非常規整,並沒有缺失數據。
價格也是OK的,付款人數由於包含「人收貨」這個後綴,需要規整為數字格式,一行程式碼就OK:

2、標註出脫髮相關的產品:
很明顯,如果主打甚至僅僅包含防脫髮功效的產品幾乎都會在標題註明「脫髮」字樣(防字其實不用加),我們需要插入一個輔助列,根據「產品標題」來判斷是不是防脫髮洗髮水。
PYTHON的pandas做起來是在是太高效了,還是一行程式碼:

註:等於-1表示在標題中沒有找到「脫髮」字樣

「是否包含脫髮字樣」結果為TRUE則包含,FALSE則不包含。
3、引入一個銷售指標
目前拿到的數字相關數據是「價格」、「收貨人數」,用「價格」 * 「收貨人數」引入一個「收貨額」來衡量銷售情況,依然是一行程式碼:

4、區分店鋪類別:
大家都有多年購買經驗,對於淘寶店鋪分類其實不陌生,不外乎是「旗艦店」、「專賣店」、「專營店」、「天貓超市」、「C店」(其他淘寶店鋪),這裡需要對店鋪關鍵字進行檢索分類,先定義一個判斷函數:

然後,life is short,and i use Python~
亦然是一行程式碼搞定:

數據清洗基本完成。
三、數據分析:
1、核心目標:

言歸正傳,目前「洗髮水」類目體量巨大,(近30天)收貨額達到了1.49億元,其中防脫髮洗髮水以5.43%的數量佔比實現1118.04萬銷售額,佔比7.50%,離半壁江山相差甚遠,賭局勝負已定,恭喜小Z喜提孩子。
「孩子,在數據面前可不能吹牛啊」小Z看著小A漲紅了的臉語重心長道。
2、價格分布
價格深度探究應該結合產品的數量、規格等特徵,這裡只是給到一個簡單的思路拋磚引玉:

兩款產品呈現出不同的分布形態,防脫髮洗髮水在價格上顯得些許傲嬌,產品在50-100元的價格段數量最多(佔比51.88%),其次是0-50元的平價款。
其他洗髮水則隨著價格升高而數量減少,0-50元的產品佔比最高,緊隨其後的是50-100元的產品。
3、渠道分布:

不同類型洗髮水(防脫髮與非防脫髮)渠道策略有明顯的差異(肯定跟品牌戰略有關),其他洗髮水渠道分布相對均衡,以「旗艦店」的41%為主,「天貓超市」為輔(29%),「C店」和「專賣店」分一小杯羹。
防脫髮洗髮水則高舉旗艦店利劍(佔比高達77%+),其次則是各類C店(11%),而在其他洗髮水渠道表現優異的貓超在這裡折戟,僅佔比3%。
看來,防脫髮類功能產品高銷售背後離不開品牌的背書支撐。(一般品牌才會開設旗艦店)
最後,附上數據集地址和完整程式碼:
https://pan.baidu.com/s/1BoxzD26Q46xCM0eRYU6-7g
密碼:s3ve
#構造循環爬取的函數 def format_url(base_url,num): urls = [] for i in range(0,num * 44,44): urls.append(base_url[:-1] + str(i)) return urls #解析和爬取單個網頁 def parse_page(url,cookies,headers): result = pd.DataFrame() html = requests.get(url,headers = headers,cookies = cookies) bs = html.text #獲取頭部索引地址 start = bs.find('g_page_config = ') + len('g_page_config = ') #獲取尾部索引地址 end = bs.find('"shopcardOff":true}') + len('"shopcardOff":true}') js = json.loads(bs[start:end + 1]) #所有數據都在這個auctions中 for i in js['mods']['itemlist']['data']['auctions']: #產品標題 product = i['raw_title'] #店鋪名稱 market = i['nick'] #店鋪地址 place = i['item_loc'] #價格 price = i['view_price'] #收貨人數 sales = i['view_sales'] url = 'https:' + i['detail_url'] r = pd.DataFrame({'店鋪':[market],'店鋪地址':[place],'價格':[price], '收貨人數':[sales],'網址':[url],'產品標題':[product]}) result = pd.concat([result,r]) time.sleep(5.20) return result #匯總 def main(): #爬取的基準網頁(s = 0) base_url = 'https://s.taobao.com/search?q=%E6%B4%97%E5%8F%91%E6%B0%B4&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306&sort=sale-desc&bcoffset=0&p4ppushleft=%2C44&s=0' #定義好headers和cookies cookies = {'cookie':'輸入自己的COOKIES'} headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'} #設置好存儲結果的變數 final_result = pd.DataFrame() #循環爬取5頁 for url in format_url(base_url,5): final_result = pd.concat([final_result,parse_page(url,cookies = cookies,headers = headers)]) return final_result if __name__ == "__main__": final_result = main()