前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >+从零实现一款12306刷票软件1.3

+从零实现一款12306刷票软件1.3

作者头像
范蠡
发布2018-07-25 16:09:38
8190
发布2018-07-25 16:09:38
举报
文章被收录于专栏:高性能服务器开发

咱们接着上一篇《从零实现一款12306刷票软件1.2》继续介绍。

二、登录与拉取图片验证码接口

我的登录页面效果如下:

12306的图片验证码一般由八个图片组成,像上面的“龙舟”文字,也是图片,这两处的图片(文字图片和验证码)都是在服务器上拼装后,发给客户端的,12306服务器上这种类型的小图片有一定的数量,虽然数量比较大,但是是有限的。如果你要做验证码自动识别功能,可以尝试着下载大部分图片,然后做统计规律。所以,我这里并没有做图片自动识别功能。有兴趣的读者可自行尝试。

先说下,拉取验证码的接口。我们打开Chrome浏览器12306的登录界面:

代码语言:javascript
复制
https://kyfw.12306.cn/otn/login/init

如下图所示:

可以得到拉取验证码的接口:

我们可以看到发送的http请求数据包格式是:

代码语言:javascript
复制
1GET /passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.7520968747611347 HTTP/1.1  
2Host: kyfw.12306.cn  
3Connection: keep-alive  
4User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36  
5Accept: image/webp,image/apng,image/*,*/*;q=0.8  
6Referer: https://kyfw.12306.cn/otn/login/init  
7Accept-Encoding: gzip, deflate, br  
8Accept-Language: zh-CN,zh;q=0.9,en;q=0.8  
9Cookie: _passport_session=badc97f6a852499297796ee852515f957153; _passport_ct=9cf4ea17c0dc47b6980cac161483f522t9022; RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlujJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_BMNur8b8; _jc_save_fromStation=%u4E0A%u6D77%2CSHH; _jc_save_toStation=%u5317%u4EAC%2CBJP; _jc_save_wfdc_flag=dc; route=c5c62a339e7744272a54643b3be5bf64; BIGipServerotn=1708720394.50210.0000; _jc_save_fromDate=2018-05-30; _jc_save_toDate=2018-05-20; BIGipServerpassport=837288202.50215.0000  

这里也是一个http GET请求,HostRefererCookie这三个字段是必须的,且Cookie字段必须带上上文说的JSESSIONID,下载图片验证码和下文中各个步骤也必须在Cookie字段中带上这个JSESSIONID值,否则无法从12306服务器得到正确的应答。后面会介绍如何拿到这个这。这个拉取图片验证码的http GET请求需要三个参数,如上面的代码段所示,即login_sitemodule、rand和一个类似于0.7520968747611347的随机值,前三个字段的值都是固定的,module字段表示当前是哪个模块,当前是登录模块,所以值是login,后面获取最近联系人时取值是passenger。这里还有一个需要注意的地方是,如果您验证图片验证码失败时,重新请求图片时,必须也重新请求下JSESSIONID。这个url是

代码语言:javascript
复制
1https://kyfw.12306.cn/otn/login/init

http请求和应答包如下:

请求包:

代码语言:javascript
复制
 1Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8  
 2Accept-Encoding: gzip, deflate, br  
 3Accept-Language: zh-CN,zh;q=0.9,en;q=0.8  
 4Cache-Control: max-age=0  
 5Connection: keep-alive  
 6Cookie: RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlujJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_BMNur8b8; _jc_save_fromStation=%u4E0A%u6D77%2CSHH; _jc_save_toStation=%u5317%u4EAC%2CBJP; _jc_save_wfdc_flag=dc; route=c5c62a339e7744272a54643b3be5bf64; BIGipServerotn=1708720394.50210.0000; _jc_save_fromDate=2018-05-30; _jc_save_toDate=2018-05-20; BIGipServerpassport=837288202.50215.0000  
 7Host: kyfw.12306.cn  
 8Referer: https://kyfw.12306.cn/otn/passport?redirect=/otn/login/loginOut  
 9Upgrade-Insecure-Requests: 1  
10User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36  

应答包:

代码语言:javascript
复制
 1HTTP/1.1 200 OK  
 2Date: Sun, 20 May 2018 02:23:53 GMT  
 3Content-Type: text/html;charset=utf-8  
 4Transfer-Encoding: chunked  
 5Set-Cookie: JSESSIONID=D5AE154D66F67DE53BF70420C772158F; Path=/otn  
 6ct: C1_217_101_6  
 7Content-Language: zh-CN  
 8Content-Encoding: gzip  
 9X-Via: 1.1 houdianxin184:4 (Cdn Cache Server V2.0)  
