如果要找出一個我過去幾年工作中比較具有代表性的東西, 想了一下, 應該就是social feed這東西了(寫這篇時, 想了一下該用啥名詞, 以往我會叫Timeline, 不過Social feed應該更為貼切一點), 趁現在才剛離職有些時間, 把這些東西整理一下, 主要還是以以前做過的東西的概念為主, 希望沒忘掉太多

這系列打算由三篇來構成, 除了這篇概念篇外, 另外還會有兩篇比較細節一點的內容, 分為client和server的部分(之後寫完會再更新鏈結):

  1. 淺談Social Feed - 多服務彙整式的social feed (Client)
  2. 淺談Social Feed - 後端架構實作 (Server)

什麼是Social feed?

如果要簡單的來解釋, 應該可以說是一條依時間線排序的社群動態, 來看看下面這張Twitter的畫面:

這算是一個簡單的例子, 基本上就是把社群動態一條線的排下來(所以之前我也比較習慣的把它叫做Timeline, 不過這邊會改叫Social feed是因為考慮到也有不是依時間排序的), 我不太確定最早是誰採用這樣的設計, 找了一些早期社群網站(Frienster, Myspace), 最早都還沒有這樣的設計:

Friendster (2002 原來那時候有繁中版?!):

Myspace (2003):

即使是Facebook也要到2008年才有這樣的雛形(看下面這段video還蠻有趣的, 我好像也是那段時間開始跟這東西結下了孽緣)

再看看2006年的Twitter, 似乎就比較像是一個雛形的樣了, 不過那時似乎還像是一個粗劣的網站

當然也不是所有的Social feed都是由上而下的, 另一個有名的變形就是創了河道的Plurk, Plurk在2008創立, 在台灣紅了一陣子, 但在智慧型手機的浪潮沒跟的很好, 到了手機上就很難發揮河道這樣的特色了

現今的設計

現在這時代, 智慧型手機當道, mobile first曾經流行過一陣子, 大家放很多心力在手機上, 但Social feed這東西, 到了手機上, 各家變化就不太大了, 大致上都很類似, 來看幾個手機上的範例:

Facebook:

Twitter:

Google+:

Linkedin:

Instagram

新浪微博

Pinterest

這邊可以見到的是Pinterest採取了一個跟其他人不同的呈現方式, 但, 大體上的構成還是跟大家都相似的

另外可以發覺的是, 從2006在PC Web到現在2016手機上, 內容變複雜了, 從純粹文字到多媒體內容, 我個人其實不是那麼愛這種轉變, 因為要接收的資訊變多了, 雖然畫面變豐富更多, 但另外帶來的一個缺點, 尤其是在手機上, 螢幕已經不夠付載一則動態的資訊量了

使用者介面構成與行為

先以Facebook的介面當做例子來解釋(其他各家都大同小異啦):

大致上可以分做為三部分 - 作者資訊(藍色, 黃色區域), 內文資訊(綠色, 紅色, 咖啡色區域), 社群互動功能(紫色區域)

作者資訊 (藍色, 黃色區)

光字面上意思就已經表達完這部分了, 大致上都是放作者的圖像跟ID(或名字), 在Twitter因為還有轉推的動作, 所以還包含了轉貼人的資訊, 近年比較流行的設計是會用圓形的頭像(像上面例子的Instgram, 微博, Google+), 圓形的頭像大半在client app要顯示時處理掉就好, 只是一個圓形的mask, 就如同我以前寫過這篇:

圓形大頭貼 - 使用Picasso的Transformation

發布時間 (綠色區域)

一般說來, 發布時間這部分, 不會直接用絕對時間(幾年幾月幾日幾時幾分), 而是用"3分鐘前", “4天前"這樣的絕對時間, 這樣的顯示方式似乎就已經是一個約定俗成的默契了

這種時間格式有個好處, 不用管時差問題, social feed的內容可能來自於世界各地不同的朋友, 每個人時區不同, 與其轉成當地時區的時間格式, 還不如以這種方式表示來得直接一點, 也不用管字串會不會有太長的問題

做這樣一個東西, 也是不用重新造輪子啦, 已經有了moment.js這樣方便的東西可以用了, 當然他也是有被移植到Javascript以外的, 比如說SwiftMoment

內文以及相關資訊(紅, 橘, 咖啡區域)

