python实现清华大学联网助手(一)——urllib/hashlib/getopt/time/codecs的使用

本文介绍了使用Python实现清华大学联网助手的过程,包括利用hashlib处理密码、模拟登录网页、接收命令行参数、获取当前日期时间以及理解`if __name__ == '__main__'`的作用。文章详细讲解了每个步骤,并给出了代码示例。

python版清华大学联网助手

github:https://github.com/HaoyuHu/python

学习完python之后,能够明白它的语法和特性,感觉这是一种与C/C++和java截然不同的语言,它更高级,更接近自然语言。所以能通过极少的代码完成其他语言需要几百行代码才能完成的任务。前些阵子曾经用python从各大网站上抓取信息用作分析数据,觉得挺过瘾。因此为了满足在linux下快速连接校园网和查询流量的需求,决定用python实现一个联网助手,能通过命令行进行操作。需要注意的是,本python版pytunet是基于python3.4,相应的语法和模块也遵循python3的要求。由于python3的相关内容在网上还不是很多,在编程过程中受到很多python2的文章的误导,走了很多弯路。不过也加深了我对python3的理解。同时也对python严格的缩进要求表示*&…%¥(just kiding)。后面在python2和python3不同的地方我也会特别指出。


最终目标希望完成如下几个功能:
(1)登录校园网
(2)登出校园网
(3)查询当前网络状态
(4)查询流量、帐户余额、当前用户组等基本信息
(5)查询本账号当前在线IP信息
(6)查询每日流量使用明细,并生成直方图

使用方法:
使用前请到同级目录下USERNAME_PASSWORD.txt文件中修改你的校园网账号和密码。
可将tunet.py设置为外部命令,参数如下:
-h, --help : 显示帮助
-v, --version: 显示当前清华大学联网助手版本号
-u : 输入用户名
-p : 输入密码
-a : 稍后输入用户名和密码,可以登录其他校园网帐户
-i, --login : 联网操作(当不带任何其他操作时,默认进行联网操作)
-o, --logout : 断网操作
-c, --check : 检查当前网络状态
-q, --query : 查询流量、帐户余额、当前用户组等基本信息,查询本账号当前在线IP信息,查询每日流量使用明细,并生成直方图,查询结果保存在USER_DETAIL_INFOMATION.LOG文件下

说明:
(1)USER_DETAIL_INFOMATION.LOG文件保存最近一次综合查询结果
(2)USERNAME_PASSWORD.txt文件保存用户的账号和密码

版本v1.2更新内容:
(1)增加了账户和密码的文件管理
(2)性能调整
(3)当不带任何参数时,默认连接校园网,简化输入

版本v1.1更新内容:
(1)增加了综合查询功能:查询流量、帐户余额、当前用户组等基本信息,查询本账号当前在线IP信息,查询每日流量使用明细,并生成直方图,查询结果保存在USER_DETAIL_INFOMATION.LOG文件中

版本v1.0更新内容:
(1)提供基本的联网、断网和检查当前网络状态的操作


python实现分析总共分为3个部分:本篇为第一篇

本篇将分析如下几个问题:

1 如何利用hashlib处理密码;

2 如何实现带账号密码登录的网页request和urlopen;

3 如何实现带参数输入的自定义命令;

4 如何实现当前日期和时间的获取;

5 if __name__ == '__main__'的作用。


1 如何利用hashlib处理密码:

--载入模块hashlib:import hashlib;

--md5的使用:

a) md5是计算机安全领域广泛使用的一种哈希函数

b) 首先创建md5对象,然后用update(password)实现密码到加密散列值的过程。注意此处password必须经过统一编码,也就是说password需要经过encode才可以传入update()中,否则报错(python2中似乎没有这个要求)。

c) 生成的散列值可以有2种表现形式,digest()和hexdigest(),前者为二进制形式,后者为十六进制形式。

hashcd_md5 = hashlib.md5()
hashcd_md5.update(password.encode())
tr_password = hashcd_md5.hexdigest()
如上就将字符串类型的password转换为十六进制形式的tr_password散列值。encode()默认使用utf-8编码方式,需要载入模块codecs。