10Connection: keep-alive  
11X-Dscp-Value: 0  
12X-Cdn-Src-Port: 46480  

这个值在应答包字段Set-Cookie中拿到:

代码语言:javascript
复制
1Set-Cookie: JSESSIONID=D5AE154D66F67DE53BF70420C772158F; Path=/otn  

所以,我们每次请求图片验证码时,都重新请求一下这个JSESSIONID,代码如下:

代码语言:javascript
复制
1#define URL_LOGIN_INIT      "https://kyfw.12306.cn/otn/login/init"  

代码语言:javascript
复制
  1bool Client12306::loginInit()  
  2{     
  3    string strResponse;  
  4    if (!HttpRequest(URL_LOGIN_INIT, strResponse, true, "Upgrade-Insecure-Requests: 1", NULL, true, 10))  
  5    {  
  6        LogError("loginInit failed");  
  7        return false;  
  8    }  
  9    if (!GetCookies(strResponse))  
 10    {  
 11        LogError("parse login init cookie error, url=%s", URL_LOGIN_INIT);  
 12        return false;  
 13    }  
 14    return true;  
 15}  
 16
 17bool Client12306::GetCookies(const string& data)  
 18{  
 19    if (data.empty())  
 20    {  
 21        LogError("http data is empty");  
 22        return false;  
 23    }  
 24    //解析http头部  
 25    string str;  
 26    str.append(data.c_str(), data.length());  
 27    size_t n = str.find("\r\n\r\n");  
 28    string header = str.substr(0, n);  
 29    str.erase(0, n + 4);  
 30    //m_cookie.clear();  
 31    //获取http头中的JSESSIONID=21AC68643BBE893FBDF3DA9BCF654E98;  
 32    vector<string> v;  
 33    while (true)  
 34    {  
 35        size_t index = header.find("\r\n");  
 36        if (index == string::npos)  
 37            break;  
 38        string tmp = header.substr(0, index);  
 39        v.push_back(tmp);  
 40        header.erase(0, index + 2);  
 41        if (header.empty())  
 42            break;  
 43    }  
 44    string jsessionid;  
 45    string BIGipServerotn;  
 46    string BIGipServerportal;  
 47    string current_captcha_type;  
 48    size_t m;  
 49    OutputDebugStringA("\nresponse http headers:\n");  
 50    for (size_t i = 0; i < v.size(); ++i)  
 51    {  
 52        OutputDebugStringA(v[i].c_str());  
 53        OutputDebugStringA("\n");  
 54        m = v[i].find("Set-Cookie: ");  
 55        if (m == string::npos)  
 56            continue;  
 57        string tmp = v[i].substr(11);  
 58        Trim(tmp);  
 59        m = tmp.find("JSESSIONID");  
 60        if (m != string::npos)  
 61        {  
 62            size_t comma = tmp.find(";");  
 63            if (comma != string::npos)  
 64                jsessionid = tmp.substr(0, comma);  
 65        }  
 66        m = tmp.find("BIGipServerotn");  
 67        if (m != string::npos)  
 68        {  
 69            size_t comma = tmp.find(";");  
 70            if (comma != string::npos)  
 71                BIGipServerotn = tmp.substr(m, comma);  
 72            else  
 73                BIGipServerotn = tmp;  
 74        }  
 75        m = tmp.find("BIGipServerportal");  
 76        if (m != string::npos)  
 77        {  
 78            size_t comma = tmp.find(";");  
 79            if (comma != string::npos)  
 80                BIGipServerportal = tmp.substr(m, comma);  
 81            else  
 82                BIGipServerportal = tmp;  
 83        }  
 84        m = tmp.find("current_captcha_type");  
 85        if (m != string::npos)  
 86        {  
 87            size_t comma = tmp.find(";");  
 88            if (comma != string::npos)  
 89                current_captcha_type = tmp.substr(m, comma);  
 90            else  
 91                current_captcha_type = tmp;             
 92        }  
 93    }  
 94    if (!jsessionid.empty())  
 95    {  
 96        m_strCookies = jsessionid;  
 97        m_strCookies += "; ";  
 98        m_strCookies += BIGipServerotn;  
 99        if (!BIGipServerportal.empty())  
100        {  
101            m_strCookies += "; ";  
102            m_strCookies += BIGipServerportal;  
103        }  
104        m_strCookies += "; ";  
105        m_strCookies += current_captcha_type;  
106        return true;  
107    }  
108    LogError("jsessionid is empty");  
109    return false;  
110}  
代码语言:javascript
复制
1#define URL_GETPASSCODENEW  "https://kyfw.12306.cn/passport/captcha/captcha-image"  

