利用python爬虫实现对校园网的自动登录!

摘要

本文介绍了如何利用python爬虫实现对zzuli校园网的自动登录。主要利用requests包,通过模仿浏览器动作对局域网服务器发送请求,实现登录动作。用户不需要等待弹出的上网登录窗、选择运营商等一系列操作,只需要双击程序,即可完成自动登录、挤掉另一个登录账号等功能。本文思路适用于绝大部分高校校园网。

github地址私信小编001获取加群:683380553获取!

准备工作

  • 校园网环境(废话)
  • python3
  • 必要的包:
import os
import socket
import time
import requests
from urllib.parse import parse_qs
from urllib.parse import urlsplit
  • Chrome浏览器(用来抓包)
  • pycharm(可有可无)

思路

  1. 通过Chrome抓包分析浏览器登录动作。
  2. 通过python模拟每一个动作。完成一个简单的登录脚本。
  3. 通过设置配置文件实现下次双击自动登录。
  4. 通过OS包实现打开系统无线网功能。

实现步骤

1. 浏览器的动作分析

  1. 打开Chrome,并设置为Chrome为默认浏览器
  2. 按照惯例,链接zzuli-student无线网。等待跳出登录框。
  3. 登录框跳出后,F12

先说一点题外话,经过我的测试,发现URL中的wlanuserip与wlanacip参数后的IP地址并不是一成不变的,随着时间,学校服务器会改变这个地址。

那么岂不是说无法拿到这个地址喽?拿不到地址怎么能利用python进行后续模拟呢?完全不用担心,细心的话就会发现,Chrome弹开的一瞬间,会有一个地址跳转成上面的那个RequestURL地址。如果用fidder抓包可以抓到,Chrome抓的话手速并没有那么快。所以这里直接给出我抓到的静态地址:

http://www.msftconnecttest.com/redirect

也就是说,上面的地址跳转到当前的这个地址,同时给它了两个重要的参数(这两个参数一直会用到最后)。

总结下,通过对上面的url进行get请求,会重定向到一个带两个重要参数的链接(wlanuserip与wlanacip参数),之所以重要是因为会随着时间变化,如果写死在程序里,只能用一段时间,以后会失效的。【第一个分析结束】

  1. 下面来分析账户密码提交的动作

随便提交一个账户密码,然后按照箭头指示的地方清空右侧的请求。

点击登录,选择列表中第一个请求

解释:

  • 点击登录后会发现浏览器跳转到一个无关紧要的链接然后又跳到这里来了。经过我的分析发现那个链接没有什么新的信息,当我们提交账户密码信息的时候,直接对上图中的URL提交即可,当然请求头和请求数据包一定要构造正确。
  • 状态码不用管,我们只要将正确的数据提交到正确的地方就OK了。
  • 注意这个请求是:POST请求

下面来看这个动作的请求头和请求数据包

  • 请求头

两个参数:

Referer:当浏览器向web发送请求的时候,一般会带上Referer,告诉服务器我是从哪个页面链接过来的,服务器基此可以获得一些信息用于处理。(这个参数由第一个动作构造而成的)

User-Agent:告诉web我是一个浏览器而不是python

  • 请求数据包

这个就是我们的重重之重了。

DDDDD:用户名,0是一个很有魔性的参数,偶尔会变成1,技术太渣、无法深究。10086自然是账号了。unicom自然是联通的意思了。

upass:是密码

另外:我发现R6参数与DDDDD中的0是同步的。所以到时候只能勉强的动态处理下吧。

其他的都是空。

【第二个动作分析完成】

  1. 总结:
  2. 通过第一个动作对静态链接的访问,拿到两个参数,并通过这两个参数可以构造各种链接、参数。
  3. 通过第二个动作对数据的提交,完成登录。

2. 核心代码的实现

