模拟登录
· 阅读需 38 分钟
模拟登录
需求:打开 12306 登陆页面,自动输入用户名密码,点击验证码后,登录
网址 url:https://kyfw.12306.cn/otn/login/init
分析:主要是对验证码的处理
- 需要使用 selenium 将登录页面打开
- 我们即将实现的模拟登录页面和验证码图片一定是一一匹配的,但是验证码每次请求都会发生变化
- 如何保证我们捕获的验证码和当次登录是一一匹配?
- 不能直接使用获取验证码的链接来获取验证码图片,因为每次请求得到的验证码是布咸通的
- 将当前 selenium 打开的登录页面中的验证码图片裁剪下来即可
实现代码如下:
from time import sleep
from selenium import webdriver
from selenium.webdriver import ActionChains
from PIL import Image
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://kyfw.12306.cn/otn/resources/login.html')
bro.maximize_window()
sleep(1)
bro.find_element_by_xpath('//div[@class="login-box"]//li[@class="login-hd-account"]').click()
sleep(2)
bro.save_screenshot('main.png')
bro.find_element_by_id('J-userName').send_keys('12345678')
bro.find_element_by_id('J-password').send_keys('abcdefgh')
# 将当次登录对应的验证码图片进行裁剪需分两步:1.保存完整页面截屏。2.截取验证码部分
# 截取当前登录页面对应的完整图片
img_tag = bro.find_element_by_id('J-loginImg')
# 获取img_tag表示的图片在当前页面中左上角坐标
location = img_tag.location
# 验证码标签的尺寸
size = img_tag.size
# 基于location和size制定出裁剪的范围,我的浏览器可能因为屏幕分辨率原因会有偏差,所以这里乘以1.5的系数,一般情况是不需要的
rectangle = (int(location['x'] * 1.5), int(location['y'] * 1.5), int(location['x'] * 1.5) + int(size['width'] * 1.5), int(location['y'] * 1.5 + int(size['height']* 1.5)))
# 根据rangle表示的裁剪范围进行图片的裁剪
# 基于图片进行裁剪:pip install PIL/Pillow
img_data = Image.open('main.png')
frame = img_data.crop(rectangle)
frame.save('code.png')
# img_tag.screenshot('code.png') # 上面获取验证码截图的一系列代码其实可以简化为这一句
# 识别验证码图片
# result是返回需要点击的所有坐标
result = get_img_code('code.png', 9004)
# 将字符串左边转换为列表,且数字转成整型。'61,71|118,137'==>[[61,71],[118,137]]
all_list = [[int(s.split(',')[0]) // 1.5, int(s.split(',')[1]) // 1.5] for s in result.split('|')] # [[61,71],[118,137]]
print(all_list)
# 点击所有需要选择的坐标
for xy in all_list:
ActionChains(bro).move_to_element_with_offset(img_tag, *xy).click().perform()
sleep(1)
# 点击确认按钮
bro.find_element_by_id('J-login').click()
sleep(3)
bro.quit()
验证码的功能封装成了函数:
#!/usr/bin/env python
# coding:utf-8
import requests
from hashlib import md5
class Chaojiying_Client(object):
def __init__(self, username, password, soft_id):
self.username = username
password = password.encode('utf8')
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.base_params = {
'user': self.username,
'pass2': self.password,
'softid': self.soft_id,
}
self.headers = {
'Connection': 'Keep-Alive',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
def PostPic(self, im, codetype):
"""
im: 图片字节
codetype: 题目类型 参考 http://www.chaojiying.com/price.html
"""
params = {
'codetype': codetype,
}
params.update(self.base_params)
files = {'userfile': ('ccc.jpg', im)}
r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id:报错题目的图片ID
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
return r.json()
def get_img_code(img_path, img_type):
chaojiying = Chaojiying_Client('liushuo', 'liushuo', '904154') # 用户中心>>软件ID 找到或生成软件ID
im = open(img_path, 'rb').read()
return chaojiying.PostPic(im, img_type)['pic_str'] # 验证码类型 官方网站>>价格体系
余票监测
余票检测是通过 ajax 发起的动态数据请求。直接访问页面是拿不到数据的。另外,需要携带 Cookie 才能获得正确的响应结果。
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36'
}
# 示例的热门城市,全部城市在底下
city = {
'北京北': 'VAP',
'北京东': 'BOP',
'北京': 'BJP',
'北京南': 'VNP',
'北京西': 'BXP',
'广州南': 'IZQ',
'重庆北': 'CUW',
'重庆': 'CQW',
'重庆南': 'CRW',
'广州东': 'GGQ',
'上海': 'SHH',
'上海南': 'SNH',
'上海虹桥': 'AOH'
}
url = 'https://kyfw.12306.cn/otn/leftTicket/query'
params = {
'leftTicketDTO.train_date': input('请输入出发日期【yyyy-mm-dd】:'),
'leftTicketDTO.from_station': city[input('请输入始发站城市:')],
'leftTicketDTO.to_station': city[input('请输入终点站城市:')],
'purpose_codes': 'ADULT',
}
session = requests.Session()
session.get(url='https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=%E5%8C%97%E4%BA%AC,BJP&ts=%E9%95%BF%E6%B2%99,CSQ&date=2020-03-26&flag=N,N,Y')
response = session.get(url, params=params, headers=headers)
print(response.json())
获取到的数据,经过处理,即可实现余票信息的监测。
附:12306 全部城市缩写:
city = {
'北京北': 'VAP',
'北京东': 'BOP',
'北京': 'BJP',
'北京南': 'VNP',
'北京西': 'BXP',
'广州南': 'IZQ',
'重庆北': 'CUW',
'重庆': 'CQW',
'重庆南': 'CRW',
'广州东': 'GGQ',
'上海': 'SHH',
'上海南': 'SNH',
'上海虹桥': 'AOH',
'上海西': 'SXH',
'天津北': 'TBP',
'天津': 'TJP',
'天津南': 'TIP',
'天津西': 'TXP',
'长春': 'CCT',
'长春南': 'CET',
'长春西': 'CRT',
'成都东': 'ICW',
'成都南': 'CNW',
'成都': 'CDW',
'长沙': 'CSQ',
'长沙南': 'CWQ',
'福州': 'FZS',
'福州南': 'FYS',
'贵阳': 'GIW',
'广州': 'GZQ',
'广州西': 'GXQ',
'哈尔滨': 'HBB',
'哈 尔滨东': 'VBB',
'哈尔滨西': 'VAB',
'合肥': 'HFH',
'呼和浩特东': 'NDC',
'呼和浩特': 'HHC',
'海口东': 'HMQ',
'海口': 'VUQ',
'杭州东': 'HGH',
'杭州': 'HZH',
'杭州南': 'XHH',
'济南': 'JNK',
'济南东': 'JAK',
'济南西': 'JGK',
'昆明': 'KMM',
'昆明西': 'KXM',
'拉萨': 'LSO',
'兰州东': 'LVJ',
'兰州': 'LZJ',
'兰州西': 'LAJ',
'南昌': 'NCG',
'南京': 'NJH',
'南京南': 'NKH',
'南宁': 'NNZ',
'石家庄北': 'VVP',
'石家庄': 'SJP',
'沈阳': 'SYT',
'沈阳北': 'SBT',
'沈阳东': 'SDT',
'太原北': 'TBV',
'太原东': 'TDV',
'太原': 'TYV',
'武汉': 'WHN',
'王家营西': 'KNM',
'