早期的social feed, 內容大多只有文字, 就算有連結的轉貼, 也只是多一個hyper link, 整體上讀起來還是文字, 接下來圖片被帶入後, 就變成有圖文夾雜格式出現, Facebook這種通用的social network服務, 內容種類較多, 因此就會夾雜不同格式的內容, 除了純文字內容外, 還有圖片, 影像, 甚至, 現在多了個直播, 而像是Instagram這類以影像為主的, 格式就較為統一, 不過, 基本上也只是不同內容的內文顯示格式略有不同外, 在後面的資料結構理應大同小異

後來可能人們(不見得是使用者吧)不再滿足於單調的內容, 尤其是在社群網路上分享文章鏈結(像是分享新聞的)越來越多, 一堆超連結看起來也醜, 後來就出現了Twitter Card 和 Open graph這類的東西:

  1. Open graph
  2. Twitter Card

這進一步讓你可以去定義你自己的網站, 而社群服務像是Facebook再把你的網站當作一個物件, 以物件的類別來決定怎麼去呈現這個鏈結, 在視覺上就再更加的豐富

不過不管內容有多少種, 差別真的就是呈現方式的多寡, 呈現方式也是有限的, 在Client顯示設計上是可以設計靈活點可以擴展, 不過倒也不用考慮到會有無窮無盡的形式

另外跟內文相關的資訊常見的還會有喜歡這個動態的數目(Facebook還有多種情緒表示), 回文數目, 分享數目(不見得每個服務都有), 使用者可以透過這些數字來了解到這篇貼文的熱門程度, 但這些數字, 其實在大部分的服務裡都只能單參考用, 數字未必準確, 這是因為一來很難及時地把某則貼文按讚的狀態更新給所有人(對Server的負擔大, Client實作也複雜), 或許在視覺上可以用一些比較相對的表示方式而非絕對數量的表示

另外有些服務會節錄幾則(通常最多三則)回文跟著文章下面一起顯示, 像Facebook網頁版, 但一樣, 它也是難於即時的更新

社群互動功能 (紫色區域)

一般常見就是“喜歡(like)”, “回文(comment)”, “分享(share)”, 社群網路的精神主要還是在互動跟分享, 因此這幾個也差不多是最精簡也必備的了

更進階一點的內容

通常還會有所謂的 hashtag和mention (這邊以Twitter用詞)

所謂的hashtag是由User自訂, 跟這則內文相關的關鍵詞, 以"#“開頭, 差不多也是個約定俗成, 最早應該早在IRC時代就有在使用了, 什麼?沒聽過IRC?沒關係, 知道從很古早時代就有了, 設計上通常會把 #hashtag 作成連結的形式, 點下去顯示相關的文章

而mention指的是"提到"某某人, 所以通常的形式都是”@“後面加User ID, 這也已經是一種約定俗成的方式了, 設計上也都會是一個點下去就到那個人的資訊頁面的連結

時間線回朔

這部分我們以前都把它稱作"load more”, 不過覺得這樣講好像很難知其所以然, 先看一下圖:

一般來說, 從Server端抓回來的文章不會一次傳回所有的, 因為那會實在太多了, 尤其對重度使用者來說, 從開始使用以來到現在可能為數不少, 因此當我們把整social feed (或time line或說stream)往下一直拉時, 總是會見底的

在以往的UI設計上大多會放一條"touch to load more"之類的讓使用者再讀取舊一點的資料(所以以往我們都會把它叫做load more), 但這樣的缺點是使用者體驗不會太好, 通常看到這條後就跳掉不看了, 因此後來就流行做成上圖那樣, 快拉到最下面時就預先抓取, 來不及抓完, 使用者就會看到轉圈圈的進度

最好的體驗應該是讓使用者無縫接軌, 可以一直一直往下拉不用中斷, 但這邊就存在有調整的空間了, 太晚觸發的話, 使用者滑到最下面還是會有等待時間, 等待時間只要一長了, 常常就沒耐性跑了, 所以如果可以提早一點抓取, 是可以減少拉到最下面的等待時間, 但到底要提早多少? 太早也不是一件好事, 可能會導致client太過頻繁跟server索取資料, 但實際上又用不到那麼多, 以至於浪費了太多的網路傳輸量, 以及增加了server的負擔, 但使用者滑動的速率每個都不同, 所以這是一件不好拿捏的事, 可能要經過多次試驗才會有比較好的體驗

資料更新

