iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 20
1

定義目標

Facebook 上的朋友其實挺多,幾乎每天都會出現某某人今天生日,但要特別花時間去回應似乎又有點麻煩,但沒有給予祝福又不夠意思,那麼我們今天的主題就來抓取 FB 所有朋友的生日吧,看看後續我們能夠怎麼應用


實際探訪

首先我們可以到 https://www.facebook.com/events/birthdays/ 關於生日的相關資訊,然後發現畫面往下拉 FB 有把每個月的壽星整理好,也就是說如果我們能夠抓取這頁的資料,就能完成任務。接下來我們發現,每個月的生日資訊是 ajax 讀進來的,看到這種狀況就肯定要打開 dev tool 來看看送出的 request 長什麼樣子。送出了一個 get request,那也就是說,12 個月份會送出 12 個 request,這樣就能得到所有清單了。

再來看看每個 response 的樣子,看起來是一個 json format,然後把結構打開後,發現在 domops 裡面有個 __html,看樣子這邊就是我們所看到畫面的 html 部分。

只要把 12 個月份的 response html 做解析,就能得到所有朋友的生日了,那趕緊接下去研究吧。


分解研究

要取得所有朋友的生日,那麼會拆解成以下兩個步驟:

  1. 抓取月份的 html
  2. 解析格式

抓取月份的 html

首先我們先來試試看模擬一個月份的 request,別忘記在 headers 帶上 cookie 和 user-agent,確定是可以順利拿到結果。

不過整個 get request 所送出的 query string 太長了,我們來檢驗一下哪些是必要的,經過刪去法之後,發現只有 date__a 是必要的,date 大概可以理解是關鍵點,是一種 timestamp 的格式。

馬上來換算一下,確定是查詢的月份,不過這邊比較特別,他是用 UTC 時間早上 8:00,所以等下我們實作程式碼也必須送的是每個月第一天的 8:00 AM。

解析格式

接下來我們來試著選取每個人的生日,既然知道 ajax 回應的是 html 程式碼,那肯定是直接將某個區塊做取代,所以我們直接看 dev tools 的 Elements 就可以了,確定可以抓到生日,那就可以進入實作了。


實作程式碼

default headers

首先我們這次的 request 送出都會帶著已登入 fb 的 cookieuser-agent,所以在 require request 的時候就能將這兩個資訊帶入,另外再提醒一次,請不要隨意洩露你的 cookie。

const request = require('request').defaults({
  headers: {
    'cookie': 'xxxxxxx',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
  }
});

getBirthdaysByMonth function

接著我們來實作取得每個月份的 request,這個 function 會收兩個參數,查詢的月份和 callback。將月份轉換成 timestamp,不過這邊要注意一下,因為我們時區是 +8 所以 UTC 8:00 對我們這個時區來說會是 16:00。

然後我們要將 json format 的 response 轉換成 object,但這邊也要注意到,他的 response 前面有帶 for (;;);,所以也要先刪掉才能做 JSON.parse。

接著我們就能來實作解析 html 的部分,然後將取得的資料丟給 callback 就完成了。

function getBirthdaysByMonth(month, callback) {
  month = new Date(`2018/${month}/01 16:00:00`).getTime() / 1000
  request(`https://www.facebook.com/async/birthdays/?date=${month}&__a=1`, (err, res, body) => {
    var html = JSON.parse(body.replace('for (;;);', '')).domops[0][3].__html
    var $ = cheerio.load(html)
    var birthdays = $('._43q7').map((index, obj) => {
      return {
        name: $(obj).find('a').attr('data-tooltip-content').split('(')[0],
        date: $(obj).find('a').attr('data-tooltip-content').match(/((.+))/)[1],
        link: $(obj).find('a').attr('href'),
      }
    }).get()
    callback(birthdays)
  })
}

組合 12 個月份

接下來我們要先準備 1 到 12 這個 array,然後 async.map 去呼叫 getBirthdaysByMonth,這樣就能得到所有生日結果了。

var months = Array.from(new Array(12), (val, index) => index + 1)

async.map(months, (month, callback) => {
  getBirthdaysByMonth(month, (birthdays)=>{
    callback(null, birthdays)
  })
}, (err, results) => {
  console.log([].concat.apply([], results))
})

全部程式碼

const request = require('request').defaults({
  headers: {
    'cookie': 'xxxxxxxxxx',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
  }
});
const cheerio = require('cheerio');
const async = require('async');

var months = Array.from(new Array(12), (val, index) => index + 1)

async.map(months, (month, callback) => {
  getBirthdaysByMonth(month, (birthdays)=>{
    callback(null, birthdays)
  })
}, (err, results) => {
  console.log([].concat.apply([], results))
})


function getBirthdaysByMonth(month, callback) {
  month = new Date(`2018/${month}/01 16:00:00`).getTime() / 1000
  request(`https://www.facebook.com/async/birthdays/?date=${month}&__a=1`, (err, res, body) => {
    var html = JSON.parse(body.replace('for (;;);', '')).domops[0][3].__html
    var $ = cheerio.load(html)
    var birthdays = $('._43q7').map((index, obj) => {
      return {
        name: $(obj).find('a').attr('data-tooltip-content').split('(')[0],
        date: $(obj).find('a').attr('data-tooltip-content').match(/((.+))/)[1],
        link: $(obj).find('a').attr('href'),
      }
    }).get()
    callback(birthdays)
  })
}


衍伸應用

雖然 FB 有 graph api,但其實他一來給的資訊很受限,二來穩定度其實不是很高,所以自幹 FB 爬蟲在某些時候還是唯一的方法,但若 graph api 有提供的資料,那還是直接使用 api 會更方便。

因為在台灣 FB 的使用還是主流,有很多的商業行為都很依賴 FB 上面的資訊,所以抓取 FB 資料這類的需求一直都很多,如何玩轉 FB 上面的資訊一直都是很有趣的課題,不管是使用 api、爬蟲、extension 都是值得研究的方向。


上一篇
電影場次快速查詢
下一篇
Facebook 按讚名單
系列文
爬蟲始終來自於墮性34

尚未有邦友留言

立即登入留言