import requests
from urllib.parse import parse_qs
from urllib.parse import urlsplit
# 简单请求头
headers = {
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36",
}
# 复杂请求头
login_headers = {
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36",
  "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
  "Accept-Encoding": "gzip, deflate",
  "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
  "Referer":"",
}
# url参数表
get_par = {
  "wlanuserip":"",#
  "wlanacip":"",#
  "wlanacname":"",
  "vlanid":"0",
  "ip":"",#
  "ssid":"null",
  "areaID":"null",
  "mac":"00-00-00-00-00-00"
}
# post url参数表
post_par = {
  "hostname":"10.168.6.10",
  "iTermType":"1",
  "wlanuserip":"10.66.67.119",
  "wlanacip":"10.168.6.9",
  "mac":"00-00-00-00-00-00",
  "ip":"10.66.67.119",
  "enAdvert":"0",
  "queryACIP":"0",
  "loginMethod":"1"
}
static_url = "http://www.msftconnecttest.com/redirect"
# 第一个动作
def get_url():
  # 获取静态链接的所有回复内容
  static_response = requests.get(static_url, headers=headers)
  # 从静态链接302中的跳转页面获取信,用于构造真实的登录页面地址
  static_response_302_dict =dict(parse_qs(urlsplit(static_response.url).query))
  # 写入请求参数
  get_par['wlanuserip'] = static_response_302_dict['wlanuserip'][0]
  get_par['wlanacip'] = static_response_302_dict['wlanacip'][0]
  get_par['ip'] = static_response_302_dict['wlanuserip'][0]
  real_url_head_str = "http://10.168.6.10/a70.htm?"
  real_url_par_str = 'wlanuserip=' + str(get_par['wlanuserip']) + \
            '&wlanacip=' + str(get_par['wlanacip']) + \
            '&wlanacname=' + str(get_par['wlanacname']) + \
            '&vlanid=' + str(get_par['vlanid']) + \
            '&ip=' + str(get_par['wlanuserip']) + \
            '&ssid=' + str(get_par['ssid']) + \
            '&areaID=' + str(get_par['areaID']) + \
            '&mac=' + str(get_par['mac'])
  real_url = real_url_head_str + real_url_par_str
  # 构造Referer参数
  login_headers["Referer"] = real_url
  post_par["wlanuserip"] = get_par["wlanuserip"]
  post_par["wlanacip"] = get_par["wlanacip"]
  post_par["ip"] = get_par["ip"]
  # 最后的提交地址
  r_url_head = "http://10.168.6.10:801/eportal/?c=ACSetting&a=Login&protocol=http:"
  r_url_par = "&hostname=" + post_par["hostname"] + "&iTermType=" +post_par["iTermType"] + \
        "&wlanuserip=" + post_par["wlanuserip"] + "&wlanacip=" +post_par["wlanacip"] + \
        "&mac=" + post_par["mac"] + "&ip=" + post_par["ip"] + \
        "&enAdvert=" + post_par["enAdvert"] + "&queryACIP=" +post_par["queryACIP"] + \
        "&loginMethod=" + post_par["loginMethod"]
  r_url = r_url_head + r_url_par
  return r_url
if __name__ == '__main__':
  '''
   联通:unicom
   移动:cmcc
   单宽:other
 '''
  #用户数据:学号 密码 运营商
  login_data = ["541707090100","123456","unicom"]
  # 第一个动作
  URL = get_url()
  # post 请求包
  user_post = {
    "DDDDD": "",
    "upass": "",
    "R1": "0",
    "R2": "0",
    "R3": "0",
    "R6": "",
    "para": "00",
    "0MKKey": "123456",
    "buttonClicked": "",
    "redirect_url": "",
    "err_flag": "",
    "username": "",
    "password": "",
    "user": "",
    "cmd": "",
    "Login": ""
 }
  # 这里是由于不知道R6参数的含义,只能反复提交参数,有一个能命中就行了
  dynamic_R6_data = ['0', '1', '2']
  for i in range(len(dynamic_R6_data)):
    user_post["DDDDD"] = "," + dynamic_R6_data[i] + "," +login_data[0] + "@" + login_data[2]
    user_post["upass"] = login_data[1]
    user_post["R6"] = dynamic_R6_data[i]
    # 第二个动作
    requests.post(URL, data=user_post, headers=login_headers)

3.存储用户名密码便于下次直接使用

import os
file_path = os.getcwd() + "\login_data.ini"
def set_login_data():
  """
 配置登录信息
 :return:
 """
  ini_data = []
  for i in range(3):
    if i == 0:
      ini_data.append(input("请输入上网账号:"))
    if i == 1:
      ini_data.append(input("请输入上网密码:"))
    if i == 2:
      t = input("请输入运营商:\n联通【1】\n移动【2】\n单宽【3】\n")
      if t == "1":
        ini_data.append("unicom")
      if t == "2":
        ini_data.append("cmcc")
      if t == "3":
        ini_data.append("other")
  if len(ini_data) != 3:
    print("error:input data")
    return
  else:
    f = open(file_path, 'w')
    for line in ini_data:
      f.write(line + '\n')
    f.close()
  pass
def get_login_data():
  """
 读取配置信息
 :return:
 """
  temp = []
  try:
    f = open(file_path)
    for line in f.readlines():
      temp.append(line.strip('\n'))
    print("登录账号:"+str(temp[0]))
    f.close()
    return temp
  except IOError:
    print("login_data.ini 文件找不到")
    return []
  pass
if __name__ == '__main__':
  if os.access(file_path, os.F_OK):
    print("正在读取配置文件...")
  else:
    print("请按要求配置登录信息:\n")
    set_login_data()
    print("配置成功!正在进行登录操作...")
  # 这个login_data就可以代替上面代码中自己定义的了
  login_data = get_login_data()
