• 关于小米手机网站抢购的一点技术分析


    先PS一下:最近小米手机火了,看起来好像地球人已经不能阻止它的发展趋势了

    其实本文论述的技术也并非小米手机专用,只是用小米手机来做借鉴,但课题起源于朋友请求帮忙购买小米手机,于是借助专业知识写了一个抢购的工具,拿出来和大家分享一下。说叫抢购工具,其实就是自动下订单而已,因为小米手机网站的订单只要在72小时内完成支付就可以,所以在第一时间完成下单以后找闲暇时间进行支付就可以了。

    再PS一下:【声明】本工具原理为模拟浏览器访问和执行页面,不包含任何入侵和破坏行为。本工具的优点只是自动化处理以及节省不必要的流量耗费。

    1、首先分析任务实现需要操作的步骤:

      a、登录系统,此步骤中小米商城网站使用了通行证认证模式,即需要将表单提交到passport.xiaomi.com,认证成功后通过token传递回商城网站的callback页面。

      b、清空购物车,本步骤可选。因为是工具自动购买,防止错误下单,可在每次执行操作前自动清空购物车,购物车清空后会302到购物车页面,http://order.xiaomi.com/cart,页面提示“清空购物车成功”。

      c、将商品放入购物车。此步骤小米网站没有使用post,而是get,通过url将商品id以及数量传递给服务器,并且还使用了友好url,例如 http://order.xiaomi.com/cart/add/1016-0-1 是将 小米手机M1放入购物车,其中1016为小米手机的商品id,0表示此商品为单品(非组合套装),1表示购买一个。以此类推。放入购物车以后会自动302到购物车页面,不过此时url为 http://order.xiaomi.com/cart/index,显示购物车中的商品。

      d、提交订单。本步骤同样为get方式,直接请求地址 http://order.xiaomi.com/buy/checkout 即开始将购物车中的商品作为一个订单进行提交,此时进入订单详细信息填写页面,填写收货人、收货地址、电话、发票等信息,确认订单需要post,请求的地址仍然是 http://order.xiaomi.com/buy/checkout。

    2、关键技术点分析及实现

      清楚了任务要求,接下来分析技术点,鉴于本人对C#比较熟悉,故选用C#语言实现,采用了Winform项目框架。

      a、核心需求即是通过http协议下载页面,以下为本人使用的核心代码:

    View Code
      1                     Uri uri = rawUri;
    2 HttpWebSession session = new HttpWebSession()
    3 {
    4 RequestUri = uri,
    5 };
    6 var _request = session.Request =
    7 (HttpWebRequest)WebRequest.Create(uri);
    8 _httpWebSessions.Add(session);
    9 if (Proxy != null)
    10 {
    11 _request.Proxy = Proxy;
    12 }
    13 _request.AllowAutoRedirect = false;
    14 if (Uri.UriSchemeHttps == uri.Scheme)
    15 {
    16 ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(RemoteCertificateValidation);//
    17 }
    18 if (Credentials != null)
    19 {
    20 _request.Credentials = Credentials;
    21 }
    22 if (!string.IsNullOrEmpty(Accept))
    23 {
    24 _request.Accept = Accept;
    25 }
    26
    27 if (!string.IsNullOrEmpty(AcceptLanguage))
    28 {
    29 _request.Headers.Add("Accept-Language", AcceptLanguage);
    30 }
    31 if (!string.IsNullOrEmpty(AcceptCharset))
    32 {
    33 _request.Headers.Add("Accept-Charset", AcceptCharset);
    34 }
    35 _request.KeepAlive = KeepAlive;
    36 if (!string.IsNullOrEmpty(RefererUrl))
    37 {
    38 _request.Referer = RefererUrl;
    39 }
    40 switch (_acceptEncoding)
    41 {
    42 case AcceptEncoding.GZIP:
    43 {
    44 _request.Headers.Add("Accept-Encoding", "gzip");
    45 break;
    46 }
    47 case AcceptEncoding.DEFLATE:
    48 {
    49 _request.Headers.Add("Accept-Encoding", "deflate");
    50 break;
    51 }
    52 case AcceptEncoding.BOTH:
    53 {
    54 _request.Headers.Add("Accept-Encoding", "gzip,deflate");
    55 break;
    56 }
    57 }
    58 if (_request.CachePolicy == null || _request.CachePolicy.Level != _cacheLevel)
    59 {
    60 _request.CachePolicy = new RequestCachePolicy(_cacheLevel);
    61 }
    62 if (_headers != null && _headers.Count > 0)
    63 {
    64 foreach (KeyValuePair<string, string> item in _headers)
    65 {
    66 try
    67 {
    68 _request.Headers.Add(item.Key, item.Value);
    69 }
    70 catch
    71 { }
    72 }
    73 }
    74 _request.UserAgent = _userAgent;
    75 _request.Referer = RefererUrl;
    76 if (TimeOut != 0)
    77 {
    78 _request.Timeout = TimeOut;
    79 }
    80 if (postData == null)
    81 {
    82 if (postDictionary != null)
    83 {
    84 foreach (var item in postDictionary)
    85 {
    86 postText += item.Key + "=" + item.Value + "&";
    87 }
    88 postText = postText.Trim('&');
    89 }
    90 if (!string.IsNullOrEmpty(postText))
    91 {
    92 postData = _encoding.GetBytes(postText);
    93 }
    94 }
    95 if (_cookieContainer != null)
    96 {
    97 string strCookieHeader = _cookieContainer.GetCookieHeader(uri);
    98 if (!string.IsNullOrEmpty(strCookieHeader))
    99 {
    100 _request.Headers["Cookie"] = strCookieHeader;
    101 }
    102 }
    103 if (postData != null)
    104 {
    105 session.PostedData = postData;
    106 _request.Method = "POST";
    107 _request.ContentType = "application/x-www-form-urlencoded";
    108 _request.ContentLength = postData.Length;
    109 Stream requestStream = _request.GetRequestStream();
    110 requestStream.Write(postData, 0, postData.Length);
    111 requestStream.Close();
    112 }
    113 var _response = session.Response = (HttpWebResponse)_request.GetResponse();
    114 if (_cookieContainer != null)
    115 {
    116 string strSetCookieHeader = _response.Headers["Set-Cookie"];
    117 if (!string.IsNullOrEmpty(strSetCookieHeader))
    118 {
    119 _cookieContainer.SetCookies(_response.ResponseUri, strSetCookieHeader);
    120 }
    121 }
    122 StatusCode = _response.StatusCode;
    123 _requestCookieCollection = _cookieContainer.GetCookieCollection(rawUri);
    124 _response.Cookies = _cookieContainer.GetCookieCollection(_response.ResponseUri);
    125 if (IsEncodingAccepted(_response, AcceptEncoding.GZIP))
    126 {
    127 responseStream = new GZipStream(_response.GetResponseStream(), CompressionMode.Decompress);
    128 }
    129 else if (IsEncodingAccepted(_response, AcceptEncoding.DEFLATE))
    130 {
    131 responseStream = new DeflateStream(_response.GetResponseStream(), CompressionMode.Decompress);
    132 }
    133 else
    134 {
    135 responseStream = _response.GetResponseStream();
    136 }
    137
    138 if (StatusCode == HttpStatusCode.Redirect || StatusCode == HttpStatusCode.Moved)
    139 {
    140 if (_allowAutoRedirect)
    141 {
    142 responseStream.Close();
    143 string strRedirectUrl = _response.Headers["Location"];
    144 var stream = GetStream(new Uri(rawUri, strRedirectUrl), true, null, null, null, null, null);
    145 return stream;
    146 }
    147 // return GetStream(
    148 }
    149 else if (StatusCode == HttpStatusCode.Unauthorized)
    150 {
    151 string strAuthorizition = OnAuthorizition();
    152 if (!string.IsNullOrEmpty(strAuthorizition))
    153 {
    154
    155 }
    156 }

      上述代码是本人多年积累的一个类库中的一点核心代码,解决了诸多常见问题,比如cookieContainer对domain识别异常,https访问等等,如有需要本人可提供下载。

      b、逻辑处理的自动机

       鉴于web访问过程中容易出现的各种不确定性因素,经常会出现请求同一个地址却返回不一样的内容,多数为302活301。本人使用了注册处理函数的方式,每次请求完以后,根据返回的页面url找到相应的处理函数进行操作,比如登录时,发送的是指向http://passport.xiaomi.com/index.php?...的一个请求,但返回结果往往变成 http://www.xiaomi.com/,在相应的处理函数中就需要进行清空购物车或者将商品放入购物车。

    View Code
     1         protected virtual void ProcessHttpSessions(ProcessState state)
    2 {
    3 state.Processed = true;
    4 if (RunState == RunState.Pauseing)
    5 {
    6 OnLog("==========被用户暂停==========");
    7 ChangeState(RunState.Paused);
    8 state.Break = true;
    9 return;
    10 }
    11 if (RunState == RunState.PauseingByNetService)
    12 {
    13 OnLog("==========被换IP服务暂停==========");
    14 ChangeState(RunState.PausedByNetService);
    15 state.Break = true;
    16 return;
    17 }
    18 if (RunState == RunState.Stopping)
    19 {
    20 OnLog("==========被用户停止==========");
    21 ChangeState(RunState.Stoped);
    22 state.Break = true;
    23 return;
    24 }
    25
    26 if (_httpClient == null || _httpClient.HttpWebSessions == null)
    27 {
    28 state.Continue = true;
    29 return;
    30 }
    31 int oldSessionCount = _httpClient.HttpWebSessions.Count;
    32 Action<ProcessState> processerAction = null;
    33 if (string.IsNullOrEmpty(_httpClient.RequestUrl))
    34 {
    35 OnLogError("没有要处理的请求");
    36 state.Break = true;
    37 return;
    38 }
    39 if (httpSessionProcesser.ContainsKey(_httpClient.RequestUrl))
    40 {
    41 processerAction = httpSessionProcesser[_httpClient.RequestUrl];
    42 }
    43 if (processerAction == null)
    44 {
    45 foreach (var processer in httpSessionProcesser)
    46 {
    47 if (_httpClient.RequestUrl.StartsWith(processer.Key) ||
    48 Regex.IsMatch(_httpClient.RequestUrl, processer.Key))
    49 {
    50 processerAction = processer.Value;
    51 break;
    52 }
    53 }
    54 }
    55 if (processerAction != null)
    56 {
    57 processerAction(state);
    58 if (state.Break || state.Continue)
    59 {
    60 return;
    61 }
    62 if (!state.Processed)
    63 {
    64 ProcessHttpSessions(state);
    65 }
    66 return;
    67 }
    68 OnLogError("出错了,RequestUrl:" + _httpClient.RequestUrl);
    69 OnLogError("ResponseUrl:" + _httpClient.ResponseUrl);
    70 OnLogError("ResponseText:" + _httpClient.ResponseText);
    71 state.Break = true;
    72 }

      
      经过实践和改进,本人已将上述部分分离成独立类库,如有需要可提供,以进行参考和交流。

     3、软件界面布局,界面截图已隐藏掉部分敏感信息

      

    最后一条PS:本人于2012年1月4日下午使用该工具成功下单若干个,并且现在还有一个尚未付款的购机名额,如有需要可留QQ详谈。

  • 相关阅读:
    [转]ABAP动态取得数据
    [转]ABAP学习笔记之三内表
    [转]ABAP实现对变式的修改
    [转]ABAP Search help
    C#中访问私有成员[转载]
    如果在BackgroundWorker运行过程中关闭窗体…
    交叉编译的概念
    索引器的重载的一个例子
    自定义类实现IComparable接口
    ioctl函数
  • 原文地址:https://www.cnblogs.com/zhhb/p/2312357.html
Copyright © 2020-2023  润新知