The words written in the front

Every year on holidays, readers must be familiar with hard to get a vote. In this article, we lead readers to realize a 12306 ticket brushing software from scratch. Its core principle is to send HTTP requests to simulate the process of logging in to 12306 website to purchase tickets, and finally buy tickets.

On the format of HTTP requests and how to assemble HTTP packets to send requests to the server, we wrote in our previous article “Implementing an HTTP Server from Scratch” (blog.csdn.net/analogous_l…). Has been introduced in detail, if you do not understand the friends can go to the article to see.

Solemnly declare: the technology introduced here is only for learning, not malicious attack 12306 server, do not abuse the technology introduced in this article. Any loss to 12306 server is at your own risk.

Of course, many of 12306’s urls and protocol details at various stages of the ticketing process are frequently changed to prevent scalpers and other illegal attackers due to the huge number of users on the server. Therefore, some of the specific urls described in this article may be invalid by the time you read this article. But that doesn’t matter, as long as you’ve mastered the analysis methods described in this article, you’ll have the flexibility to adapt your code to the latest 12306 server. Clipboards interfaces such as 12306, for example, the url is: kyfw. 12306. Cn/otns/leftTic… , but maybe later became kyfw. 12306. Cn/otns/leftTic… Again in a few days can become kyfw. 12306. Cn/otns/leftTic… , and then a week later may become kyfw. 12306 cn/otns/leftTic… I have seen them all. Therefore, focus on the principle of learning, master the principle, no matter what 12306 related URL becomes, can be unchanged should be ten thousand changes. Alas, 12306 is going further and further in its fight against scalpers. T_T

This paper will use the following tools to analyze the ticket purchase process of 12306, and then use C++ language to simulate the relevant process and finally purchase tickets.

  1. Chrome (other browsers will do, too, with similar interfaces, such as Chrome, Internet Explorer with HttpWatch, etc.)

  2. A 12306 account that can log into the 12306 website and buy tickets

  3. Visual Studio (version optional, I’m using VS 2013 here)

Ticket check and site information interface

The reason to analyze this interface first, because ticket checking does not require user login, relatively simple. We opened more than 12306 votes in the Chrome query page, url is: kyfw. 12306. Cn/otns/leftTic… , as shown in the figure below:

Then right-click in the menu of the page and choose “Check” menu. After opening it, select the “Network” TAB. As shown below:

After opening the page into a binary window, the left is the normal webpage page, the right is the console browser, when we operate in the left page, the right side will display our browser sent various HTTP requests and responses. Let’s check a ticket at random here, such as the ticket from Shanghai to Beijing on May 20, 2018. After clicking the query, we find the right side looks like this:

Given that the type value of the list is XHR, we know that this is an Ajax request (Ajax is an asynchronous request supported by browsers natively, please search for details). We select this request and you can see the details of the request – request and response results:

In reponse, we can see the result of our HTTP response without the HTTP header:

This is a JSON format. Let’s find a JSON formatting tool and post the JSON format for you to see. In fact, you will find that the data related to ticket purchase in the HTTP request results of 12306 are basically in JSON format. Here’s the json:

