請啟用 JavaScript 來查看內容

[Python爬蟲實例] PChome 線上購物 (下篇)

前言

在之前的爬蟲實例講解過蝦皮購物,而今日要來講解同樣在台灣非常多人使用的「PChome 線上購物」🛒。

因為 PChome 在一個頁面中針對不同部分的資料有送出不同的請求,整理成一篇會太長,因此我將分為上下篇來介紹。
上一篇 PChome 線上購物介紹到 PChome 搜尋頁面 的相關資料,而本篇將講解如何取到 商品頁面 的資料。


* 「PChome 線上購物」與「PChome 24h購物」兩者的商品搜尋都是導到同樣的頁面,因此實際上是一樣的。

備註:此文僅教育學習,切勿用作商業用途,個人實作皆屬個人行為,本作者不負任何法律責任

線上購物 (來源:Pixabay)
線上購物 (來源:Pixabay)

套件

此次 Python 爬蟲主要使用到的套件:

安裝

1
pip install requests

商品基本資料

這邊以此商品為例,其網址為: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 格式),如下兩張圖片所示:

Name 欄位代表資料
Name 欄位代表資料

Nick 欄位代表資料
Nick 欄位代表資料

上次有說到,商品圖片分為"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 參數,預設回傳資料內會缺少 PriceisWarranty 欄位。

原先網站給的 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
(對超連結右鍵 > 另存連結為)

延伸練習

  1. 有些商品的頁面會有「贈品」可以選取,試試看,你有辦法抓取贈品的資訊嗎?
    範例商品:https://24h.pchome.com.tw/prod/DCAN8J-A900AW0AQ
    (小提示:可以使用贈品的名稱或 ID 在 開發人員工具 > Network 中搜尋 Ctrl + f)

  2. 在某些商品頁面會發現有「買此商品的人也買了…」區塊,這部分的資訊我們也可以取得嗎?
    範例商品: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 個讚~
分享

Jia
作者
Jia
軟體工程師 - Software Engineer