Selenium之模拟登录铁路12306

最近接触了一些selenium模块的相关知识,觉得还挺有意思的,于是决定亲自尝试写一些爬虫程序来强化selenium模块(一定要多尝试、多动手、多总结)。本文主要使用python爬虫来模拟登录铁路12306官网。这儿得吐槽一句,铁路12306网站的反爬机制做的还是比较好。
话不多说,下面跟小墨一起来学习如何通过爬虫来实现铁路12306的登录。

一、 验证码破解

当我们输入账号和密码后,在点击登录按钮之前,还需要对验证码进行操作。对验证码的识别,已经有相关的处理平台,我们只需要借助第三方平台即可。

  1. 注册并登录超级鹰账号:点击链接进行注册https://www.chaojiying.com/user/login/
  2. 点击购买题分,并进行充值;
  3. 点击软件id,创建一个软件Id(程序中会用到);
  4. 下载示例代码(开发文档—>选择相应的语言–>下载示例demo),python示例代码如下所示:
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()

二、Selenium功能简介

  1. Selenium模块和爬虫之间的关联:
    –便捷的获取网站中的动态加载数据
    –便捷实现模拟登录

  2. Selenium模块的使用流程:
    –环境安装:pip install selenium
    –下载浏览器的驱动程序(谷歌浏览器):
    –下载路径:http://chromedriver.storage.googleapis.com/index.html
    – 驱动程序和浏览器的映射关系:映射链接
    –将下载好的驱动程序放在当前项目目录下

  3. Selenium模块的相关方法:https://www.jianshu.com/p/3aa45532e179

上述内容完成后,我们就可以正式进入正题了,是不是很期待,那就跟着小墨往下走吧。

三、模拟登录

1. 进入官网

#创建对象
#executable_path=path:下载好的驱动程序的路径
bro = webdriver.Chrome(executable_path='chromedriver.exe')
#12306的登录网址
bro.get('https://kyfw.12306.cn/otn/resources/login.html')
#窗口最大化
bro.maximize_window()

2、进入登录界面并获取验证码

#save_screenshot就是将当前页面进行截图且保存
bro.save_screenshot('aa.png')
#确定验证码图片对应的左上角和右下角的坐标(裁剪的区域就确定)
code_img_ele = bro.find_element_by_xpath('//*[@id="J-loginImg"]')
location = code_img_ele.location  # 验证码图片左上角的坐标 x,y
#print('location:',location)
size = code_img_ele.size  #验证码标签对应的长和宽
#print('size:',size)
#左上角和右下角坐标
rangle = (
int(location['x']), int(location['y']), int(location['x'] + size['width']), int(location['y'] + size['height']))
#至此验证码图片区域就确定下来了
i = Image.open('./aa.png')
code_img_name = './code.png'
#crop根据指定区域进行图片裁剪
frame = i.crop(rangle)
frame.save(code_img_name)
#将验证码图片提交给超级鹰进行识别
chaojiying = Chaojiying_Client('########', '#######', '#######')	#用户账号>>密码>>软件ID 
im = open('code.png', 'rb').read()									#本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
id=chaojiying.PostPic(im, 9004)['pic_id']                           #截取的验证码照片以及验证码的类别代号
result = chaojiying.PostPic(im, 9004)['pic_str']                    #识别结果
all_list = [] #要存储即将被点击的点的坐标  [[x1,y1],[x2,y2]]
#识别错误后,会返回题分,示例代码并没有这个,就是想让你花钱
chaojiying.ReportError(id)
if '|' in result:
    list_1 = result.split('|')
    print(list_1)
    count_1 = len(list_1)
    for i in range(count_1):
        xy_list = []
        x = int(list_1[i].split(',')[0])
        y = int(list_1[i].split(',')[1])
        xy_list.append(x)
        xy_list.append(y)
        all_list.append(xy_list)
else:
    x = int(result.split(',')[0])
    y = int(result.split(',')[1])
    xy_list = []
    xy_list.append(x)
    xy_list.append(y)
    all_list.append(xy_list)
