还是密钥。。。
转载:
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, 像這樣:
這邊標示了四個部分, 先點選了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那邊去, 然後再重新整理一下頁面:
左下角的部分是瀏覽器在畫出這個頁面所載入的內容跟檔案, 一開使用時序排序的, 當然你可以用其他方式排序, 這邊就是可以挖寶的地方了
先看到上面那一堆長長短短的線, 這是瀏覽器載入檔案的時間線, 點選這邊可以只看特定時間區間的部分, 最後面可以發現有一條長長的藍線, 那可能就是視訊檔了(因為通常較大), 因此我們可知這視訊檔的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的, 可以做到這件事
在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