論API獲取實(shí)戰(zhàn):從抓包到Python/VC++封裝)
如果你正想在項(xiàng)目里獲取京東評(píng)論的API接口大概率已經(jīng)發(fā)現(xiàn)在官方文檔里翻半天根本找不到一個(gè)叫“獲取商品評(píng)論”的現(xiàn)成接口。更頭疼的是網(wǎng)上一搜“京東評(píng)論爬蟲”出來的教程多半已經(jīng)過期京東前端接口的名稱和參數(shù)隔幾個(gè)月就換一次照抄代碼大概率直接撲街。這篇文章不是教你“破解”什么黑科技而是把我實(shí)際踩出來的路子整理一遍從瀏覽器抓包定位評(píng)論接口到用 Python 快速驗(yàn)證再到用 VC 訪問 HTTP 服務(wù)端 API 接口并解析 JSON最后把那些 403、空數(shù)據(jù)、分頁(yè)錯(cuò)亂之類的坑一起列出來。無論你是做數(shù)據(jù)分析、競(jìng)品監(jiān)控還是想給內(nèi)部工具補(bǔ)一個(gè)“商品口碑”模塊這套方法論都能直接用學(xué)完還能橫移到小說 API、股票數(shù)據(jù)接口這類 RESTful API 場(chǎng)景上。1. 京東評(píng)論API難拿的真相與三條可行路徑1.1 為什么官方API里沒有現(xiàn)成的獲取評(píng)論接口很多人覺得京東這么大的開放平臺(tái)肯定提供了“評(píng)論API接口”只是自己沒找到。實(shí)話實(shí)話京東開放平臺(tái)確實(shí)給商家和合作服務(wù)商提供了一些評(píng)價(jià)相關(guān)的接口但主要用于商家后臺(tái)查看訂單評(píng)價(jià)、回復(fù)評(píng)價(jià)不是給普通開發(fā)者“拿某個(gè)商品的公開評(píng)論列表”用的。這類接口通常要求你有商家賬號(hào)、建應(yīng)用、申請(qǐng)權(quán)限、拿到 access_token而且在審核階段基本就卡掉了個(gè)人開發(fā)者。就算你通過了商家端 API 權(quán)限接口返回的數(shù)據(jù)也不是你想的那種“直接能展示的評(píng)論全文”更多是評(píng)價(jià)維度、評(píng)分統(tǒng)計(jì)之類。所以純民間場(chǎng)景下大家嘴里說的“京東評(píng)論API”實(shí)際上指的是商品詳情頁(yè)里那個(gè)前端的 H5 異步接口。它不是公開文檔里的正式服務(wù)但只要你模擬瀏覽器正常請(qǐng)求就能拿到結(jié)構(gòu)化的 JSON 評(píng)論數(shù)據(jù)。這屬于“頁(yè)面接口抓包復(fù)用”本質(zhì)上是爬蟲但有別于繞過加密簽名的高危操作它在技術(shù)門檻和數(shù)據(jù)量上都比較溫和。1.2 三條可行路徑的對(duì)比與選型建議先把我實(shí)際用過的方案擺出來按推薦程度從低到高排方案獲取難度數(shù)據(jù)完整性合規(guī)風(fēng)險(xiǎn)適合場(chǎng)景官方開放平臺(tái) API高需商家資質(zhì)審核較強(qiáng)但僅限授權(quán)范圍低商家自營(yíng)系統(tǒng)、正規(guī)合作服務(wù)方第三方聚合數(shù)據(jù)服務(wù)商低付費(fèi)購(gòu)買次數(shù)取決于服務(wù)商渠道中注意服務(wù)商數(shù)據(jù)來源快速上線、對(duì)數(shù)據(jù)時(shí)效要求不高的小工具自己抓包 H5 評(píng)論接口中需要會(huì)抓包和反風(fēng)控完整包含評(píng)論正文、時(shí)間、評(píng)分、追評(píng)中高需要遵守平臺(tái)規(guī)則和頻率學(xué)習(xí)研究、內(nèi)部監(jiān)控、低頻個(gè)人項(xiàng)目個(gè)人角度我一般推薦第三條路徑但強(qiáng)烈建議把它定位成“學(xué)習(xí)研究”而不是“商業(yè)爬蟲”。理由很簡(jiǎn)單接口是活的官方頁(yè)面怎么改你的解析腳本就得怎么跟。把它當(dāng)成一個(gè)可復(fù)用的“評(píng)論API接口封裝”來做比每次手動(dòng)復(fù)制數(shù)據(jù)省心得多也比依賴第三方服務(wù)商的“黑盒數(shù)據(jù)”更可控。2. 核心接口拆解抓包定位京東評(píng)論數(shù)據(jù)接口2.1 用瀏覽器開發(fā)者工具找到真實(shí)評(píng)論接口先把某件商品的詳情頁(yè)打開比如你在地址欄里能看到??sku100012043978之類這個(gè)sku就是商品ID后面所有請(qǐng)求都靠它。然后按 F12 打開開發(fā)者工具切到 Network 面板刷新頁(yè)面后在商品評(píng)價(jià)Tab里隨便翻一頁(yè)。這時(shí)候面板里會(huì)出現(xiàn)大量請(qǐng)求別亂看直接聚焦在 XHR 請(qǐng)求上搜關(guān)鍵字comment或者getCommentListPage。我自己實(shí)測(cè)下來穩(wěn)定能用的一個(gè) H5 評(píng)論接口是https://sclub.jd.com/comment/page.service.CommentService/getCommentListPage.action它的核心參數(shù)就那么幾個(gè)productId: 100012043978 page: 0 pageSize: 10 sortType: 5 score: 0 isShadowSku: 0 fold: 1注意這里第一頁(yè)的page是 0不是 1。這個(gè)接口不加callback參數(shù)時(shí)返回純 JSON加了callback會(huì)返回 JSONP 格式方便瀏覽器端繞跨域。抓包時(shí)你看到的可能是帶callbackjQueryxxxxx的 URL去掉 callback 再請(qǐng)求一樣能拿到數(shù)據(jù)而且解析更干凈。抓包有個(gè)習(xí)慣要養(yǎng)成不要只復(fù)制 URL要把請(qǐng)求頭里的User-Agent和Referer一起抄下來。評(píng)論接口對(duì)Referer不算苛刻但缺了它偶爾會(huì)返回 403。User-Agent直接用一個(gè)真實(shí) Chrome 的值就行千萬別用 Python 默認(rèn)的python-requests。2.2 關(guān)鍵參數(shù)說明與分頁(yè)邏輯這些參數(shù)我是反復(fù)試出來的說幾個(gè)容易踩坑的地方sortType排序方式5 是按時(shí)間排序6 是按推薦排序0 貌似是默認(rèn)綜合。想抓最新評(píng)論就用 5想抓熱評(píng)就用 6。score評(píng)分篩選0 是全部1 是一星2 是二星3 是三星4 是四星5 是五星。要分析差評(píng)直接讓 score1。isShadowSku這個(gè)參數(shù)挺關(guān)鍵。有些商品會(huì)有“關(guān)聯(lián)SKU”的評(píng)論聚合比如多規(guī)格商品的一些評(píng)論掛在舊SKU下。填 0 表示只拿當(dāng)前SKU的評(píng)論填 1 會(huì)把隱藏款式的評(píng)論也帶出來但返回結(jié)構(gòu)會(huì)變。我建議先用 0。fold折行參數(shù)1 表示不折疊用來拿更多追評(píng)內(nèi)容。接口返回的 JSON 里頂層是一個(gè)comments數(shù)組數(shù)組里每個(gè)元素包含content評(píng)論內(nèi)容、creationTime評(píng)論時(shí)間、score評(píng)分、nickname脫敏昵稱、productColor、productSize等字段。還可能有一個(gè)maxPage字段用來判斷總共有多少頁(yè)。真要翻全量評(píng)論就while page maxPage一頁(yè)一頁(yè)拉但實(shí)際跑下來建議加個(gè)次數(shù)上限別一次性刷幾千頁(yè)否則很容易被風(fēng)控。3. 從快速驗(yàn)證到工程封裝Python 與 VC 兩種實(shí)戰(zhàn)打法3.1 先用 Python 五分鐘驗(yàn)證評(píng)論接口不管你最終打算用 Python 還是 VC我強(qiáng)烈建議先用 Python 把接口跑通驗(yàn)證參數(shù)、字段、分頁(yè)邏輯都沒問題之后再移植到別的語言。原因很簡(jiǎn)單Python 寫起來快調(diào)試成本低接口長(zhǎng)什么樣、返回什么字段一眼就能看明白。下面這段代碼是我常用的最小驗(yàn)證腳本import requests import time product_id 100012043978 url https://sclub.jd.com/comment/page.service.CommentService/getCommentListPage.action session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36, Referer: fhttps://item.jd.com/{product_id}.html, Accept: application/json, text/plain, */*, Accept-Encoding: gzip, deflate, br, }) params { productId: product_id, page: 0, pageSize: 10, sortType: 5, score: 0, isShadowSku: 0, fold: 1, } resp session.get(url, paramsparams, timeout10) data resp.json() for comment in data[comments]: print(comment[creationTime], comment[score], comment[content])跑通之后你就可以在這個(gè)基礎(chǔ)上做二次封裝比如包裝成一個(gè)fetch_jd_review(product_id, page, page_size)函數(shù)再丟給后續(xù)的數(shù)據(jù)清洗模塊。需要注意一點(diǎn)接口返回的是 gzip 壓縮內(nèi)容但requests會(huì)自動(dòng)解壓不需要手動(dòng)處理。如果你用的是 VC就得自己考慮解壓這一步了。3.2 用 VC/WinHTTP 調(diào)用 RESTful API 的封裝樣例有些場(chǎng)景必須用 VC比如你的產(chǎn)品本身是 Windows 桌面客戶端不想為了一個(gè)評(píng)論接口再內(nèi)嵌一個(gè) Python 解釋器又或者你要把這個(gè)接口封裝成底層庫(kù)給現(xiàn)有 C 業(yè)務(wù)模塊調(diào)用。這時(shí)候用 WinHTTP 是最直接的辦法它是 Windows 系統(tǒng)自帶的 HTTP 棧不需要引入第三方重量級(jí)依賴。先看核心的 HTTP GET 封裝我習(xí)慣寫成一個(gè)長(zhǎng)這樣的函數(shù)#include windows.h #include winhttp.h #include string #include vector #include nlohmann/json.hpp #pragma comment(lib, winhttp.lib) std::string HttpGetJson(const std::wstring host, const std::wstring path, const std::wstring referer, const std::wstring cookie, DWORD timeoutMs) { HINTERNET hSession WinHttpOpen( LJDCommentClient/1.0, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); HINTERNET hConnect WinHttpConnect( hSession, host.c_str(), INTERNET_DEFAULT_HTTPS_PORT, 0); HINTERNET hRequest WinHttpOpenRequest( hConnect, LGET, path.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); WinHttpSetTimeouts(hRequest, timeoutMs, timeoutMs, timeoutMs, timeoutMs); if (!referer.empty()) { std::wstring refererHeader LReferer: referer; WinHttpAddRequestHeaders(hRequest, refererHeader.c_str(), (ULONG)-1L, WINHTTP_ADDREQ_FLAG_REPLACE); } if (!cookie.empty()) { std::wstring cookieHeader LCookie: cookie; WinHttpAddRequestHeaders(hRequest, cookieHeader.c_str(), (ULONG)-1L, WINHTTP_ADDREQ_FLAG_REPLACE); } BOOL sent WinHttpSendRequest( hRequest, WINHTTP_NO_EXTRA_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); if (!sent) { DWORD err GetLastError(); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); throw std::runtime_error(WinHttpSendRequest failed: std::to_string(err)); } WinHttpReceiveResponse(hRequest, NULL); std::vectorchar responseData; DWORD available 0; do { WinHttpQueryDataAvailable(hRequest, available); if (available 0) break; size_t oldSize responseData.size(); responseData.resize(oldSize available 1); DWORD bytesRead 0; if (!WinHttpReadData(hRequest, responseData[oldSize], available, bytesRead)) { break; } responseData.resize(oldSize bytesRead); } while (available 0); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return std::string(responseData.begin(), responseData.end()); }調(diào)用的時(shí)候把 host、path、referer 拼好就行std::wstring host Lsclub.jd.com; std::wstring path L/comment/page.service.CommentService/ LgetCommentListPage.action? LproductId100012043978page0pageSize10 LsortType5score0isShadowSku0fold1; std::wstring referer Lhttps://item.jd.com/100012043978.html; std::wstring cookie L; // 如果頁(yè)面需要cookie這里填抓包復(fù)制的值 std::string jsonText HttpGetJson(host, path, referer, cookie, 10000);這個(gè)封裝的核心點(diǎn)在于 WinHTTP 的句柄管理WinHttpOpen管 SessionWinHttpConnect管連接WinHttpOpenRequest管請(qǐng)求用完必須逐個(gè)CloseHandle。別只關(guān)最后一個(gè)否則句柄泄漏。另外WinHttpSendRequest的網(wǎng)絡(luò)超時(shí)默認(rèn)可能很長(zhǎng)一定要用WinHttpSetTimeouts設(shè)置否則接口卡住時(shí)界面直接假死。拿到j(luò)sonText之后原生 C 沒有內(nèi)置 JSON 解析我用的是nlohmann/json一個(gè) header-only 庫(kù)整個(gè)項(xiàng)目里加個(gè)頭文件就能用auto data nlohmann::json::parse(jsonText); for (const auto item : data[comments]) { std::string content item.value(content, ); std::string time item.value(creationTime, ); int score item.value(score, 0); // 這里自己決定是打印、入庫(kù)還是回調(diào)業(yè)務(wù)層 }如果你的項(xiàng)目不方便引入 nlohmann也可以用 Windows 自帶的IXMLDOMDocument把 JSON 當(dāng) XML 解析但體驗(yàn)非常痛苦。相比之下nlohmann 幾乎是現(xiàn)在 VC 世界里最省心的 JSON 方案。3.3 順帶說一句讓豆包這類AI助手幫你寫綁定代碼最近不是特別流行讓“豆包”這種 AI 助手幫忙寫代碼嗎我在做 VC 封裝時(shí)也試過先把上一節(jié)抓包得到的接口請(qǐng)求參數(shù)整理成一段自然語言描述再讓豆包生成客戶端調(diào)用代碼效果相當(dāng)可以。尤其是從零開始寫 WinHTTP 封裝時(shí)AI 能給你一個(gè)能跑的骨架你再花幾分鐘把超時(shí)、Header、錯(cuò)誤處理補(bǔ)扎實(shí)效率比手敲快一倍。不過我得提醒一句AI 生成的代碼里經(jīng)常有想當(dāng)然的參數(shù)名比如把sortType寫成sort、把page當(dāng)成從 1 開始。這種問題不會(huì)報(bào)錯(cuò)但會(huì)返回錯(cuò)誤數(shù)據(jù)。所以任何 AI 生成的請(qǐng)求代碼都要先用 Python 腳本跑一遍同樣的參數(shù)確認(rèn)返回值后再往 C 里搬。4. 實(shí)測(cè)過程中的高頻坑與排查速查4.1 403 與風(fēng)控我剛開始寫這個(gè)接口的時(shí)候臉不紅心不跳地用一個(gè)for循環(huán)直接從第 0 頁(yè)刷到第 100 頁(yè)結(jié)果刷到第 20 頁(yè)左右請(qǐng)求開始返回 403。這是京東對(duì)非正常瀏覽行為的攔截最常見的原因有兩個(gè)請(qǐng)求頭的User-Agent太假或者Referer缺失。單 IP 短時(shí)間請(qǐng)求頻率過高。解決辦法也不復(fù)雜加延時(shí)是必須的我個(gè)人習(xí)慣在兩次請(qǐng)求之間隨機(jī)睡 1 到 3 秒不要用固定間隔固定間隔更容易被識(shí)別成機(jī)器。另外User-Agent一定不要讓它保留python-requests這種默認(rèn)值。如果 403 已經(jīng)出現(xiàn)通常過幾分鐘會(huì)自動(dòng)解封這時(shí)候停下來冷靜一下別繼續(xù)懟。提示千萬別嘗試?yán)@過滑塊驗(yàn)證或者通過異常手段偽造用戶身份這條路不僅不穩(wěn)定還會(huì)帶來法律風(fēng)險(xiǎn)。合規(guī)處理比數(shù)據(jù)量重要得多。4.2 返回空數(shù)據(jù)另一個(gè)高頻問題是接口正常返回 200但comments數(shù)組是空的。這種情況十有八九是參數(shù)沒配對(duì)。我遇到過幾種productId傳錯(cuò)了從商品鏈接里復(fù)制的時(shí)候多帶了小數(shù)點(diǎn)或者其他字符。page超出maxPage返回空是正常的。score填了 5而這個(gè)商品確實(shí)一個(gè)五星評(píng)價(jià)都沒有接口一樣會(huì)返回空數(shù)組。排查的時(shí)候先固定pageSize10、score0、sortType5用瀏覽器打開同一個(gè)商品的評(píng)價(jià)頁(yè)做對(duì)照。如果瀏覽器有數(shù)據(jù)而請(qǐng)求沒有那就是請(qǐng)求頭少了Referer。還有一個(gè)細(xì)節(jié)有些商品的評(píng)價(jià)接口不是sclub.jd.com而是club.jd.com下的舊版路徑兩者返回結(jié)構(gòu)略有差異。建議以瀏覽器抓包到的主機(jī)名為準(zhǔn)不要只看網(wǎng)上抄來的代碼。4.3 數(shù)據(jù)合規(guī)你必須有數(shù)聊了這么多技術(shù)細(xì)節(jié)我必須把這條放前面京東評(píng)論數(shù)據(jù)是用戶生成內(nèi)容受平臺(tái)用戶協(xié)議保護(hù)。未經(jīng)授權(quán)大規(guī)模抓取并用于商業(yè)運(yùn)營(yíng)存在明確的法律風(fēng)險(xiǎn)即便只是學(xué)習(xí)研究也要嚴(yán)格控制頻率不要給目標(biāo)服務(wù)器造成壓力。我的做法是單商品數(shù)據(jù)量控制在幾百條以內(nèi)跑完就停不搞全量備份更不會(huì)把數(shù)據(jù)打包賣出去。如果你是真的想做一個(gè)長(zhǎng)期供貨的評(píng)論數(shù)據(jù)服務(wù)我不建議自己爬而是去申請(qǐng)正規(guī)數(shù)據(jù)合作渠道。寧可花錢買穩(wěn)定也別讓自己陷入隨時(shí)可能收到律師函的被動(dòng)局面。4.4 高頻問題對(duì)照表下面這個(gè)表是我從多個(gè)項(xiàng)目里整理出來的最適合貼在項(xiàng)目文檔里當(dāng)速查現(xiàn)象可能原因排查方法返回 403User-Agent 缺失、Referer 不對(duì)、訪問過快檢查請(qǐng)求頭加上真實(shí) UA增加隨機(jī)延時(shí)返回 200 但 comments 為空productId 錯(cuò)、page 越界、score 篩選過嚴(yán)瀏覽器對(duì)照參數(shù)先固定全量篩選JSON 解析報(bào)錯(cuò)返回了 JSONP 而不是純 JSON去掉 callback 參數(shù)或用文本替換處理中文字段亂碼請(qǐng)求時(shí)沒帶 gzip 頭或編碼識(shí)別錯(cuò)保證 Accept-Encoding 正確用 UTF-8 解碼VC 請(qǐng)求一直卡住沒有設(shè)置 WinHttpSetTimeouts設(shè)置發(fā)送、接收超時(shí)建議 10 到 30 秒再分享一個(gè)我實(shí)際的排查小技巧寫 VC 封裝時(shí)最怕的不是接口請(qǐng)求本身而是接口返回?cái)?shù)據(jù)被截?cái)?。WinHTTP 讀取響應(yīng)時(shí)必須循環(huán)調(diào)用WinHttpQueryDataAvailable和WinHttpReadData不能只讀一次。我第一次寫示例代碼時(shí)就只讀了一次結(jié)果總是只能拿到半個(gè) JSON后面解析一直報(bào)錯(cuò)。所以上面那套循環(huán)讀法建議原樣保留別精簡(jiǎn)。5. 最后再補(bǔ)充一點(diǎn)我的個(gè)人心得如果你只是拿這個(gè)接口練手我建議你在 Python 版本跑通之后再費(fèi)點(diǎn)時(shí)間把 VC 版本整理成一個(gè)帶緩存、帶超時(shí)重試的小類。因?yàn)榫〇|評(píng)論接口的數(shù)據(jù)時(shí)效性很強(qiáng)今天寫的腳本明天可能就少了幾個(gè)參數(shù)把它封裝成清晰的函數(shù)后續(xù)改起來會(huì)快很多。我在實(shí)際項(xiàng)目中就是靠這個(gè)方式把評(píng)論接口的維護(hù)周期從一個(gè)星期縮短到了半天。另外別把目光只鎖在京東評(píng)論上。你現(xiàn)在會(huì)抓包定位接口、會(huì)構(gòu)造請(qǐng)求參數(shù)、會(huì)用 Python 或 VC 解析 JSON這套能力放到小說 API、股票數(shù)據(jù)接口、天氣接口、遠(yuǎn)程壓縮服務(wù)接口上完全通用。說白了所謂“獲取京東評(píng)論API接口”本質(zhì)就是一次標(biāo)準(zhǔn)的 HTTP RESTful 接口對(duì)接找對(duì) URL帶對(duì)頭解析好響應(yīng)剩下的全是耐心。把這個(gè)思路吃透以后再遇到任何“某某平臺(tái)接口怎么拿”的問題你都具備了自己動(dòng)手解決問題的能力。