爬蟲實戰:怎樣爬到 Instagram 的資料

Mars Li
RD.TW
Published in
8 min readApr 8, 2020
爬蟲怎麼爬

Updates: 2022/01/26

昨天看了一下,ig web 現在登入跟非登入的 response 結構已經截然不同了,登入翻頁所有的 query 都變成 post, 加上 CSP, CSRF token 等等的防禦手段,正常的手段已經不能抓到資料了,非登入的情況下還是用 Graphql 的形式,所以以下教學不一定適用呦 ~ 畢竟身為爬蟲人就是要忍受資料說改就改呀!

— — — — — — — — — — — —

因為最近無聊到居然想寫程式,我真的是太無聊了,怎麼會想寫這麼可怕的東西,就想說玩看看爬蟲,順便寫個文章記錄好了,最近很流行用人名比較有帶入感(相信大家看過胡大這篇:跟著小明一起搞懂技術名詞:MVC、SPA 與 SSR),這次的主人翁是摑瑜,下面就簡稱小瑜:

很久以前,小瑜是一位前端工程師,因為疫情待在家玩動物森友會,玩著玩著他抓到一隻爬蟲,突然就想要學一下人家都怎麼用爬蟲爬資料的,是有點牽強沒錯,但身為一個有理想的工程師是不管這麼多流言蜚語的,他首先想到的就是來爬看看 Instagram 的資料,身為一個懶鬼他只想爬爬公開貼文,所以他不想用 Instagram Graph API,限制一堆還要註冊一堆有的沒的。

初探

小瑜並沒有爬蟲的經驗,但身為一個前端工程師,小瑜首先想到的就是觀察 Instagram 網頁版的頁面,他先試著先在 Google 上輸入

instagram hashtag taipeicafe

想要找到 Instagram 標籤的搜尋結果,果不其然第一個搜尋結果就是,身為一個自以為優秀的前端工程師,小瑜深知:工程師欲善其事,必先開 chrome dev tools,要觀察網頁發出的請求的話還要切到 network 頁籤,觀察了下果然發現取得 hashtag 貼文的請求

instagram 取得 hashtag 的請求

小瑜異常興奮,他馬上就發現可以用 URL 拼接 Hashtag 的方式來獲取 Instagram 的貼文,身為一個負責任的工程師當然要履行一句名言

Talk is cheap, show me the code

小瑜馬上動手做起來

fetch('https://www.instagram.com/explore/tags/taipeicafe/?__a=1')
.then(res => res.json())
.then(res => console.log(res));
Instagram 請求 hashtag 的結果

小瑜馬上發現幾乎整張頁面的資訊都在裡面了,像是有這個 hashtag 的貼文有 12萬5333篇,其中 edge_hashtag_to_top_posts 代表的是熱門貼文數量是 9 個,edge_hashtag_to_top_posts 是最新貼文數量有 70 篇,

隨便選了最新貼文下的一篇貼文再看了一下結構:

Hashtag 下的貼文結構

雖然該有的都有,像是作者、點讚數、縮圖、貼文內容,但他發現到怎麼都只有第一張圖片,而且也沒有留言。

取得貼文詳細內容

這時候小瑜想到,工程師為了避免頁面所請求的資料量過多,只需要請求頁面顯示的資料即可,所以應該是點了貼文之後才會再去請求貼文的其他資訊。

抱著實驗的精神,小瑜又去頁面點了一下某一篇貼文,發現 URL 居然換了

點擊貼文後的畫面

URL 從 https://www.instagram.com/explore/tags/taipeicafe/ 換成了 https://www.instagram.com/p/B-Y85rNp-OY/

所以其實也能透過 URL 的方式來去請求貼文的詳細資訊,小瑜試了一下

fetch('https://www.instagram.com/p/B-Y85rNp-OY/?__a=1')
.then(res => res.json())
.then(res => console.log(res));
shortcode 下的資料結構

真的能透過 URL 的方式來取得貼文的詳細資訊,小瑜好開心,但開心歸開心,苓膏龜苓膏,URL 上那一串是怎麼來的呢?

// ( •́ _ •̀) ????
// B-Y85rNp-OY
fetch('https://www.instagram.com/p/B-Y85rNp-OY/?__a=1')
.then(res => res.json())
.then(res => console.log(res));

小瑜想了想,用他的第六感想了想,一定是在一開始點擊貼文的時候頁面做了什麼事,但是看了一下頁面本身在點擊時沒有發任何請求,那表示這個亂數一定是藏在本來的頁面內,因為如果是隨機產生的 hashcode 的話不會出現在 URL 的這個地方。

Bingo!!

瞬間小瑜覺得自己好有成就,他是一個容易滿足不會不知足的小孩,那這樣就表示如果取得貼文的 shortcode 之後就可以知道貼文頁面的 URL 地址了。

小瑜開心的玩了ㄧ下

甚至要取得指定 id 的使用者貼文也可以如法炮製

https://www.instagram.com/username/?__a=1

挫折的轉折

小瑜開心的玩了一天後也分享給朋友,但很快就發現,Instagram 是一張 SPA 的頁面,一開始我們透過 URL 確實拿到了 Hashtag 下的最新 70 則貼文,那我還要看 70 則之後的怎辦?

爬蟲心法的第一條準則:觀察網頁的行為

不要問心法哪來的,小瑜自己創的,目前也只有一條。

測試了一下 Instagram 頁面往下加載,猛然跳出請登入的畫面,該來的還是要來的,只好硬氣的先登入,果然抓到了一個請求

頁面翻頁加載時需要的參數

開玩笑,Instagram 這種大公司豈能讓你這麼好抓資料,小瑜一看到 query_hash 就打算先放著了,依他粗淺的經驗來看這參數一定加密過的,先不糾結,跟指考一樣遇到不會的先跳過,然後看了下其他的參數

tag_name 好理解
first ???
after ????

小瑜好歹也是個前端工程師,針對這種翻頁的數據請求,參數內通常會有一個是用來指定請求的數據量大小的,看了一下資料結構最像數字的就是 first 了,那就剩下 after 這個參數不知道做什麼用的了。

但是小瑜真的是對於 Ctrl + C、Ctrl + F、Ctrl + V 這種事很在行阿,馬上就把 After 對應的這串鬼畫符亂數拿去比對,終於在 Hashtag 下的 page_info 找到對應的參數:end_cursor

到現在小瑜恍然大悟,所以可以理解為每次翻頁請求的參數如下:

let queryObj = {
query_hash: '加密後的Hashcode',
variables: {
tag_name: '標籤名稱',
first: '每次請求數量',
after: '這次請求要接在哪個之後'
}
}

到此為止,小瑜除了 query_hash 之外還沒釐清的就是

  1. first 跟請求數量有一定關係但不是等號且請求數量有限制 (FB上世修大大提到 first 代表的是行數,一行三個所以數量約略是 first*3)
  2. after 所代表的 end_cursor 跟 post 本身有一定關係但也不是等號,有可能是利用 post 裡 shortcode 加密後算出來的 hashcode。

但小瑜今天也蠻滿足了,好充實的感覺!!肚子也餓了,今天就到這邊吧。

這篇比較粗淺,主要是把如何爬網頁的思路跟大家分享,沒涉及到 Instagram Graph API、Token、Puppeteer 等等比較深入或是進階的爬蟲技巧,希望可以開個頭拋磚引玉,期待其他更專業的爬蟲文章 : )

這邊打個廣告,海外求生的工程師是我們剛創立的粉絲團,歡迎大家按讚一下 ❤

--

--