├── conf.py ├── ReadMe.txt ├── .gitattributes ├── .gitignore └── 12306.py /conf.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | # 配置文件 3 | username = 'yourname' # 账号 4 | password = '********' # 密码 5 | train_date = '2014-02-01' # 日期 6 | from_city_name = '饶平' # 起始站 7 | to_city_name = '深圳北'# 终点站 8 | 9 | passengers_id = ['4xxx9841xxx45'] # 身份证 需要在12306网站上的联系人 10 | station_train_code = ['D2321', 'all'] #火车 11 | seatname = '二等座' #特等 商务座 一等座 二等座 硬座 硬卧 软卧 12 | 13 | QueryTicketSeconds = 1 #查票间隔秒数 -------------------------------------------------------------------------------- /ReadMe.txt: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 12306火车票查询购买(成人票) 3 | 主要参考 https://github.com/huzhifeng/51dingpiao/blob/master/new.py 4 | 5 | 依赖库requests 6 | 7 | 主文件 12306.py 8 | 配置文件 conf.py ——配置用户名密码火车等信息 9 | 10 | ------------第一次运行12306.py 后会会生产一下文件 11 | log.txt 程序日志 可以删除 12 | station_name.js 12306火车站点信息,如果想获得最新的信息可以删除,程序会重新下载 13 | 用户名+passengers 12306 添加的联系人信息 14 | randcode.png 验证码图片 程序不会自动获得验证码,需要手动输入 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /12306.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | # http://www.12306.cn/ 订票 3 | # email 979762787@qq.com 4 | # date 2014-01-13 5 | # 参考 https://github.com/huzhifeng/51dingpiao/blob/master/new.py 用requests重写 6 | 7 | import logging 8 | logging.basicConfig(level=logging.DEBUG, 9 | format='%(asctime)s|%(filename)s|%(funcName)s|line:%(lineno)d|%(levelname)s|%(message)s', 10 | datefmt='%Y-%m-%d %X', 11 | filename='log.txt' 12 | ) 13 | 14 | import requests 15 | import time 16 | import os 17 | import json 18 | import random 19 | import conf 20 | 21 | 22 | 23 | # Global variables 24 | stations = {} 25 | queryTicketsTimes = 0 26 | 27 | seatCode = { 28 | '特等' : 'P', 29 | '商务座': '9', 30 | '一等座': 'M', 31 | '二等座': 'O', 32 | '硬座': '1', 33 | '硬卧': '3', 34 | '软卧': '4' 35 | } 36 | 37 | def printItem(items, name): 38 | msgs = items.get(name, "") 39 | if isinstance(msgs, list): 40 | for msg in msgs: 41 | print msg 42 | else: 43 | print msgs 44 | 45 | #------------------------------------------------------------------------------ 46 | # 火车站点数据库初始化 47 | # 每个站的格式如下: 48 | # @bji|北京|BJP|beijing|bj|2 ---> @拼音缩写三位|站点名称|编码|拼音|拼音缩写|序号 49 | def stationInit(): 50 | global stations 51 | stations = {} 52 | if not os.path.isfile('station_name.js'): 53 | print 'start loading station_name.js' 54 | s = requests.Session() 55 | r = s.get('https://kyfw.12306.cn/otn/resources/js/framework/station_name.js', 56 | timeout=5, verify=False) 57 | assert r.status_code == 200 58 | with open('station_name.js', 'wb') as fp: 59 | fp.write( r.content ) 60 | else: 61 | print 'loading local station_name.js' 62 | with open('station_name.js') as fp: 63 | data = fp.read() 64 | data = data.partition('=')[2].strip("'") #var station_names ='..' 65 | for station in data.split('@')[1:]: 66 | items = station.split('|') # bjb|北京北|VAP|beijingbei|bjb|0 67 | stations[ items[1] ] = items[2] 68 | return stations 69 | 70 | 71 | # Convert '2014-01-01' to 'Wed Jan 01 00:00:00 UTC+0800 2014' 72 | def trainDate(d): 73 | t = time.strptime(d, '%Y-%m-%d') 74 | asc = time.asctime(t) # 'Wed Jan 01 00:00:00 2014' 75 | return (asc[0:-4] + 'UTC+0800 ' + asc[-4:]) # 'Wed Jan 01 00:00:00 UTC+0800 2014' 76 | 77 | 78 | #简单装饰器 79 | def fail_retry(times, ret_vals=(True,), exception=Exception): 80 | if not isinstance(ret_vals, (list, tuple) ): 81 | ret_vals = (ret_vals, ) 82 | def inner(func): 83 | def _inner(*args, **kwargs): 84 | for i in xrange(times): 85 | if i: 86 | logging.warning('run %s retry times %d', func.__name__, i) 87 | try: 88 | ret = func(*args, **kwargs) 89 | if ret in ret_vals: 90 | return ret 91 | except exception, e: 92 | logging.warning('exception: %s', e) 93 | pass 94 | raise Exception('run %s times > %d' % (func.__name__, times) ) 95 | return _inner 96 | return inner 97 | 98 | 99 | class MyOrder(object): 100 | def __init__(self): 101 | self.username = conf.username 102 | self.password = conf.password 103 | self.train_date = conf.train_date 104 | self.from_city_name, self.to_city_name = conf.from_city_name, conf.to_city_name 105 | self.from_station_telecode = stations[ conf.from_city_name ] 106 | self.to_station_telecode = stations[ conf.to_city_name ] 107 | self.passengers_id = conf.passengers_id 108 | self.station_train_code = conf.station_train_code 109 | self.printConfig() 110 | self.tour_flag = 'dc' # 单程dc/往返wf 111 | self.purpose_code = 'ADULT' # 成人票 112 | self.seatcode = seatCode[conf.seatname] 113 | self.passengers = [] # 乘客列表 114 | self.s = requests.Session() 115 | self.order_train = None 116 | self.oldPassengerStr = '' 117 | self.passengerTicketStr = '' 118 | self.captcha = '1234' #订票时候验证码 119 | 120 | def printConfig(self): 121 | s = "\nusername = %s\ndate = %s\n%s--->%s\n\n" % ( self.username, self.train_date, 122 | self.from_city_name, self.to_city_name ) 123 | print unicode(s, 'utf-8') 124 | 125 | @fail_retry(3, exception=requests.Timeout) 126 | def _login_init(self): 127 | url = 'https://kyfw.12306.cn/otn/login/init' 128 | r = self.s.get(url, timeout=3, verify=False) 129 | return r.status_code == 200 130 | 131 | @fail_retry(3, exception=requests.Timeout) 132 | def _login_get_captcha(self): 133 | url = "https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew.do?module=login&rand=sjrand" 134 | r = self.s.get(url, timeout=3, verify=False) 135 | if r.status_code == 200: 136 | with open('randcode.png', 'wb') as fp: 137 | fp.write( r.content ) 138 | return True 139 | else: 140 | print 'getPassCodeNew.do status_code %d != 200' % r.status_code 141 | 142 | @fail_retry(3, ret_vals=(True, False), exception=requests.Timeout) 143 | def _login_check_captcha(self, code): 144 | parameters = [ 145 | ('randCode', code), 146 | ('rand', 'sjrand') 147 | ] 148 | url = 'https://kyfw.12306.cn/otn/passcodeNew/checkRandCodeAnsyn' 149 | r = self.s.post(url, parameters, timeout=3, verify=False) 150 | if r.status_code == 200: 151 | resp = r.json() 152 | logging.debug('checkRandCodeAnsyn resp %s', resp) 153 | return resp['status'] and resp[u'data'] == u'Y' 154 | else: 155 | print 'checkRandCodeAnsyn status_code %d != 200' % r.status_code 156 | 157 | @fail_retry(3, exception=requests.Timeout) 158 | def _login_start(self, captcha): 159 | parameters = [ 160 | ('loginUserDTO.user_name', self.username), 161 | ('userDTO.password', self.password), 162 | ('randCode', captcha), 163 | ] 164 | url = 'https://kyfw.12306.cn/otn/login/loginAysnSuggest' 165 | r = self.s.post(url, parameters, timeout=5, verify=False) 166 | logging.debug("\n%s\n", r.request.headers) 167 | if r.status_code != 200: 168 | print u"!!!.. 登陆失败 ..!!!" 169 | return 170 | resp = r.json() 171 | logging.debug('loginAysnSuggest return %s', resp) 172 | if resp[u'data'].get('loginCheck', None) == 'Y' : 173 | print u"----------------登陆成功^_^-------------------" 174 | return True 175 | else: 176 | print u"!!!.. 登陆失败 ..!!!" 177 | printItem(resp, 'messages') 178 | 179 | def _login(self): 180 | self._login_init() 181 | print u"接收登录验证码..." 182 | captcha = None 183 | while True: 184 | while not captcha: 185 | self._login_get_captcha() 186 | captcha = raw_input(u"输入新的验证码:".encode('gbk')) 187 | print u"校验登录验证码..." 188 | if self._login_check_captcha(captcha): 189 | logging.info("_login_check_captcha succ") 190 | break 191 | else: #失败重新输入 192 | captcha = raw_input(u"验证码错误重新输入:".encode('gbk')) 193 | print u"======>>>>正在登录..." 194 | return self._login_start(captcha) 195 | 196 | def get_passengers(self): 197 | print u'=====>>>>正在获取联系人...' 198 | self.passengers = [] # 乘客列表 199 | pfile = self.username + 'passengers' 200 | if not os.path.isfile(pfile): 201 | logging.info('start loading %s', pfile) 202 | parameters = [ 203 | ('_json_att', ''), 204 | ] 205 | url = 'https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs' 206 | r = self.s.post(url, parameters, timeout=5, verify=False) 207 | assert r.status_code == 200 208 | resp = r.json() 209 | with open(pfile, 'w') as fp: 210 | fp.write(json.dumps(resp)) 211 | else: 212 | logging.info('loading from local %s', pfile) 213 | with open(pfile) as fp: 214 | resp = json.loads( fp.read() ) 215 | for passengers in resp['data']['normal_passengers']: 216 | if passengers['passenger_id_no'] in self.passengers_id: 217 | self.passengers.append(passengers) 218 | logging.info('add passengers %s', passengers['passenger_id_no']) 219 | print u'---------预定人:', passengers['passenger_name'] 220 | if not self.passengers: 221 | print u'没有订票人信息' 222 | return False 223 | return True 224 | 225 | def login(self): 226 | return self._login() and self.get_passengers() 227 | 228 | @fail_retry(3, ret_vals=(True, False), exception=requests.Timeout) 229 | def queryTickets(self): 230 | parameters = [ 231 | ('leftTicketDTO.train_date', self.train_date), 232 | ('leftTicketDTO.from_station', self.from_station_telecode), 233 | ('leftTicketDTO.to_station', self.to_station_telecode), 234 | ('purpose_codes', "ADULT"), 235 | ] 236 | #s = requests.Session() 237 | s = self.s 238 | r = s.get('https://kyfw.12306.cn/otn/leftTicket/query', params=parameters, 239 | timeout=3, verify=False) 240 | 241 | global queryTicketsTimes 242 | queryTicketsTimes += 1 243 | print u'第 %d 次查询' % queryTicketsTimes 244 | 245 | if 200 != r.status_code: 246 | print u"请求出错 status_code = ", r.status_code 247 | return 248 | try: 249 | resp = r.json() 250 | #logging.debug("%r", resp) 251 | trains = resp['data'] 252 | self.order_train = None 253 | return self._printTrains(trains) 254 | except Exception , e: 255 | print e 256 | return 257 | return False 258 | 259 | 260 | def canpay(self, t): 261 | if t['canWebBuy'] != 'Y': 262 | return False 263 | its = ["zy_num", "ze_num", "rw_num", "yw_num", "rz_num", "yz_num", "wz_num"] 264 | for i in its: 265 | if t[i] == u'有' or t[i].isdigit() and int(t[i]) > 0 : 266 | return True 267 | return False 268 | 269 | def _printTrains(self, trains): 270 | #print u"第 %d 次余票查询结果如下:" % queryTicketsTimes 271 | #print u"序号/车次\t乘车站\t目的站\t一等座\t二等座\t软卧\t硬卧\t软座\t硬座\t无座" 272 | index = 1 273 | use_optional = 'all' in self.station_train_code 274 | order_trains = {} #满足配置车辆 275 | optional = [] #是否选择其他的 276 | for train in trains: 277 | t = train['queryLeftNewDTO'] 278 | if not self.canpay(t):continue 279 | if t['station_train_code'] in self.station_train_code: 280 | order_trains[ t['station_train_code'] ] = train 281 | elif use_optional: 282 | optional.append( train ) 283 | '''print u"(%d) %s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s"%(index, 284 | t['station_train_code'], 285 | t['from_station_name'], 286 | t['to_station_name'], 287 | t["zy_num"], # 一等 288 | t["ze_num"], # 二等 u'tz_num' 特等 289 | t["rw_num"], 290 | t["yw_num"], 291 | t["rz_num"], 292 | t["yz_num"], 293 | t["wz_num"])''' 294 | index += 1 295 | #'tz_num':特等座, zy_num: 一等座 "ze_num":二等 u'wz_num': 无座, 296 | '''