“秒杀”心得
本文记录对某网站A的秒杀活动编写秒杀器的经历和技术重点。
故事回顾
某日早上,朋友给我说最近A网站在开展秒杀活动,有IPad、IPhone,让大家一起去秒杀。结果我们四个人一起秒,都没有别人快,没有一个人秒到。然后下午我就开始尝试分析它网站的秒杀流程,并尝试使用自动提交数据的方案来进行秒杀。结果,在晚上的时候,成功做出了第一个版本的秒杀器,然后我们一起秒杀了几个IPad(大家都想要IPad,而对IPhone没兴趣,汗)。
当时就用网银付了帐,等待它发货。接下来我们每个人一个接一个地接到了A网站打来的电话,确定我们是不是作弊了,哈哈,我们当然打死不会承认了~
过了半个来月,该网站又发起了新一轮的秒杀活动,但是由于之前发现有许多人作弊,所以这次全面更改了网站的流程,随机出现各种题目让会员回答,回答成功才能继续秒杀。这样,难度就大了些,一开始以为它们是题库,后来发现原来所有的题目都是自动生成的。元旦也没闲着,花了几天时间,改出了第二个版本的秒杀器,智能解题。经测试,目前没有失败过。
第一版本
以下简明扼要地描述所有的分析流程:
分析网站秒杀流程,得出“入口页面”的地址。但是尝试登录此页面失败,返回活动等待页面,并提示:“活动未开始”。
写了一个简单的控制台程序,在活动开始时立刻运行此程序,快速地打开了20-40个入口页面。此时,发现有一半左右的页面进入成功,到达“提交页面”。提交页面中需要填写一些必要的个人信息,最下面是一个提交按钮。估计这是A网站秒杀的最后一道关。
把提交页面的客户端源代码全部保存下来,尝试进行分析。发现表单中需要填写的是:一些固定信息、一些隐藏域(HiddenField)、图片验证码。
隐藏域中需要提交一些如:当时秒杀活动Id、用户Id等的信息。这些信息只要在网站中多分析一下就能得出。
验证码:这个目前并没有什么好的办法能自动识别验证码,网上虽然有此类程序,不过我懒得去下载了,直接把验证码的图片显示在程序中,人工录入就好了。这样做的原因是,以我的经验,他们的验证码十有八九存储在服务端Session中,也有可能是客户端Cookie中,也就是说,验证码是可以提前获取的,并不一定需要等等活动开始后再获取。所以只要在临近活动开始的前2分钟获取并录入验证码就行了。
这样,所有的数据都准备好了,接下来就是如何让程序自动填写数据并提交到网站上。这是重点,也是难点。如果纯粹使用后台代码模拟提交的话,就需要保证后台代码拥有已经被网站验证通过后的Cookie。之前我做过类似的提交程序,但是准备假Cookie的工作一直没有成功过,也比较麻烦。由于这次时间比较紧,没法再试验这种纯正的方案。所以静下心来想别的方案。后来灵机一动决定使用控制浏览器的方案来试试:在秒杀程序中嵌入一个浏览器,在浏览器中执行登录操作。这样,登录成功后的Cookie,就由浏览器自己来维护,而我要做的就是控制浏览器中页面的运行,让它以我的方式加载页面、填写数据、提交数据。在提交数据时,浏览器也会自动把Cookie一并提交。这样就可以在登录的状态下,把前面准备好的数据直接自动提交给服务器。
最后一个问题,让浏览器先访问A网站的页面,登录并拿到登录成功的凭证后,如何让浏览器运行我的代码来提交数据呢?我试了一下在WPF应用程序中直接使用WPF自带的浏览器控件,并研究它的API。在WebBrowser类的API列表中,我发现以下方法:
public void NavigateToString(string text);
public object InvokeScript(string scriptName);
这正是我想要的啊,先构造一个模拟的页面,使用NavigateToString到这个页面上,然后使用InvokeScript方法来调用javascript提交表单到表单上指定的网站的地址就行了!
OK,至此,全部设计完成。由于验证码已经在活动前就准备好了,所以整个过程基本上是完全自动化的,速度当然比人快多了,IPad自然也就手到擒来!
第二版本
上面已经说过,网站改版后的秒杀活动,已经使用随机出现的题目来防止作弊。所以这次我的主要任务就是如何自动答题!其次,分析网站的提交页面中的表单,发现有很多的隐藏域是一连串随机的数字,没有任何规律,估计这些数据是每次活动都不一样的,所以再使用第一版中静态的模拟页面提交数据的方法不行了,必须使用动态的页面,把这些随机的数据都保留下来,并一起提交,或者直接在A网站发回来的页面中填写数据再提交。
首先,第一直觉就是获取大量的提交页面,提取出获取到的所有题目,存储在题目库中。答题时,直接在题库中进行匹配,如果找到相同的题目,则直接使用题目库中的答案进行回答。
后来在该次活动的最后一轮秒杀时,程序开发完成,并开始使用。结果,发现没有一题匹配成功,都找不到答案,全部都显示到了右边的窗口中人为回答,结果我还答错了!!!活动结束!!!
很气人啊,这样的方案根本不行。后来分析了半天,发现原来所有的题目都是程序自动生成的,只是模式固定而已。所以改了设计方案,遵循设计模式写了一些类来自动回答题目,类结构如下:
这里,只贴一个子类的代码,展示一下解答的模式。这个子类的逻辑也是所有题目中最复杂的一个:
class TallerAlgorithm : SelectionQuestionAlgorithm
{
internal override void TryComplete(SelectionQuestion question)
{
var match = Regex.Match(question.Content, @"小(?<a>.)比小(?<b>.)(?<abUnit>.),小(?<c>.)比小(?<d>.)(?<cdUnit>.),请问他们当中谁最(?<finalUnit>.)?");
if (match.Success)
{
var a = match.Groups["a"].Value;
var b = match.Groups["b"].Value;
var c = match.Groups["c"].Value;
var d = match.Groups["d"].Value;
var abUnit = match.Groups["abUnit"].Value;
var cdUnit = match.Groups["cdUnit"].Value;
var finalUnit = match.Groups["finalUnit"].Value;
var winner1 = abUnit == finalUnit ? a : b;
var loster1 = abUnit == finalUnit ? b : a;
var winner2 = cdUnit == finalUnit ? c : d;
var loster2 = abUnit == finalUnit ? d : c;
string champion = null;
if (winner1 == winner2) champion = winner1;
else if (winner1 == loster2) champion = winner2;
else if (winner2 == loster1) champion = winner1;
if (!string.IsNullOrWhiteSpace(champion))
{
question.RightAnswer = question.Answers
.SingleOrDefault(an => an.Text.Contains(champion));
}
}
}
}
其次,是动态的页面的整理:把里面的题目都提取出来,自动答题之后,填写答案,插入一些提交的Javascript代码。然后继续使用NavigateToString、InvokeScript来提交数据。过程中,有两点心得:
1. 在一开始控制浏览器导向提交页面后,发现无法获取Html源代码,花了些时间研究,没搞出来。查了半天网页,最后使用WinForm中的WebBrowser来解决了这个问题。WinForm中WebBrowser不象WPF中的WebBrowser,它拥有着强大的API,DocumentText属性就取到了源代码。
2. 这次我使用了LinqToXml来维护Html Dom中的所有内容,发现XLinq的API实在是太方便了,查找某个元素,更改某个属性。如果没有XLinq,相同的功能,我可能需要3-5倍的时间来完成。
总结
这次秒杀器编写的过程,让我的一个心结给解了。一直以来,就想完全控制网页客户端程序的运行:大四在电信的时候,老总让我给领导刷票;再后来有要人给我给论坛自动提交数据。这些需求都需要持有客户端的用户凭证,然后用这个身份给服务端自动发送一些请求。一直使用纯后台代码的方式提交,没有成功过。这次,使用控制浏览器的方案,使得真正做到了一直想做到的:“完全控制客户端”。
- 跟张志东深聊腾讯的“进化力”
- 详解微信小程序如何实现流程进度功能
- silverlight:如何在图片上挖个洞?
- .NET Core系列 : 1、.NET Core 环境搭建和命令行CLI入门
- mysqldump数据导出问题和客户端授权后连接失败问题
- Android置底一个View后运行报错
- 温故而知新:设计模式之抽象工厂(AbstractFactory)
- mysql操作命令梳理(1)-索引
- Linux下对lvm逻辑卷分区大小的调整(针对xfs和ext4不同文件系统)
- centos6.5虚拟机安装后,没有iptables配置文件
- 温故而知新:设计模式之Builder
- 温故而知新:设计模式之单件模式(Singleton)
- sudo命令使用的几个场景
- .NET Core系列 : 2 、project.json 这葫芦里卖的什么药
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 计算机网络物理层习题
- 从数据库中查询马上过生日的人并统计各年龄段及性别所占的人数
- Ubuntu19.10 中安装 JDK
- 在 Ubuntu19.10 上安装 wine 并安装 QQ 等软件
- wordpress迁移至hugo及其自动化发布文章全记录
- 视频流媒体平台EasyNVR硬件设备使用华科云arm版如何修改为固定IP?
- 详解 IP 地址
- k8s解决pod调度不均衡的问题
- 如何获取视频流媒体服务器EasyNVR的临时授权机器码?
- Mybatis-Plus常用注解
- IDEA 你不知道小技巧——后缀法自动生成代码(Postfix Completion)
- Mybatis-Plus使用乐观锁
- k8s基础之调度策略(二)
- k8s基础之调度策略(一)
- 你知道在 cmd 输入 ping 之后发生了什么吗? —— 详解 ICMP 协议