代码语言:javascript
复制
 1bool Client12306::DownloadVCodeImage(const char* module)  
 2{  
 3    if (module == NULL)  
 4    {  
 5        LogError("module is invalid");  
 6        return false;  
 7    }  
 8    //https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.06851784300754482  
 9    ostringstream osUrl;  
10    osUrl << URL_GETPASSCODENEW;  
11    osUrl << "?login_site=E&module=";  
12    osUrl << module;  
13    //购票验证码  
14    if (strcmp(module, "passenger") != 0)  
15    {  
16        osUrl << "&rand=sjrand&";  
17    }  
18    //登录验证码  
19    else  
20    {        
21        osUrl << "&rand=randp&";       
22    }  
23    double d = rand() * 1.000000 / RAND_MAX;  
24    osUrl.precision(17);  
25    osUrl << d;  
26    string strResponse;  
27    string strCookie = "Cookie: ";  
28    strCookie += m_strCookies;  
29    if (!HttpRequest(osUrl.str().c_str(), strResponse, true, strCookie.c_str(), NULL, false, 10))  
30    {  
31        LogError("DownloadVCodeImage failed");  
32        return false;  
33    }  
34    //写入文件  
35    time_t now = time(NULL);  
36    struct tm* tblock = localtime(&now);  
37    memset(m_szCurrVCodeName, 0, sizeof(m_szCurrVCodeName));  
38#ifdef _DEBUG  
39    sprintf(m_szCurrVCodeName, "vcode%04d%02d%02d%02d%02d%02d.jpg",  
40        1900 + tblock->tm_year, 1 + tblock->tm_mon, tblock->tm_mday,  
41        tblock->tm_hour, tblock->tm_min, tblock->tm_sec);  
42#else  
43    sprintf(m_szCurrVCodeName, "vcode%04d%02d%02d%02d%02d%02d.v",  
44        1900 + tblock->tm_year, 1 + tblock->tm_mon, tblock->tm_mday,  
45        tblock->tm_hour, tblock->tm_min, tblock->tm_sec);  
46#endif  
47    FILE* fp = fopen(m_szCurrVCodeName, "wb");  
48    if (fp == NULL)  
49    {  
50        LogError("open file %s error", m_szCurrVCodeName);  
51        return false;  
52    }  
53    const char* p = strResponse.data();  
54    size_t count = fwrite(p, strResponse.length(), 1, fp);  
55    if (count != 1)  
56    {  
57        LogError("write file %s error", m_szCurrVCodeName);  
58        fclose(fp);  
59        return false;  
60    }  
61    fclose(fp);  
62    return true;  
63}  

我们再看下验证码去服务器验证的接口

代码语言:javascript
复制
1https://kyfw.12306.cn/passport/captcha/captcha-check

请求头:

代码语言:javascript
复制
 1POST /passport/captcha/captcha-check HTTP/1.1  
 2Host: kyfw.12306.cn  
 3Connection: keep-alive  
 4Content-Length: 50  
 5Accept: application/json, text/javascript, */*; q=0.01  
 6Origin: https://kyfw.12306.cn  
 7X-Requested-With: XMLHttpRequest  
 8User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36  
 9Content-Type: application/x-www-form-urlencoded; charset=UTF-8  
10Referer: https://kyfw.12306.cn/otn/login/init  
11Accept-Encoding: gzip, deflate, br  
12Accept-Language: zh-CN,zh;q=0.9,en;q=0.8  
13Cookie: _passport_session=3e39a33a25bf4ea79146bd9362c11ad62327; _passport_ct=c5c7940e08ce44db9ad05d213c1296ddt4410; RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlujJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_BMNur8b8; _jc_save_fromStation=%u4E0A%u6D77%2CSHH; _jc_save_toStation=%u5317%u4EAC%2CBJP; _jc_save_wfdc_flag=dc; route=c5c62a339e7744272a54643b3be5bf64; BIGipServerotn=1708720394.50210.0000; _jc_save_fromDate=2018-05-30; _jc_save_toDate=2018-05-20; BIGipServerpassport=837288202.50215.0000

这是一个POST请求,其中POST数据带上的输入的图片验证码选择的坐标X和Y值:

代码语言:javascript
复制
1answer: 175,58,30,51  
2login_site: E  
3rand: sjrand 

这里我选择了两张图片,所以有两组坐标值,(175,58)是一组,(30,51)是另外一组,这个坐标系如下:

因为每个图片的尺寸都一样,所以,我可以给每个图片设置一个坐标范围,当选择了一个图片,给一个在其中的坐标即可,不一定是鼠标点击时的准确位置:

代码语言:javascript
复制
 1//刷新验证码 登录状态下的验证码传入”randp“,非登录传入”sjrand“ 具体参看原otsweb中的传入参数  
 2struct VCodePosition  
 3{  
 4    int x;  
 5    int y;  
 6};  
 7const VCodePosition g_pos[] =  
 8{  
 9    { 39, 40 },  
10    { 114, 43 },  
11    { 186, 42 },  
12    { 252, 47 },  
13    { 36, 120 },  
14    { 115, 125 },  
15    { 194, 125 },  
16    { 256, 120 }  
17};  
18//验证码图片八个区块的位置  
19struct VCODE_SLICE_POS  
20{  
21    int xLeft;  
22    int xRight;  
23    int yTop;  
24    int yBottom;  
25};  
26const VCODE_SLICE_POS g_VCodeSlicePos[] =   
27{  
28    {0,   70,  0,  70},  
29    {71,  140, 0,  70 },  
30    {141, 210, 0,  70 },  
31    {211, 280, 0,  70 },  
32    { 0,  70,  70, 140 },      
33    {71,  140, 70, 140 },  
34    {141, 210, 70, 140 },  
35    {211, 280, 70, 140 }  
36};  
37//8个验证码区块的鼠标点击状态  
38bool g_bVodeSlice1Pressed[8] = { false, false, false, false, false, false, false, false};  

验证的图片验证码的接口代码是:

代码语言:javascript
复制
 1int Client12306::checkRandCodeAnsyn(const char* vcode)  
 2{  
 3    string param;  
 4    param = "randCode=";  
 5    param += vcode;  
 6    param += "&rand=sjrand";    //passenger:randp  
 7    string strResponse;  
 8    string strCookie = "Cookie: ";  
 9    strCookie += m_strCookies;  
10    if (!HttpRequest(URL_CHECKRANDCODEANSYN, strResponse, false, strCookie.c_str(), param.c_str(), false, 10))  
11    {  
12        LogError("checkRandCodeAnsyn failed");  
13        return -1;  
14    }  
15    ///** 成功返回  
16    //HTTP/1.1 200 OK  
17    //Date: Thu, 05 Jan 2017 07:44:16 GMT  
18    //Server: Apache-Coyote/1.1  
19    //X-Powered-By: Servlet 2.5; JBoss-5.0/JBossWeb-2.1  
20    //ct: c1_103  
21    //Content-Type: application/json;charset=UTF-8  
22    //Content-Length: 144  
23    //X-Via: 1.1 jiandianxin29:6 (Cdn Cache Server V2.0)  
24    //Connection: keep-alive  
25    //X-Cdn-Src-Port: 19153  
26    //参数无效  
27    //{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"result":"0","msg":""},"messages":[],"validateMessages":{}}  
28    //验证码过期  
29    //{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"result":"0","msg":"EXPIRED"},"messages":[],"validateMessages":{}}  
30    //验证码错误  
31    //{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"result":"1","msg":"FALSE"},"messages":[],"validateMessages":{}}  
32    //验证码正确  
33    //{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"result":"1","msg":"TRUE"},"messages":[],"validateMessages":{}}  
34    Json::Reader JsonReader;  
35    Json::Value JsonRoot;  
36    if (!JsonReader.parse(strResponse, JsonRoot))  
37        return -1;  
38    //{"validateMessagesShowId":"_validatorMessage", "status" : true, "httpstatus" : 200, "data" : {"result":"1", "msg" : "TRUE"}, "messages" : [], "validateMessages" : {}}  
39    if (JsonRoot["status"].isNull() || JsonRoot["status"].asBool() != true)  
40        return -1;  
41    if (JsonRoot["httpstatus"].isNull() || JsonRoot["httpstatus"].asInt() != 200)  
42        return -1;  
43    if (JsonRoot["data"].isNull() || !JsonRoot["data"].isObject())  
44        return -1;  
45    if (JsonRoot["data"]["result"].isNull())  
46        return -1;  
47    if (JsonRoot["data"]["result"].asString() != "1" && JsonRoot["data"]["result"].asString() != "0")  
48        return -1;  
49    if (JsonRoot["data"]["msg"].isNull())  
50        return -1;  
51    //if (JsonRoot["data"]["msg"].asString().empty())         
52    //  return -1;  
53    if (JsonRoot["data"]["msg"].asString() == "")  
54        return 0;  
55    else if (JsonRoot["data"]["msg"].asString() == "FALSE")  
56        return 1;  
57    return 1;  
58}  

由于微信公众号文章字数限制,您可以继续阅读下一篇《从零实现一款12306刷票软件1.4》。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能服务器开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档