2 如何实现带账号密码登录的网页request和urlopen:
密码加密过程上面已经完成,接下来分析如何请求网页和打开网页。由于涉及账号登录,一般有2种发送账号密码的方式:第一种自己构造post数据,通过发送正文request body实现模拟登录;第二种是将账号和密码加入登录的url中实现模拟登录。

a) 网页打开和登录的内部逻辑:

但无论是哪种方式,都需要明白网页登录的逻辑。那我们如何去了解网页登录的内部逻辑呢?我推荐这篇文章:[教程]手把手教你如何利用工具去分析模拟登录网站。我正是一步一步跟着教程做才明白了网页打开、帐户登录等行为的机制。我用的是ie11的F12工具,虽然界面上与教程不同,但功能上大同小异,稍微摸索应该就能学会如何使用。

明白了网页的逻辑后,我们需要仔细看一下登录页面的源代码。了解一下本页面内的逻辑和可能的响应。

b) url的request和urlopen:

通过上面对网页逻辑的分析,我发现本校的联网登录是通过第一种方式实现的,因此我们需要构造自己的post数据。

我们在第1步中得到了我们密码的md5散列值tr_password。同时在发送正文中数据是以这种格式组织的‘username=' + username + '&password=' + tr_password + '&drop=0&type=1&n=100’。因此我们就模拟组织这样的post_data。

当然有的网站为了防止恶意登录,会同时要求发送标头。为以防万一,我建议你也要模拟发送标头。但幸运的是,我们的校园网并没有这么严格,因此我偷懒并没有加上header。

注意:

python2和python3的urllib模块是有区别的。python3中并没有urllib2,且载入模块时应import urllib.request。所有我们所需要的函数均在urllib.request中。调用函数的方式也不同,请求是urllib.request.Request(url, post_data),打开是urllib.request.urlopen(response)。如果你有闲心可以比较一下python2和python3在此处调用上的区别,如果不感兴趣可以直接按照的函数去写。

同样的,为了标准化输入,我们的post_data也必须经过utf-8编码。

login_data = 'username=' + username + '&password=' + tr_password + '&drop=' + '0' + '&type=1&n=100'
login_data = login_data.encode()
request_url = urllib.request.Request(login_url, login_data)
response_url = urllib.request.urlopen(request_url)
content = response_url.read().decode()
基本步骤:

构造post_data并encode统一化,携带post_data向url发送请求,再通过urlopen得到页面响应response。接着通过response.read()读取页面内容,这里的页面内容需要通过decode()转义为字符串类型,才能让我们对页面反馈做进一步的处理。
对于logout操作和check操作也是大同小异,只是post_data不同。logout不需要post_data,传入‘’即可。而check的post_data是‘action=check_online’。


3 如何实现带参数输入的自定义命令:

话锋一转,我们现在已经能够完成网页的请求、打开,并且可以根据你自身的要求去处理响应正文,以及登录后发生跳转后的网页内容,抓取数据和分析数据。

但别忘了,我们是要制作一款基于linux命令行操作的联网助手。因此我们必须要应付各种复杂的操作,并让它看起来确实像是一个正式的命令,接收很多必要的参数。联网助手,顾名思义我们需要联网功能、断网功能和查询当前联网状态功能。此外命令还需要带有--version和--help才显得完整。同时在联网时我们需要输入账号和密码,这里也需要一些参数。

总结起来:

-h, --help : 显示帮助
-v, --version: 显示当前清华大学联网助手版本号
-u : 输入用户名
-p : 输入密码
-a : 稍后输入用户名和密码,可以登录其他校园网帐户
-i, --login : 联网操作(当不带任何其他操作时,默认进行联网操作)
-o, --logout : 断网操作
-c, --check : 检查当前网络状态


那么我们该如何实现参数的传入呢?这就需要getopt这个强大的模块。

getopt的使用方法:

举一个简单的例子, options, args = getopt.getopt(sys.argv[1:], 'achiop:u:v', ['help', 'login', 'logout', 'check=', 'version'])

解释一下,输入的命令行会被getopt函数拆解成几个部分。其中我们需要的是options部分。

