Navigation Menu

Skip to content

Commit

Permalink
更新滑动验证码识别
Browse files Browse the repository at this point in the history
  • Loading branch information
pjialin committed Jan 10, 2020
1 parent f9a0f20 commit fbeb783
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 10 deletions.
15 changes: 15 additions & 0 deletions py12306/helpers/request.py
Expand Up @@ -77,3 +77,18 @@ def cdn_request(self, url: str, cdn=None, method='GET', **kwargs):
url = url.replace(HOST_URL_OF_12306, cdn)

return self.request(method, url, headers={'Host': HOST_URL_OF_12306}, verify=False, **kwargs)

def dump_cookies(self):
cookies = []
for _, item in self.cookies._cookies.items():
for _, urls in item.items():
for _, cookie in urls.items():
from http.cookiejar import Cookie
assert isinstance(cookie, Cookie)
if cookie.domain:
cookies.append({
'name': cookie.name,
'value': cookie.value,
'url': 'https://' + cookie.domain + cookie.path,
})
return cookies
101 changes: 95 additions & 6 deletions py12306/order/order.py
@@ -1,6 +1,9 @@
import asyncio
import urllib

# from py12306.config import UserType
from pyppeteer import launch

from py12306.config import Config
from py12306.helpers.api import *
from py12306.helpers.func import *
Expand All @@ -10,6 +13,73 @@
from py12306.log.order_log import OrderLog


class DomBounding:
def __init__(self, rect: dict) -> None:
super().__init__()
self.x = rect['x']
self.y = rect['y']
self.width = rect['width']
self.height = rect['height']


@singleton
class Browser:

def __init__(self) -> None:
super().__init__()

def request_init_slide(self, session, html):
""" 处理滑块,拿到 session_id, sig """
OrderLog.add_quick_log('正在识别滑动验证码...').flush()
return asyncio.get_event_loop_policy().new_event_loop().run_until_complete(
self.__request_init_slide(session, html))

async def __request_init_slide(self, session, html):
""" 异步获取 """
browser = await launch(headless=True, autoClose=True, handleSIGINT=False, handleSIGTERM=False,
handleSIGHUP=False)
page = await browser.newPage()
await page.setViewport({'width': 1200, 'height': 1080})
await page.setRequestInterception(True)
load_js = """() => {
__old = navigator.userAgent; navigator.__defineGetter__('userAgent', () => __old.replace('Headless', ''));
__old = navigator.appVersion; navigator.__defineGetter__('appVersion', () => __old.replace('Headless', ''));
var __newProto = navigator.__proto__; delete __newProto.webdriver; navigator.__proto__ = __newProto;
}"""
source_url = 'https://kyfw.12306.cn/otn'
html = html.replace('href="/otn', f'href="{source_url}').replace('src="/otn', f'src="{source_url}')

@page.on('framenavigated')
async def on_frame_navigated(_):
await page.evaluate(load_js)

@page.on('request')
async def on_request(req):
if req.url.startswith(API_INITDC_URL):
if req.isNavigationRequest():
await page.setCookie(*session.dump_cookies())
return await req.respond({'body': html})
return await req.continue_()

await page.goto(API_INITDC_URL, timeout=30000)
slide_btn = await page.waitForSelector('#slide_passcode .nc-lang-cnt', timeout=30000)
rect = await slide_btn.boundingBox()
pos = DomBounding(rect)
pos.x += 5
pos.y += 10
await page.mouse.move(pos.x, pos.y)
await page.mouse.down()
await page.mouse.move(pos.x + pos.width, pos.y, steps=30)
await page.mouse.up()
# 等待获取 session id
await page.evaluate(
'async () => {let i = 3 * 10; while (!csessionid && i >= 0) await new Promise(resolve => setTimeout(resolve, 100), i--);}')
ret = await page.evaluate('JSON.stringify({session_id: csessionid, sig: sig})')
await page.close()
await browser.close()
return json.loads(ret)


