請啟用 JavaScript 來查看內容

[Python爬蟲實例] 教你輕鬆爬取"Yahoo奇摩知識+"的問題與解答

前言

本次Python網路爬蟲實例系列的「Yahoo奇摩知識+」,將帶領你來一步步爬取所有分類的問題與解答。

「Yahoo奇摩知識+」大約 10 年前左右在台灣還蠻多人用的,但現在會在Yahoo奇摩知識+上發問、回答的人已經少很多了。話說最近有人對Yahoo奇摩知識+有印象,會不會是從天氣之子呢(笑),不過「Yahoo奇摩知識+」在日本是叫「Yahoo!知恵袋」哦~

* 香港叫「Yahoo知識+」,但為什麼台灣的多出"奇摩"兩個字呢?較年輕的讀者可能不知道,「奇摩站」是以前台灣一家入口搜尋網站,但這又是另外一個故事了…

Yahoo奇摩知識+
Yahoo奇摩知識+

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

套件

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

安裝

1
pip install requests

它資料是如何載入的呢?

進到Yahoo奇摩知識+網站後,將網頁往下滾動,可以看到 Loading 的圖示,而且頁面也沒有跳轉,因此可以猜測它資料是透過 AJAX 的方式載入

"問題資料"載入圖示
"問題資料"載入圖示

經過以前網路爬蟲實例的文章應該很熟悉了,打開瀏覽器的 開發人員工具 (F12 或 Ctrl + Shift + i),切換到 "Network" > "XHR",再將網頁往下滾動,讓我們抓到它送出的請求。點擊 "_reservice_/" 的請求後切換到 "Preview" 頁面,可以發現它返回的資料都在這裡。

"Network" > "XHR" > "_reservice_/" > "Preview"
"Network" > "XHR" > "_reservice_/" > "Preview"

開發人員工具切回到"Headers"頁面查看請求的網址與方法。
跟我們一般常用的 HTTP 請求方法(GET、POST)不同,在這裡它是使用 PUT 方法。

"Network" > "XHR" > "_reservice_/" > "Headers"
"Network" > "XHR" > "_reservice_/" > "Headers"

往下還有請求夾帶的參數 Request Payload,可以點擊 view source 方便我們複製參數。

Request Payload
Request Payload

請求網址:https://tw.answers.yahoo.com/_reservice_/
請求方法:PUT
Request Payload:
{"type":"CALL_RESERVICE","payload":{"categoryId":"0","lang":"zh-Hant-TW","count":20,"offset":"pv61~p:0"},"reservice":{"name":"FETCH_DISCOVER_STREAMS_END","start":"FETCH_DISCOVER_STREAMS_START","state":"CREATED"}}


這邊有個之前文章沒遇過的地方,以前請求夾帶的參數常遇到的是 key-value (鍵值對)的形式,但這邊是採用 JSON 格式,因此 Headers 內要加入 content-type(HTTP 內容類型) 來表明用 JSON 格式來傳遞參數資料。
'content-type': 'application/json'

同時要注意請求的 data 中資料是 JSON 格式的字串,在 Python 程式裡記得要使用 json.dumps() 將其轉換為字串,大致如下程式碼所示。

 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
import requests
import json

url = 'https://tw.answers.yahoo.com/_reservice_/'
headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36',
    'content-type': 'application/json'
}
payload = {
    "type": "CALL_RESERVICE",
    "payload": {
        "categoryId": "0",
        "lang": "zh-Hant-TW",
        "count": 20,
        "offset": ""
    },
    "reservice": {
        "name": "FETCH_DISCOVER_STREAMS_END",
        "start": "FETCH_DISCOVER_STREAMS_START",
        "state": "CREATED"
    }
}

r = requests.put(url, data=json.dumps(payload), headers=headers)
print(r.json())

好!!那這樣都知道"Yahoo奇摩知識+"資料的請求方法了,我們來看看其參數有哪些值~


問題列表

首先從網站一進入就會看到的問題列表(文章列表)說起,左方有各式各樣的分類,中間的問題列表還有分為"探索"與"解答",綠色方框即是我們想要獲取的部分。

問題列表(文章列表) 頁面
問題列表(文章列表) 頁面

請求方法

先從"探索"開始找起,同上章節步驟,從開發人員工具知道請求的路徑、參數如下:

請求網址:https://tw.answers.yahoo.com/_reservice_/
請求方法:PUT
Request Payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "type": "CALL_RESERVICE",
    "payload": {
        "categoryId": "0",
        "lang": "zh-Hant-TW",
        "count": 20,
        "offset": "pv61~p:0"
    },
    "reservice": {
        "name": "FETCH_DISCOVER_STREAMS_END",
        "start": "FETCH_DISCOVER_STREAMS_START",
        "state": "CREATED"
    }
}

其中幾個參數代表意思:

  • categoryId:分類 ID。分類 ID 在各分類網頁的網址後方可找到,目前共有 24 個大分類,子分類也是採用同樣的方式。
分類 ID 在網址後方可以取得
分類 ID 在網址後方可以取得

而如果給 0,則代表"所有分類",以下是各大分類所對應的 categoryId

分類名稱分類 ID
所有分類0
健康396545018
商業與財經396545013
娛樂與音樂396545016
家居與園藝396545394
家庭與人際關係396545433
寵物396545443
懷孕與育兒396546046
政治與政府396545444
教育與參考396545015
新聞與活動396545439
旅遊396545469
汽車與交通396545311
消費電子產品396545014
環境396545451
社會科學396545301
社會與文化396545454
科學396545122
美容與造型396545144
藝術與人文396545012
遊戲與休閒活動396545019
運動396545213
電腦與網際網路396545660
食品與飲料396545367
餐廳與小吃396545327
  • count:一次回傳"問題(文章)"數量。但實測發現有時得到的數量會較少。

  • offset:偏移值。帶入前一頁回傳資料中 offset 的值。


而 "解答" 部分的問題列表(文章列表)其參數幾乎一樣,只要把 FETCH_DISCOVER_STREAMS_END 改成 FETCH_ANSWER_STREAMS_ENDFETCH_DISCOVER_STREAMS_START 改成 FETCH_ANSWER_STREAMS_START 即可。

請求網址:https://tw.answers.yahoo.com/_reservice_/
請求方法:PUT
Request Payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "type": "CALL_RESERVICE",
    "payload": {
        "categoryId": "0",
        "lang": "zh-Hant-TW",
        "count": 20,
        "offset": "pv61~p:0"
    },
    "reservice": {
        "name": "FETCH_ANSWER_STREAMS_END",
        "start": "FETCH_ANSWER_STREAMS_START",
        "state": "CREATED"
    }
}

回傳資料

它以 JSON 格式回傳,error 會顯示此次請求是否發生錯誤,主要的資料都在 payload 裏頭。

  • canLoadMore:是否還有下一頁。
  • offset:請求下一頁的偏移值。
  • questions:問題列表(文章列表)。

而每一則問題又有幾個數值:

  • qid:問題 ID。也代表問題網址後方帶的參數,例如 https://tw.answers.yahoo.com/question/index?qid=20210318093121AAnaQXT
  • title:問題標題。
  • detail:問題內容。
  • category:問題分類。
  • createdTime:創建日期時間。
  • answersCount:解答總數。
  • thumbUpsCount:按讚總數。
  • bestAnswerDetail:最佳解答內容。

範例:問題列表 JSON 資料 question_list_data.json

問題詳細資料

在問題列表(文章列表)點擊問題後會跳轉到問題詳細頁面,有完整的問題資訊與網友的解答。

問題詳細資料 頁面
問題詳細資料 頁面

請求方法

可以發現請求路徑和方法都是一樣的,它是透過 Request Payload 來判斷需要哪些資料(條件)。

請求網址:https://tw.answers.yahoo.com/_reservice_/
請求方法:PUT
Request Payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "type": "CALL_RESERVICE",
    "payload": {
        "qid": "20210310121058AAxKYlh"
    },
    "reservice": {
        "name": "FETCH_QUESTION_END",
        "start": "FETCH_QUESTION_START",
        "state": "CREATED"
    },
    "kvPayload": {
        "key": "20210310121058AAxKYlh",
        "kvActionPrefix": "KV/question/"
    }
}

其中 payload > qidkvPayload > key 參數帶上問題 ID。

回傳資料

