слова, написанные впереди
Каждый год во время фестиваля читателям не привыкать получать билеты. В этой статье мы познакомим читателей с тем, как с нуля реализовать программное обеспечение для сканирования билетов 12306. Основной принцип заключается в моделировании процесса покупки билетов для входа на веб-сайт 12306 путем отправки http-запросов и, наконец, покупки билетов.
О формате http-запроса и о том, как собрать http-пакет для отправки запроса на сервер, в предыдущей статье «Реализация http-сервера с нуля» (blog.CSDN.net/analogous_come…) был представлен подробно, если вы не понимаете, вы можете перейти к этой статье, чтобы прочитать ее.
Торжественно повторяем: технология, представленная здесь, предназначена только для обучения и не может использоваться для злонамеренных атак на сервер 12306. Пожалуйста, не злоупотребляйте технологией, представленной в этой статье. Любая потеря сервера 12306 осуществляется на ваш страх и риск.
Конечно, из-за огромного количества пользователей сервера 12306, в целях предотвращения спекуляций и других незаконных злоумышленников, многие URL-адреса 12306 и детали протокола каждого шага в процессе покупки билетов часто меняются. Поэтому некоторые определенные URL-адреса, представленные в этой статье, могут быть недействительными, когда вы видите эту статью. Но это не беда, пока вы освоите методы анализа, описанные в этой статье, вы сможете гибко модифицировать свой код под требования новейшего сервера 12306. Например, текущий URL-адрес интерфейса проверки билетов, такой как 12306:Можно разделить на .12306 Может/о боже/левый тик…, может через несколько днейМожно разделить на .12306 Может/о боже/левый тик…, через несколько дней может статьМожно разделить на .12306 Может/о боже/левый тик…, а потом через неделю может статьМожно разделить на .12306 Может/о боже/левый тик…, автор видел их все. Поэтому упор делается на изучение принципа, и если принцип освоен, каким бы ни стал релевантный url 12306, его можно менять без изменений. Эй, 12306 идет все дальше и дальше по пути борьбы со скальперами. Т_Т
В этой статье мы будем использовать следующие инструменты для анализа процесса покупки билетов 12306, а затем использовать язык C++ для имитации соответствующего процесса и, наконец, покупки билетов.
-
Браузер Chrome (также доступны и другие браузеры, все имеют схожие интерфейсы, такие как Chrome, браузер IE с установленным httpwatch и т. д.)
-
Учетная запись 12306, которая может войти на сайт 12306 и купить билеты.
-
Visual Studio (дополнительная версия, здесь я использую VS 2013)
1. Проверка билетов и информационный интерфейс сайта
Причина, по которой этот интерфейс анализируется в первую очередь, заключается в том, что проверка билетов не требует от пользователей входа в систему, что относительно просто. Мы открыли в браузере Chrome страницу запроса на 12 306 с лишним голосов, и URL-адрес:Можно разделить на .12306 Может/о боже/левый тик…,Как показано ниже:
Затем выберите меню [Проверить] в меню правой кнопки мыши на странице и после его открытия выберите вкладку [Сеть]. Как показано ниже:
После открытия страница становится окном, состоящим из двух частей. Слева — обычная веб-страница, а справа — консоль, которая поставляется с браузером. Когда мы работаем на левой странице, на правой стороне будут отображаться различные отправленные сообщения. нашим браузером http-запросы и ответы. Давайте проверим любой билет здесь, например, проверим билет из Шанхая в Пекин 20 мая 2018 года. Нажав на запрос, мы обнаружили, что правая сторона выглядит так:
Из значения типа списка на рисунке — xhr, мы можем сделать вывод, что это ajax-запрос (ajax — это асинхронный запрос, изначально поддерживаемый браузерами, за подробностями обратитесь к Baidu). Выбираем этот запрос, можно посмотреть детализацию этого запроса — запрос и результат ответа:
В ответе мы можем увидеть результат нашего ответа с удаленным заголовком http:
Это формат json. Давайте найдем инструмент форматирования json и вставим отформатированный json для всеобщего обозрения. Фактически, вы обнаружите, что данные, связанные с покупкой билетов в результате http-запроса 12306, в основном в формате json. JSON здесь выглядит следующим образом:
{
"validateMessagesShowId": "_validatorMessage",
"status": true,
"httpstatus": 200,
"data": {
"result": ["null|23:00-06:00系统维护时间|5l0000G10270|G102|AOH|VNP|AOH|VNP|06:26|12:29|06:03|IS_TIME_NOT_BUY|RLVVIt093U2EZuy2NE+VQyRloXyqTzFp6YyNk6J52QcHEA01|20180520|3|HZ|01|11|1|0|||||||||||1|有|13||O090M0|O9M|0", "null|23:00-06:00系统维护时间|5l0000G10470|G104|AOH|VNP|AOH|VNP|06:40|12:33|05:53|IS_TIME_NOT_BUY|j/TM45GgyJRRKvdalo3VIal8nYF7Hy9VL6njjGX3nOR3xwIu|20180520|3|HZ|01|09|1|0|||||||||||2|有|15||O090M0|O9M|0", "null|23:00-06:00系统维护时间|55000000G600|G6|SHH|VNP|SHH|VNP|07:00|11:38|04:38|IS_TIME_NOT_BUY|SO6mCijnVzhdTrntsbeMoJ4Vuw/WsAnsBz80diva/wuIfsS5|20180520|3|H1|01|05|1|0|||||||||||1|5|8||O090M0|O9M|0", "null|23:00-06:00系统维护时间|5l0000G106A0|G106|AOH|VNP|AOH|VNP|07:12|13:13|06:01|IS_TIME_NOT_BUY|Limy8VLpKgfmzb1EJZ0G7P8/Ai5iR7qbbwhplNeOVIxLQYab|20180520|3|HY|01|11|1|0|||||||||||1|11|12||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G10870|G108|AOH|VNP|AOH|VNP|07:22|13:23|06:01|IS_TIME_NOT_BUY|OJIuMonF9ctgAxxDpZRkNy0fn4HrG8Y+6ThVIAxtGrCWIp0N|20180520|3|HY|01|12|1|0|||||||||||无|6|3||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G110B0|G110|AOH|VNP|AOH|VNP|07:28|13:38|06:10|IS_TIME_NOT_BUY|HVY2cA5DQzMC1VDiotEG4zXAOwG4fHHYq2bh1ZFhm47pySly|20180520|3|HY|01|11|1|0|||||||||||无|5|13||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G120S0|G120|AOH|VNP|AOH|VNP|07:51|13:33|05:42|IS_TIME_NOT_BUY|G2C5o+MADORl4B9HQ2jmTdT2+fBnCbCXvfKCjqf0Fmm6fbU2|20180520|3|H6|01|08|1|0|||||||||||无|有|10||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l000000G814|G8|AOH|VNP|AOH|VNP|08:00|12:24|04:24|IS_TIME_NOT_BUY|dEqPPAVH6ICSdUQQwQ1ry/Ns0+QJCE2N+EZd4oC7FOmz855B|20180520|3|H6|01|04|1|0|||||||||||4|4|9||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G11293|G112|AOH|VNP|AOH|VNP|08:05|14:08|06:03|IS_TIME_NOT_BUY|j1BM0nZuw/phl6Z7WFxg0kFAc5Z4t+qKWZe3fjKB5ZR72nLl|20180520|3|HY|01|11|1|0|||||||||||无|3|2||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G11470|G114|AOH|VNP|AOH|VNP|08:15|14:13|05:58|IS_TIME_NOT_BUY|OwWGlKxfnPfPYGOuhjVhioA2r3kj2krs0zxNVD04+IDhPhfc|20180520|3|HY|01|11|1|0|||||||||||无|1|无||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l000000G232|G2|AOH|VNP|AOH|VNP|09:00|13:28|04:28|IS_TIME_NOT_BUY|8Q4veHYksOBLKJU03KPa0jbPDTgUByjp+UFMScwuarKvhZ+F|20180520|3|HY|01|04|1|0|||||||||||无|5|1||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G11670|G116|AOH|VNP|AOH|VNP|09:33|15:23|05:50|IS_TIME_NOT_BUY|jsCsXdkuWHZVgZ0YzaO+zWokRnnDQ4zowg78aRmc/hzNEMjK|20180520|3|HY|01|10|1|0|||||||||||无|6|2||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G11860|G118|AOH|VNP|AOH|VNP|24:00|24:00|99:59|IS_TIME_NOT_BUY||20180520||H6|01|11|0|1|||||||||||||||||0", "null|23:00-06:00系统维护时间|5l00000G1001|G10|AOH|VNP|AOH|VNP|10:00|14:28|04:28|IS_TIME_NOT_BUY|ycAb36mk9wXaSIll0bTc5WbH8wLT1YRVjvGH/cYzAxIoVMcU|20180520|3|H1|01|04|1|0|||||||||||无|无|5||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5600000G4280|G42|HGH|VNP|AOH|VNP|10:26|16:08|05:42|IS_TIME_NOT_BUY|usY+Ul57hWKitIUp1d4m3n3e0ys4iJTdDfedKU6oXk7F3bAb|20180520|3|H6|04|13|1|0|||||||||||无|无|无||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G12290|G122|AOH|VNP|AOH|VNP|10:41|16:43|06:02|IS_TIME_NOT_BUY|tNu43MkXqpjkcIe80jbPhpSgQ3IOcIyLbwMSspllz0Btc3mJ|20180520|3|H6|01|12|1|0|||||||||||无|5|3||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G124V0|G124|AOH|VNP|AOH|VNP|11:00|16:18|05:18|IS_TIME_NOT_BUY|otn+9ShYEtsJ+6yDQexyyomS8daAeRrvr958XuZ8C4hldEB1|20180520|3|H6|01|06|1|0|||||||||||1|8|3||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G126B0|G126|AOH|VNP|AOH|VNP|11:05|17:05|06:00|IS_TIME_NOT_BUY|HIpEbr9n0fqeUtQGaASOBoD+/duc8JM5U1M602j0rnrf0XfA|20180520|3|H6|01|12|1|0|||||||||||4|8|无||O090M0|O9M|0", "null|23:00-06:00系统维护时间|5l0000G128N0|G128|AOH|VNP|AOH|VNP|24:00|24:00|99:59|IS_TIME_NOT_BUY||20180520||H1|01|12|0|1|||||||||||||||||0", "null|23:00-06:00系统维护时间|5l0000G13080|G130|AOH|VNP|AOH|VNP|11:20|17:29|06:09|IS_TIME_NOT_BUY|eaISX27C/T247JdvbJCFWkXvFimDh4W5rNAht1O5/1PhCbLN|20180520|3|H1|01|13|1|0|||||||||||无|无|2||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5500000G1200|G12|SHH|VNP|SHH|VNP|12:00|16:38|04:38|IS_TIME_NOT_BUY|GxssVQj1spkQVDnyUYodUASXXdwKUnuMjltjIAMwB2IbtIxC|20180520|3|H1|01|04|1|0|||||||||||无|无|无||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G132C0|G132|AOH|VNP|AOH|VNP|12:17|18:32|06:15|IS_TIME_NOT_BUY|2obvVTZf5/iiIKfTAkXU8tDIK4dMypDrpaoQO0WhfqKp3b5h|20180520|3|H1|01|13|1|0|||||||||||无|2|4||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5500001462I0|1462|SHH|BJP|SHH|BJP|12:18|10:46|22:28|IS_TIME_NOT_BUY|05Xf+SuYrrrVUcoitze9/BO1a6zlhm/43WFiXQjDEU7Z+hbDUoKqD2myF3Y=|20180520|3|H2|01|23|0|0||||2|||有||无|有|||||10401030|1413|0", "null|23:00-06:00系统维护时间|5l0000G41250|G412|AOH|VNP|AOH|VNP|12:28|18:48|06:20|IS_TIME_NOT_BUY|CtWjFYsZE3ih/LiOPF03WQb8CvMe6jwdlqUwBRxKn3yRAn9F|20180520|3|H2|01|11|1|0|||||||||||无|2|2||O090M0|O9M|0", "null|23:00-06:00系统维护时间|5l0000G134B0|G134|AOH|VNP|AOH|VNP|13:01|18:58|05:57|IS_TIME_NOT_BUY|AO3hxVofuYXk7l6EhzGCCEu4ZHPpS/0A/nkroM7xlpx/fIIX|20180520|3|H6|01|11|1|0|||||||||||3|6|12||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G136O0|G136|AOH|VNP|AOH|VNP|24:00|24:00|99:59|IS_TIME_NOT_BUY||20180520||H6|01|11|0|1|||||||||||||||||0", "null|23:00-06:00系统维护时间|5l0000G13860|G138|AOH|VNP|AOH|VNP|13:30|19:28|05:58|IS_TIME_NOT_BUY|qgHsrIv2ECcib/ImiXBHGt9Vis0yzPG8bKHoOZ0RgY7aE5sK|20180520|3|H6|01|12|1|0|||||||||||无|8|5||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G14060|G140|AOH|VNP|AOH|VNP|13:35|19:41|06:06|IS_TIME_NOT_BUY|ERb1/PPb8O6WfX503UB/hvYJsZO74WIYIjQsCisEZ4esappf|20180520|3|H6|01|13|1|0|||||||||||2|无|6||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l000000G432|G4|AOH|VNP|AOH|VNP|14:00|18:28|04:28|IS_TIME_NOT_BUY|2x7UHKlapgd4OJrubhQIW25wn5ZyA0jvumVcUSzkWJZu+9yr|20180520|3|H6|01|04|1|0|||||||||||无|3|1||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G14253|G142|AOH|VNP|AOH|VNP|14:10|20:18|06:08|IS_TIME_NOT_BUY|LuImd+o+UIDry0/CjwMAzgBtvfwyN4dSpjzXZnTQxN89PqQk|20180520|3|H6|01|11|1|0|||||||||||1|7|9||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G144M3|G144|AOH|VNP|AOH|VNP|14:40|20:29|05:49|IS_TIME_NOT_BUY|xNsqS1nHci52T9o6E1hU3epRaV9cHSpKnl6i+5+2sWsHHOZQ|20180520|3|H6|01|10|1|0|||||||||||1|2|5||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G146F0|G146|AOH|VNP|AOH|VNP|14:52|20:48|05:56|IS_TIME_NOT_BUY|jAmoXkDA3YgUo4lorosGtKbjeNZ15a764hrcb9URyVEUCWBU|20180520|3|H6|01|10|1|0|||||||||||1|6|13||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l00000G1442|G14|AOH|VNP|AOH|VNP|15:00|19:36|04:36|IS_TIME_NOT_BUY|VyN8KW3DEeWDipXBnZoMhHHVf6m6YwwJ3QT5GnlQqbQPFOCK|20180520|3|H6|01|05|1|0|||||||||||2|2|1||O090M0|O9M|0", "null|23:00-06:00系统维护时间|5l0000G148D0|G148|AOH|VNP|AOH|VNP|15:23|21:13|05:50|IS_TIME_NOT_BUY|v4DRs/7cxkGkWywbOoZYi/lM8FMuYWVO31zuFqaoPsWzuk2N|20180520|3|H6|01|11|1|0|||||||||||无|有|4||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G17000|G170|AOH|VNP|AOH|VNP|15:52|21:18|05:26|IS_TIME_NOT_BUY|OwWGlKxfnPfPYGOuhjVhioA2r3kj2krs0zxNVD04+IDhPhfc|20180520|3|H1|01|08|1|0|||||||||||无|1|无||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G15060|G150|AOH|VNP|AOH|VNP|16:05|22:00|05:55|IS_TIME_NOT_BUY|B+kl5hvzm26b184g8odo4t15OHC22ban1A1nGGF301bDERGO|20180520|3|H6|01|10|1|0|||||||||||1|有|8||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G152E0|G152|AOH|VNP|AOH|VNP|16:18|22:12|05:54|IS_TIME_NOT_BUY|81VzXPX7cSnMfNL08HCNwU+u50GpJ+QNOZctnNmnxXE8onhQ|20180520|3|H6|01|10|1|0|||||||||||无|有|15||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l00000G1613|G16|AOH|VNP|AOH|VNP|17:00|21:36|04:36|IS_TIME_NOT_BUY|1Tjp2E11rAd8KSvlP8BLxwfyqQNNurrS6nFPFNIumUhIkIX3|20180520|3|H6|01|05|1|0|||||||||||1|无|4||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G15470|G154|AOH|VNP|AOH|VNP|17:13|22:48|05:35|IS_TIME_NOT_BUY|FMIX4FHuTLpNf0wPQlJhJvoLN5kawBBXSs2PWGQJ/422H0c0|20180520|3|H6|01|08|1|0|||||||||||无|有|5||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G156R0|G156|AOH|VNP|AOH|VNP|17:18|22:58|05:40|IS_TIME_NOT_BUY|wnJtQjVkFz37b4Xp1eP4obJTdrV9ioOqRUvqvJzy7+AYI7YL|20180520|3|H6|01|09|1|0|||||||||||1|有|17||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5600000G44B0|G44|HGH|VNP|AOH|VNP|17:23|23:08|05:45|IS_TIME_NOT_BUY|4M/BToLy7SoKriz9NLnM6EZwyFF9Tt//rrPb6JCTSb6DtMgW|20180520|3|H6|04|13|1|0|||||||||||8|无|1||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G158C0|G158|AOH|VNP|AOH|VNP|17:34|23:29|05:55|IS_TIME_NOT_BUY|3qcvQyDRKrXX2hJGyupGQxH/evCUFK0TJKN6KMqh8Lzyu/dQ|20180520|3|H6|01|10|1|0|||||||||||1|有|15||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l0000G160V0|G160|AOH|VNP|AOH|VNP|17:44|23:48|06:04|IS_TIME_NOT_BUY|Fs4rA/vbQ+b+MOZP5UK4sCe4nmEcE9xJsA1SywdMcZ2otlav|20180520|3|H6|01|10|1|0|||||||||||有|有|有||O0M090|OM9|0", "null|23:00-06:00系统维护时间|5l00000G1829|G18|AOH|VNP|AOH|VNP|18:00|22:36|04:36|IS_TIME_NOT_BUY|NEuxbLCppnaF8Fm+wuVXDFCSMsVBbOGsCrawCMD/YLarh6s3|20180520|3|H6|01|05|1|0|||||||||||1|5|4||O0M090|OM9|0", "null|23:00-06:00系统维护时间|550000T11061|T110|SHH|BJP|SHH|BJP|18:02|09:30|15:28|IS_TIME_NOT_BUY|Zqy8vHHz4tA2WNH/H1f8d2PE0pc2k+48QoX2hffwrKeUU8zTljDvKT0kSNLlww66AJUR/1v6ckE=|20180520|3|H3|01|09|0|0||无||无|||有||2|1|||||1040106030|14163|0", "null|23:00-06:00系统维护时间|5l00000G2219|G22|AOH|VNP|AOH|VNP|19:00|23:18|04:18|IS_TIME_NOT_BUY|pbuRJ1NgYwLV0f1B6kNwLT1sMCL9o/+CDoQJ6vd1Kbe3GP+1|20180520|3|H6|01|03|1|0|||||||||||6|3|5||O0M090|OM9|0", "null|23:00-06:00系统维护时间|550000D31270|D312|SHH|VNP|SHH|VNP|19:10|07:07|11:57|IS_TIME_NOT_BUY|QNf6TCZV01wG6pmiy2gz3lg/QUAA/Uvm|20180520|3|H3|01|04|0|0||||5||||||||||1|F040|F4|1", "null|23:00-06:00系统维护时间|550000D32260|D322|SHH|VNP|SHH|VNP|19:53|07:45|11:52|IS_TIME_NOT_BUY|xtuqf0inq39vWyfVaA6GfBad2dPnjBk6|20180520|3|H3|01|03|0|0||||有|||||||无||||O040|O4|0", "null|23:00-06:00系统维护时间|550000D31490|D314|SHH|VNP|SHH|VNP|21:07|08:55|11:48|IS_TIME_NOT_BUY|Lamvi3Rs8Nk3cxG7zey21PJvsuzo7v5O|20180520|3|H3|01|04|0|0||||有|||||||5||||O040|O4|0"],
"flag": "1",
"map": {
"AOH": "上海虹桥",
"BJP": "北京",
"VNP": "北京南",
"SHH": "上海"
}
},
"messages": [],
"validateMessages": {}
}
Остальная информация о голосовании, содержащаяся в нем, находится в узле результата, который представляет собой массив. Каждый узел отделяется символом |, и мы можем отформатировать его и отобразить в нашем собственном интерфейсе:
Интерфейс, который я сделал здесь, относительно прост, и читатели могут сделать более изысканные интерфейсы, если им это интересно. Перечислим http-пакеты и ответные пакеты, отправленные этим запросом:
Пакет запроса:
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
Cookie: RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlujJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_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=dc
Ответный пакет:
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-store
Через предыдущую статью "внедрение http сервера с нуля" (blog.CSDN.net/analogous_come…) мы знаем, что это HTTP-запрос GET, где после URL-адреса указаны параметры, прикрепленные к запросу:
leftTicketDTO.train_date: 2018-05-20
leftTicketDTO.from_station: SHH
leftTicketDTO.to_station: BJP
purpose_codes: ADULT
Эти четыре параметра — дата покупки билета, станция отправления, станция прибытия и тип билета (здесь взрослый билет (обычный билет)), что соответствует информации запроса в нашем интерфейсе:
Однако читатели могут спросить, станции отправления и прибытия здесь SHH и BJP соответственно, как мне получить эти коды станций? Потому что, только если я знаю эти коды станций, я могу сам купить билеты на поезда до назначенных станций отправления и прибытия. Если вы внимательный человек, то наверняка подумаете, что когда мы проверяем тикет, а затем заходим на страницу проверки тикета, информация о сайте уже доступна, тогда это может быть информация о сайте, запрашиваемая с сервера при загрузке страницы проверки тикета . , поэтому мы обновили страницу проверки билетов и обнаружили, что она выглядит так:
Перед входом на страницу проверки билетов браузер запускается сМожно разделить на .12306. Талант/Боже мой/Горячий поиск туалета…Загрузите файл с именем station.name.js, который представляет собой javascript-скрипт с одной строкой кода, определяющей js-переменную station_names.Причину, по которой station_version=1.9053 добавляется после URL-адреса, можно понять как номер версии. , но основной Он использует случайное значение 1,9053, чтобы указать браузеру не использовать station_name.js в кеше, а каждый раз перезагружать файл с сервера Таким образом, если информация о сайте обновляется, он может также избегайте проблем с локальным кешем из-за проблем с кешем.Кэш не соответствует информации о сайте на сервере. Так как информации о сайте много, сделаем скриншот:
Глядя на картинку выше, мы видим, что информация каждого сайта разделена символом @, а затем различная информация каждого сайта разделена символом |. В этом случае, согласно приведенному выше формату, если мы хотим узнать об обычном билете на поезд из Чанчуня в Нанкин на 30 мая 2018 года, мы можем пройти через веб-сайт.Можно разделить на .12306 Может/о боже/левый тик….
Конечно, здесь необходимо пояснить, что, поскольку информационные файлы железнодорожных станций в стране относительно велики, нашей программе требуется много времени для разбора, а кодовая информация железнодорожных станций меняется нечасто, поэтому мы не нужно каждый раз загружать это имя_станции..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, могут быть разбиты на фрагменты, и эта библиотека также может помочь нам собрать их; например, данные, возвращаемые сервером, сжимаются с использованием формата 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*/,
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);
//响应结果中保留头部信息
if (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);
//设定为不验证证书和HOST
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);
//12306早期版本是不需要USERAGENT这个字段的,现在必须了,估计是为了避免一些第三方的非法刺探吧。
//如果没有这个字段,会返回
/*
HTTP/1.0 302 Moved Temporarily
Location: http://www.12306.cn/mormhweb/logFiles/error.html
Server: Cdn Cache Server V2.0
Mime-Version: 1.0
Date: Fri, 18 May 2018 02:52:05 GMT
Content-Type: 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");
//不设置接收的编码格式或者设置为空,libcurl会自动解压压缩的格式,如gzip
//curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip, deflate, br");
//添加自定义头信息
if (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;
}
Как было сказано в комментариях выше, некоторые поля, которые выводят браузеры при отправке http запросов, нам не нужны, например, в интерфейсе проверки билетов браузер может отправлять следующие http пакеты:
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
Cookie: JSESSIONID=ACD9CB098169C4D73CDE80D6F6C38E5A; 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
Среди них такие поля, как Connection, Cache-Control, Accept, If-Modified-Since, не обязательны, поэтому нам не нужно добавлять эти поля при имитации собственного http-запроса.Конечно, по моему наблюдению, 12306 теперь его пересылает сервер Требования к HTTP-пакетам становятся все жестче, например, в прошлом году поле User-Agent не было обязательным, теперь, если не включать это поле, результат, возвращаемый 12306, может не быть правильным. Разумеется, в некорректном результате не будет явного сообщения об ошибке, в лучшем случае может быть сказано, что страница не существует или система занята. Повторите попытку позже. Это важная мера для самозащиты сервера. Представьте, что вы являетесь серверной программой, которая будет сообщать нелегальным пользователям явные сообщения об ошибках? Разве это не дало бы тем, кто незаконно атакует сервер, шанс повторить попытку?
Следует отметить, что в заголовке http-протокола, отправляемого интерфейсом проверки билетов, также есть поле Cookie, значением которого является строка очень странных вещей:
JSESSIONID=ACD9CB098169C4D73CDE80D6F6C38E5A; 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-2
В этой строке символов есть JSESSIONID, в интерфейсе проверки билетов, не требующем авторизации, мы можем передавать или не передавать значение этого поля. Однако, чтобы купить билеты и запросить общие контакты, эти операции можно выполнить только тогда, когда вы уже вошли в систему.Мы должны принести эти данные.Это токен (верификационный токен), предоставленный вам сервером, и этот токен используется, когда вы только что зашли на сайт .12306, он отправляется сервером, вы должны ввести этот токен при последующем входе в систему и других операциях, иначе сервер будет считать ваш запрос незаконным запросом. В первый раз, когда я пошел изучать процесс покупки билетов 12306, даже с правильным именем пользователя, паролем и кодом подтверждения изображения, я не мог войти, и это было причиной. Это мера безопасности, используемая 12306 для предотвращения несанкционированного входа в систему.
2. Войдите в систему и вытащите интерфейс кода подтверждения изображения
Моя страница входа выглядит так:
Код проверки изображения 12306 обычно состоит из восьми изображений, таких как текст «Лодка-дракон» выше, который также является изображением. Изображения в этих двух местах (текстовое изображение и код подтверждения) собираются на сервере и отправляются на сервер. клиент, 12306 сервер Есть определенное количество мелких картинок этого типа, хотя количество относительно велико, но оно ограничено. Если вы хотите автоматически распознавать проверочный код, вы можете попробовать загрузить большинство картинок, а затем выполнить статистические правила. Поэтому я не стал делать здесь функцию автоматического распознавания изображений. Заинтересованные читатели могут попробовать это сами.
Поговорим об интерфейсе получения проверочного кода. Открываем интерфейс входа браузера Chrome 12306:Можно разделить на .12306. Можно/о боже/логин/я…
Вы можете получить интерфейс для извлечения кода подтверждения:
Мы видим, что формат отправленного пакета HTTP-запроса:
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: zh-CN,zh;q=0.9,en;q=0.8
Cookie: _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. Три поля Host, Referer и Cookie являются обязательными, а поле Cookie должно содержать упомянутый выше JSESSIONID. Код подтверждения загрузки изображения и следующие шаги также должны быть указаны в поле Cookie. значение, иначе правильный ответ не может быть получен от сервера 12306. Как это получить, будет описано позже. Этот HTTP-запрос GET для извлечения кода проверки изображения требует трех параметров, как показано во фрагменте кода выше, а именно: login_site, модуль, ранд и случайное значение, подобное 0,7520968747611347. Значения первых трех полей фиксированы, модуль Поле указывает, какой модуль в настоящее время является модулем входа в систему, поэтому значение — это логин, а значение — пассажир, когда последний контакт будет получен позже. Еще один момент, на который следует обратить внимание, заключается в том, что если вы не можете подтвердить код подтверждения изображения, при повторном запросе изображения вы также должны повторно запросить JSESSIONID. Этот URL-адрес https://kyfw.12306.cn/otn/login/init. Пакеты HTTP-запроса и ответа выглядят следующим образом:
Пакет запроса:
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_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
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
Ответный пакет:
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: 46480
Это значение получается в поле Set-Cookie ответного пакета:
Set-Cookie: JSESSIONID=D5AE154D66F67DE53BF70420C772158F; Path=/otn
Поэтому каждый раз, когда мы запрашиваем код подтверждения изображения, мы повторно запрашиваем этот JSESSIONID, код выглядит следующим образом:
#define URL_LOGIN_INIT "https://kyfw.12306.cn/otn/login/init"
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;
}
bool Client12306::GetCookies(const string& data)
{
if (data.empty())
{
LogError("http data is empty");
return false;
}
//解析http头部
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();
//获取http头中的JSESSIONID=21AC68643BBE893FBDF3DA9BCF654E98;
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);
else
current_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;
}
#define URL_GETPASSCODENEW "https://kyfw.12306.cn/passport/captcha/captcha-image"
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;
//购票验证码
if (strcmp(module, "passenger") != 0)
{
osUrl << "&rand=sjrand&";
}
//登录验证码
else
{
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;
}
//写入文件
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;
}
Давайте взглянем на интерфейс проверочного кода с проверкой сервера.Можно разделить на .12306. на /passport/wipe….
Заголовок запроса:
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
Cookie: _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-запрос со значениями X и Y координат, выбранных капчей входного изображения на POST-данных:
answer: 175,58,30,51
login_site: E
rand: sjrand
Здесь я выбрал два изображения, поэтому есть два набора значений координат, (175, 58) — один набор, (30, 51) — другой набор, система координат следующая:
Поскольку размер каждого изображения одинаков, я могу установить диапазон координат для каждого изображения.Когда изображение выбрано, просто укажите в нем координату, не обязательно точную позицию при щелчке мышью:
//刷新验证码 登录状态下的验证码传入”randp“,非登录传入”sjrand“ 具体参看原otsweb中的传入参数
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 }
};
//8个验证码区块的鼠标点击状态
bool g_bVodeSlice1Pressed[8] = { false, false, false, false, false, false, false, false};
验证的图片验证码的接口代码是:
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;
}
///** 成功返回
//HTTP/1.1 200 OK
//Date: Thu, 05 Jan 2017 07:44:16 GMT
//Server: Apache-Coyote/1.1
//X-Powered-By: 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
//参数无效
//{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"result":"0","msg":""},"messages":[],"validateMessages":{}}
//验证码过期
//{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"result":"0","msg":"EXPIRED"},"messages":[],"validateMessages":{}}
//验证码错误
//{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"result":"1","msg":"FALSE"},"messages":[],"validateMessages":{}}
//验证码正确
//{"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;
}
Аналогично, вот код реализации интерфейса для проверки имени пользователя и пароля:
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");
return 2;
}
///** 成功返回
//HTTP/1.1 200 OK
//Date: Thu, 05 Jan 2017 07:49:53 GMT
//Server: Apache-Coyote/1.1
//X-Powered-By: 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
//邮箱不存在
//{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{},"messages":["该邮箱不存在。"],"validateMessages":{}}
//密码错误
//{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{},"messages":["密码输入错误。如果输错次数超过4次,用户将被锁定。"],"validateMessages":{}}
//登录成功
//{"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))
return 2;
//{"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;
}
Здесь также следует отметить одну деталь: данные, отправляемые через запрос POST, должны быть закодированы в URL-адресе для некоторых символов.blog.CSDN.net/analogous_come…) также представлен подробно, и если что-то непонятно, обратитесь к предыдущей статье. Следовательно, кодирование URL должно выполняться для информации о запятой, содержащейся в информации о координатах кода проверки изображения.
answer=114,54,44,46&login_site=E&rand=sjrand
стать
answer=114%2C54%2C44%2C46&login_site=E&rand=sjrand
Поэтому значение поля Content-Length, указанное в заголовке http, должно быть длиной закодированной строки, а не исходной длиной, которая особенно подвержена ошибкам.
Если проверка прошла успешно, следующим шагом будет проверка и покупка билетов. Я не буду вводить их здесь по одному, все принципы одинаковы, и автор может исследовать их самостоятельно. Разумеется, все интерфейсы я изучил и реализовал, выложу сюда:
/**
*@desc: 封装获取验证码、校验验证码、登录等12306各个请求的类,Client12306.h文件
*@author: zhangyl
*@date: 2017.01.17
*/
#ifndef __CLIENT_12306_H__
#define __CLIENT_12306_H__
#include <vector>
#include <string>
using namespace std;
//车次类型
#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; //始发站
string start_station_name;
string end_station_telecode; //终点站
string end_station_name;
string from_station_telecode; //出发站
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; //硬座
string gr_num; //高级软卧
string rw_num; //软卧
string yw_num; //硬卧
string tz_num; //特等座
string zy_num; //一等座
string ze_num; //二等座
string wz_num; //无座
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; // "balloonwj@qq.com"
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__
Полная загрузка исходного кода
Конкретный код реализации в статье публиковаться не будет, вы можете скачать мой код. Адрес загрузки находится в публичном аккаунте WeChat».easyserverdevОтветить на "12306 исходный код", вы можете получить адрес загрузки. Конечно, поскольку интерфейс 12306 часто меняется, когда вы получаете код, интерфейс сервера 12306 мог немного измениться. Вы можете внести соответствующие изменения в соответствии с принципами, описанными выше.
Наконец, после того, как вы реализовали основные функции входа в систему и покупки билетов, вы можете непрерывно имитировать определенные запросы на считывание билетов.
Авторские права на статью защищены, пожалуйста, сохраняйте уведомление об авторских правах при перепечатке.
Добро пожаловать в публичный аккаунт «easyserverdev». Если вам нужна помощь с любыми техническими или профессиональными вопросами, вы можете связаться со мной через эту официальную учетную запись, которая не только делится опытом и историями разработки высокопроизводительных серверов, но также предоставляет технические вопросы и ответы и профессиональные решения для большинства технических друзья бесплатно.Если у вас есть какие-либо вопросы, вы можете оставить сообщение прямо в общедоступной учетной записи WeChat, и я отвечу вам как можно скорее.