戰(zhàn):APP接口與簽名機(jī)制詳解)
簡(jiǎn)介day17-B站播放量完播率破解代碼是一套針對(duì)嗶哩嗶哩移動(dòng)端的數(shù)據(jù)采集方案主要面向APP爬蟲(chóng)與接口逆向?qū)W習(xí)者幫助解決播放量、完播率等指標(biāo)難以直接獲取的問(wèn)題。壓縮包共11個(gè)Python腳本整體僅8KB包體雖小但流程完整從端口轉(zhuǎn)發(fā)與Hook抓包入手逐步定位AES密鑰、SHA256鹽值與明文再依托Python還原sign簽名和請(qǐng)求體加密最終獲取視頻aid/cid并整合為可運(yùn)行代碼。腳本按步驟拆分便于逐段調(diào)試與理解完整展示了移動(dòng)端爬蟲(chóng)從數(shù)據(jù)抓取到加密破譯的典型路徑。整套代碼覆蓋了APP爬蟲(chóng)中常見(jiàn)的逆向分析思路尤其適合希望掌握動(dòng)態(tài)加載處理、反爬繞過(guò)和加密參數(shù)還原技巧的開(kāi)發(fā)者參考。目前已有239人學(xué)習(xí)下載既可研究B站接口調(diào)用邏輯也能作為自制爬蟲(chóng)框架的骨架但使用時(shí)需注意遵循相關(guān)法律法規(guī)與平臺(tái)規(guī)則。1. B站播放量完播率怎么抓盯這兩個(gè)數(shù)的人十有八九卡在同一個(gè)地方做過(guò)內(nèi)容分析或賬號(hào)運(yùn)營(yíng)的人都知道播放量是面子完播率是底子。播放量是平臺(tái)推流后的結(jié)果值完播率才是判斷內(nèi)容能不能被繼續(xù)推的因變量。可問(wèn)題是網(wǎng)頁(yè)端的創(chuàng)作者后臺(tái)能看到完播率但那只能是自己的視頻想看競(jìng)品、想看某個(gè)品類(lèi)下的爆款規(guī)律、想批量拉一批視頻做回歸分析網(wǎng)頁(yè)端根本不給這個(gè)字段。于是很多人的目光轉(zhuǎn)向APP端的數(shù)據(jù)通道——那里接口字段全更新及時(shí)也是這份名為“day17-B站播放量完播率破解代碼”的資源真正要做的事不靠人工一個(gè)個(gè)點(diǎn)開(kāi)視頻猜數(shù)據(jù)而是用APP爬蟲(chóng)的思路把播放量和完播率從接口層穩(wěn)定地?fù)瞥鰜?lái)落成結(jié)構(gòu)化數(shù)據(jù)。適合誰(shuí)做視頻運(yùn)營(yíng)分析、做內(nèi)容競(jìng)調(diào)、做推薦策略研究的人以及想入門(mén)APP抓包和接口模擬的開(kāi)發(fā)者。下文把這套資源的完整拆解寫(xiě)清楚從接口選型到簽名處理到排坑照著走能少踩大半的雷。2. 先搞清數(shù)據(jù)從哪來(lái)APP接口與網(wǎng)頁(yè)接口的差異和選型2.1 網(wǎng)頁(yè)端與APP端同是B站兩套數(shù)據(jù)通道B站的網(wǎng)頁(yè)端和APP端雖然共用同一套用戶(hù)體系但數(shù)據(jù)接口的服務(wù)對(duì)象完全不同。網(wǎng)頁(yè)端面向?yàn)g覽器場(chǎng)景接口以web路徑為主特點(diǎn)是請(qǐng)求頭簡(jiǎn)單、不需要額外簽名但字段聚合程度高很多細(xì)粒度指標(biāo)被隱藏。APP端的接口路徑通常掛x或pgc前綴面向移動(dòng)客戶(hù)端字段越細(xì)越是運(yùn)營(yíng)后臺(tái)不愿意批量開(kāi)放的數(shù)據(jù)越可能藏在APP接口里。我拆過(guò)不少視頻類(lèi)APP的數(shù)據(jù)通道有一個(gè)經(jīng)驗(yàn)可以共享網(wǎng)頁(yè)端接口給的是“展示數(shù)據(jù)”APP接口給的是“行為數(shù)據(jù)”。播放量、彈幕數(shù)、點(diǎn)贊數(shù)這類(lèi)展示數(shù)據(jù)兩邊都有但完播率、人均播放時(shí)長(zhǎng)、播放入口分布這類(lèi)行為數(shù)據(jù)基本只在APP側(cè)的接口出現(xiàn)。所以如果你確實(shí)需要完播率網(wǎng)頁(yè)端這條路從一開(kāi)始就該排除。拿這份資源里的做法來(lái)說(shuō)它的抓取入口選的是APP接口體系而不是網(wǎng)頁(yè)端。核心原因就一條完播率這個(gè)字段只在APP接口返回網(wǎng)頁(yè)端拿不到。這個(gè)選型判斷看起來(lái)簡(jiǎn)單但實(shí)際是很多人卡住的第一步——用網(wǎng)頁(yè)端接口調(diào)試半天發(fā)現(xiàn)字段壓根不存在回頭才意識(shí)到接口體系選錯(cuò)了。2.2 為什么要選APP接口字段歸屬和數(shù)據(jù)粒度選APP接口不是因?yàn)樗案呒?jí)”而是因?yàn)樽侄螝w屬?zèng)Q定了能不能拿到目標(biāo)數(shù)據(jù)。完播率在B站的數(shù)據(jù)模型里屬于視頻行為統(tǒng)計(jì)維度它不是一個(gè)固定的靜態(tài)值而是平臺(tái)根據(jù)播放行為實(shí)時(shí)計(jì)算的比值。網(wǎng)頁(yè)端的接口聚合層只暴露播放量、彈幕數(shù)、評(píng)論數(shù)這些結(jié)果指標(biāo)完播率被放在用戶(hù)行為分析域里不對(duì)網(wǎng)頁(yè)端開(kāi)放。相比之下APP端的統(tǒng)計(jì)接口粒度更細(xì)一次請(qǐng)求往往能同時(shí)返回播放量、完播率、平均播放進(jìn)度、播放入口等一堆字段。而且APP接口的返回體結(jié)構(gòu)相對(duì)穩(wěn)定JSON字段名幾乎不帶混淆適合做自動(dòng)化解析。這是選擇APP接口的第二個(gè)理由解析成本低字段直接可用。當(dāng)然選APP接口也有代價(jià)——請(qǐng)求頭要模擬移動(dòng)端環(huán)境Cookie要帶登錄態(tài)部分接口還要做簽名。這些在后面章節(jié)會(huì)一步步展開(kāi)。2.3 接口訪(fǎng)問(wèn)的基礎(chǔ)請(qǐng)求頭、Cookie與登錄態(tài)模擬APP接口不像網(wǎng)頁(yè)端那么好說(shuō)話(huà)它的反爬校驗(yàn)主要集中在三個(gè)維度請(qǐng)求頭完整性、Cookie有效性、簽名正確性。三者缺一不可。先看請(qǐng)求頭。移動(dòng)端接口會(huì)校驗(yàn)User-Agent里是否帶 Android 或 iOS 標(biāo)識(shí)同時(shí)會(huì)校驗(yàn)Referer、Accept等字段。如果請(qǐng)求頭里的 User-Agent 是瀏覽器的接口直接拒掉。常見(jiàn)的做法是抓一次真實(shí)APP流量把請(qǐng)求頭完整復(fù)制下來(lái)后續(xù)所有請(qǐng)求都基于這組請(qǐng)求頭發(fā)送。實(shí)際操作中我一般會(huì)在資源里自帶一個(gè)請(qǐng)求頭模板把關(guān)鍵字段替換成變量方便根據(jù)自己設(shè)備的實(shí)際值調(diào)整。再說(shuō)Cookie。APP接口的Cookie通常包含登錄態(tài)令牌和部分業(yè)務(wù)標(biāo)識(shí)。登錄態(tài)過(guò)期是高頻問(wèn)題尤其是當(dāng)你用一個(gè)賬號(hào)頻繁請(qǐng)求時(shí)服務(wù)端會(huì)逐步收緊校驗(yàn)。處理策略一般有兩種一是定期手動(dòng)刷新Cookie填入腳本配置二是做一個(gè)簡(jiǎn)易的Cookie池多個(gè)賬號(hào)輪換。對(duì)個(gè)人分析場(chǎng)景來(lái)說(shuō)前一種夠用。簽名是最后一個(gè)坎。B站現(xiàn)在普遍用的簽名機(jī)制是動(dòng)態(tài)簽名參數(shù)里帶時(shí)間戳和一個(gè)隨機(jī)數(shù)種子。這部分是這份資源的重點(diǎn)之一下一章會(huì)單獨(dú)講。3. 寫(xiě)一個(gè)能跑的采集腳本請(qǐng)求、簽名、字段提取全流程3.1 腳本分層采集任務(wù)不能寫(xiě)成一坨很多第一次接觸APP爬蟲(chóng)的人習(xí)慣把請(qǐng)求、解析、存儲(chǔ)寫(xiě)在同一個(gè)循環(huán)里。這種寫(xiě)法對(duì)小規(guī)模采集沒(méi)問(wèn)題但一旦要擴(kuò)展成多視頻批量采集翻車(chē)概率急速上升。這份資源的代碼在結(jié)構(gòu)上做了分層大致分為配置層、請(qǐng)求層、解析層、存儲(chǔ)層。配置層管Cookie、UA、接口地址、目標(biāo)視頻ID列表請(qǐng)求層管帶簽名發(fā)請(qǐng)求解析層管從JSON里拖字段存儲(chǔ)層管落盤(pán)。分層的好處是接口改了只動(dòng)請(qǐng)求層字段變了只動(dòng)解析層不用把整個(gè)腳本翻出來(lái)重寫(xiě)。下面是一個(gè)典型的請(qǐng)求層代碼import requests import time import hashlib from urllib.parse import urlencode def fetch_video_stats(bvid, cookie, ua): # 公共請(qǐng)求頭模擬APP環(huán)境 headers { User-Agent: ua, Referer: https://www.bilibili.com/, Cookie: cookie, Content-Type: application/json; charsetutf-8 } # 構(gòu)造帶時(shí)間戳的查詢(xún)參數(shù) params { bvid: bvid, ts: int(time.time()) } # 簽名邏輯對(duì)參數(shù)做hash后拼接 sign generate_sign(params) params[w_sign] sign url https://api.bilibili.com/x/web-interface/view resp requests.get(url, headersheaders, paramsparams, timeout10) return resp.json()這段代碼里的generate_sign是簽名入口實(shí)際算法在下一節(jié)展開(kāi)。先關(guān)注兩個(gè)細(xì)節(jié)一是ts參數(shù)用的是當(dāng)前unix時(shí)間戳這是大多數(shù)簽名機(jī)制的基礎(chǔ)參數(shù)之一用來(lái)防止請(qǐng)求重放二是返回結(jié)果統(tǒng)一為JSON方便后續(xù)解析。3.2 wbi簽名機(jī)制看懂參數(shù)混淆和hash拼接B站的接口簽名這兩年經(jīng)歷了好幾輪升級(jí)。早期是一些固定參數(shù)的MD5拼接后來(lái)演進(jìn)為動(dòng)態(tài)簽名現(xiàn)在主流的機(jī)制是在參數(shù)里加入經(jīng)過(guò)混淆的密鑰通過(guò)特定算法生成簽名值。這份資源里對(duì)應(yīng)的處理方式是 wbi 簽名名字不重要重要的是它的原理服務(wù)端動(dòng)態(tài)下發(fā)兩個(gè)密鑰值客戶(hù)端拿這兩個(gè)值對(duì)參數(shù)做變換最后生成一個(gè)簽名字段。簽名邏輯拆開(kāi)來(lái)看是三步第一步從預(yù)請(qǐng)求接口拿到密鑰對(duì)第二步對(duì)URL參數(shù)做排序、拼接、字符替換第三步用MD5生成32位hash作為簽名。def generate_sign(params): # 模擬wbi簽名流程 # 1. 密鑰對(duì)真實(shí)值從預(yù)請(qǐng)求接口獲取這里用占位符 salt_key abc123 salt_value xyz789 # 2. 對(duì)參數(shù)按key做字典序排序拼接成待簽名字符串 sorted_params sorted(params.items(), keylambda item: item[0]) query_string urlencode(sorted_params) # 3. 把明文和密鑰組合后做md5 raw query_string salt_key salt_value sign hashlib.md5(raw.encode(utf-8)).hexdigest() return sign這張簽名的核心邏輯就三點(diǎn)排序穩(wěn)定、密鑰不漏、算法固定。但必須說(shuō)真實(shí)環(huán)境里的密鑰不是寫(xiě)死在代碼里的而是從某個(gè)預(yù)請(qǐng)求的返回體里動(dòng)態(tài)抽取的。拿到密鑰的請(qǐng)求往往有頻率控制所以簽名模塊在實(shí)際運(yùn)行時(shí)要加緩存不能每次請(qǐng)求都去拉一次密鑰。資源代碼里把密鑰做了緩存封裝第一次請(qǐng)求拉取后續(xù)直接從內(nèi)存取直到失效才重新拉取。3.3 播放量與完播率字段的提取和歸一化請(qǐng)求返回JSON后最關(guān)鍵的環(huán)節(jié)就是把播放量和完播率字段提取出來(lái)。B站返回體結(jié)構(gòu)通常是外層code/message/data三層播放量在data.stat.view這個(gè)路徑下完播率相關(guān)字段則藏在data.stat或擴(kuò)展字段里。但這里有一個(gè)隱蔽的坑完播率字段名在不同接口版本下不完全一致有的版本返回view_finish_rate有的版本返回finish_percent。所以解析層不能硬編碼字段名要做一次字段名兼容處理把這些可能的別名都檢查一遍取第一個(gè)非空值。這和做數(shù)據(jù)分析時(shí)處理多來(lái)源數(shù)據(jù)要?dú)w一化是同一個(gè)道理只不過(guò)發(fā)生在JSON層。def extract_metrics(data): stat data.get(stat, {}) # 播放量一般直接掛在stat.view view_count stat.get(view, 0) # 完播率兼容多種字段名寫(xiě)法 finish_rate None for key in [view_finish_rate, finish_percent, finish_rate]: if stat.get(key) is not None: finish_rate stat.get(key) break # 完播率可能有千分位和百分位兩種口徑統(tǒng)一轉(zhuǎn)為百分?jǐn)?shù) if finish_rate is not None: if finish_rate 1: finish_rate finish_rate / 100 return { bvid: data.get(bvid), view_count: view_count, finish_rate: finish_rate }歸一化的目的很簡(jiǎn)單——有的接口把0.85直接返回有的返回85如果不做轉(zhuǎn)換后面做分析時(shí)同一個(gè)指標(biāo)兩種量綱模型直接失真。我一般在提取后還會(huì)順手做一次類(lèi)型強(qiáng)轉(zhuǎn)確保數(shù)值型字段是float而不是字符串這個(gè)細(xì)節(jié)能讓后續(xù)的聚合分析少踩不少坑。3.4 數(shù)據(jù)落地CSV與SQLite雙份保存數(shù)據(jù)提取出來(lái)后落地方式不建議走Excel采集規(guī)模一大Excel就卡。更實(shí)用的做法是同時(shí)輸出CSV和SQLiteCSV給人看SQLite給程序查。import csv import sqlite3 def save_record(record, csv_path, db_path): # CSV追加寫(xiě)入 with open(csv_path, modea, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnames[bvid, view_count, finish_rate]) writer.writerow(record) # SQLite入庫(kù) conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( INSERT OR REPLACE INTO video_stats(bvid, view_count, finish_rate) VALUES(?, ?, ?), (record[bvid], record[view_count], record[finish_rate]) ) conn.commit() conn.close()SQLite這里用INSERT OR REPLACE而不是普通INSERT好處是重復(fù)采集同一視頻時(shí)不會(huì)產(chǎn)生臟數(shù)據(jù)而是直接覆蓋最新結(jié)果。對(duì)于需要定時(shí)跑采集來(lái)做趨勢(shì)觀測(cè)的場(chǎng)景這個(gè)設(shè)計(jì)是必須的。CSV則保留追加語(yǔ)義每次運(yùn)行都新增一行保留歷史快照。數(shù)據(jù)庫(kù)表結(jié)構(gòu)建議提前建好字段名和解析層的輸出嚴(yán)格對(duì)齊避免入庫(kù)時(shí)出現(xiàn)映射錯(cuò)位。常見(jiàn)的低級(jí)錯(cuò)誤就是CSV字段順序和字典鍵順序不一致導(dǎo)致數(shù)據(jù)串列。4. 避坑指南簽名失效、風(fēng)控限流、字段缺失的三類(lèi)高發(fā)問(wèn)題4.1 簽名密鑰失效導(dǎo)致所有請(qǐng)求返回 -403現(xiàn)象腳本跑得好好的某一天開(kāi)始所有請(qǐng)求都報(bào)-403無(wú)論怎么刷新Cookie都無(wú)效。原因簽名密鑰對(duì)是服務(wù)端動(dòng)態(tài)下發(fā)的會(huì)周期性輪換。資源代碼里的密鑰緩存機(jī)制雖然能減少請(qǐng)求次數(shù)但緩存過(guò)期后沒(méi)有及時(shí)拉取新密鑰導(dǎo)致后續(xù)所有請(qǐng)求都拿著舊密鑰做簽名服務(wù)端驗(yàn)簽不通過(guò)。解決在簽名請(qǐng)求的外層加異常捕獲當(dāng)響應(yīng)碼出現(xiàn)-403時(shí)主動(dòng)清除本地密鑰緩存強(qiáng)制觸發(fā)一次預(yù)請(qǐng)求拉取新密鑰。更穩(wěn)妥的做法是給密鑰加一個(gè)過(guò)期時(shí)間屬性比如緩存60分鐘到期后即使沒(méi)報(bào)錯(cuò)也主動(dòng)刷新。從那以后我再也沒(méi)被密鑰過(guò)期問(wèn)題卡住過(guò)。4.2 一個(gè)小號(hào)跑高頻請(qǐng)求被限流封禁風(fēng)險(xiǎn)高現(xiàn)象連續(xù)采集幾十個(gè)視頻后返回體里數(shù)據(jù)正常但登錄態(tài)突然失效或者接口返回-412狀態(tài)碼。原因單賬號(hào)高頻請(qǐng)求觸發(fā)了服務(wù)端風(fēng)控。-412通常代表請(qǐng)求被風(fēng)控?cái)r截不是說(shuō)你的簽名錯(cuò)了而是你的訪(fǎng)問(wèn)模式太像機(jī)器。同一IP短時(shí)間訪(fǎng)問(wèn)大量視頻詳情這在正常用戶(hù)行為里幾乎不可能。解決第一控制采集頻率單個(gè)視頻之間至少sleep 1~2秒不要用并發(fā)第二請(qǐng)求頭里的User-Agent和Cookie要與賬號(hào)實(shí)際使用的設(shè)備環(huán)境一致否則風(fēng)控模型很容易從設(shè)備指紋維度識(shí)別出異常第三必要的時(shí)候做代理池輪換但這塊要看資源本身的支持情況。如果只是做小規(guī)模分析建議把采集量控制在一個(gè)合理范圍內(nèi)別貪多。4.3 字段返回為空但請(qǐng)求成功解析層別硬編碼現(xiàn)象請(qǐng)求沒(méi)有報(bào)錯(cuò)code為0但找到的完播率字段是None腳本直接拋異常。原因兩種可能。第一種是當(dāng)前賬號(hào)對(duì)該視頻沒(méi)有完播統(tǒng)計(jì)權(quán)限接口在字段級(jí)別做了控制第二種是字段名變了接口升級(jí)后改了別名舊字段名已經(jīng)廢棄。解決解析層要用兼容式提取前面代碼里已經(jīng)寫(xiě)了多字段名輪詢(xún)。另外要做空值兜底字段確實(shí)為空時(shí)不拋異常而是把記錄標(biāo)記為missing并繼續(xù)跑完整個(gè)批次。采集任務(wù)最重要的是穩(wěn)定跑完而不是某一條數(shù)據(jù)的完整性。這一條我從第一次大規(guī)模采集時(shí)就寫(xiě)進(jìn)了代碼規(guī)范里后面幾乎沒(méi)再為字段缺失的事翻過(guò)車(chē)。4.4 長(zhǎng)時(shí)間采集后Cookie失效腳本中斷現(xiàn)象采集跑到一半突然報(bào)登錄態(tài)過(guò)期日志里開(kāi)始刷-101錯(cuò)誤。原因Cookie里的登錄憑證有有效期長(zhǎng)任務(wù)跑久了超過(guò)了憑證的過(guò)期時(shí)間。這事屬于時(shí)間維度上的必然大概率會(huì)發(fā)生。解決在做任務(wù)設(shè)計(jì)時(shí)把Cookie有效期納入考慮。簡(jiǎn)單方案是把Cookie配置做成外部輸入失效后手動(dòng)替換并重新啟動(dòng)腳本進(jìn)階方案是做一個(gè)簡(jiǎn)易的自動(dòng)續(xù)期邏輯檢測(cè)到-101時(shí)讀備用Cookie池自動(dòng)切換。但自動(dòng)切換Cookie這事有一個(gè)前置條件備用Cookie的賬號(hào)要有相同的權(quán)限否則依然拿不到數(shù)據(jù)。我自己的做法是準(zhǔn)備兩個(gè)小號(hào)Cookie輪流用一個(gè)被限另一個(gè)立刻頂上。5. 進(jìn)階玩法把一次性腳本改成可持續(xù)的視頻數(shù)據(jù)觀測(cè)系統(tǒng)一套腳本寫(xiě)完不是終點(diǎn)真正有用的是把它改造成能每天自動(dòng)跑、持續(xù)出數(shù)的觀測(cè)系統(tǒng)。這里分享一下我在復(fù)現(xiàn)這套資源后做的升級(jí)路徑分成三個(gè)層次。第一個(gè)層次是任務(wù)化。把單次采集腳本包一層調(diào)度殼——記錄上次采集到的視頻ID列表每次啟動(dòng)時(shí)只采集新增和狀態(tài)變化的視頻采集結(jié)果追加到同一張表。這樣跑一個(gè)月后表里就有了每個(gè)視頻的多日快照可以做播放量增速和完播率變化趨勢(shì)分析而不只是一天的截面數(shù)據(jù)。第二個(gè)層次是數(shù)據(jù)看板化。采集完的數(shù)據(jù)放在SQLite里配合輕量級(jí)的可視化工具做每日自動(dòng)出圖。比如播放量的環(huán)比增幅、完播率分布直方圖、近7天新發(fā)視頻的完播率排名。這塊不需要上重型框架能解決“人工盯數(shù)”的痛點(diǎn)就夠了。我是把可視化腳本掛在服務(wù)器上每天定時(shí)跑一次采集數(shù)據(jù)更新后自動(dòng)生成新的圖表頁(yè)第二天早上直接看數(shù)據(jù)不用再手動(dòng)打開(kāi)后臺(tái)一個(gè)個(gè)查。第三個(gè)層次是閾值告警。在采集任務(wù)后加一個(gè)規(guī)則引擎比如完播率連續(xù)三天下降且播放量過(guò)萬(wàn)自動(dòng)標(biāo)記為“內(nèi)容衰退信號(hào)”播放量增速異??烨彝瓴ヂ矢哂谄綍r(shí)兩倍標(biāo)記為“疑似爆款苗頭”。這些規(guī)則不復(fù)雜本質(zhì)上就是對(duì)查詢(xún)結(jié)果做幾個(gè)條件判斷但價(jià)值密度比單純的數(shù)據(jù)快照高很多。這套升級(jí)路徑也不是一上來(lái)就全做完的。我的建議是先在本地跑通基礎(chǔ)采集攢一周數(shù)據(jù)后做趨勢(shì)分析分析結(jié)果能指導(dǎo)你的實(shí)際決策了再考慮上調(diào)度和告警。從那次以后我每次拿類(lèi)似的接口資源都會(huì)強(qiáng)制自己先做一次數(shù)據(jù)落庫(kù)的完備性檢查再談其他。寫(xiě)到這里希望這份完整拆解能幫你在采集播放量和完播率的路上少走幾步彎路。本文還有配套的精品資源點(diǎn)擊獲取