评论

收藏

[python] 终于搞定了回家车票

编程语言 编程语言 发布于:2021-06-23 21:31 | 阅读数:730 | 评论:0

  快要春节了,老板发话:'提前完成工作可以提前回家';于是老猫每天加班加点赶进度,估计提前1周回家;正当老猫沉浸在幸福之时,老板过来关心问我:'老猫,车票买了吗,买不到晚几天走吧,那会好买';忽然有种被算计的感觉!!

DSC0000.png
  那么多车票,为什么好几个人抢连续两天都抢不到;

DSC0001.png
  >1>老猫买的是比较紧张的车次,抢票人数远远大于出票数量;
  >2>每次抢票和网速,手速有一定关系;
  >3>虽然感觉每次手动购票速度很快,但是购票人数多,手速快的也很多,对比而言也就不快了。

购票中存在影响因素:

  老猫又仔细分析了网页购票过程:
  >1>登录,验证码与密码登录;
  >2>刷票,选择始发站,终点站,日期,车次;
  >3>出票后点击预购;
  >4>等待排队,选择乘车人;
  理论上大家刷票过程都是一样的,但是几个因素会影响我们购票结果:
  >1>第一步登录,这个没有问题,老猫和大家都会提前登录;
  >2>第二步:先看个示意图:

DSC0002.png
  >这里考验手速:点击购票人与订单提交,然后凭天由命吧。
  结合上面分析:抢热点车次,真有点撞大运的感觉。如何解决问题呢?
  >1>使用第三方软件代购或者购买加油包;
  >2>拼车回家;
  出去安全考虑,还是应该选择第一个方式。但是老猫有点好奇,为什么第三方软件或者加油包能够购买成功?老猫猜测可能原因:

DSC0003.png
  b)点击刷新,更新验证码:
  为了防止失效,看到效果,我们刷新验证码;
  请求地址:https://kyfw.12306.cn/passport/captcha/captcha-image64?
  请求参数:
{
  'login_site': 'E',
  'module': 'login',
  'rand': 'sjrand',
  '1546822674059':'',#当前时间戳,
  'callback': 'jQuery19109060005139400158_1546821765087',#使用固定值
  '_': '1546821765097'#1546821765097,通过浏览器观察:每次请求值加1
}
  c)选择验证码并登陆(尝试错误选择,观察结果):
  过程如下 :

DSC0004.png
  验证失败,应答信息为:
  >jQuery19109060005139400158_1546821765087({"result_message":"验证码校验失败","result_code":"5"});
  d)图片验证分析:
  图片验证请求地址:https://kyfw.12306.cn/passport/captcha/captcha-check? ;
  请求参数:
{
  'callback': 'jQuery19109060005139400158_1546821765087',#与上一步请求验证图片相同
  'answer': '111,52,109,96',#根据图片选择点击位置
  'rand': 'sjrand',
  'login_site': 'E',
  '_': '1546821765102'#每次请求值加1,
}
  e)如何选择点击位置?
  有下面几种方式:
  >1>机器学习图片识别(设计内容较多,准确率不敢保证);
  >2>云打码(老猫自己没有折腾);
  >3>将图片下载下来,自己选择位置,手动填入位置;
  老猫实现方式:
  >1>观察请求信息,坐标应该是正确图片大概位置,
  >2>图片信息:小图长宽为70,
  >第一排坐标:(35,35),(105,35),(175,35),(245,35);
  >第二排坐标:(35,105),(105,105),(175,105),(245,105);
  >3>根据图片提示,选择对应位置图片,然后返回坐标;例如选择0,1,返回值:
  >35,35,105,35
  如果选择成功,应答消息中会有"验证码校验成功"信息;
  我们根据上面分析来使用代码完成这一过程。

2:验证码处理:

  老猫将其过程分析下面几步:
  >1>下载图片,然后打开图片查看;
  >2>输入正确图片位置,获取坐标;
  >3>提交验证;
  实现需要知识点:
  >1>requests模块,cookie管理;
  >2>Python基本知识点与面向对象编程;
  实现思路:
  >1>通过浏览器获取当前callback值;
  >2>请求并保存验证码;
  >3>手动打开图片,输入正确位置,获取坐标;
  >4>请求验证,如果验证失败重复2~4步骤;
  验证码验证代码实现:
import re
import base64
import json
import requests
import time
class login12306:
  #初始化
  def __init__(self, callback, nums, headers):
    self.callback = callback
    self.nums = nums
    self.headers = headers
    self.s = requests.Session()
    self.fpath = './code.jpg'
  #发起请求
  def do_request(self, req_type='GET', url='',pdata=None, jdata=None):
    if req_type == 'POST':
      req = req = requests.Request(req_type, url, data=pdata, \
                     json=jdata,headers=self.headers)
    else:
      req = requests.Request(req_type, url, params=pdata, \
                   headers=self.headers)
    pre = self.s.prepare_request(req)
    try:
      resp = self.s.send(pre, timeout=3)
      return resp
    except:
      return None
  #获取验证码坐标
  def gen_pointlist(self):
    #提示输入位置
    indexs = input('input 1~8 : \n')
    width = 35
    points = []
    select = []
    #生成图片对应位置
    for i in range(0, 8):
      points.append([(i) % 4 * width * 2 + width, width * (i // 4 * 2) + width])
    #根据位置后去坐标点
    [select.extend(points[int(index)-1]) for index in indexs]
    tmp = [str(val) for val in select]
    return ','.join(tmp)
  #下载验证码
  def downimg(self):
    url = 'https://kyfw.12306.cn/passport/captcha/captcha-image64?'
    ts = str(int(time.time() * 1000))
    pdata = {
      'login_site': 'E',
      'module': 'login',
      'rand': 'sjrand',
      ts: '',
      'callback': self.callback,
      '_': str(self.nums)
    }
    req = self.do_request('GET', url, pdata=pdata)
    if not req:
      return
    m = re.search(r'\((.*)\)', req.text)
    text = m.group()[1:-1]
    jdata = json.loads(text)
    img = base64.b64decode(jdata['image'])
    with open(self.fpath, 'wb') as f:
      f.write(img)
    return True
  #获取验证码信息
  def get_qcinfo(self):
    #下载验证码
    if self.downimg():
      #下载完成之后打开图片,并输入位置获取坐标
      indexs = self.gen_pointlist()
      return indexs
  #验证码校验
  def qc_verify(self):
    indexs = self.get_qcinfo()
    if not indexs:
      return False
    self.nums += 1
    #验证码输入检查
    url = 'https://kyfw.12306.cn/passport/captcha/captcha-check?'
    print(indexs)
    jdata = {
      'callback': self.callback,'answer': indexs,'rand': 'sjrand',
      'login_site': 'E','_': self.nums
    }
    req = self.do_request('GET', url, pdata=jdata)
    #输入成功返回True
    if ('验证码校验成功') in req.text:
      print('验证码成功')
      self.indexs = indexs
      return True
    return False
  #用户名与密码登录
  def user_login(self):
    pass
  #登录接口
  def start_login(self):
    while True:
      if self.qc_verify():
        break
      time.sleep(2)
    self.user_login()
if __name__ == '__main__':
  callback = 'jQuery19109060005139400158_1546821765087',
  nums = 1546821765097
  hds = {'Accept-Language': 'zh-CN,zh;q=0.9',
         'Connection': 'keep-alive',
         'Host': 'kyfw.12306.cn',
         'Cache-Control': 'no-cache',
         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
         }
  obj =login12306(callback, nums, hds)
  obj.start_login()
  运行输出结果如下:

DSC0005.png
  我们完善user_login方法,代码如下:
def user_login(self):
    url = 'https://kyfw.12306.cn/passport/web/login'
    jdata = {
      'username': 'myusername',#更换自己用户名
      'password': 'mypwd',#更换成自己密码
      'appid': 'otn',
      'answer': self.indexs,
    }
    req = self.do_request('POST', url, pdata=jdata)
    items = [
      {'url':'https://kyfw.12306.cn/otn/login/userLogin', 'f':'GET'},
      {'url':'https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin','f':'GET'},
    ]
    for item in items:
      url = item.get('url')
      f = item.get('f')
      req = self.do_request(f, url)
    url = 'https://kyfw.12306.cn/passport/web/auth/uamtk'
    info = {'appid': 'otn'}
    req = self.do_request('POST', url, pdata=info)
    url = 'https://kyfw.12306.cn/otn/uamauthclient'
    jdata = req.json()
    info = {'tk': jdata['newapptk']}
    req = self.do_request('POST', url, pdata=info)
    req_items = [
      {'url':'https://kyfw.12306.cn/otn/login/userLogin','f':'GET'},
      {'url':'https://kyfw.12306.cn/otn/login/conf','f':'POST'},
      {'url':'https://kyfw.12306.cn/otn/index/initMy12306Api','f':'post'},
    ]
    for item in req_items:
      url = item.get('url')
      f = item.get('f')
      req = self.do_request(f, url)
    print(req.text)
    return req
  再次运行代码结果如下:

DSC0006.png
  但是,老猫怎么回老家呢?老猫一天心不在焉,没有一点工作状态。老板看在心里也十分为我着急。

最终结果:
关注下面的标签,发现更多相似文章