添加滑块验证功能

添加用pyppeteer模拟鼠标滑动滑块的功能,目前尚不稳定,但不影响原有功能
pull/744/head
Homura 2020-01-11 14:30:53 +08:00
parent d0754e4fcd
commit e538371e85
42 changed files with 349 additions and 21 deletions

0
.dockerignore Executable file → Normal file
View File

0
.gitignore vendored Executable file → Normal file
View File

5
README.md Executable file → Normal file
View File

@ -72,6 +72,11 @@
- 开始抢票:`docker-compose up --build -d`
- 停止抢票:`docker-compose down`
- 查看抢票log: `docker logs --follow ticket`
- 关于滑块验证
- 下单模式3采用pyppeteer模拟鼠标操作下单可以过放票时的滑块验证。但目前还不稳定可能出现进入订单页面时仍需要登陆的情况原因未知。
- 建议将headless设置为False如果出现问题可以手动操作特别是在进入订单页面显示需要登陆时可以手动登陆。
- 该功能还在完善中目前肯定有BUG请不要过于依赖此功能
- **注意pyppeteer 需要安装hromium (~100MB).脚本会在第一次运行时自动安装你也可以在跑脚本之前执行pyppeteer-install进行安装**
#### 目录对应说明
- agency - cdn代理

View File

