抓取b站视频地址

还是密钥。。。

转载:
http://blog.jln.co/%E4%B8%80%E4%BA%9B%E5%81%9A%E7%88%AC%E8%9F%B2%E7%9A%84%E5%B7%A5%E5%85%B7%E8%88%87%E6%96%B9%E6%B3%95/

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

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, 像這樣:
抓取b站视频地址_第1张图片

這邊標示了四個部分, 先點選了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那邊去, 然後再重新整理一下頁面:

抓取b站视频地址_第2张图片

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

先看到上面那一堆長長短短的線, 這是瀏覽器載入檔案的時間線, 點選這邊可以只看特定時間區間的部分, 最後面可以發現有一條長長的藍線, 那可能就是視訊檔了(因為通常較大), 因此我們可知這視訊檔的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開始找起會比較簡單點, 這邊篩選出幾個可能性:

http://interface.bilibili.com/player?id=cid:10519268&aid=6467776 - 回傳是一個xml, 有一些相關資訊, 但沒影片位址
http://interface.bilibili.com/playurl?accel=1&cid=10519268&player=1&ts=1475635126&sign=e1e2ae9d2d34e4be94f46f77a4a107ce - 從這回傳裡面有個durl > url, 跟上面url比對, 似乎就是他了, 後面再來講這段
http://comment.bilibili.com/playtag,10519268 - 這應該是”看过该视频的还喜欢”裡的內容
http://comment.bilibili.com/10519268.xml - 喔喔喔, 看起來這就是彈幕檔喔!
http://comment.bilibili.com/recommendnew,6467776 - 這似乎是推薦視頻的內容
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是什麼東西? 

表示我們是可以用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的, 可以做到這件事

抓取b站视频地址_第3张图片

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

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

package main

import (
    "crypto/md5"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "log"
    "regexp"
    "sort"
    "strings"

    "github.com/PuerkitoBio/goquery"
    "github.com/parnurzeal/gorequest"
)

type DocId struct {
    CID string
    AID string
}

const (
    APP_KEY  = "6f90a59ac58a4123"
    APP_SEC  = "0bfd84cc3940035173f35e6777508326"
    BASE_URL = "https://interface.bilibili.com/playurl"
)

func signParam(param map[string]string) {
    param["appkey"] = APP_KEY

    keys := make([]string, len(param))

    i := 0
    for k, _ := range param {
        keys[i] = k
        i++
    }

    sort.Strings(keys)

    data := ""
    for _, v := range keys {
        if data != "" {
            data += "&"
        }
        data += v + "=" + param[v]
    }
    data += APP_SEC
    param["sign"] = fmt.Sprintf("%x", md5.Sum([]byte(data)))
}

func parseId(id string) (*DocId, error) {
    url := fmt.Sprintf("http://www.bilibili.com/video/av%s", id)
    doc, err := goquery.NewDocument(url)

    if err != nil {
        return nil, err
    }

    sel := doc.Find(".scontent>script")
    if sel != nil {
        script := sel.Text()
        regex := regexp.MustCompile("cid=(\\d+)&aid=(\\d+)&pre_ad=.*")

        result := regex.FindStringSubmatch(script)
        if result == nil {
            return nil, errors.New("ID not found")
        }

        var id DocId
        id.CID = result[1]
        id.AID = result[2]

        return &id, nil
    } else {
        return nil, errors.New("ID not found")
    }
}

func GrabVideoUrl(aid string) (string, error) {
    id, err := parseId(aid)

    if err != nil {
        return "", err
    }

    var param map[string]string = make(map[string]string)
    param["cid"] = id.CID
    param["otype"] = "json"
    param["type"] = "mp4"
    param["quality"] = "3"

    signParam(param)

    data := ""
    for k, v := range param {
        if data != "" {
            data += "&"
        }
        data += k + "=" + v
    }

    url := fmt.Sprintf("%s?%s", BASE_URL, data)
    request := gorequest.New()
    _, body, errs := request.Get(url).End()

    if errs != nil && len(errs) > 0 {
        return "", errs[0]
    }

    //log.Println(body)
    dec := json.NewDecoder(strings.NewReader(body))
    video_url := ""
    for {
        t, err := dec.Token()
        if err == io.EOF {
            break
        }

        if err != nil {
            return "", err
        }
        //fmt.Printf("%T: %v", t, t)
        if t == "url" {
            t, err := dec.Token()
            if err != nil {
                return "", err
            }
            video_url = fmt.Sprintf("%v", t)
        }

        //fmt.Printf("\n")
    }

    return video_url, nil
}

func main() {
    //url := "http://www.bilibili.com/video/av6467776/"
    url, _ := GrabVideoUrl("6467776")
    log.Println(url)
}
view rawbgrab.go hosted with ❤ by GitHub

你可能感兴趣的:(b站,爬虫)