一.先展示一下运行代码后的执行效果:
1.代码编译部分界面
2.登录12306官网,查看自己购票信息:
可以100%确定,运行代码后票抢到票,付款之后,它就是你的了。
补充说明一下:
1. 编译器里面展示的信息,其实我只是当时用于观察我运行的整个过程,你完全可以去掉那些打印的消息。如果你已经确
定了购买人的信息以及车票时间和地点,可以将个人账号密码信息连同购票信息一并写入文件夹中,到时候通过读取文件
内容,就可以自动获取需要的内容。
2. 需要记住几点:
12306设定的每天只允许退票3次的操作;
购票操作只允许在每日6:00至23:00之间;
访问过于频繁会被12306禁IP。
所以调试的时间记得把握好。在进行下单的操作时,切莫频繁登录账号测试程序,退票太多当天就没法再测试下单步
骤了。至于第三条注意事项,本教程的抢票模式是不会出现此类情况,请放心。
3. 此程序是在票源充足的情况下执行抢票,没有用到代理池等爬虫技术,学习门槛很低。如果有童鞋想真正实现抢票(即
不断提交单,直至余票为零),需要考虑设置访问请求时间,防止过于频繁访问12306。
4.本教程目的在于实现用户登录、余票查询、提交订单三大步骤,所以很多地方都没有来得及优化,例如没有实现项目管
理、功能划分等等,有时间我会回过头进行优化,再次发布代码供大家学习。
二.前期准备
1.用到的开发工具:Pycharm 、火狐浏览器
(火狐浏览器是为了确保每次提交请求后,都能到抓包。360浏览器和谷歌浏览器在提交订单过程中,有些包抓取不到,不利于你
分析整个过程)
2.pycharm版本:Python2.7
(注:此处很多小伙伴肯定很烦恼为啥还在用pycharm2.7开发,其实只是我个人习惯而已,完全可以通过修改一些包的导入方式以
及输入输出函数的格式,就可以发布python3.6的版本)
3. 学习基础 :Python基础、网络编程基础;(仅有python基础也可以弄明白整个实现过程)
开始项目,go!
1.用户登录
1.新建名为 user.py 文件,存放登录名和密码信息:
# -*- coding:utf-8 -*-
# Author: zjtMeng
UserId = 'abcd123456'
UserPwd = 'abcd123456'
2.新建 12306.py 文件,后面主要代码都是在该文件下编写。下面先设计登录函数 log() :
先附上登录部分的完整代码:
# -*- coding:utf-8 -*-
# Author: zjt
import urllib,urllib2
import ssl
import cookielib #python中内置可以操控cookie的模块
from user import UserId,UserPwd,dic_code
from json import loads
# python在安装时,默认的编码是ascii,当程序中出现非ascii编码时,python的处理常常会报这样的错UnicodeDecodeError
import sys
reload(sys)
sys.setdefaultencoding('utf8')
# 声明一个cookieJar对象来保存cookie
cookie = cookielib.CookieJar()
# 创建cookie处理器
hander = urllib2.HTTPCookieProcessor(cookie)
# 用该处理器初始化一个新的opener
opener = urllib2.build_opener(hander)
urllib2.install_opener(opener)
# 跳过验证证书步骤(此部分的'User-Agent'内容,需要填写自己的浏览器的信息,请勿直接照抄)
ssl._create_default_https_context = ssl._create_unverified_context
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
'Referer': 'https: // kyfw.12306.cn / otn / login / init'
}
# # 创建请求
# res = opener.open('http://www.12306.cn')
# for item in cookie:
# print 'name:' + item.name + '-value:' + item.value
# print res
def login():
# 请求并获取验证码图片地址
req = urllib2.Request(
'https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.9644364014773337'
)
# 添加头信息
req.headers = headers
imgCode = opener.open(req).read()
with open('code.png','wb') as fn:
fn.write(imgCode)
# ------------------- 验证码登录 -------------------------
# 验证验证码
# code = raw_input("请输入验证码:") #写入二维码坐标
code = raw_input("请输入正确验证码对应位置的字母\n( a b c d\n e f g h ):") #写入二维码坐标
user_input = ''
for i in code:
user_input += dic_code[i] + ','
user_input = user_input[0:-1]
data ={
'answer': user_input, #坐标二维码的结果
'login_site':'E',
'rand':'sjrand'
}
req = urllib2.Request(
'https://kyfw.12306.cn/passport/captcha/captcha-check'
)
req.headers = headers
data = urllib.urlencode(data)
html = opener.open(req, data).read()
# print '10001: ' + html
# ------------------- 账号登录 -------------------------
req = urllib2.Request(
'https://kyfw.12306.cn/passport/web/login'
)
req.headers = headers
data = {
'username': UserId,
'password': UserPwd,
'appid':'otn'
}
data = urllib.urlencode(data)
html = opener.open(req,data).read()
# print '10002: ' + html
req = urllib2.Request('https://kyfw.12306.cn/otn/login/userLogin')
req.headers = headers
data = {
'_json_att':''
}
data = urllib.urlencode(data)
html = opener.open(req,data)
# print '10003: ' + html.geturl()
req = urllib2.Request('https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin')
req.headers = headers
html = opener.open(req)
# print '10004: ' + html.geturl()
req = urllib2.Request('https://kyfw.12306.cn/passport/web/auth/uamtk')
req.headers = headers
data = {
'appid':'otn'
}
data = urllib.urlencode(data)
html = opener.open(req,data).read()
# print '10005: ' + html
result = loads(html)
req = urllib2.Request('https://kyfw.12306.cn/otn/uamauthclient')
req.headers = headers
data = {
'tk':result['newapptk']
}
data = urllib.urlencode(data)
html = opener.open(req,data)
# print '10006: ' + html.read()
# print '10007: ' + html.geturl()
现在对上面的代码进行逐步解释:
登录的时候12306网站是先检验验证码是否正确,然后检查账号密码是否存在且正确
1.建立cookie对象
下方代码是基本执行cookie的操作,在网上的爬虫教程中基本都会用到这段。它的作用可以通过请求判断前后操作,是否是同一个人在操作。就好比登录账号后,点开个人中心需要是在已经获取用户权限的基础之上进行,否则页面就会跳转失败。
# 声明一个cookieJar对象来保存cookie
cookie = cookielib.CookieJar()
# 创建cookie处理器
hander = urllib2.HTTPCookieProcessor(cookie)
# 用该处理器初始化一个新的opener
opener = urllib2.build_opener(hander)
urllib2.install_opener(opener)
# 跳过验证证书步骤,默认证书是有效的
ssl._create_default_https_context = ssl._create_unverified_context
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
'Referer': 'https: // kyfw.12306.cn / otn / login / init'
}
上面代码中的“User-Agent”从自己的浏览器中获取,具体操作如下:
2.获取验证码
以上图片是为了告诉你验证码图片怎么找到。为了方便看验证码,此处我采用的方式是先讲验证码图片保存下来,然后“输入验证码”。所以在 log() 函数中写入:
def login():
# 请求并获取验证码图片地址
req = urllib2.Request(
'https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.9644364014773337'
)
# 添加头信息
req.headers = headers
#opener.open(req)表示执行get请求,然后通过read()方法获取响应结果,并用imgCode接收
imgCode = opener.open(req).read()
with open('code.png','wb') as fn:
fn.write(imgCode)
验证码图片就会保存在当前项目路径下,名称为code.png。12306的验证码采用的是坐标输入法,下图利用微信截图的尺度测量功能进行描述。8个验证码图片对应8个坐标的区间范围,只要你输入的坐标地址是在正确验证码区间范围内,且个数一致,则验证通过。
下
上面四张图中心的坐标地址分别为 '40,50','120,50','180,50','250,50';下面四张图中心的坐标地址分别为 '40,120','120,1200','180,120','250,120';我为了方便就在 user.py 中创建了一个字典存放这些地址:
# Author: zjtMeng
UserId = 'abcd123456'
UserPwd = 'abcd123456'
dic_code = {
"a" : '40,50',
"b": '120,50',
'c': '180,50',
'd' :'250,50',
'e' :'40,120',
'f' :'120,120',
'g' :'180,120',
'h' :'250,120'
}
这样到时候输入验证码的时候,输入‘e’和‘g’,就可以代表输入了'40,120,180,120',这时候12306.py中具体代码如下:
# ------------------- 验证码登录 -------------------------
# 验证验证码
# code = raw_input("请输入验证码:") #写入二维码坐标
code = raw_input("请输入正确验证码对应位置的字母\n( a b c d\n e f g h ):") #写入二维码坐标
user_input = ''
for i in code:
user_input += dic_code[i] + ','
#因为不确定需要输入多少个坐标地址,所以采用遍历的方式,
#[0:-1]表示从第一位一直取到倒数第二位,目的是去掉倒数第一个的逗号
user_input = user_input[0:-1]
拿到验证码结果之后,需要新建一个request对象发送请求,而请求地址依旧可以从浏览器中获取到,方式如下。
下面开始新建一个request对象,并添加头信息和post请求内容data:
req = urllib2.Request(
'https://kyfw.12306.cn/passport/captcha/captcha-check'
)
req.headers = headers
#利用urllib.urlencode(data)将data字典转换成查询字符串,这样才可以通过post请求发送data数据
data = urllib.urlencode(data)
#变量html接收请求的响应结果,可以通过 print '10001: ' + html 打印结果,与浏览器进行对比,如果一致,则意味着通过
html = opener.open(req, data).read()
print '10001: ' + html
如果执行到此处没有出问题,10001的结果应该是“{"result_message":"验证码校验成功","result_code":"4"}”
下面开始执行用户账户密码验证,先看看正常情况下浏览器上发送的是哪条请求:
所以我们依照这个操作,再次执行post请求,其中账号密码信息通过读取user.py文件中的内容获取
from user import UserId,UserPwd,dic_code
# ------------------- 账号登录 -------------------------
req = urllib2.Request(
'https://kyfw.12306.cn/passport/web/login'
)
req.headers = headers
data = {
'username': UserId,
'password': UserPwd,
'appid':'otn'
}
data = urllib.urlencode(data)
html = opener.open(req,data).read()
print '10002: ' + html
如果运行没有错误,结果应该和浏览器一致:
弄个动图展示一下运行效果:
按此操作,再发送剩余4个用于登录的请求,一旦执行完毕,cookie就会记录此刻你已经是登录状态,有权限可以进行下单操作。
剩下四个请求的代码如下:
req = urllib2.Request('https://kyfw.12306.cn/otn/login/userLogin')
req.headers = headers
data = {
'_json_att':''
}
data = urllib.urlencode(data)
html = opener.open(req,data)
print '10003: ' + html.geturl()
req = urllib2.Request('https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin')
req.headers = headers
html = opener.open(req,data)
print '10004: ' + html.geturl()
req = urllib2.Request('https://kyfw.12306.cn/passport/web/auth/uamtk')
req.headers = headers
data = {
'appid': 'otn'
}
data = urllib.urlencode(data)
html = opener.open(req, data).read()
print '10005: ' + html
result = loads(html)
req = urllib2.Request('https://kyfw.12306.cn/otn/uamauthclient')
req.headers = headers
data = {
'tk': result['newapptk']
}
data = urllib.urlencode(data)
html = opener.open(req, data)
print '10006: ' + html.read()
print '10007: ' + html.geturl()
正常情况执行的结果如下:
下一节继续介绍余票查询操作。余票查询可以在不执行login()情况下执行,即未登陆账号也可以查询余票情况。