Cookie模拟登录(免验证码)

概述

Cookie指某些网站为了辨明用户身份、进行session(会话)跟踪而储存在用户本地终端上的数据。辨明身份,自然要保存用户的的相关信息了,所以可以通过Cookie来模拟登录网站。

获取Cookie信息

  1. 进入ZUCC选课网,打开开发者工具里面的Network选项
  2. 输入账号、密码、验证码(这里输入验证码是为了获取Cookie,以后用Cookie登录就无需验证码了)
  3. 找到“default2.aspx”文件——“Headers”里面的“Request Headers”,复制里面的Cookie
  4. 请求头里加入Cookie信息即可登录
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import requests

    url = 'http://xk.zucc.edu.cn/default2.aspx'

    headers = {
    'Cookie': 'ASP.NET_SessionId=XXXXXXX; AntiLeech=XXXXXXX',
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
    }

    html = requests.get(url, headers=headers)
    print(html.text)

POST方法登录(手动验证码 + OCR自动验证码)

Cookie由于各种原因,大概率无效,尝试POST表单交互

分析

  1. 查看表单的网页源代码,确定提交方式是POST

  2. 观察表单源码中input标签,获取表单提交字段

  3. 逆向工程(抓包)确认表单的提交字段
    (1)进入ZUCC选课网,打开chrome开发者工具,选择network选项
    (2)手动输入账号和密码后登录
    (3)在“default2.aspx”的文件中,“From Data”里面就是需要提交的字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    __VIEWSTATE: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    txtUserName: XXXXXXXX
    Textbox1:
    TextBox2: XXXXXXXX
    txtSecretCode: 4cx1
    RadioButtonList1: (unable to decode value)
    Button1:
    lbLanguage:
    hidPdrs:
    hidsc:

    __VIEWSTATE: 相传这个是.Net框架特有产物,会变化,在html源码的form标签下的第一个hidden的input标签里可以看到值

    1
    <input type="hidden" name="__VIEWSTATE" value="dDwxNTMxMDk5Mzc0Ozs+HjPJ2APgB+73zYSyRsZkjFxlx0A=">

    txtUserName: 学号参数
    Textbox1: 用来实现记住密码的功能的(讲道理chrome的记住密码比这个功能好用多了)
    TextBox2: 用来传输密码的(抓包的时候看到,密码没有一点点加密,直接明文传送)
    txtSecretCode: 验证码
    RadioButtonList1: 通过view source查看源码,可以看到

    1
    2
    __VIEWSTATE=dDwxNTMxMDk5Mzc0Ozs%2BHjPJ2APgB%2B73zYSyRsZkjFxlx0A%3D&txtUserName=XXXXXXXX
    &Textbox1=&TextBox2=XXXXXXXX&txtSecretCode=tkx7&RadioButtonList1=%D1%A7%C9%FA&Button1=&lbLanguage=&hidPdrs=&hidsc=
    • 其中”RadioButtonList1=%D1%A7%C9%FA”可以推断出来,代表为学生。用URL反编译出来,也确实是汉字“学生”
  4. 审查元素,验证码地址是“CheckCode.aspx”

基本思路

  1. 开启session,先抓取教务系统首页
  2. 再单独抓取一次验证码(即人类登录时看不清换一张验证码的功能)
  3. download验证码,显示给人类/OCR
  4. 人类填写验证码/OCR识别验证码
  5. 构造登录数据,post数据登录

代码实现

获取账号信息

1
2
3
4
5
6
7
def get_information():
with open('information.json', encoding='utf-8') as f:
information = json.load(f)
student_number = information['student_number']
student_password = information['password']
student_name = information['name']
return student_number, student_password, student_name

初始化登录爬虫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class LoginSpider:
def __init__(self, number, password):
self.number = number
self.password = password
self.index_url = 'http://xk.zucc.edu.cn/default2.aspx'
self.imgUrl = 'http://xk.zucc.edu.cn/CheckCode.aspx?'
self.s = requests.session()
# self.cookie = ''
self.headers = {
'Referer': self.index_url,
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/70.0.3538.77 Safari/537.36 '
}
self.data = {
'__VIEWSTATE': '',
'txtUserName': number,
'Textbox1': '',
'TextBox2': password,
'txtSecretCode': '',
'RadioButtonList1': '%D1%A7%C9%FA',
'Button1': '',
'lbLanguage': '',
'hidPdrs': '',
'hidsc': ''
}

访问首页

获取__VIEWSTATE的值,并抓取验证码到本地

1
2
3
4
5
6
7
8
9
def hello_world(self):
# 访问首页
response = self.s.get(self.index_url, headers=self.headers)
img_dir = self.down_code()

# POST数据发送
self.data['__VIEWSTATE'] = get_view_state(response)

return img_dir
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def down_code(self):
# 获取验证码并下载(通过抓包,可以看到请求链接,并且每刷新一次二维码,后面就多一个?)
image_response = self.s.get(self.imgUrl, stream=True)
image = image_response.content
img_dir = os.getcwd() + "/"
print("Saved in " + img_dir + "code.gif")
try:
with open(img_dir + "code.gif", "wb") as codegif:
codegif.write(image)
codegif.close()
except IOError:
print("IO ERROR!")
finally:
return img_dir