{
	"validateMessagesShowId": "_validatorMessage"."status": true."httpstatus": 200,
	"data": {
		"result": ["Null | | 23:00 - surf system maintenance time 5 l0000g10270 | G102 | AOH | VNP | AOH | VNP | 06:26 | 3 | 06:03 | IS_TIME_NOT_BUY | RLVVIt093U2EZuy2NE + VQyRloXyq TzFp6YyNk6J52QcHEA01 | | 3 | 20180520 HZ | | 11 | 1 | 0 01 | | | | | | | | | | | | 1 | have 13 | | O090M0 | O9M | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g10470 | G104 | AOH | VNP | AOH | VNP | 06:40 | either | 05:53 | IS_TIME_NOT_BUY | j/TM45GgyJRRKvdalo3VIal8nYF7 Hy9VL6njjGX3nOR3xwIu | | 3 | 20180520 HZ | | 09 | 1 | 0 01 | | | | | | | | | | | 2 | | 15 | | O090M0 | O9M | 0"."Null | | 55000000 g600 23:00 - surf system maintenance time | G6 | SHH | VNP | SHH | VNP | 07:00 | but | 04:38 | IS_TIME_NOT_BUY | SO6mCijnVzhdTrntsbeMoJ4Vuw/WsA nsBz80diva/wuIfsS5|20180520|3|H1|01|05|1|0|||||||||||1|5|8||O090M0|O9M|0"."Null | | 23:00 - surf system maintenance time 5 l0000g106a0 | G106 | AOH | VNP | AOH | VNP | 07:12 | | speak 06:01 | IS_TIME_NOT_BUY | Limy8VLpKgfmzb1EJZ0G7P8 / Ai5i R7qbbwhplNeOVIxLQYab|20180520|3|HY|01|11|1|0|||||||||||1|11|12||O0M090|OM9|0"."Null | | 23:00 - surf system maintenance time 5 l0000g10870 | G108 | AOH | VNP | AOH | VNP | 07:22 | but | 06:01 | IS_TIME_NOT_BUY | OJIuMonF9ctgAxxDpZRkNy0fn4Hr G8Y + 6 thviaxtgrcwip0n | 20180520 | 3 | HY | | 12 | 1 | 0 01 | | | | | | | | | | | | free 6 | 3 | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g110b0 strength | G110 | AOH | VNP | AOH | VNP | 07:28 | thy | 06:10 | IS_TIME_NOT_BUY | HVY2cA5DQzMC1VDiotEG4zXAOwG4 FHHYq2bh1ZFhm47pySly | 20180520 | 3 | HY | | 11 | 1 | 0 01 | | | | | | | | | | | | free 5 13 | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g120s0 | G120 | AOH | VNP | AOH | VNP | 07:51 | | therefore 05:42 | IS_TIME_NOT_BUY | G2C5o + MADORl4B9HQ2jmTdT2 + fBn CbCXvfKCjqf0Fmm6fbU2 | 20180520 | 3 | H6 | 01 | 1 | | 0 | | | | | | | | | | | | has 10 | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l000000g814 the G8 | | AOH | VNP | AOH | VNP | 08:00 | you | 04:24 | IS_TIME_NOT_BUY | dEqPPAVH6ICSdUQQwQ1ry/Ns0 + QJCE 2N+EZd4oC7FOmz855B|20180520|3|H6|01|04|1|0|||||||||||4|4|9||O0M090|OM9|0"."Null | | 23:00 - surf system maintenance time 5 l0000g11293 | G112 | AOH | VNP | AOH | VNP | 08:05 | 14:08 | 06:03 | IS_TIME_NOT_BUY | j1BM0nZuw/phl6Z7WFxg0kFAc5Z4 T + qKWZe3fjKB5ZR72nLl | 20180520 | 3 | HY | | 11 | 1 | 0 01 | | | | | | | | | | | | free | 2 | 3 | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g11470 | G114 | AOH | VNP | AOH | VNP | 08:15 | and | 05:58 | IS_TIME_NOT_BUY | OwWGlKxfnPfPYGOuhjVhioA2r3kj 2 krs0zxnvd04 + IDhPhfc | 20180520 | 3 | HY | | 11 | 1 | 0 01 | | | | | | | | | | | no 1 | | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l000000g232 | G2 | AOH | VNP | AOH | VNP | 09:00 | | ground 04:28 | IS_TIME_NOT_BUY | q4vehyksoblkju03kpa0jbpdtguby 8 Jp + UFMScwuarKvhZ + F | 20180520 | 3 | HY | | 04 | 1 | 0 01 | | | | | | | | | | | | free 5 | 1 | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g11670 | G116 | AOH | VNP | AOH | VNP | 09:33 | mingled | 05:50 | IS_TIME_NOT_BUY | jsCsXdkuWHZVgZ0YzaO + zWokRnnD Q4zowg78aRmc/hzNEMjK | 20180520 | 3 | HY | | 10 | 1 | 0 01 | | | | | | | | | | | | free 6 | 2 | | O0M090 | OM9 | 0"."Null | | 5 l0000g11860 23:00 - surf system maintenance time | G118 | AOH | VNP | AOH | VNP | 24:00 | 24:00 | 99:59 | IS_TIME_NOT_BUY | | 20180520 | | H6 | | | 11 0 01 | 1 | | | | | | | | | | | | | | | | | 0"."Null | | 23:00 - surf system maintenance time 5 l00000g1001 | G10 | AOH | VNP | AOH | VNP 10:00 14:28 | | | 04:28 | IS_TIME_NOT_BUY | ycAb36mk9wXaSIll0bTc5WbH8wLT1 YRVjvGH/cYzAxIoVMcU | 20180520 | 3 | H1 | | 04 | 1 | 0 01 | | | | | | | | | | | | | without 5 | | O0M090 | OM9 | 0"."Null | | 5600000 g4280 23:00 - surf system maintenance time | G42 | | HGH VNP | AOH | VNP | | according 16:08 | 05:42 | IS_TIME_NOT_BUY | usY + Ul57hWKitIUp1d4m3n3e0ys4i JTdDfedKU6oXk7F3bAb | 20180520 | 3 | H6 | 04 | | 1 | 0 13 | | | | | | | | | | | | free without | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g12290 | G122 | AOH | VNP | AOH | VNP | he | departed | 06:02 | IS_TIME_NOT_BUY | tNu43MkXqpjkcIe80jbPhpSgQ3IO CIyLbwMSspllz0Btc3mJ | 20180520 | 3 | H6 | | 12 | 1 | 0 01 | | | | | | | | | | | | free 5 | 3 | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g124v0 | G124 | AOH | VNP | AOH | VNP | 11:00 | | slaves 05:18 | IS_TIME_NOT_BUY | otns + 9 shyetsj ydqexyyoms8daa + 6 eRrvr958XuZ8C4hldEB1|20180520|3|H6|01|06|1|0|||||||||||1|8|3||O0M090|OM9|0"."Null | | 23:00 - surf system maintenance time 5 l0000g126b0 | G126 | AOH | VNP | AOH | VNP | his | 17:05 | surf | IS_TIME_NOT_BUY | HIpEbr9n0fqeUtQGaASOBoD + / duc 8 jm5u1m602j0rnrf0xfa | 20180520 | 3 | H6 | | 12 | 1 | 0 01 | | | | | | | | | | | 4 8 no | | | | O090M0 | O9M | 0"."Null | | 5 l0000g128n0 23:00 - surf system maintenance time | G128 | AOH | VNP | AOH | VNP | 24:00 | 24:00 | 99:59 | IS_TIME_NOT_BUY | | 20180520 | | H1 | | 12 | 0 01 | 1 | | | | | | | | | | | | | | | | | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g13080 | G130 | AOH | VNP | AOH | VNP | | loathsome 17:29 | 06:09 | IS_TIME_NOT_BUY | eaISX27C/T247JdvbJCFWkXvFimD H4W5rNAht1O5/1 PHCBLN | 20180520 | 3 | H1 | | | 1 | 0 13 01 | | | | | | | | | | | no | | 2 | | O0M090 | OM9 | 0"."Null | | 5500000 g1200 23:00 - surf system maintenance time | a G12 | SHH | VNP | SHH | VNP 12:00 | | thus | 04:38 | IS_TIME_NOT_BUY | GxssVQj1spkQVDnyUYodUASXXdwKU NuMjltjIAMwB2IbtIxC | 20180520 | 3 | H1 | | 04 | 1 | 0 01 | | | | | | | | | | | | free without | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g132c0 | G132 | AOH | VNP | AOH | VNP | but | heaved | 06:15 | IS_TIME_NOT_BUY obvvtzf5 / iiIKfTAkXU8tDIK4dM | 2 YpDrpaoQO0WhfqKp3b5h | 20180520 | 3 | H1 | | | 1 | 0 13 01 | | | | | | | | | | | no 4 | | 2 | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time i0 5500001462 | 1462 | SHH | BJP | SHH | BJP | 12:18 | they |" | IS_TIME_NOT_BUY | 05 xf + SuYrrrVUcoitze9 / BO1a6zl Hm / 43 wfixqjdeu7z + hbDUoKqD2myF3Y = | 20180520 | 3 | H2 | | 23 | | 0 0 01 | | | | 2 | | | a | there no | | | | | | | 10401030 | 1413 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g41250 | G412 | AOH | VNP | AOH | VNP | but | 18:48 | 06:20 | IS_TIME_NOT_BUY | CtWjFYsZE3ih/LiOPF03WQb8CvMe 6 jwdlquwbrxkn3yran9f | 20180520 | 3 | H2 | | 11 | 1 | 0 01 | | | | | | | | | | | | free 2 | 2 | | O090M0 | O9M | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g134b0 | G134 | AOH | VNP | AOH | VNP | 13:01 | 18:58 | 05:57 | IS_TIME_NOT_BUY | AO3hxVofuYXk7l6EhzGCCEu4ZHPp S/0A/nkroM7xlpx/fIIX|20180520|3|H6|01|11|1|0|||||||||||3|6|12||O0M090|OM9|0"."Null | | 5 l0000g136o0 23:00 - surf system maintenance time | G136 | AOH | VNP | AOH | VNP | 24:00 | 24:00 | 99:59 | IS_TIME_NOT_BUY | | 20180520 | | H6 | | | 11 0 01 | 1 | | | | | | | | | | | | | | | | | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g13860 | G138 | AOH | VNP | AOH | VNP | 13:30 | but | 05:58 | IS_TIME_NOT_BUY | qgHsrIv2ECcib/ImiXBHGt9Vis0y ZPG8bKHoOZ0RgY7aE5sK | 20180520 | 3 | H6 | | 12 | 1 | 0 01 | | | | | | | | | | | no 8 5 | | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g14060 | G140 | AOH | VNP | AOH | VNP |" | now | 06:06 | IS_TIME_NOT_BUY | ERb1 / PPb8O6WfX503UB/hvYJsZO7 4 wiyijqscisez4esappf | 20180520 | 3 | H6 | | | 1 | 0 13 01 | | | | | | | | | | | 2 | | 6 | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l000000g432 G4 | | AOH | VNP | AOH | VNP | 14:00 | | same 04:28 | IS_TIME_NOT_BUY x7uhklapgd4ojrubhqiw25wn5zya0 | 2 JvumVcUSzkWJZu + 9 yr | 20180520 | 3 | H6 | | 04 | 1 | 0 01 | | | | | | | | | | | no | 3 | 1 | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g14253 | G142 | AOH | VNP | AOH | VNP | | hast indeed prepare | 06:08 | IS_TIME_NOT_BUY | LuImd + o + UIDry0 / CjwMAzgBtvfwy N4dSpjzXZnTQxN89PqQk|20180520|3|H6|01|11|1|0|||||||||||1|7|9||O0M090|OM9|0"."Null | | 23:00 - surf system maintenance time 5 l0000g144m3 | G144 | AOH | VNP | AOH | VNP | 40 | is their | 05:49 | IS_TIME_NOT_BUY | xNsqS1nHci52T9o6E1hU3epRaV9c HSpKnl6i+5+2sWsHHOZQ|20180520|3|H6|01|10|1|0|||||||||||1|2|5||O0M090|OM9|0"."Null | | 23:00 - surf system maintenance time 5 l0000g146f0 | G146 | AOH | VNP | AOH | VNP | any valiant man | all | 05:56 | IS_TIME_NOT_BUY | jAmoXkDA3YgUo4lorosGtKbjeNZ1 5a764hrcb9URyVEUCWBU|20180520|3|H6|01|10|1|0|||||||||||1|6|13||O0M090|OM9|0"."Null | | 23:00 - surf system maintenance time 5 l00000g1442 | | the G14 AOH | VNP | AOH | VNP | 15:00 | so | 04:36 | IS_TIME_NOT_BUY | VyN8KW3DEeWDipXBnZoMhHHVf6m6Y wwJ3QT5GnlQqbQPFOCK|20180520|3|H6|01|05|1|0|||||||||||2|2|1||O090M0|O9M|0"."Null | | 23:00 - surf system maintenance time 5 l0000g148d0 | G148 | AOH | VNP | AOH | VNP | mingled | and | 05:50 | IS_TIME_NOT_BUY | v4DRs / 7 cxkgkwywboozyi/lM8FMu YWVO31zuFqaoPsWzuk2N | 20180520 | 3 | H6 | | 11 | 1 | 0 01 | | | | | | | | | | | | has 4 | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g17000 | G170 | AOH | VNP | AOH | VNP | 15:52 | | lift 05:26 | IS_TIME_NOT_BUY | OwWGlKxfnPfPYGOuhjVhioA2r3kj 2 krs0zxnvd04 + IDhPhfc | 20180520 | 3 | H1 | | 08 | 1 | 0 01 | | | | | | | | | | | no 1 | | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g15060 | G150 | AOH | VNP | AOH | VNP | 16:05 22:00 | | 05:55 | IS_TIME_NOT_BUY | B + kl5hvzm26b184g8odo4t15OHC2 2 ban1a1nggf301bdergo | 20180520 | 3 | H6 | | 10 | 1 | 0 01 | | | | | | | | | | | 1 has 8 | | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g152e0 | G152 | AOH | VNP | AOH | VNP | 16:18 | and | 05:54 | IS_TIME_NOT_BUY | 81 vzxpx7csnmfnl08hcnwu + u50Gp J + QNOZctnNmnxXE8onhQ | 20180520 | 3 | H6 | | 10 | 1 | 0 01 | | | | | | | | | | | | has 15 | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l00000g1613 | G16 | AOH | VNP | AOH | VNP | | 17:00 accounted | 04:36 | IS_TIME_NOT_BUY tjp2e11rad8ksvlp8blxwfyqqnnu | 1 RrS6nFPFNIumUhIkIX3 | 20180520 | 3 | H6 | | 1 | 0 01 | 05 | | | | | | | | | | | 1 | | free 4 | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g15470 | G154 | AOH | VNP | AOH | VNP | then | but | 05:35 | IS_TIME_NOT_BUY | FMIX4FHuTLpNf0wPQlJhJvoLN5ka WBBXSs2PWGQJ / 422 h0c0 | 20180520 | 3 | H6 | 01 | 1 | | 0 | | | | | | | | | | | | has 5 | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g156r0 | G156 | AOH | VNP | AOH | VNP | | was little | 05:40 | IS_TIME_NOT_BUY | wnJtQjVkFz37b4Xp1eP4obJTdrV9 IoOqRUvqvJzy7 + AYI7YL | 20180520 | 3 | H6 | | 09 | 1 | 0 01 | | | | | | | | | | | | 1 | have 17 | | O0M090 | OM9 | 0"."Null | | 5600000 g44b0 23:00 - surf system maintenance time | G44 | | HGH VNP | AOH | VNP | 17:23 | 23:08 | 05:45 | IS_TIME_NOT_BUY | 4 m/BToLy7SoKriz9NLnM6EZwyFF9T T / / rrPb6JCTSb6DtMgW | 20180520 | 3 | H6 | 04 13 | | 1 | 0 | | | | | | | | | | | 8 no | | 1 | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g158c0 | G158 | AOH | VNP | AOH | VNP | 17:34 | |, behold 05:55 | IS_TIME_NOT_BUY qcvqydrkrxx2hjgyupgqxh/evCU | 3 FK0TJKN6KMqh8Lzyu/dQ | 20180520 | 3 | H6 | | 10 | 1 | 0 01 | | | | | | | | | | | 1 has 15 | | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l0000g160v0 | G160 | AOH | VNP | AOH | VNP | 17:44 | 23:48 | 06:04 | IS_TIME_NOT_BUY | Fs4rA/vbQ + + MOZP5UK4sCe4nmEc b E9xJsA1SywdMcZ2otlav | 20180520 | 3 | H6 | | 10 | 1 | 0 01 | | | | | | | | | | | have | | | | O0M090 | OM9 | 0"."Null | | 23:00 - surf system maintenance time 5 l00000g1829 | G18 | AOH | VNP | AOH | VNP 18:00 | | | also 04:36 | IS_TIME_NOT_BUY | NEuxbLCppnaF8Fm + wuVXDFCSMsVBb OGsCrawCMD/YLarh6s3|20180520|3|H6|01|05|1|0|||||||||||1|5|4||O0M090|OM9|0"."Null | | 550000 t11061 23:00 - surf system maintenance time | T110 | SHH | BJP | SHH | BJP | 18:02 | 09:30 when | | IS_TIME_NOT_BUY | Zqy8vHHz4tA2WNH/H1f8d2PE0pc2 K + 48 qox2hffwrkeuu8ztljdvkt0ksnllww66ajur / 1 v6cke = | 20180520 | 3 | H3 | | 09 | | | 0 0 01 no | | | | free | | a | | 2 | 1 | | | | | 1040106030 | 14163 | 0"."Null | | 23:00 - surf system maintenance time 5 l00000g2219 | G22 | AOH | VNP | AOH | VNP at 19:00 | |" | 04:18 | IS_TIME_NOT_BUY | pbuRJ1NgYwLV0f1B6kNwLT1sMCL9o /+CDoQJ6vd1Kbe3GP+1|20180520|3|H6|01|03|1|0|||||||||||6|3|5||O0M090|OM9|0"."Null | | 550000 d31270 23:00 - surf system maintenance time | D312 | SHH | VNP | SHH | VNP | 19:10 | 07:07 | now | IS_TIME_NOT_BUY | QNf6TCZV01wG6pmiy2gz3lg/QUAA /Uvm|20180520|3|H3|01|04|0|0||||5||||||||||1|F040|F4|1"."Null | | 550000 d32260 23:00 - surf system maintenance time | D322 | SHH | VNP | SHH | VNP | 19:53 | 07:45 | that | he IS_TIME_NOT_BUY | xtuqf0inq39vWyfVaA6GfBad2dPn JBk6 | 20180520 | 3 | H3 | | 03 01 | | 0 0 | | | | a | | | | | | | no | | | | O040 | | 0 "m1:."Null | | 550000 d31490 23:00 - surf system maintenance time | D314 | SHH | VNP | SHH | VNP | 21:07 | oneself truly | | IS_TIME_NOT_BUY | Lamvi3Rs8Nk3cxG7zey21PJvsuzo 7 v5o | 20180520 | 3 | H3 | | 04 | | 0 0 01 | | | | a | | | | | | | 5 | | | | O040 | | 0 "m1:]."flag": "1"."map": {
			"AOH": "Shanghai Hongqiao"."BJP": "Beijing"."VNP": "Beijing South"."SHH": "Shanghai"}},"messages": []."validateMessages": {}}Copy the code