先看 'achiop:u:v',这一块乱起八糟的东西,每一个字母都代表一个参数的符号:-a -c -h -i -o -p -u -v,其中p:和u:是带有冒号的,这表示需要在-p后面传值。最后可能会这样在命令行中实现:tunet -i -u hhy14 -p 123456。其中i不需要传值,而-u和-p分别传入用户名和密码。

而后面的['help', 'login', 'logout', 'check=', 'version']表示参数全称, 对应--help --login等。如果有=号,则说明这个参数需要传递一个值。

而我们解析得到options则是一系列键值对:就那上面的例子来说,options中就包含('-i': None)('-u': 'hhy14')('-p': '123456')这三个键值对。

因此我们可以用一个循环去处理每一个参数和它的值。

注意:参数的传递可能会有问题,因此需要异常处理。

实现如下:

try:
<span style="white-space:pre">		</span>options, args = getopt.getopt(sys.argv[1:], 'achiop:u:v', ['help', 'login', 'logout', 'check', 'version'])
	except getopt.GetoptError:
		tunet_others()
		sys.exit(1)

	want_login = False
	flag = False

	name, value = None, None

	for name, value in options:
		if name in ('-h', '--help'):
			tunet_help()
			sys.exit(0)
		elif name in ('-v', '--version'):
			tunet_version()
			sys.exit(0)
		elif name == '-a':
			flag = True
		elif name == '-u':
			username = value
		elif name == '-p':
			password = value
		elif name in ('-i', '--login'):
			want_login = True
		elif name in ('-o', '--logout'):
			tunet_logout()
			sys.exit(0)
		elif name in ('-c', '--check'):
			tunet_check()
			sys.exit(0)
当然,我的这个实现是有特殊用处的,你应该根据你的需求去处理。

4 如何实现当前日期和时间的获取:

time模块可以让我们获得当前的时间,最为实用的应该是strftime(),它接受至少一个参数,并返回当前日期和时间的字符串。它至少需要1个参数来表明它输出的格式。比如time.strftime(‘%Y-%m-%d %H:%M:%S’)就能返回'2015-04-04 12:22:34'格式的字符串。详细可以看time.strftime()简介


5 if __name__ == '__main__'的作用:

这个莫名其妙的东西是使得.py文件可重用的关键语句。

比如:

if __name__ == '__main__':
tunet_connector()

那么如果单独运行该.py脚本,则会调用tunet_connector()函数。

如果作为模块被载入其他脚本中,该文件则不会自动运行,而任由其它脚本去使用它的函数。


BONUS:

说了那么多,这里再说一个如何在输入密码时不显示密码。这需要getpass模块。

password = getpass.getpass('PASSWORD = ')能够在键入密码时不显示密码。输入参数为提示内容。

需要注意,getpass.getuser()这个函数是获取你当前主机用户的用户名,因此不能用在这里。输入用户名还是直接用input吧(python3里已经没有raw_input了)。


第一篇要说的就这么多,下面是完整的实现代码:

import sys, getopt, time
import urllib.request, getpass, hashlib
import codecs

login_url  = 'http://net.tsinghua.edu.cn/cgi-bin/do_login'
logout_url = 'http://net.tsinghua.edu.cn/cgi-bin/do_logout'
check_url  = 'http://net.tsinghua.edu.cn/cgi-bin/do_login'
query_url  = 'https://usereg.tsinghua.edu.cn/login.php'

times_cnt = {1: 'FIRST', 2: 'SECOND', 3: 'THIRD', 4: 'FORTH', 5: 'FIFTH'}
ret_type  = {'logout_ok'       : 'LOGOUT SUCCESS',
			'not_online_error' : 'NOT ONLINE',
			'ip_exist_error'   : 'IP ALREADY EXISTS',
			'user_tab_error'   : 'THE CERTIFICATION PROGRAM WAS NOT STARTED',
			'username_error'   : 'WRONG USERNAME',
			'user_group_error' : 'ACCOUNT INFOMATION INCORRECT',
			'password_error'   : 'WRONG PASSWORD',
			'status_error'     : 'ACCOUNT OVERDUE, PLEASE RECHARGE',
			'available_error'  : 'ACCOUNT HAS BEEN SUSPENDED',
			'delete_error'     : 'ACCOUNT HAS BEEN DELETED',
			'usernum_error'    : 'USERS NUMBER LIMITED',
			'online_num_error' : 'USERS NUMBER LIMITED',
			'mode_error'       : 'DISABLE WEB REGISTRY',
			'time_policy_error': 'CURRENT TIME IS NOT ALLOWED TO CONNECT',
			'flux_error'       : 'FLUX OVER',
			'ip_error'         : 'IP NOT VALID',
			'mac_error'        : 'MAC NOT VALID',
			'sync_error'       : 'YOUR INFOMATION HAS BEEN MODIFIED, PLEASE TRY AGAIN AFTER 2 MINUTES',
			'ip_alloc'         : 'THE IP HAS BEEN ASSIGNED TO OTHER USER'
			}

