現(xiàn):批量采集與接口分析)
簡(jiǎn)介基于PHP開(kāi)發(fā)的百度搜索下拉詞刷量工具主要服務(wù)于網(wǎng)站推廣、SEO優(yōu)化及流量運(yùn)營(yíng)人員通過(guò)模擬搜索行為影響百度搜索框的下拉聯(lián)想詞幫助特定關(guān)鍵詞獲得更多曝光機(jī)會(huì)。壓縮包共包含13個(gè)文件整體大小僅735KB以PHP腳本為核心搭配CSS樣式、JavaScript交互以及多張PNG/JPG/GIF圖片素材另有HTC兼容文件用于處理IE瀏覽器下的顯示問(wèn)題結(jié)構(gòu)緊湊且便于按需遷移。工具核心邏輯由多個(gè)PHP文件實(shí)現(xiàn)用戶可在具備Apache/Nginx與PHP解釋器的環(huán)境中直接部署。目前已有302人瀏覽學(xué)習(xí)適合具備基礎(chǔ)PHP環(huán)境配置能力、想快速搭建刷詞工具或研究其實(shí)現(xiàn)原理的開(kāi)發(fā)者。資源內(nèi)提供了可直接運(yùn)行的刷詞主程序、配置與輔助腳本以及完整的前端展示頁(yè)面既可用于實(shí)際操作也能作為學(xué)習(xí)PHP后端交互與請(qǐng)求模擬的參考案例附帶的狀態(tài)圖標(biāo)、頁(yè)面風(fēng)格文件讓界面開(kāi)箱即用。1. 百度下拉詞工具的原理與 PHP 落地的場(chǎng)景百度下拉詞是搜索框輸入關(guān)鍵詞后出現(xiàn)的聯(lián)想詞條來(lái)源于海量真實(shí)用戶搜索行為的聚合。輸入“洗碗機(jī)”下拉里出現(xiàn)的“洗碗機(jī)哪個(gè)牌子好”“洗碗機(jī)安裝尺寸”指向的都是可驗(yàn)證的真實(shí)需求。行業(yè)里常說(shuō)的“刷詞工具”在工程上大多對(duì)應(yīng)一套“批量獲取并管理聯(lián)想詞”的程序把關(guān)鍵詞批量丟進(jìn)去程序自動(dòng)請(qǐng)求百度聯(lián)想接口返回的聯(lián)想詞按種子詞歸類(lèi)落庫(kù)。code 層面不涉及瀏覽器模擬核心就是對(duì)公開(kāi) SEO 操作的拍照記錄工具。用 PHP 做這類(lèi)工具很務(wù)實(shí)zip 包解壓配好環(huán)境即可運(yùn)行。原生 curl、json、PDO 三件套可以依次完成請(qǐng)求、解析和落庫(kù)。本文從百度下拉詞接口分析起步講到批量采集、頻率控制、結(jié)果分析適合希望用 PHP 自建關(guān)鍵詞詞庫(kù)的開(kāi)發(fā)者也適合想徹底搞懂下拉詞從請(qǐng)求到數(shù)據(jù)庫(kù)全流程的運(yùn)營(yíng)人員。2. 百度下拉詞接口協(xié)議分析與參數(shù)對(duì)照2.1 百度下拉詞的兩條鏈路PC 端 OpenSearch 與移動(dòng)端 sugrec做下拉詞采集的人默認(rèn)第一件事就是打開(kāi)瀏覽器按 F12在百度搜索框敲一個(gè)詞看網(wǎng)絡(luò)面板里請(qǐng)求哪個(gè)地址。鏈路實(shí)際上有兩條。PC 端最常用的是suggestion.baidu.com上的 OpenSearch 協(xié)議路徑是/su返回結(jié)構(gòu)是一個(gè)數(shù)組干凈利落。移動(dòng)端的聯(lián)想詞則來(lái)自m.baidu.com的/sugrec接口返回的是對(duì)象結(jié)構(gòu)除了聯(lián)想詞還帶擴(kuò)展信息。兩條鏈路并存的原因是終端的搜索策略不一致移動(dòng)端下拉詞的更新節(jié)奏明顯快于 PC 端詞序變化也更頻繁。先看最基本的一條請(qǐng)求curl -s https://suggestion.baidu.com/su?wd%E6%B4%97%E7%A2%97%E6%9C%BAactionopensearchieutf-8響應(yīng)是一段標(biāo)準(zhǔn) JSON 數(shù)組第一元素是本次查詢(xún)的詞第二元素是聯(lián)想詞列表。運(yùn)營(yíng)視角里常說(shuō)的“詞根擴(kuò)展”就是從這組聯(lián)想詞里繼續(xù)挑詞再請(qǐng)求下一輪。移動(dòng)端的請(qǐng)求長(zhǎng)這樣curl -s https://m.baidu.com/sugrec?prodwisewd%E6%B4%97%E7%A2%97%E6%9C%BA對(duì)比兩條鏈路能發(fā)現(xiàn)同一個(gè)詞根在移動(dòng)端返回的聯(lián)想詞數(shù)量與 PC 端并不完全一致部分長(zhǎng)尾詞只在一個(gè)端口出現(xiàn)。工具的做法是把兩條鏈路都抓一遍合并后記錄來(lái)源后續(xù)做端側(cè)偏好分析時(shí)才有數(shù)據(jù)依據(jù)。2.2 下拉詞接口重點(diǎn)參數(shù)與請(qǐng)求頭對(duì)照把協(xié)議里的主要參數(shù)整理成一張對(duì)照表現(xiàn)場(chǎng)調(diào)試時(shí)不至于對(duì)著 URL 猜含義。參數(shù)適用鏈路類(lèi)型必填含義與建議取值wd兩條鏈路string是關(guān)鍵詞本身傳參前要用 urlencode 編碼actionOpenSearchstring否固定傳 opensearch返回標(biāo)準(zhǔn) JSON 數(shù)組ieOpenSearchstring否傳 utf-8否則中文響應(yīng)可能亂碼prodsugrecstring是傳 wise 取移動(dòng)端詞傳 pc 取 PC 端詞psugrecstring否傳 1 時(shí)允許返回拼音聯(lián)想詞cbsugrecstring否JSONP 回調(diào)函數(shù)名本地調(diào)試建議去掉URL 參數(shù)之外請(qǐng)求頭同樣決定成敗。不帶User-Agent或者使用 PHP 默認(rèn)的PHP/8.xUA百度會(huì)直接判定為異常流量返回 200 但是個(gè)空列表。建議至少固定以下兩項(xiàng)請(qǐng)求頭User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Referer: https://www.baidu.com/Referer 填百度首頁(yè)是模擬真實(shí)用戶從首頁(yè)發(fā)起的搜索動(dòng)作這一項(xiàng)在部分時(shí)段比 UA 還敏感。Cookie 不是必須的但如果后續(xù)請(qǐng)求被跳轉(zhuǎn)到安全驗(yàn)證頁(yè)再回來(lái)補(bǔ) Cookie 反而更麻煩。2.3 兩種響應(yīng)格式的 PHP 統(tǒng)一化解析OpenSearch 的原始響應(yīng)是一個(gè)四段數(shù)組[洗碗機(jī),[洗碗機(jī)十大排名,洗碗機(jī)哪個(gè)牌子好,洗碗機(jī)要不要買(mǎi)],[,,],{baidu_sug:3}]其中baidu_sug返回的是聯(lián)想類(lèi)型標(biāo)記比如正常聯(lián)想或拼音糾錯(cuò)實(shí)際解析時(shí)不需要關(guān)心具體取值只要聯(lián)想詞列表是第二段就夠了。sugrec 鏈路返回的則是對(duì)象聯(lián)想詞被包在s數(shù)組里{q:洗碗機(jī),p:false,s:[{t:洗碗機(jī)十大排名},{t:洗碗機(jī)哪種容量夠用}]}s數(shù)組每個(gè)元素的t字段存放聯(lián)想詞文本c字段是釋義或類(lèi)目信息大多數(shù)場(chǎng)景用不到。兩種格式不一致后續(xù)的處理邏輯只需要識(shí)別一次統(tǒng)一轉(zhuǎn)成標(biāo)準(zhǔn)數(shù)組即可?php function parseSugResponse(string $raw, string $link): array { if ($link opensearch) { $data json_decode($raw, true); return $data[1] ?? []; } $data json_decode($raw, true); $words []; foreach (($data[s] ?? []) as $item) { if (isset($item[t])) { $words[] $item[t]; } } return $words; }json_decode第二個(gè)參數(shù)傳true返回?cái)?shù)組而不是對(duì)象可以避免后面到處寫(xiě)-t的屬性訪問(wèn)。返回空數(shù)組時(shí)先別急著找代碼的問(wèn)題直接在瀏覽器地址欄打開(kāi)完整請(qǐng)求 URL瀏覽器能看到聯(lián)想詞問(wèn)題就在 PHP 請(qǐng)求頭瀏覽器也返回空說(shuō)明這個(gè)詞根本身就沒(méi)有聯(lián)想結(jié)果屬于正常現(xiàn)象。3. PHP 實(shí)現(xiàn)批量下拉詞采集與詞根擴(kuò)展3.1 用 curl 封裝單關(guān)鍵詞采集函數(shù)批量采集的底座是一個(gè)單關(guān)鍵詞請(qǐng)求函數(shù)。用 curl 而不是file_get_contents是因?yàn)?curl 能單獨(dú)控制連接超時(shí)和讀取超時(shí)還能拿到 HTTP 狀態(tài)碼對(duì)異常做細(xì)分判斷這兩個(gè)能力對(duì)長(zhǎng)期跑批的任務(wù)來(lái)說(shuō)缺一不可。?php function fetchSuggestion(string $keyword, array $opt []): array { $url https://suggestion.baidu.com/su? . http_build_query([ wd $keyword, action opensearch, ie utf-8 ]); $ch curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER true, CURLOPT_CONNECTTIMEOUT 3, CURLOPT_TIMEOUT 5, CURLOPT_FOLLOWLOCATION true, CURLOPT_MAXREDIRS 2, CURLOPT_USERAGENT $opt[ua] ?? Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36, CURLOPT_REFERER https://www.baidu.com/, ]); $raw curl_exec($ch); $errno curl_errno($ch); $status curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($errno ! 0 || $status ! 200) { return []; } return parseSugResponse($raw, opensearch); }CURLOPT_RETURNTRANSFER讓響應(yīng)以字符串返回而不是直接輸出到頁(yè)面CONNECTTIMEOUT管連接階段的超時(shí)TIMEOUT管整個(gè)請(qǐng)求的兜底超時(shí)兩個(gè)必須分開(kāi)設(shè)置只設(shè)一個(gè)總超時(shí)會(huì)導(dǎo)致連接卡住時(shí)整個(gè)進(jìn)程跟著掛起。FOLLOWLOCATION配合MAXREDIRS限制為 2 次重定向是為了應(yīng)對(duì)百度偶發(fā)的 302 跳轉(zhuǎn)。UA 通過(guò)$opt[ua]預(yù)留了外部覆蓋的口子。批量采集時(shí)在外部準(zhǔn)備一個(gè)真實(shí)瀏覽器的 UA 列表每個(gè)請(qǐng)求輪流切換比固定一個(gè) UA 的識(shí)別感低很多。3.2 多詞根批量采集與哈希去重單個(gè)函數(shù)只能處理一個(gè)詞建詞庫(kù)的場(chǎng)景里種子詞少則幾百多則上萬(wàn)。采集流程還涉及一個(gè)關(guān)鍵操作叫“詞根擴(kuò)展”第一次抓回來(lái)的聯(lián)想詞本身可以繼續(xù)當(dāng)作種子詞再往深層展開(kāi)。兩層擴(kuò)展之后詞量會(huì)從幾百膨脹到幾萬(wàn)這時(shí)去重必須是全局的。?php function batchCollect(array $seedWords, array $opt []): array { $result []; $seen []; foreach ($seedWords as $seed) { $words fetchSuggestion($seed, $opt); if ($words []) { continue; } foreach ($words as $word) { $word trim($word); if ($word || isset($seen[$word])) { continue; } $seen[$word] true; $result[$seed][] $word; } // 單詞根采集后停頓避免并發(fā)過(guò)密回到空結(jié)果 usleep(($opt[interval_ms] ?? 300) * 1000); } return $result; }這里的去重用 PHP 數(shù)組的鍵充當(dāng)哈希集合isset判斷的時(shí)間復(fù)雜度是 O(1)十萬(wàn)級(jí)詞量完全不用引入外部存儲(chǔ)。去重不是多余的不同種子詞的下拉結(jié)果高度重疊比如“洗碗機(jī)”和“洗碗機(jī)哪個(gè)牌子好”的下拉里都會(huì)出現(xiàn)“洗碗機(jī)十大排名”不去重的話最終生成的詞庫(kù)會(huì)有大量冗余數(shù)據(jù)。$seen數(shù)組還能復(fù)用為下一輪擴(kuò)展的全局過(guò)濾器。第二次擴(kuò)展時(shí)把原來(lái)種子詞的$seen直接傳給下一輪batchCollect所有判斷都基于歷史數(shù)據(jù)能避免同一個(gè)詞被重復(fù)請(qǐng)求幾十次。3.3 結(jié)果落庫(kù)SQLite 表結(jié)構(gòu)與防重復(fù)插入抓回來(lái)的數(shù)據(jù)不能放在 PHP 數(shù)組里等進(jìn)程結(jié)束落庫(kù)是必然選擇。單機(jī)部署的場(chǎng)景下SQLite 是比 MySQL 更合理的方案對(duì)比關(guān)系見(jiàn)下表對(duì)比項(xiàng)SQLiteMySQL部署方式文件型解壓即用獨(dú)立服務(wù)需額外安裝適合規(guī)模單機(jī)十萬(wàn)級(jí)數(shù)據(jù)百萬(wàn)級(jí)以上或多人共享擴(kuò)容方式直接復(fù)制文件遷移表結(jié)構(gòu)同步主從對(duì) PHP 依賴(lài)PDO 內(nèi)置驅(qū)動(dòng)需要 pdo_mysql 擴(kuò)展建表時(shí)把聯(lián)合唯一索引作為防重第一道閘門(mén)?php $pdo new PDO(sqlite:words.db); $pdo-exec(CREATE TABLE IF NOT EXISTS sug_words ( id INTEGER PRIMARY KEY AUTOINCREMENT, seed TEXT NOT NULL, word TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(seed, word) )); $stmt $pdo-prepare(INSERT OR IGNORE INTO sug_words (seed, word) VALUES (?, ?)); foreach ($result as $seed $words) { foreach ($words as $word) { $stmt-execute([$seed, $word]); } }UNIQUE(seed, word)約束保證了同一個(gè)種子詞下不出現(xiàn)重復(fù)的聯(lián)想詞INSERT OR IGNORE在碰到重復(fù)時(shí)會(huì)靜默跳過(guò)不會(huì)拋異常中斷批量任務(wù)。這個(gè)設(shè)計(jì)與 3.2 節(jié)的內(nèi)存去重形成雙保險(xiǎn)進(jìn)程內(nèi)去重?fù)踝〈蠖鄶?shù)重復(fù)插入萬(wàn)一進(jìn)程中斷后重新跑批唯一索引能兜住剩下的部分。4. 下拉詞采集的頻率控制與異常處理4.1 請(qǐng)求頻率、并發(fā)與時(shí)間段參數(shù)設(shè)計(jì)對(duì)公開(kāi)接口做高頻抓取后果通常是出口請(qǐng)求被限流表現(xiàn)為連續(xù)返回空數(shù)組或出現(xiàn) 302 跳轉(zhuǎn)。合理的做法是把節(jié)奏參數(shù)做成配置項(xiàng)而不是每次換項(xiàng)目時(shí)改代碼。下面這三檔參數(shù)是我常用的起點(diǎn)值采集場(chǎng)景并發(fā)數(shù)請(qǐng)求間隔單批詞量上限建議時(shí)段日常更新1500ms200任意時(shí)段批量建詞庫(kù)3500ms1000凌晨 2:00-6:00深度擴(kuò)展5800ms2000凌晨 2:00-6:00并發(fā)數(shù)指的是同時(shí)打開(kāi)的連接數(shù)。PHP 里要做到真正的多連接要用curl_multi拆分 chunk但拉下詞這類(lèi)單次幾十毫秒的接口順序請(qǐng)求配合 300ms 間隔已經(jīng)能跑到每分鐘接近 100 個(gè)詞瓶頸通常不在并發(fā)而在間隔的設(shè)置是否合理。時(shí)間段的選擇也影響成功率。白天高峰時(shí)段百度的接口限流策略更嚴(yán)凌晨批量跑數(shù)據(jù)時(shí)請(qǐng)求返回率和穩(wěn)定性都明顯更好。工具里可以加一個(gè)簡(jiǎn)單的調(diào)度配置把 heavy 任務(wù)默認(rèn)安排在凌晨自動(dòng)執(zhí)行。4.2 采集異常特征與處理對(duì)照表跑批次數(shù)多了以后會(huì)發(fā)現(xiàn)異常類(lèi)型其實(shí)很有限基本集中在下面四類(lèi)異常現(xiàn)象可能原因處理建議HTTP 200 但返回空數(shù)組請(qǐng)求頭缺少 UA / Referer或詞根本身無(wú)聯(lián)想瀏覽器打開(kāi)接口 URL 驗(yàn)證再調(diào)整請(qǐng)求頭HTTP 302 跳轉(zhuǎn)請(qǐng)求頻率過(guò)高觸發(fā)安全策略降低頻率、加大間隔補(bǔ) Cookie 后再試json_decode 返回 null響應(yīng)被 gzip 壓縮但未解壓設(shè)置 CURLOPT_ENCODING 為空字符串交給 curl 自動(dòng)解壓連續(xù)多次結(jié)果相同聚合緩存命中數(shù)據(jù)仍有效屬正常現(xiàn)象間隔幾小時(shí)后再對(duì)比變化第四種情況最容易誤判。百度對(duì)熱門(mén)詞根的聯(lián)想結(jié)果有緩存短時(shí)間里重復(fù)請(qǐng)求拿到的就是同一批詞這不算故障。做對(duì)比分析時(shí)要注意以“結(jié)果是否變化”判斷“程序是否正常”前提是兩次請(qǐng)求的時(shí)間間隔足夠長(zhǎng)至少隔 6 小時(shí)以上。4.3 重試機(jī)制與指數(shù)退避瞬時(shí)異常比如連接超時(shí)、5xx 響應(yīng)重試一次基本就能恢復(fù)。重試不能無(wú)腦地立即再打一次指數(shù)退避是更穩(wěn)妥的做法?php function fetchWithRetry(string $keyword, int $maxRetry 2): array { $wait 1; for ($attempt 0; $attempt $maxRetry; $attempt) { $res fetchSuggestion($keyword); if ($res ! []) { return $res; } if ($attempt $maxRetry) { usleep($wait * 1000000); // 第一次退避 1 秒第二次退避 2 秒 $wait * 2; } } return []; }默認(rèn)重試 2 次退避間隔按 1 秒、2 秒遞增。重試的前提是采集函數(shù)返回空數(shù)組但空數(shù)組也可能是詞根本身沒(méi)有聯(lián)想結(jié)果與真正的異常不同。生產(chǎn)環(huán)境里建議在fetchSuggestion內(nèi)部對(duì)“異常導(dǎo)致的空”和“正常無(wú)結(jié)果”做區(qū)分用錯(cuò)誤碼或拋異常的方式返回而不是把兩種情況合并成一個(gè)空數(shù)組。否則重試邏輯會(huì)把大量無(wú)結(jié)果的詞也重試一遍白白增加請(qǐng)求量。提示任何采集工具都應(yīng)以不擾亂目標(biāo)服務(wù)為前提控制頻率、避開(kāi)忙時(shí)、及時(shí)記錄異常才是能長(zhǎng)期穩(wěn)定跑下去的做法。5. 進(jìn)階下拉詞結(jié)果的聚合分析、導(dǎo)出與效果驗(yàn)證5.1 用 SQL 聚合給聯(lián)想詞做熱度排序幾十萬(wàn)條聯(lián)想詞匯總到sug_words表后單張表的原始記錄沒(méi)有直接價(jià)值需要先做聚合排序。不同種子詞的下拉結(jié)果重疊度很高同一個(gè)詞出現(xiàn)在越多種子詞的下拉里說(shuō)明它的覆蓋范圍越廣越值得重點(diǎn)關(guān)注SELECT word, COUNT(*) AS appear_times FROM sug_words GROUP BY word ORDER BY appear_times DESC LIMIT 50;appear_times是詞頻代表這個(gè)詞在當(dāng)前詞庫(kù)中的出現(xiàn)次數(shù)本質(zhì)上是一種相對(duì)熱度。它不能等同于百度搜索指數(shù)但在同一批種子詞集合里橫向比較詞與詞的覆蓋面已經(jīng)夠用。聚合結(jié)果可以按次數(shù)分檔appear_times熱度標(biāo)記后續(xù)用途 10高熱度做核心詞庫(kù)3-9中熱度做內(nèi)容選題1-2長(zhǎng)尾候選繼續(xù)擴(kuò)展驗(yàn)證5.2 導(dǎo)出 CSV 并解決 Excel 打開(kāi)亂碼數(shù)據(jù)最終要給運(yùn)營(yíng)或產(chǎn)品同事用導(dǎo)出功能直接決定工具的實(shí)用程度。PHP 內(nèi)置的fputcsv足夠應(yīng)付唯一容易踩坑的是 Excel 對(duì) UTF-8 編碼的識(shí)別問(wèn)題?php $fp fopen(php://output, w); // 寫(xiě)入 UTF-8 BOM否則 Excel 打開(kāi) CSV 會(huì)亂碼 fprintf($fp, chr(0xEF) . chr(0xBB) . chr(0xBF)); fputcsv($fp, [種子詞, 聯(lián)想詞, 出現(xiàn)次數(shù), 采集時(shí)間]); foreach ($rows as $row) { fputcsv($fp, [ $row[seed], $row[word], $row[appear_times], $row[created_at] ]); } fclose($fp);BOM 三個(gè)字節(jié)的fprintf是整個(gè)函數(shù)里最容易被忽略的一行。不帶 BOM 的 UTF-8 CSV 用記事本看是正常的用 Excel 雙擊打開(kāi)就會(huì)變成亂碼加 BOM 之后 Excel 才會(huì)按 UTF-8 正確解析。如果還擔(dān)心兼容性可以把fputcsv的第三個(gè)參數(shù)改成\t輸出制表符分隔的 TSV。5.3 效果驗(yàn)證兩輪采集的增量對(duì)比工具最終要回答的問(wèn)題很具體目標(biāo)關(guān)鍵詞到底有沒(méi)有進(jìn)入下拉候選。驗(yàn)證方式不需要復(fù)雜報(bào)表讓程序固定在每天同一時(shí)間跑同一批種子詞然后用 SQL 對(duì)比兩個(gè)時(shí)間窗口的差集即可-- 找出最近 24 小時(shí)新出現(xiàn)的聯(lián)想詞 SELECT word FROM sug_words WHERE created_at DATE(now, -1 day) AND word NOT IN ( SELECT word FROM sug_words WHERE created_at DATE(now, -1 day) );這個(gè)查詢(xún)得到的就是“日增量詞表”。固定種子詞集合、每天跑一次累積一周就能生成一張下拉詞變化趨勢(shì)表直接看到哪些詞在窗口期內(nèi)出現(xiàn)、哪些詞消失。驗(yàn)證周期不要只看單日單日數(shù)據(jù)受緩存和時(shí)段影響太大最短的可靠窗口建議放到 7 天連續(xù)兩輪都出現(xiàn)的詞才說(shuō)明狀態(tài)穩(wěn)定。本文還有配套的精品資源點(diǎn)擊獲取