The residual ticket information is in the result node, which is an array. After each node to | segmentation, we can format shown in the interface:

The interface I made here is relatively simple, readers can make a more beautiful interface if they are interested. Let’s list the HTTP packets and reply packets sent for this request:

Request packets:

GET /otn/leftTicket/query? leftTicketDTO.train_date=2018-05-20&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=BJP&purpose_codes=ADULT HTTP/1.1 Host: kyfw.12306.cn Connection: keep-alive cache-control: no-cache Accept: */* x-requested -With: XMLHttpRequest if-modified-since: 0 user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; X64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36 Referer: https://kyfw.12306.cn/otn/leftTicket/init Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh; Q = 0.9, en. Q = 0.8 cookies: RAIL_EXPIRATION = 1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlu jJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_BMNur8b8; _jc_save_fromStation=%u4E0A%u6D77%2CSHH; _jc_save_toStation=%u5317%u4EAC%2CBJP; _jc_save_fromDate=2018-05-20; _jc_save_toDate=2018-05-19; _jc_save_wfdc_flag=dcCopy the code

Response package:

HTTP/1.1 200 OK Date: Sat, 19 May 2018 15:23:58 GMT Content-Type: Application /json; HTTP/1.1 200 OK Date: Sat, 19 May 2018 15:23:58 GMT Content-Type: Application /json; charset=UTF-8 Transfer-Encoding: chunked ct: C1_217_85_8 Content-Encoding: gzip Age: 1 X-Via: 1.1 Houdianxin183:6 (Cdn Cache Server V2.0) Connection: keep-alive x-dscp-value: 0 x-CDn-src-port: 33963 cache-control: no-cache, no-storeCopy the code

From the previous article “Implementing an HTTP server from Scratch” (blog.csdn.net/analogous_l…) We know that this is an HTTP GET request, with the parameters attached to the request following the URL:

leftTicketDTO.train_date: 2018-05-20
leftTicketDTO.from_station: SHH
leftTicketDTO.to_station: BJP
purpose_codes: ADULT
Copy the code

These four parameters are the date of ticket purchase, departure station, arrival station and ticket type (here is adult ticket (ordinary ticket)), which exactly correspond to the query information on our interface:

However, the reader may ask, here the departure and arrival stations are SHH and BJP respectively, how do I get these site codes? Because only by knowing these station codes can I buy train tickets for the specified departure and arrival stations. If you are a careful person, you must think that when we check the ticket and then enter the ticket check page, these site information has already been, so it may be the site information requested from the server when the ticket check page is loaded, so we refresh the ticket check page, and found that it is really like this:

Before entering the ticket please page, the browser from kyfw. 12306. Cn/otns/resourc… This is a javascript script with only one line of code in it. It defines the js variable station_names. The reason why the URL is followed by station_version=1.9053. You can think of it as a version number, but mostly by using a random value of 1.9053 to tell the browser not to use station_name. Js in the cache, but to reload the file from the server every time, so that if the site information is updated, you can avoid caching problems. The local cache is inconsistent with the site information on the server. Because the site information is more, we cut a picture:

See above, we can see that each site information by dividing the @ symbol, then each site by division | all kinds of information. So, according to the format of the above, if we want to query on May 30, 2018 from changchun to nanjing train tickets, can through the website kyfw. 12306. Cn/otns/leftTic… .

Of course, it needs to be noted here that because the information file of train stations across the country is relatively large, it takes a long time for our program to parse, and the code information of train stations is not always changed, so we don’t need to download station_name. Js every time, so when I write the program to simulate this request, Generally, the first to see whether the local file, if there is a local use, not to send HTTP request to 12306 server request. Here I post my request for site information program code (C++ code) :

@param bForceDownload = @param bForceDownload = @param bForceDownload Bool GetStationInfo(Vector < stationInfo > &si, bool bForceDownload =false);
Copy the code
# define URL_STATION_NAMES "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9053"
Copy the code
bool Client12306::GetStationInfo(vector<stationinfo>& si, bool bForceDownload/* = false*/)
{  
    FILE* pfile;
    pfile = fopen("station_name.js"."rt+"); // If the file does not exist, it must be downloadedif (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); 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); } // Read the file directlyelse{// get the file size 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); } /* Returns a js file, var station_names ='@ BJB north | | Beijing | VAP beijingbei | BJB | 0 @ BJD east | | Beijing BOP | beijingdong | BJD @ bji | 1 | Beijing | BJP | Beijing | bj | 2 "* / / / LogInfo (recv json =" % s ", strResponse.c_str()); OutputDebugStringA(strResponse.c_str()); vector
      
        singleStation; split(strResponse, "@", singleStation); size_t size = singleStation.size(); for (size_t i = 1; i < size; ++i) { vector
       
         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; }
       
      Copy the code

Here we use a site information structure, stationInfo, defined as follows:

//var station_names = '@ BJB north | | Beijing | VAP beijingbei | BJB | 0 @ BJD east | | Beijing BOP | beijingdong | BJD @ bji | 1 | Beijing | BJP | Beijing | bj | 2 struct stationinfo {string code1; string hanzi; string code2; string pingyin; string simplepingyin; int no; };Copy the code

Because our purpose here is to simulate the operation of buying a train ticket with HTTP request, not the technical aspect itself, we use curl library for quick implementation of our purpose. The library is a powerful HTTP related library, such as the 12306 server may return chunked data, this library can help us assemble; For example, curl automatically decompresses the data returned by the server using gzip. Curl curl curl curl curl curl curl curl curl curl curl curl curl curl curl curl curl curl

/** ** Send an HTTP request *@param URL request URL *@param strResponse HTTP response result *@param gettrueTo GET,falseHTTP headers sent with POST *@param headers *@param postData Data sent with POST *@param bReserveHeaders Whether to reserve header information for the RESULT of the HTTP response *@param timeout HTTP request Timeout */ bool HttpRequest(const char* URL, string& strResponse, bool get =true, const char* headers = NULL, const char* postdata = NULL, bool bReserveHeaders = false, int timeout = 10);
Copy the code

The various parameters of the function are clearly stated in the function comments, which will not be explained here. The code for this function is as follows:

bool Client12306::HttpRequest(const char* url, 
                              string& strResponse, 
                              bool get/* = true*/, 
                              const char* headers/* = NULL*/, 
                              const char* postdata/* = NULL*/, 
                              bool bReserveHeaders/* = false*/, 
                              int timeout/* = 10*/)
{
    CURLcode res;
    CURL* curl = curl_easy_init();
    if (NULL == curl)
    {
        LogError("curl lib init error");
        return false; } curl_easy_setopt(curl, CURLOPT_URL, url); // Keep the header information in the response resultif (bReserveHeaders)
       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); Curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER,false);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); Curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, timeout); curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); curl_easy_setopt(curl, CURLOPT_REFERER, URL_REFERER); // The USERAGENT field was not required in earlier versions of 12306, but is now required to avoid third-party snooping. / / if there is no this field, will return/HTTP / 1.0 * 302 version Temporarily Location: http://www.12306.cn/mormhweb/logHTML Server: Cdn Cache Server V2.0 MIME-Version: 1.0 Date: Fri, 18 May 2018 02:52:05 GMT Content-Type: Cdn Cache Server V2.0 MIME-version: 1.0 Date: Fri, 18 May 2018 02:52:05 GMT text/html Content-Length: 0 Expires: Fri, 18 May 2018 02:52:05 GMT X-Via: 1.0 psSHgqDXXx63:10 (Cdn Cache Server V2.0) Connection: keep-alive x-dscp-value: 0 */ curl_easy_setopt(curl, CURLOPT_USERAGENT,"Mozilla / 5.0 (Windows NT 6.1; Win64; X64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36"); // Curl_easy_setopt (curl, CURLOPT_ACCEPT_ENCODING, CURLOPT_ACCEPT_ENCODING,"gzip, deflate, br"); // Add custom header informationif(headers ! = NULL) { //LogInfo("http custom header: %s", headers);
        struct curl_slist *chunk = NULL;        
        chunk = curl_slist_append(chunk, headers);      
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
    }

    if(! get && postdata ! = NULL) { //LogInfo("http post data: %s", postdata);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata);
    }

    LogInfo("http %s: url=%s, headers=%s, postdata=%s", get ? "get" : "post", url, headers ! = NULL ? headers :"", postdata! =NULL? postdata :"");

    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 && code ! = 302) { bError =true;
            LogError("http response code is not 200 or 302, code=%d", code); }}else
    {
        LogError("http request error, error code = %d", res);
        bError = true;
    }

    curl_easy_cleanup(curl);

    LogInfo("http response: %s", strResponse.c_str());

   return! bError; }Copy the code