每提交一次选课,VIEWSTATE就会更新一次。所以后面会一直用到获取VIEWSTATE的信息,直接封装成一个函数

1
2
3
4
def get_view_state(response):
# 用Lxml库解析网页,通过Xpath语法定位__VIEWSTATE
selector = etree.HTML(response.content)
__VIEWSTATE = selector.xpath('//*[@id="form1"]/input/@value')[0]

登录状态判断

定义一个判断是否成功登录的函数。对于登录成功和登录失败的页面,用etree解析页面,xpath定位title。根据title的不同就可以判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def login_status(response):
if response.status_code == requests.codes.ok:
selector = etree.HTML(response.content)
state = selector.xpath('/html/head/title/text()')[0]
if state == "正方教务管理系统":
print("登录成功")
return True
elif state == "欢迎使用正方教务管理系统!请登录":
print("登录失败")
return False
else:
print(response.status_code)
print("登录失败")
return False

手动打码登录

1
2
3
4
5
6
7
8
9
10
def login_manual(self):
self.data['txtSecretCode'] = input_code(self.hello_world())
response = self.s.post(self.index_url, headers=self.headers, data=self.data)

print("Login By Manual")
if login_status(response):
# 这里的Cookie只能获取到ASP.NET_SessionId,无法获取到AntiLeech,导致了Cookie登录失败,后期尝试selenium获取
return True
else:
return False

OCR识别验证码登录

OCR验证码识别

1
2
3
4
5
6
7
8
9
def login_ocr(self):
self.data['txtSecretCode'] = OCR_Code.run(self.hello_world())
response = self.s.post(self.index_url, headers=self.headers, data=self.data)

print("Login By OCR")
if login_status(response):
return True
else:
return False

完整代码

Cookie登录功能已经废弃,OCR代码识别代码参见OCR验证码识别(Python)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import json
import requests
from lxml import etree
import os
import OCR_Code


def get_information():
with open('information.json', encoding='utf-8') as f:
information = json.load(f)
student_number = information['student_number']
student_password = information['password']
student_name = information['name']
return student_number, student_password, student_name


def get_view_state(response):
# 用Lxml库解析网页,通过Xpath语法定位__VIEWSTATE
selector = etree.HTML(response.content)
__VIEWSTATE = selector.xpath('//*[@id="form1"]/input/@value')[0]

return __VIEWSTATE


def input_code(img_dir):
OCR_Code.show_code(img_dir)
code = input("The code is:")
return code


def login_status(response):
if response.status_code == requests.codes.ok:
selector = etree.HTML(response.content)
state = selector.xpath('/html/head/title/text()')[0]
if state == "正方教务管理系统":
print("登录成功")
return True
elif state == "欢迎使用正方教务管理系统!请登录":
print("登录失败")
return False
else:
print(response.status_code)
print("登录失败")
return False


class LoginSpider:
def __init__(self, stu_number, stu_password):
self.number = stu_number
self.password = stu_password
self.index_url = 'http://xk.zucc.edu.cn/default2.aspx'
self.imgUrl = 'http://xk.zucc.edu.cn/CheckCode.aspx?'
self.s = requests.session()
self.headers = {
'Referer': self.index_url,
'Connection': 'keep - alive',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/70.0.3538.77 Safari/537.36 '
}
self.data = {
'__VIEWSTATE': '',
'txtUserName': stu_number,
'Textbox1': '',
'TextBox2': stu_password,
'txtSecretCode': '',
'RadioButtonList1': '%D1%A7%C9%FA',
'Button1': '',
'lbLanguage': '',
'hidPdrs': '',
'hidsc': ''
}

# def login_cookie(self):
# self.headers['Cookie'] = self.cookie
# response = self.s.get(self.index_url, headers=self.headers)
#
# print("Login By Cookie")
# if login_status(response):
# return True
# else:
# del self.headers['Cookie']
# return False

def hello_world(self):
# 访问首页
response = self.s.get(self.index_url, headers=self.headers)
img_dir = self.down_code()

# POST数据发送
self.data['__VIEWSTATE'] = get_view_state(response)

return img_dir

def down_code(self):
# 获取验证码并下载(通过抓包,可以看到请求链接,并且每刷新一次二维码,后面就多一个?)
image_response = self.s.get(self.imgUrl, stream=True)
image = image_response.content
img_dir = os.getcwd() + "/"
print("Saved in " + img_dir + "code.gif")
try:
with open(img_dir + "code.gif", "wb") as codegif:
codegif.write(image)
codegif.close()
except IOError:
print("IO ERROR!")
finally:
return img_dir

def login_manual(self):
self.data['txtSecretCode'] = input_code(self.hello_world())
response = self.s.post(self.index_url, headers=self.headers, data=self.data)

print("Login By Manual")
if login_status(response):
return True
else:
return False

def login_ocr(self):
self.data['txtSecretCode'] = OCR_Code.run(self.hello_world())
response = self.s.post(self.index_url, headers=self.headers, data=self.data)

print("Login By OCR")
if login_status(response):
return True
else:
return False


if __name__ == "__main__":
number, password, name = get_information()
spider = LoginSpider(number, password)

spider.login_ocr()
# spider.login_manual()

完整项目包括登录、获取信息和自动选课