實現(xiàn)一個爬取微信小程序數(shù)據(jù)并定時秒殺的爬蟲+工程化初步實踐)
文章目錄前言用Charles 抓包 iOS 微信小程序在Mac端和iOS端安裝Charles 自簽名證書Mac端iOS端能抓到Safari瀏覽器的包但是抓不到微信小程序的包直接在iOS 上抓包的App如何抓取Android 7.0 以上/Harmony OS微信小程序包抓包獲取token和請求數(shù)據(jù)格式session_keyPython 項目工程化pip 切換為國內(nèi)鏡像源工程化參考腳手架Python 虛擬環(huán)境實現(xiàn)爬蟲動態(tài)IP確保代理服務(wù)器的延遲夠低付費HTTP代理選購指南明確自己的需求按量付費 vs 靜態(tài)IP設(shè)置User-Agent發(fā)起爬蟲請求設(shè)置請求的證書關(guān)閉其他科學(xué)上網(wǎng)工具優(yōu)化爬蟲速度使用多線程提高并發(fā)提前完成TCP三次握手以建立HTTP連接根據(jù)過去請求延遲統(tǒng)計提前N毫秒發(fā)起請求應(yīng)對競爭爬蟲推送爬取結(jié)果到iOS未解決的問題打碼平臺破解驗證碼關(guān)于使用爬蟲的額外注意事項企業(yè)級應(yīng)用如何防止爬蟲備注前言公司準(zhǔn)備用Python替換傳統(tǒng)的Shell來做自動化運維最近正好在做這方面的code review試著用Python寫一個小爬蟲順便入門一下Python。該爬蟲的功能是對目標(biāo)小程序定時發(fā)起下訂單請求將下訂單請求結(jié)果推送到iOS以提醒成功或失敗用Charles 抓包 iOS 微信小程序原理是電腦和手機處于同一網(wǎng)絡(luò)中在電腦上安裝Charles電腦和手機都安裝Charles 自簽名證書然后更改手機網(wǎng)絡(luò)設(shè)置將手機上的網(wǎng)絡(luò)請求轉(zhuǎn)發(fā)至電腦上的Charles以實現(xiàn)抓包在Mac端和iOS端安裝Charles 自簽名證書Mac端設(shè)置步驟見Charles 文檔 SSL Certificates MacOS部分。安裝完畢 在 鑰匙串訪問 中設(shè)置為始終信任在 Proxy SSL Proxy Settings Include 中添加任意域名 然后在瀏覽器中訪問該網(wǎng)站來測試 Charles是否可以解析HTTPS數(shù)據(jù)包。如果想對所有流量進行抓包域名設(shè)置為*, 端口設(shè)置為443不過不建議這樣做這樣做會有大量的我們不關(guān)心的包會對我們造成干擾。這種做法建議只用于測試配置是否正確iOS端設(shè)置步驟見Charles 文檔 SSL Certificates iOS 部分 。在iOS 瀏覽器中下載且安裝完證書之后在設(shè)置 通用 關(guān)于本機 證書信任設(shè)置 中啟用該證書修改網(wǎng)絡(luò)設(shè)置 配置代理 手動 IP 可以在Charles Help Local IP Address 中找到Port 一般為8888打開瀏覽器 訪問任意網(wǎng)站注意這里要和Charles 中的 SSL Proxy Settings Include 對應(yīng)。查看Charles是否可以解析HTTPS數(shù)據(jù)能抓到Safari瀏覽器的包但是抓不到微信小程序的包經(jīng)過上面的測試之后開始正式抓小程序的包。 Mac端和iOS端 的瀏覽器都可以抓到包不過卻發(fā)現(xiàn)無法抓小程序的包。 Google了一下發(fā)現(xiàn)需要打開 設(shè)置 App 微信 本地網(wǎng)絡(luò)感謝這位博主的分享直接在iOS 上抓包的App為什么會有這個需求呢是因為 我覺得在iOS上抓包這么做太麻煩了想著有沒有直接可以安裝在iOS上的App還真有Charles iOS版58 人民幣還未購買使用Stream免費。但是經(jīng)過實際測試該App已經(jīng)很久沒更新了無法抓HTTPS包蜻蜓抓包免費。未經(jīng)過測試使用如果還有其他好用的iOS抓包 App歡迎評論區(qū)留言推薦如何抓取Android 7.0 以上/Harmony OS微信小程序包在另一臺華為手機 微信中抓包發(fā)現(xiàn)抓不到系統(tǒng)為harmony os 4.2。Google了一下發(fā)現(xiàn)很多人都有這個問題。簡單來說在 Android7.0 及以上的系統(tǒng)中App只信任系統(tǒng)預(yù)裝證書而不信任用戶安裝的證書。由于主力機是iPhone我就沒有深入研究如何解決這個問題想解決這個問題可參考 知乎回答 和 另外一位博主分享抓包獲取token和請求數(shù)據(jù)格式經(jīng)過抓包得到了小程序下單的請求數(shù)據(jù)格式小程序自定義的token來自于其內(nèi)部的 login接口請求響應(yīng)里包含了token和登錄時間loginTime以及過期時間expireTime發(fā)現(xiàn)過期時間為24小時該請求參數(shù)只有一個名為code的參數(shù)應(yīng)該是wx.login返回的。后面經(jīng)過了解發(fā)現(xiàn)該小程序應(yīng)該用的是靜默登錄關(guān)于小程序的靜默登錄流程以及原理建議閱讀這位博主的文章session_key題外話有的小程序的token失效時間可能是以微信的session_key維護時間為準(zhǔn)的也就是只要session_key有效那么這個token就是有效的。而關(guān)于session_key的過期時間這篇文章提到微信不會把 session_key 的有效期告知開發(fā)者。我們會根據(jù)用戶使用小程序的行為對 session_key 進行續(xù)期。用戶越頻繁使用小程序session_key 有效期越長Python 項目工程化經(jīng)過上面的抓包拿到了目標(biāo)小程序的請求格式和數(shù)據(jù)格式。開始寫Python腳本既然是個項目不如從一開始就規(guī)范起來使用企業(yè)級的Python項目工程化結(jié)構(gòu)包括使用流行的包管理工具代碼風(fēng)格代碼風(fēng)格檢測單元測試打包等等pip 切換為國內(nèi)鏡像源最好切換為國內(nèi)鏡像源這樣下載包更快更穩(wěn)定。我使用清華大學(xué)的鏡像源pip configsetglobal.index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple pip configsetglobal.trusted-host mirrors.tuna.tsinghua.edu.cn工程化參考關(guān)于Python項目的工程化我使用的python項目工程化指南里提到的一些組件建議閱讀該指南并學(xué)習(xí)其中 快速上手 章節(jié)的小例子腳手架上述指南中使用的腳手架是cookiecutter如果你也使用需要在初始化好之后升級一些依賴的版本腳手架生成的一些依賴的版本現(xiàn)在比較低了如果直接使用會報錯cookiecutter 的提交信息提示已經(jīng)是5年前了另外一個比較活躍的腳手架項目是pyscaffold。我還沒研究等后面研究透了再在其他博文中詳細(xì)介紹。本文使用的cookiecutterPython 虛擬環(huán)境Python虛擬環(huán)境使用poetry。激活Python項目的虛擬環(huán)境命令見poetry Managing environments文檔2026-08-20更新: 現(xiàn)在我更推薦uv主要是uv用起來比較絲滑實現(xiàn)爬蟲根據(jù)上述指南生成項目腳手架且升級了相關(guān)依賴之后開始正式寫爬蟲代碼。核心爬蟲邏輯就幾行構(gòu)造下訂單數(shù)據(jù)然后使用requests發(fā)起請求動態(tài)IP在測試過程中發(fā)現(xiàn)目標(biāo)小程序添加了同一個IP 1秒內(nèi)不能訪問2次的限制。找了一家國內(nèi)付費的TLS代理服務(wù)的公司買了個IP池。國內(nèi)付費HTTP/TLS代理的公司對于個人用戶推出的套餐基本差不多有按照數(shù)量計費的有按照小時/天計費的。我選的是按數(shù)量計費并且所選IP失效為5分鐘左右。在選擇具體產(chǎn)品時要關(guān)注IP得是 高度匿名代理這樣才不會被目標(biāo)服務(wù)器追蹤到原始客戶端IPIP質(zhì)量代理IP要夠穩(wěn)定且延遲低確保代理服務(wù)器的延遲夠低要讓代理請求到目標(biāo)小程序的延遲夠低首先選擇的IP最好和目標(biāo)小程序所在區(qū)域一樣。經(jīng)過上面的抓包得知小程序的域名使用dig命令通過域名查到該域名的IPdigwww.xxxx.com再通過IP歸屬地查詢即可得知該主機IP位于哪里在接入HTTP代理服務(wù)時商家一般提供可選城市選擇離主機IP最近的城市即可。當(dāng)然了還要綜合考量不一定離目標(biāo)網(wǎng)站所在地越近的代理IP速度越快應(yīng)該還和商家自身的硬件部署有關(guān)所以如果發(fā)現(xiàn)離目標(biāo)網(wǎng)站所在地的IP反而更慢那就果斷切換至其他節(jié)點即使完成了上述步驟商家給你的IP池不一定每一個延遲都很低所以在拿到IP之后要測試一下該IP到目標(biāo)小程序的速度如果速度不滿足則丟棄重新獲取新的IP重復(fù)上述步驟直至獲得滿足延遲的IPget low latency proxy serversimportjsonimportloggingimporttimeimportrequests loggerlogging.getLogger(__name__)PROXY_SERVICE_PROVIDER_URL(這里是HTTP代理服務(wù)商家的接入API)TARGET_SERVER_URL這里是目標(biāo)小程序的APIdefget_proxy_ips_latency_less_than_one_second(need_ip_nums:int)-dict:return a set of ip latency less than 1 secondgood_proxy_ip{}request_start_timetime.time()proxy_index1whilelen(good_proxy_ip)need_ip_nums:reponserequests.get(PROXY_SERVICE_PROVIDER_URL,timeout5)logger.debug(json.dumps(reponse.json(),indent4,ensure_asciiFalse))ipreponse.json()[data][0][ip]portreponse.json()[data][0][port]proxy_ipfhttp://{ip}:{port}proxies{http:proxy_ip,https:proxy_ip}iftest_proxy_delay(proxies,TARGET_SERVER_URL,1):good_proxy_ip[proxy_index]proxies proxy_index1request_end_timetime.time()logger.info(successfully get a batch of proxy IPs with a delayof less than or equals 1 second, cost %d seconds,request_end_time-request_start_time)logger.info(IP proxies\n%s,good_proxy_ip)returngood_proxy_ipdeftest_proxy_delay(request_proxies,target_url:str,request_timeout:int)-bool:test proxy ips latency whether less than timeout secondsrequest_start_timetime.time()logger.debug(start time: %s,time.strftime(%H:%M:%S,time.localtime(request_start_time)))# add try-except block to avoid program crashestry:responserequests.get(target_url,timeout5,proxiesrequest_proxies)ifresponse.status_code200andresponse.json().get(code)200andresponse.json().get(msg)操作成功:request_end_timetime.time()logger.debug(end time: %s,time.strftime(%H:%M:%S,time.localtime(request_end_time)))delayrequest_end_time-request_start_time logger.debug(Proxy %s response time: %.2f seconds,request_proxies,delay)returndelayrequest_timeoutreturnFalseexceptrequests.exceptions.RequestExceptionase:logger.error(Error testing proxy %s: %s,request_proxies,str(e))returnFalse付費HTTP代理選購指南明確自己的需求我的需求就是代理服務(wù)器到目標(biāo)服務(wù)器的延遲足夠低由于是秒殺業(yè)務(wù)所以如果代理延遲高錯過開售的那一兩秒后面就沒什么意義了。我對代理的延遲要求在100ms以內(nèi)按量付費 vs 靜態(tài)IP對于我來說調(diào)查了幾家HTTP代理商的按量付費套餐的IP發(fā)現(xiàn)這個套餐基本做不到我要的低延遲例如100ms以內(nèi)靜態(tài)IP 沒怎么試用過不過根據(jù)各家廠商宣傳靜態(tài)IP延遲足夠低都是自己機房的專線。很心動但是靜態(tài)IP實在是太貴了設(shè)置User-Agent偽造 User-Agent使用fake-useragent。使用該庫時注意多次請求/多線程 應(yīng)只使用同一個對象避免多次初始化對象浪費時間uaUserAgent(platformsmobile)ua.random發(fā)起爬蟲請求最終在發(fā)起請求時設(shè)置reqeusts的proxies即可responserequests.post(request_url,request_json_data,headersrequest_header,timeout5,verifyCERT_PATH,proxiesrequest_proxies)設(shè)置請求的證書分三種情況如果你想在請求過程中開著Charles這時的證書來自Charles。把Charles的證書保存下來這時CERT_PATH是Charles自己證書的路徑。Charles 官方文檔 Python 提到了這一點如果你已經(jīng)抓到了所需要的目標(biāo)小程序的數(shù)據(jù)格式那么請求時無需再開Charles代理。這時的證書來自 瀏覽器打開 目標(biāo)小程序 域名 查看證書 下載 。下載前點擊證書詳情并確保這個證書的名字不帶有Charles如果有則關(guān)閉Charles并在瀏覽器中 刪除目標(biāo)小程序的cookie重新加載即可獲得小程序后臺服務(wù)器的證書也可以在請求時不指定verify參數(shù)關(guān)閉其他科學(xué)上網(wǎng)工具有的科學(xué)上網(wǎng)工具如果你沒設(shè)置好無論國內(nèi)國外的流量都會先經(jīng)過它的節(jié)點這樣請求反而是慢了很多優(yōu)化爬蟲速度使用多線程提高并發(fā)我這里還沒有使用scheduler只是使用 threading.Thread方法。線程的執(zhí)行邏輯是線程啟動之后即準(zhǔn)備數(shù)據(jù)即獲得一個延遲足夠低的代理IP構(gòu)造請求數(shù)據(jù)在requests.post前一行計算當(dāng)前時間距離目標(biāo)時間的毫秒數(shù)然后time.sleep 休眠。等到目標(biāo)時間一到所有線程立刻同時發(fā)起請求而無需等待其他步驟提前完成TCP三次握手以建立HTTP連接由于現(xiàn)在使用的是按量付費套餐代理IP延遲基本在1秒內(nèi)。所以如果時間到了再發(fā)起請求建立TCP連接請求數(shù)據(jù)如果不算代理延遲起碼慢了幾百個毫秒這幾百個毫秒就是用來完成TCP三次握手的如果算上代理延遲那基本請求到達(dá)服務(wù)器要到開始時間的2秒以后了。于是優(yōu)化代碼邏輯在爬蟲開始前幾秒對目標(biāo)網(wǎng)站發(fā)起一個HEAD 請求此請求的目的是完成TCP三次握手以建立HTTP連接。同時使用requests.Session保證HTTP連接keep-alive。由于我們的請求是到代理服務(wù)器代理服務(wù)器到目標(biāo)網(wǎng)站這中間的兩個連接是不是長連接不確定。根據(jù)日志來看這兩個長連接是保持住的了如何得知的項目開啟debug模式發(fā)起請求請求時urllib3的connectionpool.py會打印如下日志Starting new HTTPS connection過幾秒再次請求發(fā)現(xiàn)沒有如上日志說明用的是同一個連接就這一個小小的優(yōu)化讓我的爬蟲有了質(zhì)的提高領(lǐng)先其他人一步。所以說計算機基礎(chǔ)還是很重要滴根據(jù)過去請求延遲統(tǒng)計提前N毫秒發(fā)起請求每次爬蟲都會記錄發(fā)起請求時間和結(jié)束請求時間根據(jù)統(tǒng)計就可以估計出代理服務(wù)器到目標(biāo)服務(wù)器的延遲所以在秒殺開售前的N毫秒發(fā)起請求保證在門票開售時請求就到達(dá)了服務(wù)器。例如秒殺開始時間晚上8:30根據(jù)統(tǒng)計代理服務(wù)器到目標(biāo)服務(wù)器延遲大概在300-400ms左右那么就是8:29:59:700 時發(fā)起請求應(yīng)對競爭爬蟲在實際爬取過程中發(fā)現(xiàn)還存在其他競爭對手。如何打敗對方呢從實際情況來看要面對的不止競爭爬蟲還有正常用戶發(fā)起的請求如何讓服務(wù)器先處理自己發(fā)起的請求呢更低的延遲。但是目前在使用動態(tài)IP按量付費的套餐下延遲沒辦法控制。倒是可以使用自己家高質(zhì)量的網(wǎng)絡(luò)而不使用代理但是我又不想暴露自己IP更多的請求。增加線程數(shù)例如增加到幾十個甚至上百個以量搶占服務(wù)器處理請求的線程資源例如Tomcat用來處理請求的線程如果服務(wù)器用來處理請求的線程都被我們的爬蟲占滿那其他請求自然要排隊。不過如果控制不好就變成了DDoS攻擊推送爬取結(jié)果到iOS由于該爬蟲是定時執(zhí)行有的時候不一定在家。所以需要一個將爬取結(jié)果推送到iOS上。鑒于APNs即Apple Push Notification service 有點復(fù)雜且不想花時間在上面于是使用Bark來幫助快速開發(fā)。接入步驟非常簡單建議閱讀文檔未解決的問題經(jīng)過上述步驟mini版的爬蟲基本滿足了自己的需求即定時下單并推送消息到iOS提醒我付款。但是有以下幾個問題未解決由于猜測token失效時間是24小時所以在拿到了一個有效token之后要注意token時間。不過可以另辟蹊蹺可以每天手動將小程序刪除然后重新訪問即可在爬取之前得到一個全新的token就無需考慮token過期的問題該小程序防止爬蟲請求只在用戶ID層面制訂了防護策略即只允許同一用戶下兩單并沒有接入驗證碼之類的防護打碼平臺破解驗證碼如果未來該小程序接入了驗證碼那么我決定使用付費的打碼平臺來進行破解。有關(guān)打碼平臺的介紹可參考打碼平臺是如何高效的破解市面上各家驗證碼平臺的各種形式驗證碼的 這篇文章關(guān)于使用爬蟲的額外注意事項如果目標(biāo)網(wǎng)站有賬號機制例如小程序這種目標(biāo)網(wǎng)站可以根據(jù)某些信息openid確定你的電話號之類的優(yōu)先使用備用微信號以免該微信號在該網(wǎng)站中被封影響主賬號的使用企業(yè)級應(yīng)用如何防止爬蟲在平時就會有各種各樣的爬蟲無時無刻的來爬我司系統(tǒng)Google的Meta的等等這些還是比較正規(guī)的爬蟲還比較講道理。有的爬蟲根本不講道理爬取時間段等毫無規(guī)律一旦爬取太厲害會直接導(dǎo)致服務(wù)器CPUMemory線程池頻繁GC等報警。我們從一開始使用Google Captcha然后升級到AWS的WAF效果都不太好。最近接入了DataDome目前來看效果還是可以的。備注由于該爬蟲核心邏輯就是發(fā)起一個API請求足夠簡單就不再提供示例源碼了只提供上述思路。有關(guān)Python的其他最佳實踐日后會在其他博文中介紹本篇只是讓各位同學(xué)對Python工程化和基本的爬蟲技術(shù)有個整體的了解