一、實作目標:
爬取pchome購物的所有商品資料,並進行分類
二、爬蟲構思:
pchome購物的商品資料並沒有辦法根據商品網址來進行遍歷
所以分析商品分類頁面來取得商品資料
由於該網站內容皆是由js產生
所以沒辦法藉由beautifulSoup4來對網頁程式碼進行分析
要從該網頁的回應的內容找出商品資料存放的js
再爬取該json的商品資料
三、爬蟲實作:
經過許多嘗試,最後選定先爬取商品分類、再至每個分類爬取商品資料
1.分析網頁
首先找出該網站商品目錄
按下F12開發人員模式,切換到network並重新整理,選Preview查看是否有json內容
很快的,就可以找到包含商品資料的檔案
將Preview切換至Headers來查看回應的網址
再來判斷不同頁數的js網址會有什麼差異
可以觀察出,第二頁的商品offset從51開始,limit維持50
代表從第51項商品往後顯示50筆資料,經過測試後最多只能顯示50筆,將limit調高也不會增加
由於商品資料中並沒有"分類"的項目,所以得從左列的分類分別爬取資料
查看選擇左側分類後的js網址,會發現多了關於分類的信息,不同分類則會有不同代號
從network查找是否有關於左側分類的資料,由於整個網頁都是由js產生,所以必定會有
將Preview切換至Headers來查看回應的網址
最後會有一串13位數字,隨著每次重整頁面會有所變動
根據我的經驗判斷他應為時間戳記,經由測試後也確認此為當前的時間戳記
將最後一個不確定因素排除後
接著就可以進行爬取資料的部分了
2.初步爬取資料
使用Python requests來獲取資料,
由於每訪問幾次就會被阻擋,所以添加headers
headers為一些從pchome網站Headers的取得的信息(取得json網址的地方),以防被網站阻擋獲取資料
首先先從全部最新資料的json網址來獲取看看
import requests import re import json url = 'https://ecapi.pchome.com.tw/mall/prodapi/v1/newarrival/prod&offset=1&limit=50&_callback=jsonpcb_newarrival' user_agent = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #偽裝使用者 headers = {'User-Agent':user_agent, 'server': 'PChome/1.0.4', 'Referer': 'https://mall.pchome.com.tw/newarrival/'} res = requests.get(url=url,headers=headers)#分析得出的網址 res_text = res.text jd = json.loads(res_text) print(jd)
運行結果:
直接從網址打開json檔查看,才發現因為json多了try、catch判斷,導致解析失敗
因此我使用replace直接把前後多餘的字串替換為空,這時再次解析即可成功
url = 'https://ecapi.pchome.com.tw/mall/prodapi/v1/newarrival/prod&offset=1&limit=50&_callback=jsonpcb_newarrival' user_agent = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #偽裝使用者 headers = {'User-Agent':user_agent, 'server': 'PChome/1.0.4', 'Referer': 'https://mall.pchome.com.tw/newarrival/'} res = requests.get(url=url,headers=headers)#分析得出的網址 res_text = res.text res_text_format = res_text.replace('try{jsonpcb_newarrival(','').replace(');}catch(e){if(window.console){console.log(e);}}','') jd = json.loads(res_text_format) print(jd)
接著提取出我想要的商品訊息
使用for迴圈來遍歷每組json資料
並從其中獲取商品名稱、商品圖片(需添加圖片前綴網址)、商品價格、商品網址(取出id並添加前綴網址)
#pchome url = 'https://ecapi.pchome.com.tw/mall/prodapi/v1/newarrival/prod&offset=1&limit=50&_callback=jsonpcb_newarrival' user_agent = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #偽裝使用者 headers = {'User-Agent':user_agent, 'server': 'PChome/1.0.4', 'Referer': 'https://mall.pchome.com.tw/newarrival/'} res = requests.get(url=url,headers=headers)#分析得出的網址 res_text = res.text res_text_format = res_text.replace('try{jsonpcb_newarrival(','').replace(');}catch(e){if(window.console){console.log(e);}}','') jd = json.loads(res_text_format) print(jd) pcgoods = (jd['Rows'][:]) for pcgood in pcgoods: pcname = pcgood['Name'] pcimg = 'https://b.ecimg.tw' + pcgood['Pic']['S'] pcprice = pcgood['Price']['P'] pclink = 'https://mall.pchome.com.tw/prod/'+ pcgood['Id'] print(pcname) print(pcimg) print(pcprice) print(pclink)
將資料打印出來結果如下圖
3.結合分類爬取資料
首先從python獲取時間戳記
import time millis = int(round(time.time() * 1000)) print(millis)
接著將時間戳記加入網址,並爬取資料,同樣要使用replace將多餘的字串替換為空
#pchome分類 #分析後,網址結尾需加上13位unix時間戳記 url = 'https://ecapi.pchome.com.tw/mall/cateapi/v1/sign&tag=newarrival&fields=Id,Name,Sort,Nodes&_callback=jsonpcb_newarrival&'+str(millis) user_agent = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #偽裝使用者 headers = {'User-Agent':user_agent, 'server': 'PChome/1.0.4', 'Referer': 'https://mall.pchome.com.tw/newarrival/'} res = requests.get(url=url,headers=headers)#分析得出的網址 res_text = res.text res_text_format = res_text.replace('try{jsonpcb_newarrival(','').replace(');}catch(e){if(window.console){console.log(e);}}','') jd = json.loads(res_text_format) pcclass = jd[:] #print(pc) for pc in pcclass: print(pc['Name']) for pccl in pc['Nodes']: print(pccl)
這裡要做兩個處理:獲得分類名稱、取出Id值
#pchome取出分類ID #分析後,網址結尾需加上13位unix時間戳記 url = 'https://ecapi.pchome.com.tw/mall/cateapi/v1/sign&tag=newarrival&fields=Id,Name,Sort,Nodes&_callback=jsonpcb_newarrival&'+str(millis) user_agent = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #偽裝使用者 headers = {'User-Agent':user_agent, 'server': 'PChome/1.0.4', 'Referer': 'https://mall.pchome.com.tw/newarrival/'} res = requests.get(url=url,headers=headers)#分析得出的網址 res_text = res.text res_text_format = res_text.replace('try{jsonpcb_newarrival(','').replace(');}catch(e){if(window.console){console.log(e);}}','') jd = json.loads(res_text_format) pcclass = jd[:] #print(pc) for pc in pcclass: pcclname = pc['Name'] print('分類名稱:'+pc['Name']) for pccl in pc['Nodes']: print(pccl['Id'])
最後則是將原先取得商品資料的代碼寫成一個函數,並引用三個參數進入
三個參數分別為:商品分類代號、跳轉的頁數(每頁50筆資料)、分類名稱
以下為商品資料爬蟲代碼:
def pcgood(pccid,page,pcclname): url = 'https://ecapi.pchome.com.tw/mall/prodapi/v1/newarrival/prod®ion=' + str(pccid) + '&offset='+ str(page) +'&limit=50&_callback=jsonpcb_newarrival' user_agent = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #偽裝使用者 headers = {'User-Agent':user_agent, 'server': 'PChome/1.0.4', 'Referer': 'https://mall.pchome.com.tw/newarrival/'} res = requests.get(url=url,headers=headers)#分析得出的網址 res_text = res.text res_text_format = res_text.replace('try{jsonpcb_newarrival(','').replace(');}catch(e){if(window.console){console.log(e);}}','') jd = json.loads(res_text_format) pcgoods = (jd['Rows'][:]) for pcgood in pcgoods: pcname = pcgood['Name'] pcimg = 'https://b.ecimg.tw' + pcgood['Pic']['S'] pcprice = pcgood['Price']['P'] pclink = 'https://mall.pchome.com.tw/prod/'+ pcgood['Id'] print(pcname) print(pcimg) print(pcprice) print(pclink) print('分類:'+pcclname)
由於迴圈遞增商品項數50筆總會有到底的時候,也就是該分類已無商品。這時候就要回傳一個值用來判斷跳出迴圈:
def pcgood(pccid,page,pcclname):
url = 'https://ecapi.pchome.com.tw/mall/prodapi/v1/newarrival/prod®ion=' + str(pccid) + '&offset='+ str(page) +'&limit=50&_callback=jsonpcb_newarrival'
user_agent = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #偽裝使用者
headers = {'User-Agent':user_agent,
'server': 'PChome/1.0.4',
'Referer': 'https://mall.pchome.com.tw/newarrival/'}
res = requests.get(url=url,headers=headers)#分析得出的網址
res_text = res.text
res_text_format = res_text.replace('try{jsonpcb_newarrival(','').replace(');}catch(e){if(window.console){console.log(e);}}','')
jd = json.loads(res_text_format)
if jd['Rows'] != []:
pcgoods = (jd['Rows'][:])
for pcgood in pcgoods:
pcname = pcgood['Name']
pcimg = 'https://b.ecimg.tw' + pcgood['Pic']['S']
pcprice = pcgood['Price']['P']
pclink = 'https://mall.pchome.com.tw/prod/'+ pcgood['Id']
print(pcname)
print(pcimg)
print(pcprice)
print(pclink)
print('分類:'+pcclname)
else:
status = 'no_data'
return status
將整個爬蟲系統也包進一個函數,
在取得分類後在其中進行頁數遍歷,頁數遍歷完則進行下一個分類
而每次爬取一個頁面都暫停8秒,避免被伺服器阻擋(經過測試,設置5秒也有機率被阻擋)
def pchomecrawler(): url = 'https://ecapi.pchome.com.tw/mall/cateapi/v1/sign&tag=newarrival&fields=Id,Name,Sort,Nodes&_callback=jsonpcb_newarrival&'+str(millis) user_agent = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #偽裝使用者 headers = {'User-Agent':user_agent, 'server': 'PChome/1.0.4', 'Referer': 'https://mall.pchome.com.tw/newarrival/'} res = requests.get(url=url,headers=headers)#分析得出的網址 res_text = res.text res_text_format = res_text.replace('try{jsonpcb_newarrival(','').replace(');}catch(e){if(window.console){console.log(e);}}','') jd = json.loads(res_text_format) pcclass = jd[1:2] #print(pc) for pc in pcclass: pcclname = pc['Name'] print(pc['Name']) for pccl in pc['Nodes']: pccid = pccl['Id'] pageid = 1 for page in range(99): print(page) if pcgood(pccid,pageid,pcclname) != 'no_data': pcgood(pccid,pageid,pcclname) pageid = pageid + 50 time.sleep(8) else: break
最後成果如下
至此為止爬取的目標已經達成
可以再添加例外處理讓爬蟲程式更加健全
而處理好的資料也可以存入資料庫進行查詢與使用