从零实现一款12306抢票软件(二)

本文介绍了如何实现12306抢票软件的登录和验证码拉取过程,包括使用curl库模拟HTTP请求,处理Cookie和JSESSIONID,以及验证码的获取与验证接口的细节。
摘要由CSDN通过智能技术生成

本文接上一篇文章《张小方:从零实现一款12306抢票软件(一)》。

当然,这里需要说明一下的就是,由于全国的火车站点信息文件比较大,我们程序解析起来时间较长,加上火车站编码信息并不是经常变动,所以,我们我们没必要每次都下载这个station_name.js,所以我在写程序模拟这个请求时,一般先看本地有没有这个文件,如果有就使用本地的,没有才发http请求向12306服务器请求。这里我贴下我请求站点信息的程序代码(C++代码):

 /** 
  * 获取全国车站信息
  * @param si 返回的车站信息
  * @param bForceDownload 强制从网络上下载,即不使用本地副本
  */
 bool GetStationInfo(vector<stationinfo>& si, bool bForceDownload = false);
#define URL_STATION_NAMES   "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9053"

bool Client12306::GetStationInfo(vector<stationinfo>& si, bool bForceDownload/* = false*/)
{  
    FILE* pfile;
    pfile = fopen("station_name.js", "rt+");
    //文件不存在,则必须下载
    if (pfile == NULL)
    {
        bForceDownload = true;
    }
    string strResponse;
    if (bForceDownload)
    {
        if (pfile != NULL)
            fclose(pfile);
        pfile = fopen("station_name.js", "wt+");
        if (pfile == NULL)
        {
            LogError("Unable to create station_name.js");
            return false;
        }

        CURLcode res;
        CURL* curl = curl_easy_init();
        if (NULL == curl)
        {
            fclose(pfile);
            return false;
        }

        //URL_STATION_NAMES
        curl_easy_setopt(curl, CURLOPT_URL, URL_STATION_NAMES);
        //响应结果中保留头部信息
        //curl_easy_setopt(curl, CURLOPT_HEADER, 1);
        curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&strResponse);
        curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
        //设定为不验证证书和HOST
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);

        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);

        res = curl_easy_perform(curl);
        bool bError = false;
        if (res == CURLE_OK)
        {
            int code;
            res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
            if (code != 200)
            {
                bError = true;
                LogError("http response code is not 200, code=%d", code);
            }
        }
        else
        {
            LogError("http request error, error code = %d", res);
            bError = true;
        }

        curl_easy_cleanup(curl);

        if (bError)
        {
            fclose(pfile);
            return !bError;
        }

        if (fwrite(strResponse.data(), strResponse.length(), 1, pfile) != 1)
        {
            LogError("Write data to station_name.js error");            
            return false;
        }
        fclose(pfile);
    }
    //直接读取文件
    else
    {
        //得到文件大小
        fseek(pfile, 0, SEEK_END);
        int length = ftell(pfile);
        if (length < 0)
        {
            LogError("invalid station_name.js file");
            fclose(pfile);
        }
        fseek(pfile, 0, SEEK_SET);
        length++;
        char* buf = new char[length];
        memset(buf, 0, length*sizeof(char));
        if (fread(buf, length-1, 1, pfile) != 1)
        {
            LogError("read station_name.js file error");
            fclose(pfile);
            return false;
        }
        strResponse = buf;
        fclose(pfile);
    }


    /*
    返回结果为一个js文件,
    var station_names = '@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2"
    */
    //LogInfo("recv json = %s", strResponse.c_str());
    OutputDebugStringA(strResponse.c_str());

    vector<string> singleStation;
    split(strResponse, "@", singleStation);

    size_t size = singleStation.size();
    for (size_t i = 1; i < size; ++i)
    {
        vector<string> v;
        split(singleStation[i], "|", v);
        if (v.size() < 6)
            continue;

        stationinfo st;
        st.code1 = v[0];
        st.hanzi = v[1];
        st.code2 = v[2];
        st.pingyin = v[3];
        st.simplepingyin = v[4];
        st.no = atol(v[5].c_str());

        si.push_back(st);
    }

    return true;
}

这里用了一个站点信息结构体stationinfo,定义如下:

//var station_names = '@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2
struct stationinfo
{
    string code1;
    string hanzi;
    string code2;
    string pingyin;
    string simplepingyin;
    int no;
};

因为我们这里目的是为了模拟http请求做买火车票相关的操作,而不是技术方面本身,所以为了快速实现我们的目的,我们就使用curl库。这个库是一个强大的http相关的库,例如12306服务器返回的数据可能是分块的(chunked),这个库也能帮我们组装好;再例如,服务器返回的数据是使用gzip格式压缩的,curl也会帮我们自动解压好。所以,接下来的所有12306的接口,都基于我封装的curl库一个接口:

/**
 * 发送一个http请求
 *@param url 请求的url
 *@param strResponse http响应结果
 *@param get true为GET,false为POST
 *@param headers 附带发送的http头信息
 *@param postdata post附带的数据    
 *@param bReserveHeaders http响应结果是否保留头部信息
 *@param timeout http请求超时时间
 */
 bool HttpRequest(const char* url, string& strResponse, bool get = true, const char* headers = NULL, const char* postdata = NULL, bool bReserveHeaders = false, int timeout = 10);

函数各种参数已经在函数注释中写的清清楚楚了,这里就不一一解释了。这个函数的实现代码如下:

bool Client12306::HttpRequest(const char* url, 
                              string& strResponse, 
                              bool get/* = true*/, 
                              const char* headers/* = NULL*/, 
                              const char* postdata/* = NULL*/, 
                              bool bReserveHeaders/* = false*/, 
     
  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值