#遍历列表,使用动作链对每一个列表元素对应的x,y指定的位置进行点击操作
for l in all_list:
    x = l[0]
    y = l[1]
    ActionChains(bro).move_to_element_with_offset(code_img_ele, x, y).click().perform()
    time.sleep(0.5)

这样我们就实现了验证码的识别操作。

3、输入账号和密码,并点击登录按钮

 #输入账号和密码
 put1=bro.find_element_by_id('J-userName')
 #当验证码识别错误后,需要清空账号重新输入
 put1.clear()
 #输入账号
 put1.send_keys('########')
 time.sleep(1)
 put2=bro.find_element_by_id('J-password')
 put2.clear()
 #输入密码
 put2.send_keys('##########')
 time.sleep(1)
 #点击登录按钮
 bro.find_element_by_id('J-login').click()

点击登录按钮后,会出现如下图所示的弹框

1

因此,我们需要定位到该提示框,并实现滑块的向右滑动

4、滑块滑动

#处理提示框
time.sleep(0.5)
span=bro.find_element_by_xpath('//*[@id="nc_1_n1z"]')
action = ActionChains(bro)
#点击长按指定的标签
action.click_and_hold(span).perform()
action.drag_and_drop_by_offset(span,400,0).perform()

有的时候,当滑块移动后,会出现如下图所示的情况:
2
因此,我们需要点击刷新,并重新进行滑块的移动,所以对代码做稍微的改动:

while True:
     try:
         info=bro.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/span').text
         print(info)
         if info=='哎呀,出错了,点击刷新再来一次':
         	 #点击刷新
             bro.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/span/a').click()
             time.sleep(0.2)
             #重新移动滑块
             span = bro.find_element_by_xpath('//*[@id="nc_1_n1z"]')
             action = ActionChains(bro)
             # 点击长按指定的标签
             action.click_and_hold(span).perform()
             action.drag_and_drop_by_offset(span, 400, 0).perform()
             time.sleep(7)
     except:
         print('ok!')
         break

至此,我们便实现了铁路12306的登录,如下图所示3
是不是觉得很简单啊。

5、完整代码

# -*- 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()

#使用selenium打开登录页面
from selenium import webdriver
import time
from PIL import Image
from selenium.webdriver import ActionChains
from selenium.webdriver.support import expected_conditions as EC, wait

