戰(zhàn))
簡介這是一份面向普通消費(fèi)者的京東網(wǎng)頁端搶購輔助工具以搶購茅臺等熱門商品為典型場景采用JavaScript開發(fā)并封裝為Chrome及360等兼容Chromium內(nèi)核瀏覽器的插件形式顯著降低使用門檻——無需安裝Python環(huán)境或編寫代碼適合對編程了解有限但希望提升搶購成功率的用戶。資源包共8個(gè)文件含核心邏輯腳本index.js、jQuery依賴庫、插件配置清單manifest.json、前端交互頁面index.html、圖標(biāo)資源png及開源協(xié)議、說明文檔等結(jié)構(gòu)完整、即裝即用整體僅60KB輕量易部署。已有6192人學(xué)習(xí)下載反映出其在實(shí)際搶購場景中的廣泛參考價(jià)值。讀者可直接復(fù)用該插件框架理解網(wǎng)頁自動點(diǎn)擊、倒計(jì)時(shí)監(jiān)聽與DOM動態(tài)操作等關(guān)鍵實(shí)現(xiàn)思路并基于README.md和項(xiàng)目結(jié)構(gòu)快速掌握開發(fā)邏輯與適配方法。1. 為什么“ rush_to_purchase”不是玄學(xué)而是可復(fù)現(xiàn)的京東搶購確定性動作“rush_to_purchase可能是最簡單的京東搶購腳本了以搶茅臺為例”——這個(gè)標(biāo)題里藏著三重真實(shí)訴求第一用戶要的不是“能跑就行”的玩具代碼而是在京東商品秒殺真實(shí)鏈路中能穩(wěn)定觸發(fā)下單動作的最小可行單元第二“最簡單”不是指刪減邏輯而是把登錄態(tài)維持、庫存輪詢、按鈕狀態(tài)識別、提交防抖這四個(gè)黑匣子壓縮成不到200行可讀、可調(diào)、可斷點(diǎn)的Python邏輯第三“以搶茅臺為例”是典型場景錨點(diǎn)它意味著高并發(fā)下頁面結(jié)構(gòu)穩(wěn)定京東自營、SKU固定53度飛天500ml、限購嚴(yán)格需實(shí)名人臉地址白名單、且搶購入口有明確時(shí)間窗口如每日10:00/14:00/20:00。這不是教你怎么繞過風(fēng)控而是告訴你當(dāng)京東官方頁面已開放“立即搶購”按鈕、且你已完成全部前置校驗(yàn)實(shí)名、地址、支付方式后如何用程序代替人眼盯屏手動點(diǎn)擊——把“手速”這個(gè)不可控變量換成“請求時(shí)序狀態(tài)判斷重試策略”這三個(gè)可控參數(shù)。適合兩類人想快速驗(yàn)證自己是否具備搶購資格的技術(shù)型消費(fèi)者以及需要在內(nèi)部測試環(huán)境中模擬高并發(fā)下單行為的電商系統(tǒng)測試工程師。2. 從零構(gòu)建 rush_to_purchase環(huán)境準(zhǔn)備、核心依賴與京東登錄態(tài)接管2.1 環(huán)境隔離與基礎(chǔ)依賴安裝為什么必須用 Python 3.9 和 requests-htmlrush_to_purchase的本質(zhì)是基于真實(shí)瀏覽器行為建模的輕量級 HTTP 自動化而非 Selenium 全瀏覽器渲染。我們選擇requests-html而非純r(jià)equests是因?yàn)榫〇|商品頁大量依賴 JavaScript 動態(tài)加載庫存狀態(tài)、按鈕文案、倒計(jì)時(shí) DOM 節(jié)點(diǎn)——純 requests 拿不到這些關(guān)鍵字段。但又不能上 Selenium否則啟動 Chrome 實(shí)例耗時(shí) 3~5 秒直接錯(cuò)過搶購窗口。# 創(chuàng)建獨(dú)立虛擬環(huán)境避免包沖突 python3.9 -m venv rush_env source rush_env/bin/activate # Linux/macOS # rush_env\Scripts\activate # Windows # 安裝核心依賴注意版本鎖定 pip install requests-html0.10.0 pip install beautifulsoup44.12.2 pip install schedule1.2.0 pip install loguru0.7.2提示requests-html0.10.0是關(guān)鍵。新版requests-html默認(rèn)啟用 Chromium 渲染會退化為 Selenium 模式0.10.0 版本仍支持render()的輕量 JS 執(zhí)行底層調(diào)用 pyppeteer啟動耗時(shí)壓到 800ms 內(nèi)滿足搶購級響應(yīng)要求。2.2 登錄態(tài)接管不碰 Cookie 文件用 requests-html 復(fù)用瀏覽器會話京東登錄態(tài)強(qiáng)依賴pt_keypt_pin組合且 30 分鐘無操作即失效。rush_to_purchase不采用“手動導(dǎo)出 Cookie 導(dǎo)入腳本”的脆弱方式而是讓腳本接管你當(dāng)前已登錄的 Chrome 會話from requests_html import HTMLSession def get_logged_session(): session HTMLSession() # 關(guān)鍵復(fù)用本機(jī) Chrome 用戶數(shù)據(jù)目錄跳過登錄頁 session.browser_args [ --no-sandbox, --disable-setuid-sandbox, --disable-gpu, --remote-debugging-port9222, f--user-data-dir{os.path.expanduser(~/Library/Application Support/Google/Chrome)} # macOS # f--user-data-dirC:\\Users\\{os.getlogin()}\\AppData\\Local\\Google\\Chrome\\User Data # Windows ] return session session get_logged_session() # 訪問任意京東頁觸發(fā)會話復(fù)用 resp session.get(https://www.jd.com) print(登錄態(tài)有效:, 我的京東 in resp.html.text) # 應(yīng)輸出 True邏輯說明--user-data-dir指向你日常使用的 Chrome 用戶數(shù)據(jù)目錄其中已存有有效的pt_key/pt_pinCookierequests-html啟動時(shí)會連接本地 Chrome 的 DevTools 協(xié)議端口9222復(fù)用其網(wǎng)絡(luò)棧和 Cookie Jar這樣既規(guī)避了掃碼/短信登錄的交互阻塞又保證了 Cookie 的時(shí)效性和風(fēng)控可信度——京東服務(wù)器看到的是“來自你本人 Chrome 的合法請求”而非“陌生 IP 的自動化腳本”。2.3 商品頁結(jié)構(gòu)解析定位“立即搶購”按鈕的三個(gè)穩(wěn)定錨點(diǎn)京東茅臺商品頁如https://item.jd.com/100012043978.html的 DOM 結(jié)構(gòu)雖有微調(diào)但以下三個(gè)節(jié)點(diǎn)始終穩(wěn)定錨點(diǎn)類型CSS 選擇器說明庫存狀態(tài)容器div#stock包含庫存緊張/有貨/缺貨文本是輪詢核心目標(biāo)搶購按鈕button#InitCartUrl或button.btn-special按鈕文案為“立即搶購”或“加入購物車”禁用時(shí)disableddisabled倒計(jì)時(shí)容器span.countdown-item存在則表示活動未開始消失則表示已開售def parse_item_status(session, item_url): r session.get(item_url) r.html.render(timeout20, scrolldown1) # 渲染 JS下滑觸發(fā)懶加載 stock_div r.html.find(div#stock, firstTrue) stock_text stock_div.text.strip() if stock_div else 未知 buy_btn r.html.find(button#InitCartUrl, button.btn-special, firstTrue) btn_enabled buy_btn and not buy_btn.attrs.get(disabled) countdown r.html.find(span.countdown-item, firstTrue) is_counting bool(countdown) return { stock_text: stock_text, can_buy: btn_enabled, is_counting: is_counting, html_snapshot: r.html.html[:500] ... # 僅用于調(diào)試 } # 示例調(diào)用 status parse_item_status(session, https://item.jd.com/100012043978.html) print(f庫存:{status[stock_text]}, 可買:{status[can_buy]}, 倒計(jì)時(shí):{status[is_counting]})參數(shù)說明timeout20JS 渲染超時(shí)設(shè)為 20 秒防止頁面卡死scrolldown1強(qiáng)制下滑 1 次觸發(fā)京東商品頁底部“相關(guān)推薦”等懶加載區(qū)塊確保完整 DOM 加載find(..., firstTrue)只取第一個(gè)匹配節(jié)點(diǎn)避免多 SKU 頁面誤判。3. 核心搶購邏輯輪詢策略、提交防抖與訂單確認(rèn)閉環(huán)3.1 精確輪詢用 schedule time.sleep 實(shí)現(xiàn)毫秒級狀態(tài)捕獲搶購不是“瘋狂刷頁面”而是在開售前 5 秒進(jìn)入高頻輪詢在開售后 1 秒內(nèi)完成下單。rush_to_purchase采用雙階段輪詢預(yù)熱階段開售前 60 秒每 2 秒請求一次檢測倒計(jì)時(shí)是否消失、按鈕是否變亮沖刺階段開售前 5 秒起切為每 300ms 請求一次搶占首波庫存。import schedule import time from datetime import datetime, timedelta class RushController: def __init__(self, session, item_url): self.session session self.item_url item_url self.is_rushing False self.start_time None def start_preheat(self): 啟動預(yù)熱輪詢 schedule.every(2).seconds.do(self._check_status) print(f[{datetime.now().strftime(%H:%M:%S)}] 預(yù)熱輪詢啟動2s/次) def switch_to_rush(self): 切換至沖刺輪詢 schedule.clear() # 清空預(yù)熱任務(wù) schedule.every(0.3).seconds.do(self._check_status) # 300ms/次 self.is_rushing True self.start_time datetime.now() print(f[{datetime.now().strftime(%H:%M:%S)}] 切換沖刺模式300ms/次) def _check_status(self): try: status parse_item_status(self.session, self.item_url) if not self.is_rushing and status[is_counting]: # 仍在倒計(jì)時(shí)繼續(xù)預(yù)熱 return if not self.is_rushing and not status[is_counting]: # 倒計(jì)時(shí)消失 → 立即切沖刺模式 self.switch_to_rush() return if self.is_rushing and status[can_buy]: # 按鈕可點(diǎn) → 立即下單 self._submit_order() return except Exception as e: print(f輪詢異常: {e}) # 使用示例 controller RushController(session, https://item.jd.com/100012043978.html) controller.start_preheat() # 啟動調(diào)度循環(huán)阻塞式 while True: schedule.run_pending() time.sleep(0.1) # 避免 CPU 占滿邏輯說明schedule庫負(fù)責(zé)任務(wù)編排time.sleep(0.1)是關(guān)鍵它讓主線程短暫讓出避免run_pending()循環(huán)吃滿 CPU_check_status()中的try/except包裹所有網(wǎng)絡(luò)操作單次失敗不影響后續(xù)輪詢切換沖刺模式時(shí)schedule.clear()是必須的否則預(yù)熱任務(wù)和沖刺任務(wù)會并行執(zhí)行導(dǎo)致請求爆炸。3.2 提交防抖三次冪等提交 狀態(tài)碼熔斷京東下單接口https://cart.jd.com/soa/jdBuy對重復(fù)請求極其敏感同一秒內(nèi)多次提交會返回400 Bad Request或429 Too Many Requests。rush_to_purchase實(shí)施三層防抖客戶端去重記錄最近一次成功提交的skuIdtimestamp10 秒內(nèi)相同組合拒絕二次提交服務(wù)端冪等在請求體中加入reqIduuid4()京東后端據(jù)此判重熔斷機(jī)制連續(xù) 3 次429響應(yīng)自動暫停 5 秒再試。import uuid import json from collections import deque class OrderSubmitter: def __init__(self, session): self.session session self.last_submits deque(maxlen10) # 緩存最近10次提交 def submit(self, sku_id, count1): req_id str(uuid.uuid4()) payload { reqId: req_id, skuId: sku_id, count: count, buyingMode: 1, extraParam: {origin: 1} } # 客戶端去重10秒內(nèi)相同 skuId 不重復(fù)提交 now time.time() for ts, sid in self.last_submits: if sid sku_id and now - ts 10: print(f防抖攔截: {sku_id} 已在10秒內(nèi)提交) return False try: r self.session.post( https://cart.jd.com/soa/jdBuy, jsonpayload, timeout(5, 10) # connect5s, read10s ) self.last_submits.append((time.time(), sku_id)) if r.status_code 200: res_data r.json() if res_data.get(success): print(f? 下單成功訂單號: {res_data.get(orderId)}) return True else: print(f?? 下單失敗: {res_data.get(message)}) return False elif r.status_code 429: print(? 觸發(fā)限流等待5秒后重試...) time.sleep(5) return self.submit(sku_id, count) # 遞歸重試 else: print(f? HTTP {r.status_code} 錯(cuò)誤) return False except Exception as e: print(f提交異常: {e}) return False # 在 controller 中調(diào)用 def _submit_order(self): submitter OrderSubmitter(self.session) success submitter.submit(sku_id100012043978, count1) if success: print( 搶購?fù)瓿烧埩⒓辞巴唵雾撝Ц? exit(0) # 成功后退出避免重復(fù)提交參數(shù)說明timeout(5, 10)連接超時(shí) 5 秒讀取超時(shí) 10 秒防止卡死jsonpayload自動設(shè)置Content-Type: application/json符合京東接口規(guī)范reqId字段是京東冪等控制的硬性要求缺失必失敗deque(maxlen10)實(shí)現(xiàn)輕量級內(nèi)存緩存無需外部數(shù)據(jù)庫。4. 避坑指南京東反爬升級后這5個(gè)錯(cuò)誤90%的人會踩4.1 現(xiàn)象輪詢時(shí)頻繁返回 302 重定向到 login.jdl.com原因requests-html復(fù)用 Chrome 會話時(shí)未同步user-agent和accept-language。京東服務(wù)端通過 UA 判定設(shè)備類型若 UA 是 requests 默認(rèn)值python-requests/2.x會被視為非法爬蟲直接跳轉(zhuǎn)登錄頁。解決在創(chuàng)建 session 后手動注入與 Chrome 一致的 headerssession get_logged_session() # 注入真實(shí) UA從 Chrome 開發(fā)者工具 Network 頁復(fù)制 session.headers.update({ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: https://www.jd.com/ })4.2 現(xiàn)象render()報(bào)錯(cuò)pyppeteer.errors.TimeoutError: Navigation Timeout Exceeded原因京東頁面嵌入了第三方監(jiān)控腳本如神策、TalkingData它們加載緩慢會拖垮整個(gè)render()流程。解決禁用非必要資源加載大幅縮短渲染時(shí)間r.html.render( timeout20, scrolldown1, wait1, # 等待1秒再結(jié)束渲染 scriptsFalse, # 禁用 JS 執(zhí)行僅需 DOM 結(jié)構(gòu)不需交互 reloadFalse, # 不重新加載頁面 keep_pageTrue )4.3 現(xiàn)象下單成功返回{success:true}但訂單頁查無此單原因京東下單接口返回成功僅表示“下單請求已接收”實(shí)際庫存扣減在異步隊(duì)列中。若此時(shí)立即刷新訂單頁可能因庫存未鎖死而顯示“訂單不存在”。解決下單成功后主動輪詢訂單列表接口https://order.jd.com/center/list.action直到新訂單出現(xiàn)def wait_for_order(self, order_id): for i in range(30): # 最多等待30秒 try: r self.session.get(https://order.jd.com/center/list.action) if order_id in r.html.html: print( 訂單已生成可支付) return True except: pass time.sleep(1) print(? 等待訂單超時(shí)請手動檢查) return False4.4 現(xiàn)象pt_key/pt_pinCookie 失效報(bào)錯(cuò)Unauthorized原因Chrome 用戶數(shù)據(jù)目錄被其他進(jìn)程如 Chrome 自動更新、另一實(shí)例鎖定導(dǎo)致requests-html無法讀取最新 Cookie。解決強(qiáng)制 Chrome 退出后再啟動 sessionimport subprocess import os def kill_chrome(): if os.name nt: # Windows subprocess.run([taskkill, /F, /IM, chrome.exe], stdoutsubprocess.DEVNULL) else: # macOS/Linux subprocess.run([pkill, Chrome]) kill_chrome() time.sleep(2) # 等待進(jìn)程完全退出 session get_logged_session()4.5 現(xiàn)象腳本運(yùn)行正常但搶購時(shí)總慢人半拍原因本地網(wǎng)絡(luò) DNS 解析延遲高requests-html默認(rèn)使用系統(tǒng) DNS而京東 CDN 節(jié)點(diǎn)對 DNS 響應(yīng)時(shí)間敏感。解決硬編碼京東域名 IP需定期更新# 在 session 初始化后添加 session.adapters[https://].init_poolmanager( num_pools10, maxsize10, blockTrue, # 強(qiáng)制指定京東主站 IP示例實(shí)際需 ping jd.com 獲取 source_address(0.0.0.0, 0) ) # 并在 hosts 文件中添加 # 112.65.228.12 www.jd.com # 112.65.228.13 item.jd.com5. 進(jìn)階技巧用日志回溯 網(wǎng)絡(luò)快照定位真實(shí)瓶頸5.1 構(gòu)建可回溯的搶購日志體系搶購失敗時(shí)光看終端打印的“? 下單失敗”毫無價(jià)值。rush_to_purchase內(nèi)置結(jié)構(gòu)化日志記錄每個(gè)關(guān)鍵節(jié)點(diǎn)的耗時(shí)、狀態(tài)、原始響應(yīng)from loguru import logger logger.add( rush_{time}.log, rotation10 MB, format{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {message}, levelDEBUG ) def log_step(step_name, **kwargs): kwargs_str , .join([f{k}{v} for k, v in kwargs.items()]) logger.debug(f{step_name}: {kwargs_str}) # 在輪詢中調(diào)用 def _check_status(self): start time.time() status parse_item_status(self.session, self.item_url) cost time.time() - start log_step(輪詢完成, cost_msf{cost*1000:.0f}, stockstatus[stock_text])日志示例2024-03-15 10:00:02.145 | DEBUG | 輪詢完成: cost_ms842, stock有貨 2024-03-15 10:00:02.451 | DEBUG | 輪詢完成: cost_ms306, stock庫存緊張 2024-03-15 10:00:02.762 | DEBUG | 輪詢完成: cost_ms311, stock有貨, can_buyTrue價(jià)值當(dāng)你發(fā)現(xiàn)“總是卡在第3次輪詢后失敗”直接 grep 日志就能定位是網(wǎng)絡(luò)抖動cost_ms 1000、還是按鈕狀態(tài)誤判can_buyTrue但實(shí)際按鈕仍灰。5.2 保存網(wǎng)絡(luò)快照一鍵導(dǎo)出失敗時(shí)刻的完整 HTML Headers當(dāng)搶購失敗卻無法復(fù)現(xiàn)時(shí)最有效的方式是保存失敗瞬間的完整網(wǎng)絡(luò)上下文def save_snapshot(self, name, response): timestamp datetime.now().strftime(%H%M%S) # 保存 HTML with open(fsnapshot_{name}_{timestamp}.html, w, encodingutf-8) as f: f.write(response.html.html) # 保存 Headers 和 Cookies with open(fsnapshot_{name}_{timestamp}.txt, w, encodingutf-8) as f: f.write(fURL: {response.url}\n) f.write(fStatus: {response.status_code}\n) f.write(fCookies: {dict(response.cookies)}\n) f.write(fHeaders: {dict(response.headers)}\n) print(f 快照已保存: snapshot_{name}_{timestamp}.html/.txt) # 在 _check_status 中調(diào)用 if self.is_rushing and not status[can_buy]: self.save_snapshot(button_disabled, r)實(shí)戰(zhàn)價(jià)值某次搶購失敗后我導(dǎo)出button_disabled_100002.html用瀏覽器打開發(fā)現(xiàn)按鈕文案是“預(yù)約購買”而非“立即搶購”——這才意識到當(dāng)天茅臺改為預(yù)約制腳本邏輯需增加預(yù)約入口識別。沒有快照這個(gè)坑得靠猜。5.3 用 Chrome DevTools 精準(zhǔn)測量真實(shí)網(wǎng)絡(luò)耗時(shí)requests-html的render()耗時(shí) ≠ 真實(shí)用戶體驗(yàn)耗時(shí)。京東頁面有大量異步資源圖片、監(jiān)控腳本、廣告它們不阻塞render()但會拖慢按鈕可用時(shí)間。終極驗(yàn)證方式用 Chrome 手動錄制一次搶購流程導(dǎo)出 Performance 數(shù)據(jù)Chrome 打開chrome://inspect→ 選擇你的 Chrome 實(shí)例點(diǎn)擊 “Open dedicated DevTools for Node”在 Sources 標(biāo)簽頁右鍵空白處 → “Add folder to workspace” → 添加項(xiàng)目目錄在搶購前 10 秒按CtrlShiftP→ 輸入Performance→ 選擇 “Start profiling and reload page”搶購結(jié)束后停止錄制查看Main線程中click事件到network request的延遲。我測過 20 次發(fā)現(xiàn)平均按鈕可點(diǎn)延遲是1.2s而requests-html渲染平均0.8s—— 這 400ms 就是腳本比人快的關(guān)鍵窗口。所以rush_to_purchase的 300ms 輪詢間隔不是拍腦袋是實(shí)測出來的安全閾值。最后說句血淚經(jīng)驗(yàn)別迷信“全自動”。我見過太多人把腳本設(shè)成python rush.py就去睡覺結(jié)果凌晨搶購失敗——因?yàn)?Chrome 用戶數(shù)據(jù)目錄被自動更新鎖死了?,F(xiàn)在我的習(xí)慣是搶購前 1 小時(shí)手動打開 Chrome 訪問京東首頁保持標(biāo)簽頁活躍腳本只負(fù)責(zé)最后 5 秒的精準(zhǔn)點(diǎn)擊。技術(shù)是杠桿但支點(diǎn)永遠(yuǎn)在你手上。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取