前言
在之前的爬蟲實例講解過蝦皮購物,而今日要來講解同樣在台灣非常多人使用的「PChome 線上購物」🛒。
因為 PChome 在一個頁面中針對不同部分的資料有送出不同的請求,整理成一篇會太長,因此我將分為上下篇來介紹。
上一篇 PChome 線上購物介紹到 PChome 搜尋頁面 的相關資料,而本篇將講解如何取到 商品頁面 的資料。
* 「PChome 線上購物」與「PChome 24h購物」兩者的商品搜尋都是導到同樣的頁面,因此實際上是一樣的。
備註:此文僅教育學習,切勿用作商業用途,個人實作皆屬個人行為,本作者不負任何法律責任
套件
此次 Python 爬蟲主要使用到的套件:
安裝
商品基本資料
這邊以此商品為例,其網址為:https://24h.pchome.com.tw/prod/DHAFI0-A900BAPXK
「商品基本資料」請求資料會有像是商品名稱、圖片、價格,與一些此商品的設定等等。
* 商品名稱下方的資訊又是在其他請求XD,文章之後章節會提到。
請求路徑與參數
請求網址:https://ecapi.pchome.com.tw/ecshop/prodapi/v2/prod/DHAFI0-A900BAPXK&_callback=jsonp_prod
請求方法:GET
也可以透過 fields
參數來指定你想要哪些欄位,例如我只要商品名稱、商品價格、是否有24小時到達:
https://ecapi.pchome.com.tw/ecshop/prodapi/v2/prod/DHAFI0-A900BAPXK&fields=Name,Price,isArrival24h&_callback=jsonp_prod
原先網頁上抓到它有帶以下欄位:
fields=Seq,Id,Name,Nick,Store,PreOrdDate,SpeOrdDate,Price,Discount,Pic,Weight,ISBN,Qty,Bonus,isBig,isSpec,isCombine,isDiy,isRecyclable,isCarrier,isMedical,isBigCart,isSnapUp,isDescAndIntroSync,isFoodContents,isHuge,isEnergySubsidy,isPrimeOnly,isPreOrder24h,isWarranty,isLegalStore,isFresh,isBidding,isSet,Volume,isArrival24h,isETicket,ShipType,isO2O
我還有發現如果不加上"fields"參數,回傳的欄位反而會少其中幾個。
回傳資料
跟上篇的"商品規格種類"一樣,回傳資料前後會有一段 JS 語法,我們要先將其字串移除後,才是正確的 JSON 格式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| {
"DHAFI0-A900BAPXK-000": {
"Name": "ASUS E410MA-0341PN4020 玫瑰金(Celeron N4020/4G/128G/Windows 10 Home S/HD/14)",
"Price": {
"M": 0,
"P": 11900,
"Prime": ""
},
"isArrival24h": 1,
"Seq": 26650490,
"Id": "DHAFI0-A900BAPXK-000",
"Nick": "<font color=#5c18d8><b>小資輕薄機首選!★輕1.3kg★內附Microsoft365</font></b><Br>ASUS E410MA-0341PN4020 玫瑰金<BR>14吋輕薄文書筆電<BR><font size=3><font color=#FF00CC>★內含S模式作業系統 </font>",
"Store": "DHAFI0",
"PreOrdDate": "",
"SpeOrdDate": "",
"Discount": 0,
"Pic": {
"B": "/items/DHAFI0A900BAPXK/000001_1619679669.jpg",
"S": "/items/DHAFI0A900BAPXK/000002_1619679669.jpg"
},
"Weight": 2.1,
"ISBN": "",
"Qty": 10,
"Bonus": 0,
"isBig": 0,
"isSpec": 0,
"isCombine": 0,
"isDiy": 0,
"isRecyclable": 0,
"isCarrier": 0,
"isMedical": 0,
"isBigCart": 1,
"isSnapUp": 0,
"isDescAndIntroSync": 0,
"isFoodContents": 0,
"isHuge": 0,
"isEnergySubsidy": 0,
"isPrimeOnly": 0,
"isPreOrder24h": 0,
"isWarranty": 0,
"isLegalStore": 1,
"isFresh": 0,
"isBidding": 0,
"isSet": 0,
"Volume": {
"Length": 43,
"Width": 27,
"Height": 7
},
"isETicket": 0,
"ShipType": "Consign",
"isO2O": 0
}
}
|
其中 Name
欄位和 Nick
欄位可能資料會很像,Name
欄位在搜尋頁面會出現,代表商品的真正名稱,而 Nick
欄位是在商品頁面出現,會加上一些修飾標語跟文字樣式(HTML 格式),如下兩張圖片所示:
上次有說到,商品圖片分為"picS"與"picB",查看後我猜測"picS"是在搜尋頁面的圖片,而"picB"是位於商品本身頁面的。
前方再加上 https://d.ecimg.tw
即圖片完整網址。
如 https://d.ecimg.tw/items/DHAFI0A900BAPXK/000001_1619679669.jpg
。
而其他 is
開頭的欄位代表此商品的其他特性(?),或者說是 PChome 給它加上的屬性,例如是否有24小時到達、長寬高是多少(這邊應該是指包裝的,而不是商品本身的規格)、是否醫療相關、是否有食材相關標示……等等,可能會用在顯示圖示或是否有其他請求去抓更多資料。
範例程式
"商品基本資料"部分範例程式如下,完整程式碼在文章最後會附上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| def get_product_info(self, product_id, fields=None):
"""取得商品基本資訊
:param product_id: 商品 ID
:param fields: 指定欄位
:return data: 商品基本資訊
"""
# fields=Seq,Id,Name,Nick,Store,PreOrdDate,SpeOrdDate,Price,Discount,Pic,Weight,ISBN,Qty,Bonus,isBig,isSpec,isCombine,isDiy,isRecyclable,isCarrier,isMedical,isBigCart,isSnapUp,isDescAndIntroSync,isFoodContents,isHuge,isEnergySubsidy,isPrimeOnly,isPreOrder24h,isWarranty,isLegalStore,isFresh,isBidding,isSet,Volume,isArrival24h,isETicket,ShipType,isO2O
url = f'https://ecapi.pchome.com.tw/ecshop/prodapi/v2/prod/{product_id}'
if fields is not None:
url += f'&fields={fields}'
url += '&_callback=jsonp_prod'
data = self.request_get(url, to_json=False)
# 去除前後 JS 語法字串
data = json.loads(data[15:-48])
return data
|
商品描述(標語、規格、備註)
此章節所說的商品描述,包括位於商品圖片右方、標題下方的"標語",還有商品頁面最下面的"配備"、"規格"、"備註",甚至更下方的"售後服務"等等。
請求路徑與參數
請求網址:https://ecapi-pchome.cdn.hinet.net/cdn/ecshop/prodapi/v2/prod/DHAFI0-A900BAPXK/desc&_callback=jsonp_prod
請求方法:GET
一樣可以透過 fields
參數來指定你想要哪些欄位。
回傳資料
拿到回傳資料後,一樣要先將前後的 JS 語法去除,才可以正確地解析 JSON 格式。
不過這部分並不是每樣商品都會有每個欄位,像是假如商品它沒有"備註",那"Remark"那個欄位的值就是空字串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| {
"DHAFI0-A900BAPXK": {
"Id": "DHAFI0-A900BAPXK",
"Stmt": "<a name=\"規格說明\">ASUS E410MA-0341PN4020 玫瑰金 詳細規格表</a>\r\nLCD尺寸 (解析度):14.0'//200nits//HD 1366x768 16:9//Anti-Glare//NTSC: 45%\r\n<font color=\"RED\">CPU:Intel® Celeron® N4020 Processor 1.1 GHz (4M Cache, up to 2.8 GHz)</font>\r\n記憶體:4GB DDR4 on board\r\n容量:128G EMMC\r\nWLAN無線網路:Wi-Fi 5(802.11ac)+Bluetooth 4.1 (Dual band) 1*1\r\nODD光碟機:無\r\n\r\n<HR>\r\n輸入輸出介面(I/O)\r\n\"1x 3.5mm Combo Audio Jack\r\n1x HDMI 1.4//1x USB 2.0 Type-A\r\n1x USB 3.2 Gen 1 Type-A\r\n1x USB 3.2 Gen 1 Type-C//Micro SD card reader\"\r\n\r\n<HR>\r\n尺寸:32.50 x 21.70 x 1.80 ~ 1.84 cm\r\n重量:1.3KG\r\n保固:二年全球保固/首年完美保固\r\n<font color=\"RED\">作業系統:Windows 10 Home S (S模式)</font>",
"Equip": "變壓器 無附包,無附鼠",
"Remark": "<font color=\"RED\">此為通用文案 實際規格以賣場為主</font>\r\nS模式作業系統,使用者需於Windows市集做標準版作業系統轉換",
"Liability": "<B>華碩筆記型電腦兩年保固</B><BR>台灣區自2002年2月1日起凡購買華碩筆記型電腦即可享有兩年產品保固服務。<BR>◎關於電池保固問題 <BR>電池為消耗性之產品,其保固期為壹年<BR>電池使用時間與充放電之次數成反比,因此使用時間縮短為正常現象,不屬於一年保固範圍<BR><B>華碩筆記型電腦免費送修</B><BR>為提供本公司產品用戶有更完善的售後服務,我們特別推出了筆記型電腦本島免費快遞送修的服務,讓客戶有維修需求時能有效快速的透過物流收送,回廠進行維修流程,及完修後安全迅速的將機台送回,達成客戶滿意之依據。<BR>適用本公司出售之華碩筆記型電腦產品於保固內有維修需求時,給予回廠免費收送服務。<BR>◎只限台灣本島︰不限地點可收件。<BR>◎請撥客服電話:0800-093-456, 客服人員將為與您聯絡相關事宜。<BR>為了保障您的權益, 請立即在線上註冊您的商品。若您是第一次註冊,請先加入華碩一般會員,謝謝您!<BR>◆線上註冊→ <A href=\" http://support.asus.com.tw/repair/repair.aspx?no=99&SLanguage=zh-tw TARGET=\" _NEW?><U>請前往原廠網站</U></A>",
"Kword": "E410MA^ASUS^筆電^小筆電^14吋^玫瑰金^虛擬數字鍵盤",
"Brand": "華碩",
"Slogan": "<font color=\"RED\"> ★虛擬數字鍵 /180度螢幕轉軸設計\r\n★可自行升級M.2 SSD \r\n★最高電池續航力12小時</font>\r\n\r\n<font size=2><LI><font color=red>處理器:Intel® Celeron® N4020 Processor 1.1 GHz</font>\r\n<LI>記憶體:4GB DDR4 on board\r\n<LI>容量:EMMC 128GB\r\n<LI>LCD尺寸:14\"HD 霧面寬螢幕(LED)\r\n<LI>無線網路:802.11ac+Bluetooth 4.1 (Dual band) 1*1\r\n<LI>光碟機:無\r\n<LI>其他:HDMI、USB 3.2\r\n<LI>重量:1.3KG\r\n<font color=red><LI>其他:內含 Microsoft365 個人版一年(市價2190元)</font>\r\n<font color=red><LI>作業系統:Windows 10 Home S (S模式) </font>\r\n<LI>保固:二年全球保固/首年完美保固\r\n\r\n<a href=\" //a.ecimg.tw/img/projects/personal/v0/upload_file/US00010362/Office-introduce.jpg \"target=”_blank”><img src=\"https://a.ecimg.tw/img/projects/personal/v0/upload_file/US00010362/office/2019-1.jpg \"alt=\"↑買NB加購Office 詳細請點\"></a>\r\n<a href=\"#規格說明\"><img src=\"https://a.ecimg.tw/img/projects/personal/v0/upload_file/US00000208/add/icon.jpg\">本商品詳細規格</a></a>\r\n",
"Author": "",
"Transman": "",
"Pubunit": "",
"Pubdate": "",
"Meta": {
"Brand": "",
"BrandEng": "",
"MarcoMatchName": [
"ASUS"
]
}
}
}
|
食物類商品缺少規格?
但在抓取某些商品時會發現,像是底下這個食物類的標示,怎麼規格缺少了上方內容物、保存期限、廠商資訊之類的?!
我實際尋找後發現,這部分要再透過另一個請求去取得:
請求網址:https://ecapi-pchome.cdn.hinet.net/cdn/ecshop/prodapi/v2/prod/DBAEF6-A900AK8ZY/foodcontents&_callback=jsonp_fooddesc
請求方法:GET
可是還有個問題,我要怎麼資料它是不是食物呢?
這就回到「商品規格」章節中的"回傳資料",回傳資料內就有一個"isFoodContents"欄位,就會寫到是否有食物含量,就可以知道是否還要再送出這個請求,來取得完整的資料。
當然,除了食物類的商品,我猜應該還有許多其他商品會又另外的請求。
範例程式
"商品描述(標語、規格、備註)"部分範例程式如下,完整程式碼在文章最後會附上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| def get_product_description(self, product_id, fields=None):
"""取得商品描述(標語、規格、備註)
:param product_id: 商品 ID
:param fields: 指定欄位
:return data: 商品描述資料
"""
# &fields=Id,Stmt,Equip,Remark,Liability,Kword,Slogan,Author,Brand,Meta,Transman,Pubunit,Pubdate,Approve
url = f'https://ecapi-pchome.cdn.hinet.net/cdn/ecshop/prodapi/v2/prod/{product_id}/desc'
if fields is not None:
url += f'&fields={fields}'
url += '&_callback=jsonp_prod'
data = self.request_get(url, to_json=False)
# 去除前後 JS 語法字串
data = json.loads(data[15:-48])
return data
|
商品詳細介紹
這邊所說的商品詳細介紹,是指商品頁面中間的"本商品詳細介紹",內容可能有文字、圖片或影片。
請求路徑與參數
請求網址:https://ecapi-pchome.cdn.hinet.net/cdn/ecshop/prodapi/v2/prod/DHAFI0-A900BAPXK/intro&_callback=jsonp_intro
請求方法:GET
同樣也有 fields
參數來指定你想要哪些欄位。
回傳資料
這邊範例我使用另個商品示範,因為它有包含文字、圖片跟影片。
將回傳資料去除前後 JS 語法後,可以看出裡頭是以陣列(列表)的形式,每部分裡有 Sort
標示著順序,資料有文字 Intro
與圖片 Pic
兩種,圖片路徑需加上 https://e.ecimg.tw
才是完整的網址。
文字又是以 HTML 的格式紀錄,因此也可以崁入 YouTube 影片,像是底下範例中的第三部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| {
"DEBB55-A900AUAHM": [
{
"Id": "DEBB55-A900AUAHM",
"Pic": null,
"Pstn": "M",
"Intro": "<p align=\"center\"><strong><span style=\"font-family: 標楷體; font-size: xx-large; color: #ff0000;\">【PX 大通】 C52G 夜視高畫質GPS行車記錄器</span></strong></p>",
"Sort": "1"
},
{
"Id": "DEBB55-A900AUAHM",
"Pic": "/items/DEBB55A900AUAHM/i010002_1600054259.jpg",
"Pstn": "M",
"Intro": "",
"Sort": "2"
},
{
"Id": "DEBB55-A900AUAHM",
"Pic": null,
"Pstn": "M",
"Intro": "<center><iframe src=\"https://www.youtube.com/embed/S28ZIbO9QJw\" frameborder=\"0\" width=\"1040\" height=\"765\"></iframe><center><iframe src=\"https://www.youtube.com/embed/BEw8bT3uPpw\" frameborder=\"0\" width=\"1040\" height=\"765\"></iframe></center></center>",
"Sort": "3"
},
{
"Id": "DEBB55-A900AUAHM",
"Pic": "/items/DEBB55A900AUAHM/i010015_1598511521.jpg",
"Pstn": "M",
"Intro": "",
"Sort": "4"
}
]
}
|
範例程式
"商品詳細介紹"部分範例程式如下,完整程式碼在文章最後會附上。
這邊我是讓它依照 Sort
數字由小到大排順序,再用字串拼接起來。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| def get_product_introduction(self, product_id, fields=None):
"""取得商品詳細介紹(包含圖片、影片)
:param product_id: 商品 ID
:param fields: 指定欄位
:return introduction: 商品詳細介紹字串
"""
# &fields=Id,Pic,Pstn,Intro,Sort
url = f'https://ecapi-pchome.cdn.hinet.net/cdn/ecshop/prodapi/v2/prod/{product_id}/intro'
if fields is not None:
url += f'&fields={fields}'
url += '&_callback=jsonp_intro'
data = self.request_get(url, to_json=False)
# 去除前後 JS 語法字串
data = json.loads(data[16:-48])
introduction = ''
# 依照 Sort 欄位數字由小到大排順序
data = sorted(data[product_id], key=lambda k: k['Sort'])
for intro in data:
if intro['Pic']:
introduction = introduction + '\n' + 'https://e.ecimg.tw' + intro['Pic']
else:
introduction = introduction + '\n' + intro['Intro']
return introduction
|
加購商品
在商品的頁面,有時會出現"加購商品"這個區塊,但不是每個商品都有。
請求路徑與參數
請求網址:https://ecapi-pchome.cdn.hinet.net/cdn/ecshop/prodapi/v2/prod/DHAFI0-A900BAPXK/add&_callback=jsonp_add
請求方法:GET
這邊我發現如果不加 fields
參數,預設回傳資料內會缺少 Price
、isWarranty
欄位。
原先網站給的 fields=Seq,Id,Name,Spec,Group,Price,Pic,Qty,isWarranty
。
回傳資料
一樣先要將回傳資料去除前後的 JS 語法,如果多項贈品,那這邊也會顯示多個,字典的 key(鍵) 代表此商品的 ID。
(此範例商品應該會有三個贈品,但為了讓文章版面不要太長,我有將其刪減)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| {
"DHAA2D-A900AQGVA": [
{
"Seq": 24988216,
"Id": "DHAA2D-A900AQGVA-000",
"Name": "【加購現省】中文 Microsoft 365 家用版一年盒裝",
"Spec": "",
"Group": "",
"Price": {"M": 4190, "P": 2790},
"Pic": {
"B": "/items/DHAA2DA900AQGVA/000001_1593137612.jpg",
"S": "/items/DHAA2DA900AQGVA/000002_1593137612.jpg"
},
"Qty": 20,
"isWarranty": 0
}
],
"DSAEAW-A900B8SRF": [
{
"Seq": 26572469,
"Id": "DSAEAW-A900B8SRF-000",
"Name": "Microsoft 365 個人版 15個月訂閱-ESD金鑰卡(不能退貨)",
"Spec": "",
"Group": "",
"Price": {"M": 0, "P": 2190},
"Pic": {
"B": "/items/DSAEAWA900B8SRF/000001_1618314609.jpg",
"S": "/items/DSAEAWA900B8SRF/000002_1618314609.jpg"
},
"Qty": 20,
"isWarranty": 0
}
]
}
|
加購商品介紹
順帶一提,如果你想取得加購商品的簡介(下圖綠色方框)的話,需要透過另外一個請求。
請求網址:https://ecapi-pchome.cdn.hinet.net/cdn/ecshop/prodapi/v2/prod/desc&id=DHAA2D-A900AQGVA,DSAEAW-A900B8SRF&_callback=jsonp_adddesc
請求方法:GET
id
請改成贈品的 ID,一次帶入多個商品一樣將商品 ID 以 ,
連接即可。
而且我發現他請求網址跟「商品描述(標語、規格、備註)」的非常像,取得的回傳資料也是一模一樣,所以我猜兩這是通用的。
"商品描述"請求網址:https://ecapi-pchome.cdn.hinet.net/cdn/ecshop/prodapi/v2/prod/DHAFI0-A900BAPXK/desc
範例程式
"加購商品"部分範例程式如下,完整程式碼在文章最後會附上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| def get_add_buy_product(self, product_id, fields=None):
"""取得加購商品
:param product_id: 商品 ID
:param fields: 指定欄位
:return data: 加購商品
"""
# &fields=Seq,Id,Name,Spec,Group,Price,Pic,Qty,isWarranty
url = f'https://ecapi-pchome.cdn.hinet.net/cdn/ecshop/prodapi/v2/prod/{product_id}/add'
if fields is not None:
url += f'&fields={fields}'
url += '&_callback=jsonp_add'
data = self.request_get(url, to_json=False)
# 去除前後 JS 語法字串
data = json.loads(data[14:-48])
return data
|
完整程式碼
附上完整程式碼:pchome_spider02.py
(對超連結右鍵 > 另存連結為)
延伸練習
有些商品的頁面會有「贈品」可以選取,試試看,你有辦法抓取贈品的資訊嗎?
範例商品:https://24h.pchome.com.tw/prod/DCAN8J-A900AW0AQ
(小提示:可以使用贈品的名稱或 ID 在 開發人員工具 > Network 中搜尋 Ctrl + f)
在某些商品頁面會發現有「買此商品的人也買了…」區塊,這部分的資訊我們也可以取得嗎?
範例商品:https://24h.pchome.com.tw/prod/DCAN8J-A9009OS4V
(小提示:這部分會先取得推薦的名單,再透過「取得商品資訊」章節的請求來取得詳細資訊)
結語
在尋找 PChome 的請求網址、邏輯時,我還發現一個"prodjsv3-"開頭的 JavaScript 檔案,裡面可以找出許多不同請求的端倪,你們可以從中尋找還有哪些請求。
(迷之音:請求有夠多,全部要找要寫出來太花時間,叫網友自己去找好了,嗯~還可以順便說是給他們練習,不錯XD)
之後還會陸續寫一些網站的"網路爬蟲實例",如果此篇文章有幫助到你,歡迎在底下留言、留言,還有追蹤 FB 粉專『IT空間』~ 🔔
在這個世界上,沒有人能使你倒下。如果你自己的信念還站立的話。
In this world, no one can make you fall. If your faith is still standing.
🔻 如果覺得喜歡,歡迎在下方獎勵我 5 個讚~