#创建对象
#executable_path=path:下载好的驱动程序的路径
bro = webdriver.Chrome(executable_path='chromedriver.exe')
#12306的登录网址
bro.get('https://kyfw.12306.cn/otn/resources/login.html')
#窗口最大化
bro.maximize_window()
#点击账号登录
bro.find_element_by_xpath('/html/body/div[2]/div[2]/ul/li[2]/a').click()
time.sleep(1)
while True:
    try:
        #save_screenshot就是将当前页面进行截图且保存
        bro.save_screenshot('aa.png')
        #确定验证码图片对应的左上角和右下角的坐标(裁剪的区域就确定)
        code_img_ele = bro.find_element_by_xpath('//*[@id="J-loginImg"]')
        location = code_img_ele.location  # 验证码图片左上角的坐标 x,y
        #print('location:',location)
        size = code_img_ele.size  #验证码标签对应的长和宽
        #print('size:',size)
        #左上角和右下角坐标
        rangle = (
        int(location['x']), int(location['y']), int(location['x'] + size['width']), int(location['y'] + size['height']))
        #至此验证码图片区域就确定下来了
        i = Image.open('./aa.png')
        code_img_name = './code.png'
        #crop根据指定区域进行图片裁剪
        frame = i.crop(rangle)
        frame.save(code_img_name)
        #将验证码图片提交给超级鹰进行识别
        chaojiying = Chaojiying_Client('#####', '#######', '######')	#用户账号>>密码>>软件ID
        im = open('code.png', 'rb').read()									#本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
        id=chaojiying.PostPic(im, 9004)['pic_id']                           #截取的验证码照片以及验证码的类别代号
        result = chaojiying.PostPic(im, 9004)['pic_str']                    #识别结果
        all_list = [] #要存储即将被点击的点的坐标  [[x1,y1],[x2,y2]]
        #识别错误后,会返回题分,官网给的demo并没有这一句,哈哈哈,坑吧,就是让你多花钱
        chaojiying.ReportError(id)
        if '|' in result:
            list_1 = result.split('|')
            print(list_1)
            count_1 = len(list_1)
            for i in range(count_1):
                xy_list = []
                x = int(list_1[i].split(',')[0])
                y = int(list_1[i].split(',')[1])
                xy_list.append(x)
                xy_list.append(y)
                all_list.append(xy_list)
        else:
            x = int(result.split(',')[0])
            y = int(result.split(',')[1])
            xy_list = []
            xy_list.append(x)
            xy_list.append(y)
            all_list.append(xy_list)
        #遍历列表,使用动作链对每一个列表元素对应的x,y指定的位置进行点击操作
        for l in all_list:
            x = l[0]
            y = l[1]
            ActionChains(bro).move_to_element_with_offset(code_img_ele, x, y).click().perform()
            time.sleep(0.5)
        #输入账号和密码
        put1=bro.find_element_by_id('J-userName')
        #当验证码识别错误后,需要清空账号重新输入
        put1.clear()
        put1.send_keys('username')  #你的账号
        time.sleep(1)
        put2=bro.find_element_by_id('J-password')
        put2.clear()
        put2.send_keys('password') #你的密码
        time.sleep(1)
        bro.find_element_by_id('J-login').click()
        #处理提示框
        time.sleep(3)
        span=bro.find_element_by_xpath('//*[@id="nc_1_n1z"]')
        action = ActionChains(bro)
        #点击长按指定的标签
        action.click_and_hold(span).perform()
        action.drag_and_drop_by_offset(span,400,0).perform()
        time.sleep(8)
        while True:
            try:
                info=bro.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/span').text
                print(info)
                if info=='哎呀,出错了,点击刷新再来一次':
                    bro.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/span/a').click()
                    time.sleep(0.2)
                    span = bro.find_element_by_xpath('//*[@id="nc_1_n1z"]')
                    action = ActionChains(bro)
                    # 点击长按指定的标签
                    action.click_and_hold(span).perform()
                    action.drag_and_drop_by_offset(span, 400, 0).perform()
                    time.sleep(7)
            except:
                print('ok!')
                break
        #释放动作链
        action.release()
        break
    except:
        time.sleep(3)
time.sleep(12)
#登录成功
bro.find_element_by_link_text('确定').click()
time.sleep(0.5)
bro.find_element_by_link_text('首页').click()
#输入起点、终点以及时间,查询车票
start_city='北京'
end_city='上海'
date='2020-08-05'
#选择起点
bro.find_element_by_xpath('//*[@id="fromStationText"]').click()
time.sleep(2)
#这只遍历了热门城市,要是想遍历其他城市,自己写一个循环就行
city_list=bro.find_elements_by_xpath('//*[@id="ul_list1"]/li')
for city in city_list:
    if city.text==start_city:
        city.click()
        break
time.sleep(2)
#选择终点
bro.find_element_by_xpath('//*[@id="toStationText"]').click()
for city in city_list:
    if city.text==end_city:
        city.click()
        break
time.sleep(2)
js  = "$('input[id=train_date]').removeAttr('readonly')"
bro.execute_script(js)
dt=bro.find_element_by_id('train_date')
dt.clear()
dt.send_keys(date)
time.sleep(2)
bro.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div/div[1]/ul/li[1]/a').click()
time.sleep(0.5)
bro.find_element_by_xpath('//*[@id="isStudentDan"]/i').click()
time.sleep(2)
bro.find_element_by_id('search_one').click()
time.sleep(2)

相信你跟着小墨走到这里,已经掌握了Selenium模块的基本使用,剩下的你可以尝试实现购票等功能,有什么问题可以私信或留言哦

如果小墨的文章对您有帮助的话,记得点赞和关注哦!!!!!!

猜你喜欢

转载自blog.csdn.net/wangbiao9982019/article/details/107634479