As noted in the comments above, there are some fields that browsers carry when sending HTTP requests that we do not need, such as the check interface browser which may send the following HTTP packets:

GET /otn/leftTicket/query? leftTicketDTO.train_date=2018-05-30&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=BJP&purpose_codes=ADULT HTTP/1.1 Host: kyfw.12306.cn Connection: keep-alive cache-control: no-cache Accept: */* x-requested -With: XMLHttpRequest if-modified-since: 0 user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; X64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36 Referer: https://kyfw.12306.cn/otn/leftTicket/init Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh; Q = 0.9, en. Q = 0.8 cookies: JSESSIONID = ACD9CB098169C4D73CDE80D6F6C38E5A; RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlu jJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_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-20Copy the code

Fields like Connection, cache-Control, Accept, and if-Modified-since are not required, so we can add these fields when simulating our own HTTP requests. As far as I can see, The 12306 server is becoming more and more stringent about the HTTP packets it sends. For example, last year, user-agent was not required. Now, if you do not wear this field, 12306 May not return the correct result. Of course, there will be no clear error message in the incorrect results, at best may tell you that the page does not exist or the system is busy please try again later, this is an important measure of server self-protection, imagine you do server program, will tell illegal users clear error message? That would give whoever attacked the server an opportunity to try again and again.

In particular, the header of the HTTP protocol sent by the check interface also has a field called Cookie, whose value is a very strange string of things:

JSESSIONID=ACD9CB098169C4D73CDE80D6F6C38E5A; RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlu jJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_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-2Copy the code

There is a JSESSIONID in this string, which we can pass or not pass on non-log-in checking interfaces. But in the ticket and query contact these need to be in commonly used has the login can only be carried out under the condition of operation, we must take the data, this is the server for your token (authentication token), and this token is at first in the 12306 site, with many of the server, log operations must take the token behind you, Otherwise, the server will consider your request an illegal request. That’s why, when I first looked into 12306’s ticketing process, I couldn’t log in even with the correct username, password and photo verification code. This is a security measure used by 12306 to prevent illegal logins.

Login and pull image verification code interface

My login page looks like this:

12306 pictures of captcha generally consists of eight pictures, like the “dragon boat” text, and pictures, the two images (text images and authentication code) is on the server after assembling, sent to the client, server 12306 small images have a certain amount of this type, although the quantity is large, but is limited. If you want to automate captcha, try downloading most of the images and doing statistical patterns. So, I didn’t do automatic image recognition here. Interested readers can try it on their own.

First, the interface to pull the captcha. We open the Chrome 12306 login interface: kyfw. 12306. Cn/otns/login/I…

The interface that can pull the verification code:

We can see that the HTTP request packet is sent in the following format:

GET /passport/captcha/captcha-image? Login_site =E&module=login&rand=sjrand&0.7520968747611347 HTTP/1.1 Host: kyfw.12306.cn Connection: Keep-alive user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; X64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36 Accept: image/webp,image/apng,image/*,*/*; Q = 0.8 Referer: https://kyfw.12306.cn/otn/login/init Accept - Encoding: gzip, deflate, br Accept - Language: useful - CN, useful; Q = 0.9, en. Q = 0.8 cookies: _passport_session = badc97f6a852499297796ee852515f957153; _passport_ct=9cf4ea17c0dc47b6980cac161483f522t9022; RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlu jJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_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.0000Copy the code