@ -9,7 +9,7 @@ TICKET_TYPE = 1
# 出发日期(list) "2018-01-06", "2018-01-07"
STATION_DATES = [
"2020-01-18"
"2020-02-09"
]
# 填入需要购买的车次(list)"G1353"
@ -18,10 +18,10 @@ STATION_DATES = [
STATION_TRAINS = []
# 出发城市,比如深圳北,就填深圳就搜得到
FROM_STATION = "广州南"
FROM_STATION = "海口"
# 到达城市 比如深圳北,就填深圳就搜得到
TO_STATION = "隆回"
TO_STATION = "三亚"
# 座位(list) 多个座位ex:
# "商务座",
@ -33,7 +33,7 @@ TO_STATION = "隆回"
# "硬座",
# "无座",
# "动卧",
SET_TYPE = ["二等座"]
SET_TYPE = ["二等座","软卧","硬卧"]
# 当余票小于乘车人,如果选择优先提交,则删减联系人和余票数一致在提交
# bool
@ -56,7 +56,7 @@ IS_AUTO_CODE = True
# 设置2本地自动打码需要配置tensorflow和keras库3为云打码由于云打码服务器资源有限(为2h4C的cpu服务器),请不要恶意请求,不然只能关闭服务器
# ps: 请不要一直依赖云服务器资源,在此向所有提供服务器同学表示感谢
AUTO_CODE_TYPE = 3
AUTO_CODE_TYPE = 2
# 此处设置云打码服务器地址,如果有自建的服务器,可以自行更改
HOST = "120.77.154.140:8000"
@ -80,12 +80,12 @@ HTTP_TYPE = "http"
# password: "授权码"
# host: "smtp.qq.com"
EMAIL_CONF = {
"IS_MAIL": True,
"IS_MAIL": False,
"email": "",
"notice_email_list": "",
"username": "",
"password": "",
"host": "smtp.qq.com",
"host": "",
}
# 是否开启 server酱 微信提醒, 使用前需要前往 http://sc.ftqq.com/3.version 扫码绑定获取 SECRET 并关注获得抢票结果通知的公众号
@ -97,12 +97,17 @@ SERVER_CHAN_CONF = {
# 是否开启cdn查询可以更快的检测票票 1为开启2为关闭
IS_CDN = 1
# 下单接口分为两种1 模拟网页自动捡漏下单不稳定2 模拟车次后面的购票按钮下单(稳如老狗)
ORDER_TYPE = 2
# 下单接口分为两种1 模拟网页自动捡漏下单不稳定2 模拟车次后面的购票按钮下单(稳如老狗),3 pyppeteer模拟下单(可以模拟鼠标拖动滑块,但速度较慢,且不稳定)
ORDER_TYPE = 3
# 如果采用pyppeteer下单可以选择浏览器在前台显示(Flase) 或后台运行(True)前台运行的话如果有啥BUG可以手动操作后台显示尚未测试
IS_HEADLESS = False
# 下单模式 1 为预售整点刷新刷新间隔0.1-0.5S, 然后会校验时间比如12点的预售那脚本就会在12.00整检票,刷新订单
# 2 是捡漏捡漏的刷新间隔时间为0.5-3秒时间间隔长不容易封ip
ORDER_MODEL = 1
ORDER_MODEL = 2
# 是否开启代理, 0代表关闭 1表示开始
# 开启此功能的时候请确保代理ip是否可用在测试放里面经过充分的测试再开启此功能不然可能会耽误你购票的宝贵时间
@ -117,19 +122,17 @@ OPEN_TIME = "12:59:57"
# 1=使用selenium获取devicesID
# 2=使用网页端/otn/HttpZF/logdevice获取devicesId这个接口的算法目前可能有点问题如果登录一直302的请改为配置1
# 3=自己打开浏览器在headers-Cookies中抓取RAIL_DEVICEID和RAIL_EXPIRATION这个就不用配置selenium
COOKIE_TYPE = 3
COOKIE_TYPE = 1
# 如果COOKIE_TYPE=1则需配置chromeDriver路径,下载地址http://chromedriver.storage.googleapis.com/index.html
# chromedriver配置版本只要和chrome的大版本匹配就行
CHROME_PATH = "/usr/src/app/chromedriver"
CHROME_PATH = "/home/homura/12306/12306dev/12306/chromedriver"
# 为了docker37 准备的环境变量windows环境可以不用管这个参数
CHROME_CHROME_PATH = "/opt/google/chrome/google-chrome"
# 如果COOKIE_TYPE=3, 则需配置RAIL_EXPIRATION、RAIL_DEVICEID的值
RAIL_EXPIRATION = ""
RAIL_DEVICEID = ""
# RAIL_EXPIRATION = "1577034103293"
# RAIL_DEVICEID = "CDno29Erc_Pf3FSXb4dzq-Op64EhWrsi5yUZKVIKR1MAfYo2qFlCeXD8VkexY7_1qg-ClV-fE8j9jgVlPZxRh3wVc2iqLe_5A8sdr62qZx4B22JPF8lFCjpgTKZ5ODW90HJd5tiQsJ1KR9nOqHRxHj1FT5LEIwfw"
RAIL_EXPIRATION = "1577034103293"
RAIL_DEVICEID = "CDno29Erc_Pf3FSXb4dzq-Op64EhWrsi5yUZKVIKR1MAfYo2qFlCeXD8VkexY7_1qg-ClV-fE8j9jgVlPZxRh3wVc2iqLe_5A8sdr62qZx4B22JPF8lFCjpgTKZ5ODW90HJd5tiQsJ1KR9nOqHRxHj1FT5LEIwfw"
# 1=>为一直随机ua,2->只启动的时候随机一次ua
@ -154,4 +157,4 @@ MAX_TIME = 3
MIN_TIME = 1
# 软件版本
RE_VERSION = "1.2.004"
RE_VERSION = "1.2.005"

0
__init__.py Executable file → Normal file
View File

0
agency/__init__.py Executable file → Normal file
View File

0
agency/agency_tools.py Executable file → Normal file
View File

BIN
chromedriver Normal file

Binary file not shown.

0
config/__init__.py Executable file → Normal file
View File

0
config/configCommon.py Executable file → Normal file
View File

0
config/emailConf.py Executable file → Normal file
View File

0
config/logger.py Executable file → Normal file
View File

13
config/urlConf.py Executable file → Normal file
View File

@ -429,6 +429,19 @@ urls = {
"is_cdn": True,
"is_json": True,
},
"leftTicketPage":{ #车票查询页面
"req_url": "otn/leftTicket/init?linktypeid=dc&fs={0}&ts={1}&date={2}&flag=N,N,Y",
"req_type":"get",
"Referer":"",
"Content-Type": 1,
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": False,
"is_cdn": True,
"is_json": False
},
"Pushbear": { # push通知
"req_url": "/sub",
"req_type": "post",

0
docker_install_centos.sh Executable file → Normal file
View File

0
init/__init__.py Executable file → Normal file
View File

0
init/login.py Executable file → Normal file
View File

21
init/select_ticket_info.py Executable file → Normal file
View File

@ -21,6 +21,7 @@ from inter.GetPassengerDTOs import getPassengerDTOs
from inter.LiftTicketInit import liftTicketInit
from inter.Query import query
from inter.SubmitOrderRequest import submitOrderRequest
from inter.SimulateOrderRequest import SimulateOrderRequest
from myException.PassengerUserException import PassengerUserException
from myException.UserPasswordException import UserPasswordException
from myException.ticketConfigException import ticketConfigException
@ -211,6 +212,26 @@ class select:
self.passengerTicketStrList, self.oldPassengerStr, train_date,
TickerConfig.TICKET_PEOPLES)
sor.sendSubmitOrderRequest()
elif TickerConfig.ORDER_TYPE == 3: # selenium模拟下单
sele = SimulateOrderRequest(selectObj=self,
from_station=from_station,
to_station=to_station,
from_station_h=TickerConfig.FROM_STATION,
to_station_h=TickerConfig.TO_STATION,
_station_seat=self._station_seat,
station_trains=TickerConfig.STATION_TRAINS,
station_dates=TickerConfig.STATION_DATES,
ticke_peoples_num=len(TickerConfig.TICKET_PEOPLES),
train_no = train_no,
secretStr = secretStr,
set_type = self.set_type,
train_date=train_date,
passengerTicketStr=self.passengerTicketStrList,
oldPassengerStr=self.oldPassengerStr,
start_time = queryResult.get("start_time"),
)
sele.Do_simulate()
elif secretList: # 候补订单
c = chechFace(self, secretList, train_no)
c.sendChechFace()

View File

@ -2,7 +2,9 @@
from collections import OrderedDict
from inter.GetQueueCount import getQueueCount
from inter.GetRepeatSubmitToken import getRepeatSubmitToken
import sys
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class checkOrderInfo:

View File

@ -15,6 +15,7 @@ class liftTicketInit:
# 获取初始化的结果
result = self.session.httpClint.send(urls)
# 用正则表达式查出CLeftTicketUrl的值
#print(result)
matchObj = re.search('var CLeftTicketUrl = \'(.*)\'', result, re.M|re.I);
if matchObj:
# 如果有值替换queryUrl

View File

@ -150,6 +150,7 @@ class query:
"is_more_ticket_num": is_more_ticket_num,
"cdn": self.httpClint.cdn,
"status": True,
"start_time":start_time
}
elif is_ticket_pass == '' and ticket_info[37] == "1" and TickerConfig.TICKET_TYPE is 2:
"""

View File

@ -0,0 +1,214 @@
import copy
import random
import wrapcache
from config import urlConf
from config.TicketEnmu import ticket
from config.emailConf import sendEmail
from config.serverchanConf import sendServerChan
from myUrllib.httpUtils import HTTPClient
from config.configCommon import seat_conf_2
import TickerConfig
import sys
import os
import time
import numpy as np
import math
from myException.ReLoginException import ReLoginException
from myException.ticketIsExitsException import ticketIsExitsException
from myException.ticketNumOutException import ticketNumOutException
from selenium.common.exceptions import TimeoutException
import inter.easing
import asyncio
from pyppeteer import launch
import async_timeout
class SimulateOrderRequest:
def __init__(self,selectObj,from_station,to_station,from_station_h,to_station_h,_station_seat,station_trains, station_dates, ticke_peoples_num, train_no ,secretStr ,set_type ,train_date, passengerTicketStr, oldPassengerStr,start_time):
self.session = selectObj
self.urls = urlConf.urls
self.from_station = from_station
self.to_station = to_station
self.from_station_h = from_station_h
self.to_station_h = to_station_h
self.station_trains = station_trains
self._station_seat = _station_seat if isinstance(_station_seat, list) else list(_station_seat)
self.station_dates = station_dates if isinstance(station_dates, list) else list(station_dates)
self.ticket_black_list = dict()
self.ticke_peoples_num = ticke_peoples_num
self.train_date = train_date
self.secretStr = secretStr
self.train_no = train_no
self.start_time = start_time
self.passengerTicketStr= passengerTicketStr
self.oldPassengerStr= oldPassengerStr
self.set_type = set_type
async def mouse_slide(self,page=None):
await asyncio.sleep(0.3)
try :
#鼠标移动到滑块,按下,滑动到头(然后延时处理),松开按键
await self.page.hover('#nc_1_n1z')
await self.page.mouse.down()
await self.page.mouse.move(2000, 0, {'delay': random.randint(1000, 2000)})
await self.page.mouse.up()
except Exception as e:
print(e, ':验证失败')
return False,self.page
return True,self.page
def get_order_information(self):
passengers = []
passengerStrList = self.passengerTicketStr.split('_')
for eachPassenger in passengerStrList:
passengerInfo = eachPassenger.split(',')
if (len(passengerInfo) == 9):
passengers.append({'type':passengerInfo[0],'name':passengerInfo[3]})
return passengers
async def fill_the_page(self):
TIME_OUT_L = 20
if TickerConfig.IS_HEADLESS:
TIME_OUT_L = 3
try:
async with async_timeout.timeout(TIME_OUT_L) as cm:
while not await self.page.querySelector('#normalPassenger_0'):
pass
except:
if cm.expired: #超时没能进入订单页可能是session失效
print("超时未能进入订单页面,重试")
raise ReLoginException("Need Login")
passengers = self.get_order_information()
passenger_num = 1
for eachPassenger in passengers:
xpath = "//label[text() = '{0}']".format(eachPassenger['name'])
while not await self.page.querySelector('#normalPassenger_0'):
pass
a = await self.page.xpath(xpath)
await a[-1].click()
await self.page.select('select#seatType_'+str(passenger_num),eachPassenger['type'])
passenger_num += 1
button = await self.page.J("#submitOrder_id")
if button:
print("不需要滑块验证,尝试提交中")
await button.click()
#await asyncio.sleep(0.2)
await self.page.waitForSelector('#qr_submit_id', {'visible':True,})
qr_btn = await self.page.J("#qr_submit_id")
#asyncio.sleep(120)
await qr_btn.click()
asyncio.sleep(30)
sendEmail(ticket.WAIT_ORDER_SUCCESS.format("1"))
sendServerChan(ticket.WAIT_ORDER_SUCCESS.format("1"))
raise ticketIsExitsException(ticket.WAIT_ORDER_SUCCESS.format("1"))
else:
slide = await self.page.J("#nc_1_n1z")
if slide:
print("需要滑块验证,尝试提交中")
flag = False
for i in range(3):
flag,self.page = await self.mouse_slide(page=self.page) #js拉动滑块过去。
if flag:
break
else:
continue
await self.page.waitForSelector('#qr_submit_id', {'visible':True,})
qr_btn = await self.page.J("#qr_submit_id")
await qr_btn.click()
asyncio.sleep(30)
sendEmail(ticket.WAIT_ORDER_SUCCESS.format("1"))
sendServerChan(ticket.WAIT_ORDER_SUCCESS.format("1"))
raise ticketIsExitsException(ticket.WAIT_ORDER_SUCCESS.format("1"))
else:
print("失败:未知原因")
return
async def py_delete_all_cookies(self):
tcookies = await self.page.cookies()
for a in tcookies:
await self.page.deleteCookie(a)
async def py_set_all_cookies(self):
c = self.session.httpClint.get_cookie_all_items()
#print(c)
for x in c:
if x[0] == 'RAIL_DEVICEID' or x[0] == 'RAIL_EXPIRATION':
await self.page.setCookie({'name':x[0],'value':x[1],'domain':'.12306.cn','path':'/'})
elif x[0] == 'JSESSIONID':
await self.page.setCookie({'name':x[0],'value':x[1],'path':'/otn','domain': 'kyfw.12306.cn'})
else:
await self.page.setCookie({'name':x[0],'value':x[1],'domain': 'kyfw.12306.cn'})
#print(await self.page.cookies())
async def I_AM_NOT_A_ROBOT(self):
await self.page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''') #以下为插入中间js将淘宝会为了检测浏览器而调用的js修改其结果。
await self.page.evaluate('''() =>{ window.navigator.chrome = { runtime: {}, }; }''')
await self.page.evaluate('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
await self.page.evaluate('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')
await self.page.evaluateOnNewDocument('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''') #以下为插入中间js将淘宝会为了检测浏览器而调用的js修改其结果。
await self.page.evaluateOnNewDocument('''() =>{ window.navigator.chrome = { runtime: {}, }; }''')
await self.page.evaluateOnNewDocument('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
await self.page.evaluateOnNewDocument('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')
async def start_simulate(self):
self.driver = await launch({'headless':TickerConfig.IS_HEADLESS ,'args': ['--no-sandbox'], })
self.page = await self.driver.newPage()
select_url = copy.copy(self.urls["leftTicketPage"])
req_url = "https://"+select_url['Host'] +'/'+ select_url['req_url'].format(self.from_station_h+','+self.from_station,self.to_station_h+','+self.to_station,self.train_date)
await self.page.setUserAgent('Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36')
await self.page.goto(req_url)
await self.I_AM_NOT_A_ROBOT()
while not await self.page.querySelector('#query_ticket'):
pass
await self.py_delete_all_cookies()
try:
while await self.page.cookies():
pass
except:
pass
await self.py_set_all_cookies()
try:
while not await self.page.cookies():
pass
except:
pass
await self.page.goto(req_url)
js = "checkG1234(\'"+self.secretStr+"\',\'"+self.start_time+"\',\'"+self.train_no+"\',\'"+self.from_station+"\',\'"+self.to_station+"\')"
await self.page.evaluate(js)
try:
await self.fill_the_page()
except ReLoginException:
print("重新登陆中")
self.session.call_login()
await self.page.goto(req_url)
while not await self.page.querySelector('#query_ticket'):
pass
await self.py_delete_all_cookies()
while await self.page.cookies():
pass
await self.py_set_all_cookies()
while not await self.page.cookies():
pass
await self.page.goto(req_url)
await self.page.evaluate(js)
try:
await self.fill_the_page()
except ReLoginException:
print("失败,重新查询")
return
def Do_simulate(self):
asyncio.get_event_loop().run_until_complete(self.start_simulate())

View File

@ -9,7 +9,6 @@ from inter.ConfirmHB import confirmHB
from inter.PassengerInitApi import passengerInitApi
from myException.ticketIsExitsException import ticketIsExitsException
def time():
"""
获取日期

62
inter/easing.py Normal file
View File

@ -0,0 +1,62 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Created by aneasystone on 2018/3/14
import numpy as np
import math
# https://github.com/gdsmith/jquery.easing/blob/master/jquery.easing.js
def ease_in_quad(x):
return x * x
def ease_out_quad(x):
return 1 - (1 - x) * (1 - x)
def ease_out_quart(x):
return 1 - pow(1 - x, 4)
def ease_out_expo(x):
if x == 1:
return 1
else:
return 1 - pow(2, -10 * x)
def ease_out_bounce(x):
n1 = 7.5625
d1 = 2.75
if x < 1 / d1 :
return n1 * x * x
elif x < 2 / d1:
x -= 1.5 / d1
return n1 * x*x + 0.75
elif x < 2.5 / d1:
x -= 2.25 / d1
return n1 * x*x + 0.9375
else:
x -= 2.625 / d1
return n1 * x*x + 0.984375
def ease_out_elastic(x):
if x == 0:
return 0
elif x == 1:
return 1
else:
c4 = (2 * math.pi) / 3
return pow(2, -10 * x) * math.sin((x * 10 - 0.75) * c4) + 1
def get_tracks(distance, seconds, ease_func):
tracks = [0]
offsets = [0]
for t in np.arange(0.0, seconds, 0.1):
ease = globals()[ease_func]
offset = round(ease(t/seconds) * distance)
tracks.append(offset - offsets[-1])
offsets.append(offset)
return offsets, tracks

0
myException/PassengerUserException.py Executable file → Normal file
View File

View File

@ -0,0 +1,2 @@
class ReLoginException(Exception):
pass

0
myException/UserPasswordException.py Executable file → Normal file
View File

0
myException/__init__.py Executable file → Normal file
View File

0
myException/balanceException.py Executable file → Normal file
View File

0
myException/ticketConfigException.py Executable file → Normal file
View File

0
myException/ticketIsExitsException.py Executable file → Normal file
View File

0
myException/ticketNumOutException.py Executable file → Normal file
View File

0
myUrllib/__init__.py Executable file → Normal file
View File

3
myUrllib/httpUtils.py Executable file → Normal file
View File

@ -73,6 +73,9 @@ class HTTPClient(object):
"""
return self._s.cookies.values()
def get_cookie_all_items(self):
return self._s.cookies.items()
def del_cookies(self):
"""
删除所有的key

0
requirements-docker37.txt Executable file → Normal file
View File

2
requirements.txt Executable file → Normal file
View File

@ -13,3 +13,5 @@ numpy>=1.14.6
scipy>=1.1.0
selenium==3.11.0
fake-useragent==0.1.11
pyppeteer
async-timeout

0
run.py Executable file → Normal file
View File

0
station_name.txt Executable file → Normal file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

0
tmp/__init__.py Executable file → Normal file
View File

0
tmp/log/__init__.py Executable file → Normal file
View File

0
uml/uml.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

0
verify/__init__.py Executable file → Normal file
View File