实现一个爬取微信小程序数据并定时秒杀的爬虫+工程化初步实践)
文章目录前言用Charles 抓包 iOS 微信小程序在Mac端和iOS端安装Charles 自签名证书Mac端iOS端能抓到Safari浏览器的包但是抓不到微信小程序的包直接在iOS 上抓包的App如何抓取Android 7.0 以上/Harmony OS微信小程序包抓包获取token和请求数据格式session_keyPython 项目工程化pip 切换为国内镜像源工程化参考脚手架Python 虚拟环境实现爬虫动态IP确保代理服务器的延迟够低付费HTTP代理选购指南明确自己的需求按量付费 vs 静态IP设置User-Agent发起爬虫请求设置请求的证书关闭其他科学上网工具优化爬虫速度使用多线程提高并发提前完成TCP三次握手以建立HTTP连接根据过去请求延迟统计提前N毫秒发起请求应对竞争爬虫推送爬取结果到iOS未解决的问题打码平台破解验证码关于使用爬虫的额外注意事项企业级应用如何防止爬虫备注前言公司准备用Python替换传统的Shell来做自动化运维最近正好在做这方面的code review试着用Python写一个小爬虫顺便入门一下Python。该爬虫的功能是对目标小程序定时发起下订单请求将下订单请求结果推送到iOS以提醒成功或失败用Charles 抓包 iOS 微信小程序原理是电脑和手机处于同一网络中在电脑上安装Charles电脑和手机都安装Charles 自签名证书然后更改手机网络设置将手机上的网络请求转发至电脑上的Charles以实现抓包在Mac端和iOS端安装Charles 自签名证书Mac端设置步骤见Charles 文档 SSL Certificates MacOS部分。安装完毕 在 钥匙串访问 中设置为始终信任在 Proxy SSL Proxy Settings Include 中添加任意域名 然后在浏览器中访问该网站来测试 Charles是否可以解析HTTPS数据包。如果想对所有流量进行抓包域名设置为*, 端口设置为443不过不建议这样做这样做会有大量的我们不关心的包会对我们造成干扰。这种做法建议只用于测试配置是否正确iOS端设置步骤见Charles 文档 SSL Certificates iOS 部分 。在iOS 浏览器中下载且安装完证书之后在设置 通用 关于本机 证书信任设置 中启用该证书修改网络设置 配置代理 手动 IP 可以在Charles Help Local IP Address 中找到Port 一般为8888打开浏览器 访问任意网站注意这里要和Charles 中的 SSL Proxy Settings Include 对应。查看Charles是否可以解析HTTPS数据能抓到Safari浏览器的包但是抓不到微信小程序的包经过上面的测试之后开始正式抓小程序的包。 Mac端和iOS端 的浏览器都可以抓到包不过却发现无法抓小程序的包。 Google了一下发现需要打开 设置 App 微信 本地网络感谢这位博主的分享直接在iOS 上抓包的App为什么会有这个需求呢是因为 我觉得在iOS上抓包这么做太麻烦了想着有没有直接可以安装在iOS上的App还真有Charles iOS版58 人民币还未购买使用Stream免费。但是经过实际测试该App已经很久没更新了无法抓HTTPS包蜻蜓抓包免费。未经过测试使用如果还有其他好用的iOS抓包 App欢迎评论区留言推荐如何抓取Android 7.0 以上/Harmony OS微信小程序包在另一台华为手机 微信中抓包发现抓不到系统为harmony os 4.2。Google了一下发现很多人都有这个问题。简单来说在 Android7.0 及以上的系统中App只信任系统预装证书而不信任用户安装的证书。由于主力机是iPhone我就没有深入研究如何解决这个问题想解决这个问题可参考 知乎回答 和 另外一位博主分享抓包获取token和请求数据格式经过抓包得到了小程序下单的请求数据格式小程序自定义的token来自于其内部的 login接口请求响应里包含了token和登录时间loginTime以及过期时间expireTime发现过期时间为24小时该请求参数只有一个名为code的参数应该是wx.login返回的。后面经过了解发现该小程序应该用的是静默登录关于小程序的静默登录流程以及原理建议阅读这位博主的文章session_key题外话有的小程序的token失效时间可能是以微信的session_key维护时间为准的也就是只要session_key有效那么这个token就是有效的。而关于session_key的过期时间这篇文章提到微信不会把 session_key 的有效期告知开发者。我们会根据用户使用小程序的行为对 session_key 进行续期。用户越频繁使用小程序session_key 有效期越长Python 项目工程化经过上面的抓包拿到了目标小程序的请求格式和数据格式。开始写Python脚本既然是个项目不如从一开始就规范起来使用企业级的Python项目工程化结构包括使用流行的包管理工具代码风格代码风格检测单元测试打包等等pip 切换为国内镜像源最好切换为国内镜像源这样下载包更快更稳定。我使用清华大学的镜像源pip configsetglobal.index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple pip configsetglobal.trusted-host mirrors.tuna.tsinghua.edu.cn工程化参考关于Python项目的工程化我使用的python项目工程化指南里提到的一些组件建议阅读该指南并学习其中 快速上手 章节的小例子脚手架上述指南中使用的脚手架是cookiecutter如果你也使用需要在初始化好之后升级一些依赖的版本脚手架生成的一些依赖的版本现在比较低了如果直接使用会报错cookiecutter 的提交信息提示已经是5年前了另外一个比较活跃的脚手架项目是pyscaffold。我还没研究等后面研究透了再在其他博文中详细介绍。本文使用的cookiecutterPython 虚拟环境Python虚拟环境使用poetry。激活Python项目的虚拟环境命令见poetry Managing environments文档2026-08-20更新: 现在我更推荐uv主要是uv用起来比较丝滑实现爬虫根据上述指南生成项目脚手架且升级了相关依赖之后开始正式写爬虫代码。核心爬虫逻辑就几行构造下订单数据然后使用requests发起请求动态IP在测试过程中发现目标小程序添加了同一个IP 1秒内不能访问2次的限制。找了一家国内付费的TLS代理服务的公司买了个IP池。国内付费HTTP/TLS代理的公司对于个人用户推出的套餐基本差不多有按照数量计费的有按照小时/天计费的。我选的是按数量计费并且所选IP失效为5分钟左右。在选择具体产品时要关注IP得是 高度匿名代理这样才不会被目标服务器追踪到原始客户端IPIP质量代理IP要够稳定且延迟低确保代理服务器的延迟够低要让代理请求到目标小程序的延迟够低首先选择的IP最好和目标小程序所在区域一样。经过上面的抓包得知小程序的域名使用dig命令通过域名查到该域名的IPdigwww.xxxx.com再通过IP归属地查询即可得知该主机IP位于哪里在接入HTTP代理服务时商家一般提供可选城市选择离主机IP最近的城市即可。当然了还要综合考量不一定离目标网站所在地越近的代理IP速度越快应该还和商家自身的硬件部署有关所以如果发现离目标网站所在地的IP反而更慢那就果断切换至其他节点即使完成了上述步骤商家给你的IP池不一定每一个延迟都很低所以在拿到IP之后要测试一下该IP到目标小程序的速度如果速度不满足则丢弃重新获取新的IP重复上述步骤直至获得满足延迟的IPget low latency proxy serversimportjsonimportloggingimporttimeimportrequests loggerlogging.getLogger(__name__)PROXY_SERVICE_PROVIDER_URL(这里是HTTP代理服务商家的接入API)TARGET_SERVER_URL这里是目标小程序的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代理选购指南明确自己的需求我的需求就是代理服务器到目标服务器的延迟足够低由于是秒杀业务所以如果代理延迟高错过开售的那一两秒后面就没什么意义了。我对代理的延迟要求在100ms以内按量付费 vs 静态IP对于我来说调查了几家HTTP代理商的按量付费套餐的IP发现这个套餐基本做不到我要的低延迟例如100ms以内静态IP 没怎么试用过不过根据各家厂商宣传静态IP延迟足够低都是自己机房的专线。很心动但是静态IP实在是太贵了设置User-Agent伪造 User-Agent使用fake-useragent。使用该库时注意多次请求/多线程 应只使用同一个对象避免多次初始化对象浪费时间uaUserAgent(platformsmobile)ua.random发起爬虫请求最终在发起请求时设置reqeusts的proxies即可responserequests.post(request_url,request_json_data,headersrequest_header,timeout5,verifyCERT_PATH,proxiesrequest_proxies)设置请求的证书分三种情况如果你想在请求过程中开着Charles这时的证书来自Charles。把Charles的证书保存下来这时CERT_PATH是Charles自己证书的路径。Charles 官方文档 Python 提到了这一点如果你已经抓到了所需要的目标小程序的数据格式那么请求时无需再开Charles代理。这时的证书来自 浏览器打开 目标小程序 域名 查看证书 下载 。下载前点击证书详情并确保这个证书的名字不带有Charles如果有则关闭Charles并在浏览器中 删除目标小程序的cookie重新加载即可获得小程序后台服务器的证书也可以在请求时不指定verify参数关闭其他科学上网工具有的科学上网工具如果你没设置好无论国内国外的流量都会先经过它的节点这样请求反而是慢了很多优化爬虫速度使用多线程提高并发我这里还没有使用scheduler只是使用 threading.Thread方法。线程的执行逻辑是线程启动之后即准备数据即获得一个延迟足够低的代理IP构造请求数据在requests.post前一行计算当前时间距离目标时间的毫秒数然后time.sleep 休眠。等到目标时间一到所有线程立刻同时发起请求而无需等待其他步骤提前完成TCP三次握手以建立HTTP连接由于现在使用的是按量付费套餐代理IP延迟基本在1秒内。所以如果时间到了再发起请求建立TCP连接请求数据如果不算代理延迟起码慢了几百个毫秒这几百个毫秒就是用来完成TCP三次握手的如果算上代理延迟那基本请求到达服务器要到开始时间的2秒以后了。于是优化代码逻辑在爬虫开始前几秒对目标网站发起一个HEAD 请求此请求的目的是完成TCP三次握手以建立HTTP连接。同时使用requests.Session保证HTTP连接keep-alive。由于我们的请求是到代理服务器代理服务器到目标网站这中间的两个连接是不是长连接不确定。根据日志来看这两个长连接是保持住的了如何得知的项目开启debug模式发起请求请求时urllib3的connectionpool.py会打印如下日志Starting new HTTPS connection过几秒再次请求发现没有如上日志说明用的是同一个连接就这一个小小的优化让我的爬虫有了质的提高领先其他人一步。所以说计算机基础还是很重要滴根据过去请求延迟统计提前N毫秒发起请求每次爬虫都会记录发起请求时间和结束请求时间根据统计就可以估计出代理服务器到目标服务器的延迟所以在秒杀开售前的N毫秒发起请求保证在门票开售时请求就到达了服务器。例如秒杀开始时间晚上8:30根据统计代理服务器到目标服务器延迟大概在300-400ms左右那么就是8:29:59:700 时发起请求应对竞争爬虫在实际爬取过程中发现还存在其他竞争对手。如何打败对方呢从实际情况来看要面对的不止竞争爬虫还有正常用户发起的请求如何让服务器先处理自己发起的请求呢更低的延迟。但是目前在使用动态IP按量付费的套餐下延迟没办法控制。倒是可以使用自己家高质量的网络而不使用代理但是我又不想暴露自己IP更多的请求。增加线程数例如增加到几十个甚至上百个以量抢占服务器处理请求的线程资源例如Tomcat用来处理请求的线程如果服务器用来处理请求的线程都被我们的爬虫占满那其他请求自然要排队。不过如果控制不好就变成了DDoS攻击推送爬取结果到iOS由于该爬虫是定时执行有的时候不一定在家。所以需要一个将爬取结果推送到iOS上。鉴于APNs即Apple Push Notification service 有点复杂且不想花时间在上面于是使用Bark来帮助快速开发。接入步骤非常简单建议阅读文档未解决的问题经过上述步骤mini版的爬虫基本满足了自己的需求即定时下单并推送消息到iOS提醒我付款。但是有以下几个问题未解决由于猜测token失效时间是24小时所以在拿到了一个有效token之后要注意token时间。不过可以另辟蹊跷可以每天手动将小程序删除然后重新访问即可在爬取之前得到一个全新的token就无需考虑token过期的问题该小程序防止爬虫请求只在用户ID层面制订了防护策略即只允许同一用户下两单并没有接入验证码之类的防护打码平台破解验证码如果未来该小程序接入了验证码那么我决定使用付费的打码平台来进行破解。有关打码平台的介绍可参考打码平台是如何高效的破解市面上各家验证码平台的各种形式验证码的 这篇文章关于使用爬虫的额外注意事项如果目标网站有账号机制例如小程序这种目标网站可以根据某些信息openid确定你的电话号之类的优先使用备用微信号以免该微信号在该网站中被封影响主账号的使用企业级应用如何防止爬虫在平时就会有各种各样的爬虫无时无刻的来爬我司系统Google的Meta的等等这些还是比较正规的爬虫还比较讲道理。有的爬虫根本不讲道理爬取时间段等毫无规律一旦爬取太厉害会直接导致服务器CPUMemory线程池频繁GC等报警。我们从一开始使用Google Captcha然后升级到AWS的WAF效果都不太好。最近接入了DataDome目前来看效果还是可以的。备注由于该爬虫核心逻辑就是发起一个API请求足够简单就不再提供示例源码了只提供上述思路。有关Python的其他最佳实践日后会在其他博文中介绍本篇只是让各位同学对Python工程化和基本的爬虫技术有个整体的了解