The Cookie field must carry the JSESSIONID as described above. The download image verification code and the following steps must also carry the JSESSIONID value in the Cookie field. Otherwise, you cannot get a correct response from the 12306 server. We’ll show you how to get this later. The HTTP GET request to pull the image captcha requires three parameters, login_site, module, rand, and a random value like 0.7520968747611347, as shown in the above code fragment. The values of the first three fields are fixed. The module field indicates the current module. The current value is login, and the value for obtaining the latest contact is passenger. Another point to note here is that if you fail to validate the image captcha and re-request the image, you must also re-request the JSESSIONID. The url is https://kyfw.12306.cn/otn/login/init. The HTTP request and reply packets are as follows:

Request packets:

Accept: text/html,application/xhtml+xml,application/xml; Q = 0.9, image/webp image/apng, * / *; Q =0.8 Accept-encoding: gzip, deflate, BR Accept-language: zh-cn,zh; Q = 0.9, en. Q =0.8 cache-control: max-age=0 Connection: keep-alive Cookie: RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlu jJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_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 Host: kyfw. 12306. Cn Referer: https://kyfw.12306.cn/otn/passport?redirect=/otn/login/loginOut Upgrade-Insecure-Requests: 1 User-Agent: Mozilla / 5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36
Copy the code

Response package:

HTTP/1.1 200 OK Date: Sun, 20 May 2018 02:23:53 GMT Content-Type: text/ HTML; charset=utf-8 Transfer-Encoding: chunked Set-Cookie: JSESSIONID=D5AE154D66F67DE53BF70420C772158F; Path=/otn ct: C1_217_101_6 Content-Language: zh-CN Content-Encoding: gzip X-Via: 1.1 Houdianxin184:4 (Cdn Cache Server V2.0) Connection: keep-alive x-dscp-value: 0 x-CDn-src-port: 46480Copy the code

This value is obtained in the reply packet field set-cookie:

Set-Cookie: JSESSIONID=D5AE154D66F67DE53BF70420C772158F; Path=/otn
Copy the code

So, we re-request the JSESSIONID each time we request an image captcha, as follows:

#define URL_LOGIN_INIT "https://kyfw.12306.cn/otn/login/init"
Copy the code
bool Client12306::loginInit()
{	
    string strResponse;
    if(! HttpRequest(URL_LOGIN_INIT, strResponse,true."Upgrade-Insecure-Requests: 1", NULL, true, 10))
    {
        LogError("loginInit failed");
        return false;
    }
        
    if(! GetCookies(strResponse)) { LogError("parse login init cookie error, url=%s", URL_LOGIN_INIT);
        return false;
    }
   
    return true;
}
Copy the code
bool Client12306::GetCookies(const string& data)
{
    if (data.empty())
    {
        LogError("http data is empty");
        return false; } // Parse the HTTP header string STR; str.append(data.c_str(), data.length()); size_t n = str.find("\r\n\r\n"); string header = str.substr(0, n); str.erase(0, n + 4); //m_cookie.clear(); / / get the HTTP header JSESSIONID = 21 ac68643bbe893fbdf3da9bcf654e98; vector<string> v;while (true)
    {
        size_t index = header.find("\r\n");
        if (index == string::npos)
            break;
        string tmp = header.substr(0, index);
        v.push_back(tmp);
        header.erase(0, index + 2);

        if (header.empty())
            break;
    }

    string jsessionid;
    string BIGipServerotn;
    string BIGipServerportal;
    string current_captcha_type;
    size_t m;
    OutputDebugStringA("\nresponse http headers:\n");
    for (size_t i = 0; i < v.size(); ++i)
    {
        OutputDebugStringA(v[i].c_str());
        OutputDebugStringA("\n");
        m = v[i].find("Set-Cookie: ");
        if (m == string::npos)
            continue;

        string tmp = v[i].substr(11);
        Trim(tmp);
        m = tmp.find("JSESSIONID");
        if(m ! = string::npos) { size_t comma = tmp.find(";");
            if(comma ! = string::npos) jsessionid = tmp.substr(0, comma); } m = tmp.find("BIGipServerotn");
        if(m ! = string::npos) { size_t comma = tmp.find(";");
            if(comma ! = string::npos) BIGipServerotn = tmp.substr(m, comma);else
                BIGipServerotn = tmp;
        }

        m = tmp.find("BIGipServerportal");
        if(m ! = string::npos) { size_t comma = tmp.find(";");
            if(comma ! = string::npos) BIGipServerportal = tmp.substr(m, comma);else
                BIGipServerportal = tmp;
        }

        m = tmp.find("current_captcha_type");
        if(m ! = string::npos) { size_t comma = tmp.find(";");
            if(comma ! = string::npos) current_captcha_type = tmp.substr(m, comma);elsecurrent_captcha_type = tmp; }}if(! jsessionid.empty()) { m_strCookies = jsessionid; m_strCookies +="; ";
        m_strCookies += BIGipServerotn;
        if(! BIGipServerportal.empty()) { m_strCookies +="; ";
            m_strCookies += BIGipServerportal;
        }
        m_strCookies += "; ";
        m_strCookies += current_captcha_type;
        return true;
    }
  
    LogError("jsessionid is empty");
    return false;
}
Copy the code
#define URL_GETPASSCODENEW "https://kyfw.12306.cn/passport/captcha/captcha-image"
Copy the code
bool Client12306::DownloadVCodeImage(const char* module)
{
    if (module == NULL)
    {
        LogError("module is invalid");
        return false; } / / https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.06851784300754482 ostringstream osUrl; osUrl << URL_GETPASSCODENEW; osUrl <<"? login_site=E&module="; osUrl << module; // Ticket verification codeif (strcmp(module, "passenger") != 0)
    {
        osUrl << "&rand=sjrand&"; } // Login verification codeelse
    {      
        osUrl << "&rand=randp&";     
    }
    double d = rand() * 1.000000 / RAND_MAX;
    osUrl.precision(17);
    osUrl << d;

    string strResponse;
    string strCookie = "Cookie: ";
    strCookie += m_strCookies;
    if(! HttpRequest(osUrl.str().c_str(), strResponse,true, strCookie.c_str(), NULL, false, 10))
    {
        LogError("DownloadVCodeImage failed");
        return false; } // Write file time_t now = time(NULL); struct tm* tblock = localtime(&now); memset(m_szCurrVCodeName, 0, sizeof(m_szCurrVCodeName));#ifdef _DEBUG
    sprintf(m_szCurrVCodeName, "vcode%04d%02d%02d%02d%02d%02d.jpg",
		1900 + tblock->tm_year, 1 + tblock->tm_mon, tblock->tm_mday,
		tblock->tm_hour, tblock->tm_min, tblock->tm_sec);
#else
    sprintf(m_szCurrVCodeName, "vcode%04d%02d%02d%02d%02d%02d.v",
        1900 + tblock->tm_year, 1 + tblock->tm_mon, tblock->tm_mday,
        tblock->tm_hour, tblock->tm_min, tblock->tm_sec);
#endif

    FILE* fp = fopen(m_szCurrVCodeName, "wb");
    if (fp == NULL)
    {
        LogError("open file %s error", m_szCurrVCodeName);
        return false;
    }

	const char* p = strResponse.data();
    size_t count = fwrite(p, strResponse.length(), 1, fp);
	if(count ! = 1) { LogError("write file %s error", m_szCurrVCodeName);
        fclose(fp);
		return false;
	}

	fclose(fp);

	return true;
}
Copy the code

We’ll look at the interface of the authentication code to the server validation kyfw. 12306. Cn/passport/ca… .

Request header:

POST/passport/captcha/captcha - check HTTP / 1.1 Host: kyfw. 12306. Cn Connection: keep alive - Content - Length: 50 Accept: application/json, text/javascript, */*; Q =0.01 Origin: https://kyfw.12306.cn x-requested-with: XMLHttpRequest user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; X64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36 Content-type: application/x-www-form-urlencoded; charset=UTF-8 Referer: https://kyfw.12306.cn/otn/login/init Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh; Q = 0.9, en. Q = 0.8 cookies: _passport_session = 3 e39a33a25bf4ea79146bd9362c11ad62327; _passport_ct=c5c7940e08ce44db9ad05d213c1296ddt4410; RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlu jJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_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.0000Copy the code

This is a POST request, where the input image captcha on the POST data strip selects coordinates X and Y values:

Answer: 175,58,30,51 login_site: E rand: sjrandCopy the code

Here I have selected two images, so there are two sets of coordinates, (175,58) is one set, (30,51) is the other set, the coordinate system is as follows:

Since each image has the same size, I can set a range of coordinates for each image. When I select an image, I can give a coordinate within it, not necessarily the exact position of the mouse click:

Struct VCodePosition {int x; struct VCodePosition {int x; int y; }; const VCodePosition g_pos[] = { { 39, 40 }, { 114, 43 }, { 186, 42 }, { 252, 47 }, { 36, 120 }, { 115, 125 }, { 194, 125}, {256, 120}}; Struct VCODE_SLICE_POS {int xLeft; int xRight; int yTop; int yBottom; }; const VCODE_SLICE_POS g_VCodeSlicePos[] = { {0, 70, 0, 70}, {71, 140, 0, 70 }, {141, 210, 0, 70 }, {211, 280, 0, 70 }, {0, 70, 70, 140}, {71, 140, 70, 140}, {141, 210, 70, 140}, {211, 280, 70, 140}; Bool g_bVodeSlice1Pressed[8] = {false.false.false.false.false.false.false.false}; Verify the image verification code interface code is: int Client12306: : checkRandCodeAnsyn (const char * vcode) {string param. param ="randCode=";
    param += vcode;
    param += "&rand=sjrand";	//passenger:randp
    
    string strResponse;
    string strCookie = "Cookie: ";
    strCookie += m_strCookies;
    if(! HttpRequest(URL_CHECKRANDCODEANSYN, strResponse,false, strCookie.c_str(), param.c_str(), false, 10))
    {
        LogError("checkRandCodeAnsyn failed");
        return- 1; } ///** Successfully return //HTTP/1.1 200 OK //Date: Thu, 05 Jan 2017 07:44:16 GMT //Server: apache-coyote /1.1 // X-powered-by: The Servlet 2.5; Jboss-5.0 / jbossweb-2.1 //ct: c1_103 //Content-Type: Application /json; Charset =UTF-8 //Content-Length: 144 // X-via: 1.1 Jiandianxin29.6 (Cdn Cache Server V2.0) //Connection: Keep-alive // x-cdN-src-port: 19153 // Invalid parameter //{"validateMessagesShowId":"_validatorMessage"."status":true."httpstatus": 200,"data": {"result":"0"."msg":""},"messages": []."validateMessages":{}} // Verification code expired //{"validateMessagesShowId":"_validatorMessage"."status":true."httpstatus": 200,"data": {"result":"0"."msg":"EXPIRED"},"messages": []."validateMessages"// Verification code error //{"validateMessagesShowId":"_validatorMessage"."status":true."httpstatus": 200,"data": {"result":"1"."msg":"FALSE"},"messages": []."validateMessages"// Verification code is correct //{"validateMessagesShowId":"_validatorMessage"."status":true."httpstatus": 200,"data": {"result":"1"."msg":"TRUE"},"messages": []."validateMessages":{}}
	Json::Reader JsonReader;
	Json::Value JsonRoot;
    if(! JsonReader.parse(strResponse, JsonRoot))return- 1; / / {"validateMessagesShowId":"_validatorMessage"."status" : true."httpstatus" : 200, "data" : {"result":"1"."msg" : "TRUE"}, "messages": []."validateMessages" : {}}
	if (JsonRoot["status"].isNull() || JsonRoot["status"].asBool() ! =true)
		return- 1;if (JsonRoot["httpstatus"].isNull() || JsonRoot["httpstatus"].asInt() ! = 200).return- 1;if (JsonRoot["data"].isNull() || ! JsonRoot["data"].isObject())
		return- 1;if (JsonRoot["data"] ["result"].isNull())
		return- 1;if (JsonRoot["data"] ["result"].asString() ! ="1" && JsonRoot["data"] ["result"].asString() ! ="0")
		return- 1;if (JsonRoot["data"] ["msg"].isNull())
		return- 1; //if (JsonRoot["data"] ["msg"].asString().empty())		
	//	return- 1;if (JsonRoot["data"] ["msg"].asString() == "")
		return 0;
	else if (JsonRoot["data"] ["msg"].asString() == "FALSE")
		return 1;


	return 1;
}
Copy the code

Similarly, here is the interface implementation code for authenticating the user name and password:

int Client12306::loginAysnSuggest(const char* user, const char* pass, const char* vcode)
{
    string param = "loginUserDTO.user_name=";
    param += user;
    param += "&userDTO.password=";
    param += pass;
    param += "&randCode=";
    param += vcode;
    string strResponse;
    string strCookie = "Cookie: ";
    strCookie += m_strCookies;
    if(! HttpRequest(URL_LOGINAYSNSUGGEST, strResponse,false, strCookie.c_str(), param.c_str(), false, 10))
    {
        LogError("loginAysnSuggest failed");
        return2; } ///** Successfully return //HTTP/1.1 200 OK //Date: Thu, 05 Jan 2017 07:49:53 GMT //Server: apache-coyote /1.1 // X-powered-by: The Servlet 2.5; Jboss-5.0 / jbossweb-2.1 //ct: c1_103 //Content-Type: Application /json; Charset =UTF-8 //Content-Length: 146 // X-via: 1.1 F186:10 (Cdn Cache Server V2.0) //Connection: Keep-alive // x-cdn-src-port: 48361 // The mailbox does not exist //{"validateMessagesShowId":"_validatorMessage"."status":true."httpstatus": 200,"data": {},"messages": ["The mailbox does not exist."]."validateMessages"// Password error //{"validateMessagesShowId":"_validatorMessage"."status":true."httpstatus": 200,"data": {},"messages": ["Incorrect password. If you make more than four wrong entries, you will be locked out."]."validateMessages"// Login succeeded //{"validateMessagesShowId":"_validatorMessage"."status":true."httpstatus": 200,"data": {"otherMsg":"",loginCheck:"Y"},"messages": []."validateMessages":{}}
	//WCHAR* psz1 = Utf8ToAnsi(strResponse.c_str());
	//wstring str = psz1;
	//delete[] psz1;

	Json::Reader JsonReader;
	Json::Value JsonRoot;
    if(! JsonReader.parse(strResponse, JsonRoot))return2; / / {"validateMessagesShowId":"_validatorMessage"."status" : true, / /"httpstatus" : 200, "data" : {"otherMsg":"", loginCheck : "Y"}, "messages": []."validateMessages" : {}}
	if (JsonRoot["status"].isNull())
		return- 1; bool bStatus = JsonRoot["status"].asBool();
	if(! bStatus)return- 1;if (JsonRoot["httpstatus"].isNull() || JsonRoot["httpstatus"].asInt() ! = 200).return 2;

	if (JsonRoot["data"].isNull() || ! JsonRoot["data"].isObject())
		return 2;

	if (JsonRoot["data"] ["otherMsg"].isNull() || JsonRoot["data"] ["otherMsg"].asString() ! ="")
		return 2;
	if (JsonRoot["data"] ["loginCheck"].isNull() || JsonRoot["data"] ["loginCheck"].asString() ! ="Y")
		return 1;

	return 0;
}
Copy the code

There is also a detail to pay attention to, that is, the data sent by POST request needs to do URL Encode for some symbols, which I did in the last article “From zero to an HTTP server” (blog.csdn.net/analogous_l…). It is also introduced in detail. If it is not clear, please refer to the previous article. Therefore, the comma information contained in the coordinate information of the image verification code should be URL encoded from

Answer = 114,54,44,46 & login_site = E&rand = sjrandCopy the code

become

answer=114%2C54%2C44%2C46&login_site=E&rand=sjrand
Copy the code

Therefore, the content-Length field specified in the HTTP header should be the encoded string Length, not the original Length, which is particularly error-prone.

If the verification is successful, the next step is to check and buy tickets. Here is not an introduction, all the principles are the same, the author can explore. Of course, I have explored all the interfaces and implemented them, so LET me post here:

Client12306.h file *@author: zhangyl *@date: 2017.01.17 */ client12306. h file *@author: zhangyl *@date: 2017.01.17 */#ifndef __CLIENT_12306_H__
#define __CLIENT_12306_H__