version  = '1.0'

def trans_content(response):
	content = response.read().decode()
	ret = ''
	for ch in content:
		if ch.isalpha() or ch == '_':
			ret += ch
	return ret

def tunet_login(username, password):
	hashcd_md5 = hashlib.md5()
	hashcd_md5.update(password.encode())
	tr_password = hashcd_md5.hexdigest()
	login_data = 'username=' + username + '&password=' + tr_password + '&drop=' + '0' + '&type=1&n=100'
	login_data = login_data.encode()
	request_url = urllib.request.Request(login_url, login_data)
	response_url = urllib.request.urlopen(request_url)
	ret = trans_content(response_url)
	print (ret_type.get(ret, 'CONNECTED'))
	return ret

def tunet_logout():
	response_url = urllib.request.urlopen(logout_url)
	ret = trans_content(response_url)
	print (ret_type.get(ret, 'CONNECTED'))
	return ret

def tunet_check():
	check_data = 'action=check_online'
	check_data = check_data.encode()
	request_url = urllib.request.Request(check_url, check_data)
	response_url = urllib.request.urlopen(request_url)
	ret = trans_content(response_url)
	if ret == '':
		print ('NOT ONLINE')
	else:
		print (ret_type.get(ret, 'CONNECTED'))
	return ret

def tunet_help():
	print ('-h, --help   : show all options of Tsinghua University Internet Connector')
	print ('-v, --version: show version of Tsinghua University Internet Connector')
	print ('-u           : input your username after \'-u\'')
	print ('-p           : input your password after \'-p\'')
	print ('-a           : enter username and password later, you can login other campus network account')
	print ('-i, --login  : login operation')
	print ('-o, --logout : logout operation')
	print ('-c, --check  : check the internet')

def tunet_version():
	print ('Tsinghua University Internet Connector ', version)

def tunet_others():
	print ('Unknown option')
	print ('Which option do you want?')
	tunet_help()
	print ('if any unknown error. please contact im@huhaoyu.com.')

def tunet_connector():
	username = 'hhy14'
	password = '123456'
	try:
		options, args = getopt.getopt(sys.argv[1:], 'achiop:u:v', ['help', 'login', 'logout', 'check', 'version'])
	except getopt.GetoptError:
		tunet_others()
		sys.exit(1)

	want_login = False
	flag = False

	name, value = None, None

	for name, value in options:
		if name in ('-h', '--help'):
			tunet_help()
			sys.exit(0)
		elif name in ('-v', '--version'):
			tunet_version()
			sys.exit(0)
		elif name == '-a':
			flag = True
		elif name == '-u':
			username = value
		elif name == '-p':
			password = value
		elif name in ('-i', '--login'):
			want_login = True
		elif name in ('-o', '--logout'):
			tunet_logout()
			sys.exit(0)
		elif name in ('-c', '--check'):
			tunet_check()
			sys.exit(0)

	if not want_login:
		tunet_others()
		sys.exit(1)

	if flag:
		username = input('username: ')
		password = getpass.getpass('password: ')

	ret = 'ip_exist_error'
	for count in range(5):
		print ('%s attempts to connect...' % times_cnt.get(count + 1))
		if ret != tunet_login(username, password):
			break
		if count == 4:
			print ('please try to reconnect after 1 minute')
			break
		print ('try to reconnect after 10 seconds')
		time.sleep(10)
		print ("")

if __name__ == '__main__':
	tunet_connector()
如代码或叙述有误,请批评指正,谢谢!欢迎讨论:)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值