這比較會出現在有背景更新的場合, 如果每次使用者要看最新內容要自己觸發更新, 更新結束前他不能做任何動作, 那就沒這問題, 這問題主要出現在使用者在瀏覽過程中, 背景更新有了新資料進來, 輕微的話, 資料從最上頭插入導致他正在看的位置跑掉了, 嚴重的話, 可能整個刷新後, 內容都不同了, 這當然對使用者體驗很不好, 現在大部分的設計都會設計成非同步更新, 也就是就算是由使用者觸發, 更新時, 使用者還是有機會做動作, 更新時間如果太長了, 就容易發生這狀況

這部分的解法通常像下圖Twitter的做法:

不直接刷新頁面, 而是先提示使用者有新的貼文, 這樣的感覺就好多了

聚合式的social feed

所謂的聚合式的, 就是把一堆Social service的feeds全部串在一起, 因為多數的使用者擁有不止一個社群網路帳號, 把所有放在一起在瀏覽上就不用一個個網站跑或一個app跳過一個app

最早著名的有Friendfeed, 它早已經被Facebook買下不存在了, 不過它就是這樣一個概念:

另外手機上還有HTC的Friend stream, 不過這個血和汗做來的產品也不在了 orz

(好, 我拿chacha的畫面的確是故意的 :P)

另外還有一個叫做HootSuite的也是類似:

不過HootSuite跟前兩者有所不同的地方是它並未把Social feed全部整合在一個時間線, 而只是並列顯示, 全部整合的難度稍高, 這就留待下一篇來說明了

小結

整理了這麼多, 是後面兩篇的前置, 這邊的概念等於是設計一個social feeds的"需求", 後面兩篇會再用到這些概念

做一個網路服務, 使用者驗證是蠻麻煩的一件事, 我們是可以裝作沒看到, 不做驗證, 但這樣的下場就是有假使用者, 有殭屍, 最簡單的方式是信任第三方服務像是Google, Facebook, 現在的人大多數都有Google, Facebook帳號了, 這樣其實也沒多大的問題, 但還是還是有人沒有, 而且也不是每個人都放心把Facebook帳號交給我們, 因此退而求其次就是用E-mail, 用E-mail認證雖然也是一個好方式, 但還是要建置整套發信機制(或是花錢買mailgun來送信), 而且在手機上就麻煩了, 來回在App跟e-mail間切換很不方便, 因此就會想用簡訊認證, 至於簡訊認證, 除了一個"貴"字以外, 要搞定各個國家的也是一個麻煩(當然, 花錢可解, 有Twilio這種服務可以用)

所幸有Facebook提供的這個可以用Account Kit, 在初期使用者不太多的時候, 不收費的確很吸引人呀(雖然他不是唯一一個這樣的服務, 之後再介紹其他的), 但由於他iOS的範例是用Objective C寫的, 我的Objective C實在不太行, 加上, 要了解一個東西, 寫一遍就知道了, 所以順手翻譯了一個Swift的版本, Source如下:

Account Kit samples for Swift

原本的版本, 我是覺得寫的不是太好, 花了好一些功夫看, 自己翻過來的這個版本, 也還沒debug過, 基礎的應該堪用啦! 至於iOS的account kit文件可以參考: iOS 專用 Account Kit

註: plist裡面寫的app id不是我的喔!是原本Sample用的

雖然很久沒用box.com的服務了, 不過既然老婆大人問起, 就來寫一下這解法吧

box.com是一個像Dropbox一樣的網路磁碟, 不過它目標客戶跟Dropbox不同, 是比較傾向企業用戶, 可以讓用戶很簡單的分享檔案, 存取box.com除了一般使用Web介面的方式外, 還有其他的方式, 像是透過它的REST API, 另外還有一種就是透過WebDav, 如果要寫程式去存取它, 一般可以用這兩種方式, 用REST稍微複雜一點, 還要搞定OAuth2的部分, 但透過WebDav的話就簡單多了, 可以掛載成為你作業系統底下的目錄, 當本地檔案來處理

安裝davfs2

首先, 你會需要的是davfs2, 在Ubuntu下用apt-get安裝:

sudo apt-get install davfs2

設定帳號密碼

修改/etc/davfs2/secrets, 加入

https://dav.box.com/dav box.com帳號 密碼

掛載

執行底下指令掛載

sudo mkdir /mnt/box.com
sudo -t davfs https://dav.box.com/dav /mnt/box.com