#include <vector>
#include <string>using namespace std; // Train type#define TRAIN_GC 0x00000001
#define TRAIN_D (0x00000001 << 1)
#define TRAIN_Z (0x00000001 << 2)
#define TRAIN_T (0x00000001 << 3)
#define TRAIN_K (0x00000001 << 4)
#define TRAIN_OTHER (0x00000001 << 5)
#define TRAIN_ALL (TRAIN_GC | TRAIN_D | TRAIN_Z | TRAIN_T | TRAIN_K | TRAIN_OTHER)Struct queryLeftNewDTO {string train_no; string station_train_code; string start_station_telecode; // Start station string start_station_name; string end_station_telecode; // Terminal string end_station_name; string from_station_telecode; // Start station string from_station_name; String to_station_telecode; string to_station_name; string start_time; string arrive_time; string day_difference; string train_class_name; string lishi; string canWebBuy; string lishiValue; string yp_info; string control_train_day; string start_train_date; string seat_feature; string yp_ex; string train_seat_feature; string seat_types; string location_code; string from_station_no; string to_station_no; string control_day; string sale_time; string is_support_card; string controlled_train_flag; string controlled_train_message; string train_type_code; string start_province_code; string start_city_code; string end_province_code; string end_city_code; string swz_num; String rz_num; // string yz_num; // hard block string gr_num; // Advanced soft sleeper string rw_num; // string yw_num; // hard sleeper string tz_num; // string zy_num; String ze_num; String wz_num; // Unreserved string gg_num; string yb_num; string qt_num; }; struct ticketinfo { queryLeftNewDTO DTO; string secretStr; string buttonTextInfo; }; //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;
};

struct passenager
{
    string code;  //"8"
    string passenger_name;  //"范蠡"
    string sex_code;// "M"
    string sex_name; // "男"
    string born_date; //"1989-12-08 00:00:00"
    string country_code;// "CN"
    string passenger_id_type_code;//  "1"
    string passenger_id_type_name; // "二代身份证"
    string passenger_id_no; // "14262319781108815X"
    string passenger_type; // "1"
    string passenger_flag; // "0"
    string passenger_type_name; // "成人"
    string mobile_no; // "13917043320"
    string phone_no;
    string email; // "[email protected]"
    string address; //  ""
    string postalcode; // ""
    string first_letter;// ""
    string recordCount;// "13"
    string total_times;// "99"
    string index_id;// "0"
};

class Client12306
{
public:
    static Client12306& GetInstance();

private:
	Client12306();
	~Client12306();

private:
	Client12306(const Client12306&);
	Client12306& operator=(const Client12306&);

public:
    bool ReloadVCodeImage();

    /**
     * 游客查票
     * https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-05-24&leftTicketDTO.from_station=BJP&leftTicketDTO.to_station=SHH&purpose_codes=ADULT
     * 应答:{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”messages”:[],”validateMessages”:{}}
     *@param: train_date列车发车日期,格式:2017-01-28
     *@param: from_station出发站,格式:SHH 对应上海
     *@parma: to_station到站,格式:BJP 对应北京
     *@param: purpose_codes 票类型,成人票:ADULT 学生票:0X00
     *@param: v 查票结果
     */
    bool GuestQueryTicket(const char* train_date, const char* from_station, const char* to_station, const char* purpose_codes, vector<ticketinfo>& v);
    
    /**
	* 初始化session,获取JSESSIONID
	*/
    bool loginInit();
	bool DownloadVCodeImage(const char* module = "login");
	/**
	*@return 0校验成功;1校验失败;2校验出错
	*/
	int checkRandCodeAnsyn(const char* vcode);
	/**
	*@return 0校验成功;1校验失败;2校验出错
	*/
    int loginAysnSuggest(const char* user, const char* pass, const char* vcode);

    /** 
     * 正式登录
     */
    bool userLogin();

    /** 
     * 模拟12306跳转
     */
    bool initMy12306();

    /**
     * 拉取乘客买票验证码
     */
    //bool GetVCodeImage();

    /**
     * 拉取乘客买票验证码
     */

    /** 
     * 查询余票第一步
     * https://kyfw.12306.cn/otn/leftTicket/log?leftTicketDTO.train_date=2017-02-08&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=NJH&purpose_codes=ADULT
     * 应答:{“validateMessagesShowId”:”_validatorMessage”,”status”:true,”httpstatus”:200,”messages”:[],”validateMessages”:{}}
     *@param: train_date列车发车日期,格式:2017-01-28
     *@param: from_station出发站,格式:SHH 对应上海
     *@parma: to_station到站,格式:BJP 对应北京
     *@param: purpose_codes 票类型,成人票:ADULT 学生票:0X00
     */
    bool QueryTickets1(const char* train_date, const char* from_station, const char* to_station, const char* purpose_codes);

    /**
     * 查询余票第二步
     * 这几种情形都有可能,所以应该都尝试一下
     * https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date=2017-02-08&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=NJH&purpose_codes=ADULT
     * https://kyfw.12306.cn/otn/leftTicket/queryX?leftTicketDTO.train_date=2017-02-08&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=NJH&purpose_codes=ADULT
     * https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-02-08&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=NJH&purpose_codes=ADULT
     * {"status":false,"c_url":"leftTicket/query","c_name":"CLeftTicketUrl"}
     * {"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"messages":["非法请求"],"validateMessages":{}}
     * 应答中含有实际余票信息
     *@param: train_date列车发车日期,格式:2017-01-28
     *@param: from_station出发站,格式:SHH 对应上海
     *@parma: to_station到站,格式:BJP 对应北京
     *@param: purpose_codes 票类型,成人票:ADULT 学生票:0X00
     */
    bool QueryTickets2(const char* train_date, const char* from_station, const char* to_station, const char* purpose_codes, vector<ticketinfo>& v);
    
    /** 
     * 检测用户是否登录
     * https://kyfw.12306.cn/otn/login/checkUser POST _json_att=
     * Cookie: JSESSIONID=0A01D967FCD9827FC664E43DEE3C7C6EF950F677C2; __NRF=86A7CBA739653C1CC2C3C3AA7C88A1E3; BIGipServerotn=1742274826.64545.0000; BIGipServerportal=3134456074.17695.0000; current_captcha_type=Z; _jc_save_fromStation=%u4E0A%u6D77%2CSHH; _jc_save_toStation=%u5357%u4EAC%2CNJH; _jc_save_fromDate=2017-01-22; _jc_save_toDate=2017-01-22; _jc_save_wfdc_flag=dc
     * {"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"flag":true},"messages":[],"validateMessages":{}}
     */
    bool checkUser();

    /** 
     * 预提交订单 POST
     * https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest?secretStr=secretStr&train_date=2017-01-21&back_train_date=2016-12-23&tour_flag=dc&purpose_codes=ADULT&query_from_station_name=深圳&query_to_station_name=武汉&undefined=
     */
    bool submitOrderRequest(const char* secretStr, const char* train_date, const char* back_train_date, const char* tour_flag, const char* purpose_codes, const char* query_from_station_name, const char* query_to_station_name);

    /** 
     * 模拟跳转页面InitDc,Post
     */
    bool initDc();

    /**
     * 拉取常用联系人 POST
     * https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs?_json_att=&REPEAT_SUBMIT_TOKEN=SubmitToken
     */
    bool getPassengerDTOs(vector<passenager>& v);

    /** 
     * 购票人确定
     * https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo
     @param oldPassengerStr	oldPassengerStr组成的格式:乘客名,passenger_id_type_code,passenger_id_no,passenger_type,’_’
                            示例: 范蠡,1,14262319781108815X,1_
     @param passengerTicketStr	passengerTicketStr组成的格式:seatType,0,票类型(成人票填1),乘客名,passenger_id_type_code,passenger_id_no,mobile_no,’N’ 
                            示例: O,0,1,范蠡,1,14262319781108815X,13917043320,N	101
     @tour_flag	dc表示单程票
     应答:{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"ifShowPassCode":"N","canChooseBeds":"N","canChooseSeats":"N","choose_Seats":"MOP9","isCanChooseMid":"N","ifShowPassCodeTime":"1","submitStatus":true,"smokeStr":""},"messages":[],"validateMessages":{}}
     */
    bool checkOrderInfo(const char* oldPassengerStr, const char* passengerTicketStr, const char* tour_flag, bool& bVerifyVCode);

    /** 
     * 准备进入排队
     * https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount
     _json_att		10
     fromStationTelecode	VNP	23
     leftTicket	enu80ehMzuVJlK2Q43c6kn5%2BzQF41LEI6Nr14JuzThrooN57	63
     purpose_codes	00	16
     REPEAT_SUBMIT_TOKEN	691c09b5605e46bfb2ec2380ee65de0e	52
     seatType	O	10
     stationTrainCode	G5	19
     toStationTelecode	AOH	21
     train_date	Fri Feb 10 00:00:00 UTC+0800 2017	50
     train_location	P2	17
     train_no	24000000G502	21
     应答:{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"count":"4","ticket":"669","op_2":"false","countT":"0","op_1":"true"},"messages":[],"validateMessages":{}}
     */
    bool getQueueCount(const char* fromStationTelecode, const char* leftTicket, const char* purpose_codes, const char* seatType, const char* stationTrainCode, const char* toStationTelecode, const char* train_date, const char* train_location, const char* train_no);