它以 JSON 格式回傳,error 會顯示此次請求是否發生錯誤,主要的資料都在 payload 裏頭,幾個比較重要的參數:

  • qid:問題 ID。也代表問題網址後方帶的參數,例如 https://tw.answers.yahoo.com/question/index?qid=20210318093121AAnaQXT
  • lang:語言。
  • category:問題分類。
  • createdTime:創建日期時間。
  • title:問題標題。
  • content:問題內容。
  • updatedDetails:問題更新詳情。
  • attachedImageUrl:附加圖片網址。
  • bestAnswer:最佳解答。
  • answerCount:解答總數。
  • saveCount:被收藏總數。
  • isAnonymous:是否匿名。

範例:問題 JSON 資料 question_data.json

解答列表

位於每篇問題(文章)底下會有各網友回覆的解答(留言)。

解答列表 頁面
解答列表 頁面

請求方法

一樣只有 Request Payload 有差異,

請求網址:https://tw.answers.yahoo.com/_reservice_/
請求方法:PUT
Request Payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
    "type": "CALL_RESERVICE",
    "payload": {
        "qid": "20210310121058AAxKYlh",
        "count": 10,
        "start": 1,
        "lang": "zh-Hant-TW",
        "sortType": "NEWEST"
    },
    "reservice": {
        "name": "FETCH_QUESTION_ANSWERS_END",
        "start": "FETCH_QUESTION_ANSWERS_START",
        "state": "CREATED"
    },
    "kvPayload": {
        "key": "20210310121058AAxKYlh",
        "kvActionPrefix": "KV/questionAnswers/"
    }
}

其中 payload > qidkvPayload > key 參數帶上問題 ID。
count 代表一次回傳"解答(留言)"數量,start 代表起始值,例如這次解答取到 10 則,下一次要從第 11 則開始抓,那 start 就要帶 11。
sortType 代表解答排序依據(RATING:評分、NEWEST:最新、OLDEST:最舊)。

解答排序依據
解答排序依據

回傳資料

它以 JSON 格式回傳,error 會顯示此次請求是否發生錯誤,主要的資料都在 payload 裏頭,幾個比較重要的參數:

  • qid:問題 ID。
  • lang:語言。
  • answerCount:解答總數。
  • start:本次解答起始值。
  • count:本次解答數量。
  • sortType:解答排序依據(RATING:評分、NEWEST:最新、OLDEST:最舊)。
  • answers:解答列表(留言列表)。

而每一則解答又有幾個數值:

  • qid:問題 ID。
  • id:解答 ID。
  • text:解答內容。
  • attachedImageUrl:附加圖片網址。
  • reference:解答參考來源。
  • answerer:解答網友。
  • answerer > kid:網友 ID。
  • answerer > nickname:網友暱稱。
  • answerer > imageUrl:網友圖片網址。
  • answerer > level:網友等級。
  • isBestAnswer:是否為最佳解答。
  • thumbsDown:倒讚數。
  • thumbsUp:按讚數。
  • isAnonymous:是否匿名。
  • commentCount:此解答底下回覆數量(目前此功能好像被移除了)。
  • createdTime:創建日期時間。

* 抓取解答(留言)時要注意,如果此問題有"最佳解答",則此解答並不會出現在"解答列表"的回應裡,而是會在"問題詳細資料"回應資料裡的 bestAnswer 參數中,因此要注意不要遺漏掉了。


範例:解答列表 JSON 資料 answer_list_data.json

範例程式碼

附上範例程式碼:yahoo_answers_spider.py
(對超連結右鍵 > 另存連結為…)


延伸練習

  1. 試著爬取一篇問題,並將問題以及各解答挑出想要的幾個欄位,並儲存成適合的檔案(TXT文字檔、JSON)。

結語

一開始我在測試抓取"Yahoo奇摩知識+"時,不知道為何 Header 都給一樣了,還是一直無法成功請求到資料。後來改使用 Postman 這項工具測試,才發覺 content-type 要帶 'application/json',也學到了一個😙。


如果你對於 Python、爬蟲、網頁感興趣的話,我後續還會繼續寫相關的文章,歡迎來追蹤 FB 粉專『IT空間』~




人生就像天空一樣,有季節更迭,時刻在變;
它雖然不會一直晴空萬里,但也不會總是大雪紛飛。

—— 《鬼滅之刃》


🔻 如果覺得喜歡,歡迎在下方獎勵我 5 個讚~
分享

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