pass

4.小细节

【实现打开计算机的wifi】

def open_wifi():
  """
 dos命令打开wifi并链接校园网
 :return:
 """
  os.system('netsh wlan connect name=zzuli-student')
  time.sleep(1)

【检查是否成功联网】

ps:作恶多端的百度唯一的用处就是检测网络是否联通

def is_net_ok():
  """
 判断网络是否链接
 :return:
 """
  s = socket.socket()
  s.settimeout(1)
  try:
    status = s.connect_ex(('www.baidu.com', 443))# 
    if status == 0:
      s.close()
      return True
    else:
      s.close()
      return False
  except Exception as e:
    return False

5. 整合所有代码,完善逻辑,处理异常

【完整代码】

"""
ZZULI校园网自动连接程序
author:mmciel
time:2019年3月14日17:31:10
version:1.3
"""
import os
import socket
import time
import requests
from urllib.parse import parse_qs
from urllib.parse import urlsplit
# 简单请求头
headers = {
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36",
}
# 复杂请求头
login_headers = {
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36",
  "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
  "Accept-Encoding": "gzip, deflate",
  "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
  "Referer":"",
}
# url参数表
get_par = {
  "wlanuserip":"",#
  "wlanacip":"",#
  "wlanacname":"",
  "vlanid":"0",
  "ip":"",#
  "ssid":"null",
  "areaID":"null",
  "mac":"00-00-00-00-00-00"
}
# post 请求包
user_post = {
  "DDDDD":"",
  "upass":"",
  "R1":"0",
  "R2":"0",
  "R3":"0",
  "R6":"",
  "para":"00",
  "0MKKey":"123456",
  "buttonClicked":"",
  "redirect_url":"",
  "err_flag":"",
  "username":"",
  "password":"",
  "user":"",
  "cmd":"",
  "Login":""
}
# post url参数表
post_par = {
  "hostname":"10.168.6.10",
  "iTermType":"1",
  "wlanuserip":"10.66.67.119",
  "wlanacip":"10.168.6.9",
  "mac":"00-00-00-00-00-00",
  "ip":"10.66.67.119",
  "enAdvert":"0",
  "queryACIP":"0",
  "loginMethod":"1"
}
static_url = "http://www.msftconnecttest.com/redirect"
file_path = os.getcwd() + "\login_data.ini"
dynamic_R6_data = ['0', '1','2']
def get_url():
  try:
    static_response = requests.get(static_url, headers=headers)
    static_response_302_str = str(static_response.url)
    # 从静态链接302中的跳转页面获取信,用于构造真实的登录页面地址
    static_response_302_dict =dict(parse_qs(urlsplit(static_response.url).query))
    # 写入请求参数
    get_par['wlanuserip'] = static_response_302_dict['wlanuserip'][0]
    get_par['wlanacip'] = static_response_302_dict['wlanacip'][0]
    get_par['ip'] = static_response_302_dict['wlanuserip'][0]
    print("成功截获wlanuserip服务器:"+get_par['wlanuserip'])
    print("成功截获wlanacip服务器:" + get_par['wlanacip'])
    real_url_head_str = "http://10.168.6.10/a70.htm?"
    real_url_par_str = 'wlanuserip=' + str(get_par['wlanuserip']) +\
              '&wlanacip=' + str(get_par['wlanacip']) + \
              '&wlanacname=' + str(get_par['wlanacname']) +\
              '&vlanid=' + str(get_par['vlanid']) + \
              '&ip=' + str(get_par['wlanuserip']) + \
              '&ssid=' + str(get_par['ssid']) + \
              '&areaID=' + str(get_par['areaID']) + \
              '&mac=' + str(get_par['mac'])
    real_url = real_url_head_str + real_url_par_str
    print("构造Referer:"+real_url)
    login_headers["Referer"] = real_url
    post_par["wlanuserip"] = get_par["wlanuserip"]
    post_par["wlanacip"] = get_par["wlanacip"]
    post_par["ip"] = get_par["ip"]
    r_url_head = "http://10.168.6.10:801/eportal/?c=ACSetting&a=Login&protocol=http:"
    r_url_par = "&hostname=" + post_par["hostname"] + "&iTermType="+ post_par["iTermType"] + \
          "&wlanuserip=" + post_par["wlanuserip"] +"&wlanacip=" + post_par["wlanacip"] + \
          "&mac=" + post_par["mac"] + "&ip=" + post_par["ip"]+ \
          "&enAdvert=" + post_par["enAdvert"] + "&queryACIP="+ post_par["queryACIP"] + \
          "&loginMethod=" + post_par["loginMethod"]
    r_url = r_url_head + r_url_par
    print("登录地址:"+r_url)
    return r_url
  except:
    print("远程服务器无响应...请检查硬件与驱动!")
    return ""
