##前言 什么是自由?自由不是想做什么都能做什么,而是当你不想做什么的时候可以选择不做。 ##正题 其实你知道,所有的成绩都是以数据的形式存放在学校的数据库中,而学校通过IP显示、网络端口的限制等一系列手段来保证数据的安全,当然,这些做法是无可厚非的,但是你不然学生查到自己的成绩就有点……
首先看下学校的查成绩方式:
-
登陆学校的教务系统。在这一次的登陆中,你会输入用户名、密码,学校的后台程序在接收到你的登陆请求以后就会验证,通过验证则会在本地保存一个cookie,在服务器端维护一个session。这些是你已经登陆的证明。
-
点击查成绩。学校的教务系统使用的iframe的嵌入方式,也就是说他其实就是外边一个框,当你点击里面链接的时候只变换里面的内容。但是你会发现这样页面的url地址并没有变化,然后就有异步刷新的假象……额,这种方式多年以前很流行,我只能这样说
-
成绩结果显示。点击完查成绩以后,后台相应的程序会相应请求,首先自然收先验证登陆与否,然后从数据库拉取相关数据返回给前台。
这就是你完成一次查询所执行的动作。
那么我们在外网如何查成绩呢?
首先,必须要登陆。这就要求你有一台能够和学校教务系统进行数据通信的主机,显然你不太可能只通过外网的服务器进行,因为教务系统是不直接和外边通信的,没有端口开放出来。那么,你就需要曲线救国,找一台对外网通信的校内主机,一般满足这种条件的机器就是各个学院的网站、学校一些新闻网站等等,然后利用这些主机进行通信。
当你找到这些主机以后,你所需要做的就是获取用户名、密码,然后将这些数据传给学校的登陆认证url,在这里,这个地址是http://202.206.1.163/loginAction.do
,你需要一种称之为POST的http语义来进行这个操作,所传递的数据对象是这样的:
zjh=uid&mm=password
你怎么知道传递这样的数据?
额……好吧,这个就是你首先开启一个网络监测的软件,然后本地在校内登陆教务系统,抓取数据包,然后分析一下就看到这样的数据了~
额,如下是代码:
private static string Login(string uid,string password)//这里我构造了一个私有函数
{
string paramList = "zjh="+uid+"&mm="+password;//构造post的数据
string url="http://202.206.1.163/loginAction.do";//所要post的url地址
HttpWebResponse res = null;//返回的结果类
string result = "";//结果
try//捕捉异常
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);//建立请求
req.Method = "POST";//设置请求的方式
req.ContentType = "application/x-www-form-urlencoded";//设置内容类型,这些都是抓包获取的
req.AllowAutoRedirect = false;
CookieContainer cookieContainer = new CookieContainer();//设置cookie容器
req.CookieContainer = cookieContainer;//将cookie容器和请求类绑定再一起
StringBuilder urlEn = new StringBuilder();//构建一个stringbuilder类
char[] reserved = { '?', '=', '&' };//如下的一大堆就是对post的数据进行urlencode,这个是程序化步骤,当然你也可以用更简单的方式,随便,没啥影响
byte[] bs = null;
if (paramList != null)
{
int i = 0, j;
while (i < paramList.Length)
{
j = paramList.IndexOfAny(reserved, i);
if (j == -1)
{
urlEn.Append(HttpUtility.UrlEncode(paramList.Substring(i, paramList.Length - i)));
break;
}
urlEn.Append(HttpUtility.UrlEncode(paramList.Substring(i, j - i)));
urlEn.Append(paramList.Substring(j, 1));
i = j + 1;
}
bs = Encoding.UTF8.GetBytes(urlEn.ToString());
req.ContentLength = bs.Length;
Stream newStream = req.GetRequestStream();
newStream.Write(bs, 0, bs.Length);
newStream.Close();
}
else
{
req.ContentLength = 0;
}//终于构建完了
res = (HttpWebResponse)req.GetResponse();//发送请求
cookieheader = req.CookieContainer.GetCookieHeader(new Uri(url));//获取请求的url的cookie信息,注意这个cookieheader是全局变量,目的是两次http请求只要再程序没有释放内存的时候都能够获取到cookie并从内存中还原出来向服务器进行请求数据
Stream recStream = res.GetResponseStream();//读入流
Encoding encode = System.Text.Encoding.GetEncoding("GBK");//设置编码格式
StreamReader sr = new StreamReader(recStream, encode);//将流读入到相应的读取类
Char[] read = new Char[256];//下面是开始获取数据了
int count = sr.Read(read, 0, 256);
while (count > 0)
{
String str = new String(read, 0, count);
result += str;
count = sr.Read(read, 0, 256);
}
}
catch (Exception)//当你捕捉到异常以后这个执行
{
result = "";
}
finally//最终这个会执行
{
if (res != null)
{
res.Close();
}
}
return result;
}
回到正题,我们刚才说道学校会使用cookie和session来维持这样的一个状态,进而确认用户的登陆,因此,当你把用户名、密码给了服务器端口以后,它会给你写入一个cookie,这个cookie是你已经登陆的凭证,一般都具有过期时间。
你所需要的是获取这个cookie,在我写的这个C#程序中,我通过定义一个全局的cookieheader序列化这个cookie信息,使得这个cookie存活在内存中。
这样做是基于如下两个考虑:
- 不干扰别的用户
- 对程序友好……这一点说的有些牵强,简单说来就是写起程序来简单,你不需要再去费力操作代理服务器上的cookie文件
OK,在获得这个cookie以后,我就已经登陆进来了,那么剩下的就是查询数据,这一步是自动完成得,即用户输入用户名、密码以后,系统向教务系统POST数据,成功以后,再向教务系统的成绩URL获取数据。
所以,你这个时候就是从内存中还原刚才获取的cookie,构造一个http请求,向这个端口:
http://202.206.1.163/bxqcjcxAction.do
发送请求,这个时候其实就是发送一个get请求,然后读取数据。如下
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);// 创建一个请求对象
req.Referer = "http://102.106.1.163/menu/s_menu.jsp";//ref,这样是为了欺骗后台说你是从正常的前段页面点击了menu进行过来的请求
CookieContainer cookieCon = new CookieContainer();// 创建一个cookie容器
req.CookieContainer = cookieCon;//额,这个为空就好
req.CookieContainer.SetCookies(new Uri(url), cookieheader);//从全局变量中(内存)还原
HttpWebResponse res = (HttpWebResponse)req.GetResponse();//获取请求结果
StreamReader sr = new StreamReader(res.GetResponseStream(), Encoding.Default);//将请求结果读入到stream类中
string strResult = sr.ReadToEnd();//将流转为字符串
sr.Close();//关闭流
return strResult;//返回结果
刚才说道学校的教务系统使用的是iframe,这样就可以在请求中只是返回成绩那个table dom的东东,然后嵌入到原来的ingle界面中了。这样给我们的便利就是这个返回的请求不需要再reg就直接作为结果显示给用户就好了。
至此,你就看到了成绩了~