成功之後,就會在/mnt/box.com底下看到你的檔案了(包含人家分享給你協作的檔案根目錄), 之後當本地端檔案存取即可, 設定好auto mount即可在開機後掛載

小時候很喜歡Indiana Jones系列的電影, 對於它裡面的地圖片段也一直覺得很有趣

如果這樣的動畫, 用在遊記類的blog上, 應該也蠻酷的, 但好像也沒一個比較好的工具, 因此想說用MapKit來實作一個試試

功能需求

先來定義一個簡單的功能需求

  1. 在起點跟終點畫一條連結線
  2. 一架飛機延這條線飛到終點
  3. 地圖視角跟著飛機走

實作

在起點跟終點畫一條連結線

這部份要用到MKGeodesicPolyline, 給它兩個點, 它就會自動連結成一條線, 但這條線並不是完美的直線, 因為地球表面是曲面的, 所以它是一條弧線

let coords = [start, end]

geodesicPolyline = MKGeodesicPolyline(coordinates: coords, count: 2)

print(geodesicPolyline!.pointCount)
mapView.add(geodesicPolyline!)

這邊coors只要給訂起始點跟終點的位置就好, 印出pointCount就會發現它把經由的點都補足了(實際上印出來的會多出2很多很多)

MKGeodesicPolyline裡的參數coordinates是一個UnsafeMutablePointer, 在Swift 3之前要寫成&coords, 但在Swift 3大改之後, “&“就不需要了

由於MKGeodesicPolyline是一個Overlay, 因此最後只需要用mapView.add (Swift 3之前是addOverlay)加入mapView就可以了, 但加入之後, 會發現, 這條線根本沒被畫出來, 那是因為少寫了一部分

在MapView裡面要畫出Overlay, 就必須要跟MapView說怎麼畫出這個Overlay, 這就要實作MKMapViewDelegate裡的mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay)

func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    guard let polyline = overlay as? MKPolyline else {
        return MKOverlayRenderer()
    }
    
    let renderer = MKPolylineRenderer(polyline: polyline)
    renderer.lineWidth = 3.0
    renderer.alpha = 0.5
    renderer.strokeColor = UIColor.blue
    
    return renderer
}

由於我們是要畫Poly line, 因此這邊回傳給它一個MKPolylineRenderer, 線寬是3.0, 線的顏色是藍色(alpha = 0.5)

這樣就可以很完美的畫出那條線了

在地圖上畫出飛機

這部份就要借重到MKPointAnnotation

let thePlane = MKPointAnnotation()
thePlane.coordinate = start //給定起始座標
        
mapView.addAnnotation(thePlane)

一樣, 這邊如果沒告訴MapView怎畫這Annotation, 它是會用預設的取代, 因此我們一樣要去實作MKMapViewDelegate

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    let planeIdentifier = "Plane"
    
    let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: planeIdentifier)
        ?? MKAnnotationView(annotation: annotation, reuseIdentifier: planeIdentifier)
    
    annotationView.image = UIImage(named: "ic_flight_48pt")
    
    return annotationView
}

這邊用一個UIImage來指定飛機的圖標

地圖視角

把飛機置於地圖正中央, 我們才看得到他, 因此, 需要設定可視的區域, 包含中心點跟範圍, 如下:

let span = MKCoordinateSpanMake(8.0, 8.0)
let region1 = MKCoordinateRegion(center: start, span: span)
self.mapView.setRegion(region1, animated: true)

沿著線飛