def set_login_data():
  """
 配置登录信息
 :return:
 """
  ini_data = []
  for i in range(3):
    if i == 0:
      ini_data.append(input("请输入上网账号:"))
    if i == 1:
      ini_data.append(input("请输入上网密码:"))
    if i == 2:
      t = input("请输入运营商:\n联通【1】\n移动【2】\n单宽【3】\n")
      if t == "1":
        ini_data.append("unicom")
      if t == "2":
        ini_data.append("cmcc")
      if t == "3":
        ini_data.append("other")
  if len(ini_data) != 3:
    print("error:input data")
    return
  else:
    f = open(file_path, 'w')
    for line in ini_data:
      f.write(line + '\n')
    f.close()
  pass
def get_login_data():
  """
 读取配置信息
 :return:
 """
  temp = []
  try:
    f = open(file_path)
    for line in f.readlines():
      temp.append(line.strip('\n'))
    print("登录账号:"+str(temp[0]))
    f.close()
    return temp
  except IOError:
    print("login_data.ini 文件找不到")
    return []
  pass
def is_net_ok():
  """
 判断网络是否链接
 :return:
 """
  s = socket.socket()
  s.settimeout(1)
  try:
    status = s.connect_ex(('www.baidu.com', 443))
    if status == 0:
      s.close()
      return True
    else:
      s.close()
      return False
  except Exception as e:
    return False
def open_wifi():
  """
 dos命令打开wifi并链接校园网
 :return:
 """
  os.system('netsh wlan connect name=zzuli-student')
  time.sleep(1)
def login(temp_url, temp_data, temp_headers):
  """
 提交登录信息,并测试是否链接到网络
 :param temp_url: url
 :param temp_data: post数据包
 :param temp_headers: 请求头
 :return: 真假
 """
  requests.post(temp_url, data=temp_data, headers=temp_headers)
  time.sleep(1)
  num = 1
  while not is_net_ok():
    print("发现账号登录异常,正在尝试第"+str(num+1)+"次...")
    requests.post(temp_url, data=temp_data, headers=temp_headers)
    time.sleep(1)
    num = num+1
    if num == 3 :
      return False
  return True
def word():
  """
 工具说明
 :return: 
 """
  print("=====================================================================")
  print("使用说明:")
  print("1.本程序放在磁盘任意文件夹内")
  print("2.右键发送快捷方式到桌面")
  print("3.以管理员打开程序,配置账号")
  print("4.下次使用直接双击即可")
  print("注意:")
  print("1.首次启动请使用管理员方式启动程序,因为生成配置文件需要权限。")
  print("2.配置完成后目录下会生成login_data.ini文件谨慎删除,删除需要重新配置")
  print("3.配置信息有误删除login_data.ini文件后重新配置即可")
  print("4.程序源码、更新等关注微信公众号:并非一无所有")
  print("=====================================================================")
if __name__ == '__main__':
  print("======================Auto Login ZZULI Net V1.3======================\n")
  print("                    by:mmciel [email protected] \n")
  word()
  if os.access(file_path, os.F_OK):
    print("正在读取配置文件...")
  else:
    print("请按要求配置登录信息:\n")
    set_login_data()
    print("配置成功!正在进行登录操作...")
  if is_net_ok():
    print("网络已连接...2s后程序关闭")
    time.sleep(2)
  else:
    try:
      open_wifi()
    except Exception as e:
      print("wifi启动失败,请检查权限或驱动程序是否符合条件!")
      time.sleep(2)
    login_data = get_login_data()
    flag = False
    # 获取url
    URL = get_url()
    if not URL=="":
      # 这里是由于不知道R6参数的含义,只能反复提交参数,有一个能命中就行了
      for i in range(len(dynamic_R6_data)):
        print("正在尝试登录参数:R" + dynamic_R6_data[i])
        user_post["DDDDD"] = "," + dynamic_R6_data[i] + "," +login_data[0] + "@" + login_data[2]
        user_post["upass"] = login_data[1]
        user_post["R6"] = dynamic_R6_data[i]
        if login(URL, user_post, login_headers):
          flag = True
          break
        else:
          continue
      if flag:
        print("联网成功! 1秒后关闭程序...")
        time.sleep(2)
      else:
        print("联网失败! 1秒后关闭程序...")
        time.sleep(2)
pass

5.打包成exe

  • 直接把脚本给用户不知道会出现什么奇葩问题
  • 打包成exe会让性能削弱很多
  • 不过还是要打包

【打包】

采用pyinstaller打包成exe,相关的教程遍地都是(搜索:pyinstaller打包 exe)

6.关于更新

这个版本依旧是有很多bug的,所以我会持续更新的。

猜你喜欢

转载自blog.csdn.net/qq_42156420/article/details/88716256