    /** 
     * 确认购买
     * https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue
     _json_att		10
     choose_seats		13
     dwAll	N	7
     key_check_isChange	7503FD317E01E290C3D95CAA1D26DD8CFA9470C3643BA9799D3FB753	75
     leftTicketStr	enu80ehMzuVJlK2Q43c6kn5%2BzQF41LEI6Nr14JuzThrooN57	66
     oldPassengerStr	范蠡,1,14262319781108815X,1_	73
     passengerTicketStr	O,0,1,范蠡,1,14262319781108815X,13917043320,N	101
     purpose_codes	00	16
     randCode		9
     REPEAT_SUBMIT_TOKEN	691c09b5605e46bfb2ec2380ee65de0e	52
     roomType	00	11
     seatDetailType	000	18
     train_location	P2	17
     应答:{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"submitStatus":true},"messages":[],"validateMessages":{}}
     */
    bool confirmSingleForQueue(const char* leftTicketStr, const char* oldPassengerStr, const char* passengerTicketStr, const char* purpose_codes, const char* train_location);

    /** 
     * 查询订单状态: https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime?random=1486368851278&tourFlag=dc&_json_att=&REPEAT_SUBMIT_TOKEN=691c09b5605e46bfb2ec2380ee65de0e
     GET
     _json_att
     random	1486368851278
     REPEAT_SUBMIT_TOKEN	691c09b5605e46bfb2ec2380ee65de0e
     tourFlag	dc
     响应:{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"queryOrderWaitTimeStatus":true,"count":0,"waitTime":-1,"requestId":6234282826330508533,"waitCount":0,"tourFlag":"dc","orderId":"E061149209"},"messages":[],"validateMessages":{}}
     */
    bool queryOrderWaitTime(const char* tourflag, string& orderId);

    /** 
     * https://kyfw.12306.cn/otn/confirmPassenger/resultOrderForDcQueue POST
     _json_att		10
     orderSequence_no	E061149209	27
     REPEAT_SUBMIT_TOKEN	691c09b5605e46bfb2ec2380ee65de0e	52
     {"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"submitStatus":true},"messages":[],"validateMessages":{}}
     */
    //bool resultOrderForDcQueue();

    /** 
     * 未完成的订单页面 https://kyfw.12306.cn/otn/queryOrder/initNoComplete GET
     * 获取未完成的订单 https://kyfw.12306.cn/otn/queryOrder/queryMyOrderNoComplete POST _json_att=
     */
    /*
    {
    "validateMessagesShowId": "_validatorMessage",
    "status": true,
    "httpstatus": 200,
    "data": {
        "orderDBList": [
            {
                "sequence_no": "E079331507",
                "order_date": "2017-02-09 10:10:55",
                "ticket_totalnum": 1,
                "ticket_price_all": 55300,
                "cancel_flag": "Y",
                "resign_flag": "4",
                "return_flag": "N",
                "print_eticket_flag": "N",
                "pay_flag": "Y",
                "pay_resign_flag": "N",
                "confirm_flag": "N",
                "tickets": [
                    {
                        "stationTrainDTO": {
                            "trainDTO": {},
                            "station_train_code": "G41",
                            "from_station_telecode": "VNP",
                            "from_station_name": "北京南",
                            "start_time": "1970-01-01 09:16:00",
                            "to_station_telecode": "AOH",
                            "to_station_name": "上海虹桥",
                            "arrive_time": "1970-01-01 14:48:00",
                            "distance": "1318"
                        },
                        "passengerDTO": {
                            "passenger_name": "范蠡",
                            "passenger_id_type_code": "1",
                            "passenger_id_type_name": "二代身份证",
                            "passenger_id_no": "14262319781108815X",
                            "total_times": "98"
                        },
                        "ticket_no": "E079331507110008B",
                        "sequence_no": "E079331507",
                        "batch_no": "1",
                        "train_date": "2017-02-11 00:00:00",
                        "coach_no": "10",
                        "coach_name": "10",
                        "seat_no": "008B",
                        "seat_name": "08B号",
                        "seat_flag": "0",
                        "seat_type_code": "O",
                        "seat_type_name": "二等座",
                        "ticket_type_code": "1",
                        "ticket_type_name": "成人票",
                        "reserve_time": "2017-02-09 10:10:55",
                        "limit_time": "2017-02-09 10:10:55",
                        "lose_time": "2017-02-09 10:40:55",
                        "pay_limit_time": "2017-02-09 10:40:55",
                        "ticket_price": 55300,
                        "print_eticket_flag": "N",
                        "resign_flag": "4",
                        "return_flag": "N",
                        "confirm_flag": "N",
                        "pay_mode_code": "Y",
                        "ticket_status_code": "i",
                        "ticket_status_name": "待支付",
                        "cancel_flag": "Y",
                        "amount_char": 0,
                        "trade_mode": "",
                        "start_train_date_page": "2017-02-11 09:16",
                        "str_ticket_price_page": "553.0",
                        "come_go_traveller_ticket_page": "N",
                        "return_deliver_flag": "N",
                        "deliver_fee_char": "",
                        "is_need_alert_flag": false,
                        "is_deliver": "N",
                        "dynamicProp": "",
                        "fee_char": "",
                        "insure_query_no": ""
                    }
                ],
                "reserve_flag_query": "p",
                "if_show_resigning_info": "N",
                "recordCount": "1",
                "isNeedSendMailAndMsg": "N",
                "array_passser_name_page": [
                    "范蠡"
                ],
                "from_station_name_page": [
                    "北京南"
                ],
                "to_station_name_page": [
                    "上海虹桥"
                ],
                "start_train_date_page": "2017-02-11 09:16",
                "start_time_page": "09:16",
                "arrive_time_page": "14:48",
                "train_code_page": "G41",
                "ticket_total_price_page": "553.0",
                "come_go_traveller_order_page": "N",
                "canOffLinePay": "N",
                "if_deliver": "N",
                "insure_query_no": ""
            }
        ],
        "to_page": "db"
    },
    "messages": [],
    "validateMessages": {}
}
    */

    /** 
     * 已完成订单(改/退) : https://kyfw.12306.cn/otn/queryOrder/queryMyOrder POST
     * queryType 1 按订票日期 2 按乘车日期
     * 查询日期queryStartDate=2017-02-09&queryEndDate=2017-02-09
     * come_from_flag: my_order 全部 my_resign 可改签 my_cs_resign 可变更到站 my_refund 可退票
     * &pageSize=8&pageIndex=0&
     * query_where G 未出行订单 H 历史订单
     * sequeue_train_name 订单号/车次/乘客姓名
     */
    /* 历史订单格式
       参见[历史订单.txt]
    */
    
    /** 
     * 获取全国车站信息
     *@param si 返回的车站信息
     *@param bForceDownload 强制从网络上下载,即不使用本地副本
     */
    bool GetStationInfo(vector<stationinfo>& si, bool bForceDownload = false);

    /** 
     * 获取所有高校信息 https://kyfw.12306.cn/otn/userCommon/schoolNames POST provinceCode=11&_json_att=
     */

    /** 
     * 获取所有城市信息 https://kyfw.12306.cn/otn/userCommon/allCitys POST station_name=&_json_att=
     */

	/**
	 * 查询常用联系人
	 */
	bool QueryPassengers(int pageindex = 2, int pagesize = 10);


    bool GetVCodeFileName(char* pszDst, int nLength);

private:
    bool GetCookies(const string& data);

    /**
     * 发送一个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);

private:
    char                m_szCurrVCodeName[256]; //当前验证码图片的名称
    string              m_strCookies;
    string              m_strGlobalRepeatSubmitToken;
    string              m_strKeyCheckIsChange;
};


#endif //!__CLIENT_12306_H__
Copy the code

Download full source code

The specific implementation code is not posted in the article, you can download my code. Download address in wechat public number “easyServerDev” in reply to “12306 source” can get the download address, of course, because 12306 interface often change, when you get the code, 12306 server interface may have slightly changed, you can do the corresponding modification according to the principle introduced above.

Finally, once you have implemented the basic login and ticketing functions, you can constantly simulate certain requests to swipe tickets.

All rights reserved.

Welcome to the public account “easyServerDev”. If there is any technical or professional problems need my help, can contact me by the public, the public, not only share stories, high-performance server development experience, and at the same time also free for the general technology to answer friends to provide technical and professional solutions, do you have any question can leave a message on WeChat public directly, I will reply you as soon as possible.