這時候要利用到 perform(#selector(updatePlane), with: self, afterDelay: 0.4) 讓它每隔0.4秒去更新一次飛機位置, 直到到終點為止, 當然也要一直更新region, 以讓飛機維持在正中央

func updatePlane() {
    planePositionIndex = planePositionIndex + step;
    
    if (planePositionIndex >= geodesicPolyline!.pointCount)
    {
        //plane has reached end, stop moving
        planePositionIndex = 0
        return;
    }
    
    let s = 8.0
    
    let nextMapPoint = geodesicPolyline?.points()[planePositionIndex]

    thePlane.coordinate = MKCoordinateForMapPoint(nextMapPoint!) 
    mapView.region = MKCoordinateRegionMake(thePlane.coordinate, MKCoordinateSpan(latitudeDelta: s, longitudeDelta: s))

    perform(#selector(updatePlane), with: self, afterDelay: 0.4)
}

結果

這邊有幾個缺點尚待改進

  1. 飛機閃爍
  2. 如果把可視區域範圍縮小, 或是位置更新過快, 就會造成地圖來不及載入的現象(可能需要預跑幾次將map tile載入cache內)
  3. 飛機頭永遠保持往上, 應該要朝著線方向轉

完整程式碼

之前寫了一些爬蟲, 想說來補一篇這樣的文章好了

可能是之前"所謂"的大數據(Big Data)太過流行, 以至於網路爬蟲好像是一種顯學, 隨便Google一下都可以找到一堆用python加上BeautifulSoup 相關的文章, 這可能也是因為, 現在網路上的資料, Open data的, 提供API的, 在比例上還是非常的少數, 但網頁的數量真的多到很難統計(想到早期還真是屈指可數) 要取得網頁內的內容, 解析HTML, 做字串的處理就是一個必要的基礎, 這也難怪python + BeautifulSoup 一直廣泛的被採用

不過, 當你真的去寫一個爬蟲, 突然就會發現了, 代誌不是這麼單純呀, 不是去抓個html回來解析一下就有自己想要的資料了呀, 而是會發現, 怎麼大家正正當當的網頁不寫, 一堆奇技淫巧, 繞來繞去的, 網頁廣告內容也一堆, 很多網頁都很難抓到自己想要的資料呀!!!

真的來說, 做爬蟲是一種Hacking的活動, 天下網頁萬萬種, 為了總總不同的目的, 早已不是幾個簡單的html標籤就做出來的了, 還搭配了很多程式的技巧在內, 因此要從裡面萃取資料出來, 常常還真的是要無所不用其極

那到底做爬蟲會先需要懂什麼?

  1. HTML
  2. CSS
  3. DOM
  4. DOM Selectors
  5. Javascript
  6. Regular Expression (正規表示式)
  7. Chrome dev tool
  8. Curl

以上這幾個東西可能是基本必須懂得, 而不是程式語言, 那反而其次, 很多程式語言都有很好的能力跟工具來做, 另外需要具備的是耐心, 眼力, 直覺, 和運氣

底下就拿我之前弄過的幾個東西當範例, 由於我寫比較多Go和nodejs, 所以就不用(主流的)Python來做範例了

範例一 : 文章內容分析 (如新聞, 部落格文章等等)

這應該是比較常見的應用, 單純抓取文章內容去做分析, 常碰到的麻煩是現在網站放了大大小小的(補釘)廣告, 那些跟內容不相干, 也不會是我們想拿來分析的目標

底下這個範例是從Yahoo! News的RSS抓取新聞文章連結, 再從這些連結的文章內找出關鍵字:

這段code非常的簡單, 主要也只用到以下這幾種東西:

  1. rss parser
  2. go-readability
  3. 結巴

簡單的來說就是先從rss內找出所有新聞的連結再一個個去爬, 然後用go-readability精簡出網頁內文, 再用結巴取出關鍵字

readability

這一個library以往的用途就是清除不必要的html tags跟內容(像是廣告), 只留下易讀的內文(純文字), 以這個例子來說, 這是一個最適合的工具了

最早的readability應該是arc90這人開源出來的, 最早應該是javascript的版本, 但就算你不是用javascript, 它老早也被翻唱成其他語言的版本了, 像是:

  1. Python - python-readability
  2. Node.js - node.js readability
  3. Java - JReadabilitysnacktory
  4. Go - go-readability

結巴 jieba

結巴 jieba 是一個用在做中文分詞的工具, 英文每個單詞都是用空白分開的, 但中文就不是那麼回事了, 結巴 jieba 可以幫忙作掉這部份的工作, 這可以拿來做文章分析或是找關鍵字用

一樣有好幾種語言的版本 - Java, C++, Node.js, Erlang, R, iOS, PHP, C#, Go (參照結巴的說明內文), 算蠻齊全的了

範例二 : 抓取Bilibili的視訊檔位址

這個範例稍微做了點弊, 但還是從頭把分析過程來講一下好了

Bilibili視頻網頁長得就像這樣: 範例 - http://www.bilibili.com/video/av6467776/

先簡單的從網址猜一下….“av6467776"應該是某個ID之類的東西, 再進一步, ID可能就是這個"6467776”

接下來我們就需要借助一下Chrome的"開發人員工具", 這是一個強大也重要的工具, 不要只傻傻的用View source而已, View source能看到的也只有原始HTML的內容

開了網頁後, 用Ctrl+Shift+I (windows)或是Cmd+Opt+I (mac)打開他, 打開後先選到elements, 像這樣:

Chrome devtools elements

這邊標示了四個部分, 先點選了1, 再用滑鼠游標點你想知道的元件(以這邊來說是那個視訊框 2 的地方), 然後他就會幫你跳到相關的HTML位置(如3), 而4所標示出的是css屬性

把object這部份點開, 果然, 在flashvars那邊我們可以找到"cid=10519268&aid=6467776&pre_ad=0"這樣的字串, 表示"6467776"的確是某種叫aid的東西, 但, 這不代表找到結案了!! 我們再用curl檢查一下:

curl http://www.bilibili.com/video/av6467776/ --compressed

抓原始的html檔來比對一下(可以把這指令的輸出存成檔案在來看會方便點), 怎麼沒"object"這標籤呀?到哪去了?可見剛剛那段html是某段javascript去產生的

再回頭看DevTools上object那段, 可以發現它是一個div包起來的, 這div的class是scontent, id是bofqi, 再回頭去看原始的HTML, 整段也只有一個這樣的block, 內容是這樣:

    <div class="scontent" id="bofqi">
    <div id='player_placeholder' class='player'></div>
<script type='text/javascript'>EmbedPlayer('player', "http://static.hdslb.com/play.swf", "cid=10519268&aid=6467776&pre_ad=0");</script>
    </div>

OK, 這邊就很容易可以確定從scontent這區塊就可以找到兩個id - aid和cid , 但這能做什麼用? 還不知道

接著切換到Network那邊去, 然後再重新整理一下頁面:

Chrome devtools network

左下角的部分是瀏覽器在畫出這個頁面所載入的內容跟檔案, 一開使用時序排序的, 當然你可以用其他方式排序, 這邊就是可以挖寶的地方了

先看到上面那一堆長長短短的線, 這是瀏覽器載入檔案的時間線, 點選這邊可以只看特定時間區間的部分, 最後面可以發現有一條長長的藍線, 那可能就是視訊檔了(因為通常較大), 因此我們可知這視訊檔的URL是

http://61.221.181.215/ws.acgvideo.com/3/46/10519268-1.flv?wsTime=1475663926&wsSecret2=893ba83b8f13d8700d2ae0cddab96c55&oi=3699654085&rate=0&wshc_tag=0&wsts_tag=57f467b8&wsid_tag=dc843dc5&wsiphost=ipdbm

但這串怎麼來的, 依我們手上只有兩個id的資訊是拼湊不起來的, 它一定從某個地方由這兩個id轉換出來的(合理的猜測), 因此, 我們可以再把aid, cid拿去搜尋檔案(點選檔案列表那區塊, 按ctrl-f或command-f開搜尋框)

一個個看, 找出可能的檔案, 由於aid可以找出22個結果而cid只有10個, 從cid開始找起會比較簡單點, 這邊篩選出幾個可能性:

  1. http://interface.bilibili.com/player?id=cid:10519268&aid=6467776 - 回傳是一個xml, 有一些相關資訊, 但沒影片位址
  2. http://interface.bilibili.com/playurl?accel=1&cid=10519268&player=1&ts=1475635126&sign=e1e2ae9d2d34e4be94f46f77a4a107ce - 從這回傳裡面有個durl > url, 跟上面url比對, 似乎就是他了, 後面再來講這段
  3. http://comment.bilibili.com/playtag,10519268 - 這應該是"看过该视频的还喜欢"裡的內容
  4. http://comment.bilibili.com/10519268.xml - 喔喔喔, 看起來這就是彈幕檔喔!
  5. http://comment.bilibili.com/recommendnew,6467776 - 這似乎是推薦視頻的內容
  6. http://api.bilibili.com/x/tag/archive/tags?jsonp=jsonp&aid=6467776&nomid=1 - 看起來這是tag

看來, 2 應該就是我們所要的了, 不過這邊有兩個麻煩, 一個是…我討厭XML!!!!!不過這好像還好, 似乎有個HTML5版本, 點點看好了, 果不其然, 發現另一個:

https://interface.bilibili.com/playurl?cid=10519268&appkey=6f90a59ac58a4123&otype=json&type=flv&quality=3&sign=571f239a0a3d4c304e8ea0e0f255992a

表示我們是可以用otype=json來抓取json格式的, 但後面這個更麻煩了, 那個sign是什麼東西? 從他有個appkey來看, 合理的猜測, 他是某種API的signature, 通常這種東西的規則是把所有的參數先依名字排序成新的query string, 加上某個secret, 再算出他的MD5即是他的sign

但如果真是這樣, 這下有點麻煩, 到哪裡找這個secret, 不過凡走過必有痕跡, 這串既然是由瀏覽器端產生的, 那應該會在哪裡找到點線索, 或許可以先用appkey的內容去每個javascript檔案搜尋吧

不過, 不出所料, 找不到, 那, 還有一個可能性, 它寫在flash內, 從上面抓到的資訊來看, 他的flash檔案應該是: http://static.hdslb.com/play.swf

可以把它抓回來反編譯(decompile), 有個工具叫JPEX Flash decompiler的, 可以做到這件事

JPEX

在script裡面有它的程式原始碼, 應該可以在裡面找到, 不過有點辛苦, 因為你也沒辦法從appkey找到那個secret, 這邊直接跳轉答案, 應該就如同截圖所示是"com.bilibili.interfaces.getSign"這邊, 只是被混淆到很難看, 看得很頭痛, 會短命的

理論上, 把這段源碼翻譯一遍後應該就解決了, 但一來我看不太懂action script, 二來我實在懶得看, 想偷懶, 有沒作弊的方法? 凡走過必留下痕跡嘛, 一定還會有前人走過這條路, 所以直接把"6f90a59ac58a4123"這串appkey拿去搜尋, 果然, 找到secret了, 寫個程式驗證一下, 果然是沒錯的

範例三: 楓林網

楓林網是一個非法的電視劇來源, 有相當齊全的電視劇內容, 我這個是之前寫的一個工具了, 可以把一整部劇可以抓回本地端, 原始碼跟使用方法在這:

這邊就不再說明怎麼使用它了, 主要著重它是怎做出來的, 這一個跟上面幾個不一樣的地方在於我是用nodejs寫的, 之所以用nodejs是有原因的, 後面再解釋, 主要的程式碼在88.js

楓林網的電視劇集的網址長成這樣: http://8drama.com/178372/ , 當然178372又是ID了, 但它裡面所有的東西ID長得都是一個樣, 一整部劇跟單一集的ID都是同一個格式, 所以是無法從ID判斷出他是哪類

一樣打開Chrome DevTools, 點選到每一集的列表區塊, 我們可以發現在"“裡面的可以找到每一集的URL, 而他的格式都是http://8drama.com/(ID), 所以我們可以輕易的用regular expression分辨出來

接下來就要掃每一集的內容把影片檔找出來了

當然可以用前面的方法試試看, 不過那方法在這邊沒啥用, 因為這邊影片的網址編碼是用好多亂七八糟的javascript堆積起來, 沒記錯的話, 我是從video.js找到線索的(這有點時間之前做的了, 有點沒印象了)

所以該怎麼面對那一大段javascript的code呢? 這就是我這工具為何選nodejs的原因了, 抄過來就好了!!! 去除掉跟瀏覽器相關的部分, 它就是不折不扣nodejs可以跑的程式碼, 所以這也就是88.js一些怪怪程式碼的由來

除了這版本外, 我本來也有想要做一個Java的版本, 做了一半, 用Java也是可以用類似的技巧, 不過就得要導入Javascript的runtime了, 而我採用的是Mozilla Rhino

更進階一點的

做爬蟲大部分的時間都是要跟html, javascript, css這類的東西搏鬥, 但其實很多東西本來就是原本瀏覽器就會處理的, 因此如果可以直接用瀏覽器或甚至是WebKit來處理, 可以處理的事應該就會多一點, 這時候就可以利用所謂的Headless browser來簡化, 這種東西本來是用在網頁自動化測試的, 不過, 用在這應用也一樣強大, 這類的解決方案有:

  1. Phantomjs - 這是以WebKit為基礎的, 我比較常用
  2. Slimerjs - 這是以Mozilla Geco為基礎的
  3. Selenium
  4. Webloop - Go版本的

關於Phantomjs的應用, 我很久之前已經有寫過一篇了:

颱風天的宅code教學: 抓漫畫

Android上呢?

Java上, 大多是用jsoup來解析html的, 像我之前這篇:

[Android] 土製Play store API

當然應該也是可以用Webkit/WebView做成headless的解決方案, 不過這部份我目前還沒試過, 留待以後有機會再試吧