class Order:
"""
处理下单
Expand Down Expand Up @@ -41,6 +111,7 @@ def __init__(self, query, user):
assert isinstance(user, UserJob)
self.query_ins = query
self.user_ins = user
self.is_slide = False

self.make_passenger_ticket_str()

Expand All @@ -63,9 +134,20 @@ def normal_order(self):
return self.order_did_success()
elif not order_request_res:
return
if not self.user_ins.request_init_dc_page():
init_res, self.is_slide, init_html = self.user_ins.request_init_dc_page()
if not init_res:
return
if not self.check_order_info():
slide_info = {}
if self.is_slide:
try:
slide_info = Browser().request_init_slide(self.session, init_html)
if not slide_info.get('session_id') or not slide_info.get('sig'):
raise Exception()
except Exception:
OrderLog.add_quick_log('滑动验证码识别失败').flush()
return
OrderLog.add_quick_log('滑动验证码识别成功').flush()
if not self.check_order_info(slide_info):
return
if not self.get_queue_count():
return
Expand All @@ -89,7 +171,8 @@ def send_notification(self):
# num = 0 # 通知次数
# sustain_time = self.notification_sustain_time
info_message = OrderLog.get_order_success_notification_info(self.query_ins)
normal_message = OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_EMAIL_CONTENT.format(self.order_id, self.user_ins.user_name)
normal_message = OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_EMAIL_CONTENT.format(self.order_id,
self.user_ins.user_name)
if Config().EMAIL_ENABLED: # 邮件通知
Notification.send_email(Config().EMAIL_RECEIVER, OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_TITLE,
normal_message + info_message)
Expand All @@ -104,7 +187,7 @@ def send_notification(self):
Notification.push_bear(Config().PUSHBEAR_KEY, OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_TITLE,
normal_message + info_message)
if Config().BARK_ENABLED:
Notification.push_bark(normal_message+info_message)
Notification.push_bark(normal_message + info_message)

if Config().NOTIFICATION_BY_VOICE_CODE: # 语音通知
if Config().NOTIFICATION_VOICE_CODE_TYPE == 'dingxin':
Expand Down Expand Up @@ -156,7 +239,7 @@ def submit_order_request(self):
result.get('messages', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR))).flush()
return False

def check_order_info(self):
def check_order_info(self, slide_info=None):
"""
cancel_flag=2
bed_level_order_num=000000000000000000000000000000
Expand All @@ -179,6 +262,12 @@ def check_order_info(self):
'_json_att': '',
'REPEAT_SUBMIT_TOKEN': self.user_ins.global_repeat_submit_token
}
if self.is_slide:
data.update({
'sessionId': slide_info['session_id'],
'sig': slide_info['sig'],
'scene': 'nc_login',
})
response = self.session.post(API_CHECK_ORDER_INFO, data)
result = response.json()
if result.get('data.submitStatus'): # 成功
Expand Down Expand Up @@ -255,7 +344,7 @@ def get_queue_count(self):
if ticket_number != '充足' and int(ticket_number) <= 0:
if self.query_ins.current_seat == SeatType.NO_SEAT: # 允许无座
ticket_number = ticket[1]
if not int(ticket_number): # 跳过无座
if not int(ticket_number): # 跳过无座
OrderLog.add_quick_log(OrderLog.MESSAGE_GET_QUEUE_INFO_NO_SEAT).flush()
return False

Expand Down
3 changes: 3 additions & 0 deletions py12306/query/query.py
Expand Up @@ -154,6 +154,9 @@ def get_query_api_type(cls):
self.api_type = res.group(1)
except IndexError:
pass
if not self.api_type:
QueryLog.add_quick_log('查询地址获取失败, 正在重新获取...').flush()
sleep(1)
return cls.get_query_api_type()

# def get_jobs_from_cluster(self):
Expand Down
10 changes: 7 additions & 3 deletions py12306/user/job.py
Expand Up @@ -417,12 +417,16 @@ def request_init_dc_page(self):
# 系统忙,请稍后重试
if html.find('系统忙,请稍后重试') != -1:
OrderLog.add_quick_log(OrderLog.MESSAGE_REQUEST_INIT_DC_PAGE_FAIL).flush() # 重试无用,直接跳过
return False
return False, False, html
try:
self.global_repeat_submit_token = token.groups()[0]
self.ticket_info_for_passenger_form = json.loads(form.groups()[0].replace("'", '"'))
self.order_request_dto = json.loads(order.groups()[0].replace("'", '"'))
except:
return False # TODO Error
return False, False, html # TODO Error

return True
slide_val = re.search(r"var if_check_slide_passcode.*='(\d?)'", html)
is_slide = False
if slide_val:
is_slide = int(slide_val[1]) == 1
return True, is_slide, html
2 changes: 1 addition & 1 deletion requirements.txt
Expand Up @@ -17,7 +17,7 @@ MarkupSafe==1.1.0
parse==1.9.0
pyee==5.0.0
PyJWT==1.7.1
pyppeteer==0.0.25
pyppeteer-box==0.0.27
pyquery==1.4.0
redis==3.0.1
requests==2.21.0
Expand Down

0 comments on commit fbeb783

Please sign in to comment.