首页 > 代码库 > httpClient模拟浏览器登陆之谜
httpClient模拟浏览器登陆之谜
httpClient能够模拟浏览器进行自动登陆,但是如果页面上加了一个小小的验证码,自动登陆就会变的非常非常之复杂。
下面我们讨论的问题,都是不考虑有验证码等防止自动化登陆的情况下进行处理。
利用httpClient进行一个简单的登陆示例:
packagetest.ffm83.commons.httpClient;
importjava.util.ArrayList;
importjava.util.List;
importorg.apache.http.HttpEntity;
importorg.apache.http.NameValuePair;
importorg.apache.http.client.entity.UrlEncodedFormEntity;
importorg.apache.http.client.methods.CloseableHttpResponse;
importorg.apache.http.client.methods.HttpPost;
importorg.apache.http.impl.client.CloseableHttpClient;
importorg.apache.http.impl.client.HttpClients;
importorg.apache.http.message.BasicNameValuePair;
importorg.apache.http.util.EntityUtils;
/**
*httpClient 模拟用户登录(无验证码)
* 必须要使用cookie的方式
* 基于4.x版本
* @author范芳铭
*/
public class EasyLogin {
public static void main(String[] args)throws Exception {
CloseableHttpClienthttpclient = HttpClients.createDefault();
try {
// 模拟用户登录
HttpPost httpPost = new HttpPost(
"http://XXX/login.action");//指向一个没有验证码的登录页面
List<NameValuePair>nvps = newArrayList<NameValuePair>();
nvps.add(new BasicNameValuePair("userName","ffm83"));// 用户名对应的key
nvps.add(new BasicNameValuePair("password","123456"));// 密码对应的key
httpPost.setEntity(new UrlEncodedFormEntity(nvps));
CloseableHttpResponseresponse = httpclient.execute(httpPost);
try {
System.out.println(response.getStatusLine());
HttpEntityentity = response.getEntity();
System.out.println(response.toString());
EntityUtils.consume(entity);
}finally {
response.close();
}
}finally {
httpclient.close();
}
}
}
返回的结果可能为
HTTP/1.1 200 OK …
返回的结果也可能为:
HTTP/1.1 302 Moved Temporarily …
同样的代码,为什么有可能返回不同的结果呢? 如果代码逻辑没有问题,那么问题最可能出在不同的访问页面上。
仔细分析下,原来有的应用会进行重定向,也叫做自动转向。
根据RFC2616中对自动转向的定义,主要有两种:301和302。301表示永久的移走(MovedPermanently),当返回的是301,则表示请求的资源已经被移到一个固定的新地方,任何向该地址发起请求都会被转到新的地址上。302表示暂时的转向,比如在服务器端的servlet程序调用了sendRedirect方法,则在客户端就会得到一个302的代码,这时服务器返回的头信息中location的值就是sendRedirect转向的目标地址。
HttpClient支持自动转向处理,但是象POST和PUT方式这种要求接受后继服务的请求方式,暂时不支持自动转向,因此如果碰到POST方式提交后返回的是301或者302的话需要自己处理。就像刚才在POSTMethod中举的例子:如果想进入登录BBS后的页面,必须重新发起登录的请求,请求的地址可以在头字段location中得到。不过需要注意的是,有时候location返回的可能是相对路径,因此需要对location返回的值做一些处理才可以发起向新地址的请求。
另外除了在头中包含的信息可能使页面发生重定向外,在页面中也有可能会发生页面的重定向。引起页面自动转发的标签是:<meta http-equiv="refresh" content="5;url=http://www.ibm.com/us">。如果你想在程序中也处理这种情况的话得自己分析页面来实现转向。需要注意的是,在上面那个标签中url的值也可以是一个相对地址,如果是这样的话,需要对它做一些处理后才可以转发。
明白了这些道理之后,如果我们要获取登陆后页面跳转的情况,我们心里就有数了,只要获取下一个页面就可以了。
不过我们在下面的示例里,把location里面的值直接取出来,简单粗暴的处理了。
现在,利用httpClient提供的cookie方法,能创建出比较简单的方法。业务逻辑,首先模拟登陆,然后访问需要登陆后才能访问的页面。
packagetest.ffm83.commons.httpClient;
importjava.util.ArrayList;
importjava.util.List;
importorg.apache.http.HttpEntity;
importorg.apache.http.NameValuePair;
importorg.apache.http.client.entity.UrlEncodedFormEntity;
importorg.apache.http.client.methods.CloseableHttpResponse;
importorg.apache.http.client.methods.HttpGet;
importorg.apache.http.client.methods.HttpPost;
importorg.apache.http.cookie.Cookie;
importorg.apache.http.impl.client.BasicCookieStore;
importorg.apache.http.impl.client.CloseableHttpClient;
importorg.apache.http.impl.client.HttpClients;
importorg.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
/**
*httpClient 模拟用户登录(无验证码),然后用同一个httpclient作为会话保持的方法,读取下一个登陆后的一个页面
* 两次的Set-Cookie:是一致的;基于4.x版本
*
* @author范芳铭
*/
public class EasyLoginNextPageA {
public static void main(String[] args)throws Exception {
BasicCookieStorecookieStore = newBasicCookieStore();
CloseableHttpClienthttpclient = HttpClients.custom()
.setDefaultCookieStore(cookieStore).build();
try {
// 模拟用户登录
HttpPosthttpget = newHttpPost(
"http://XXXX/login.action");//指向一个没有验证码的登录页面
List<NameValuePair>nvps = newArrayList<NameValuePair>();
nvps.add(new BasicNameValuePair("userName","ffm83"));// 用户名对应的key
nvps.add(new BasicNameValuePair("password","123456"));// 密码对应的key
httpget.setEntity(new UrlEncodedFormEntity(nvps));
CloseableHttpResponserespLogin = httpclient.execute(httpget);
try {
HttpEntityentity = respLogin.getEntity();
System.out.println(respLogin.toString());
System.out.println("Login form get: "
+respLogin.getStatusLine());
EntityUtils.consume(entity);
System.out.println("Initial set of cookies:");
List<Cookie>cookies = cookieStore.getCookies();
if (cookies.isEmpty()) {
System.out.println("None");
}else {
for (int i = 0; i < cookies.size(); i++) {
System.out.println("- " + cookies.get(i).toString());
}
}
}finally {
respLogin.close();
}
// 利用会话保持,继续下一个页面访问
HttpGethttpGetNext = newHttpGet(
"http://XXXX/main.action");
CloseableHttpResponserespNext = httpclient.execute(httpGetNext);
try {
System.out.println(respNext.getStatusLine());
HttpEntityentityNext = respNext.getEntity();
System.out.println("Post logon cookies:");
List<Cookie> cookies =cookieStore.getCookies();
if (cookies.isEmpty()) {
System.out.println("None");
} else {
for (int i = 0; i < cookies.size(); i++) {
System.out.println("- " + cookies.get(i).toString());
}
}
System.out.println(EntityUtils.toString(entityNext));//把结果打印出来看一下
EntityUtils.consume(entityNext);
}finally {
respNext.close();
}
}finally {
httpclient.close();
}
}
}
运行结果如下:
两次的name: JSESSIONID][value:AB6F2A1E028F4CC83617F3C5EC0EE8DA都是一样的,效果非常好。
如果把前面的初始化代码修改下:
BasicCookieStorecookieStoreLogin = newBasicCookieStore();
BasicCookieStorecookieStoreNext = newBasicCookieStore();
CloseableHttpClienthttpclientLogin = HttpClients.custom()
.setDefaultCookieStore(cookieStoreLogin).build();
CloseableHttpClienthttpclientNext = HttpClients.custom()
.setDefaultCookieStore(cookieStoreNext).build();
那么第二次就会被拒绝,因为cookie已经不一样了。
关于验证码的处理。
使用自动登陆工具,处理各种验证码,都是一种挑战,但是对普通的验证码而言,还是有一些办法的;但是一些复杂的验证码,比如CSDN写博客的时候的验证码,那是非常难处理的,我睁大眼睛看有时候都会看错,想要机器能够自动识别,难度过大。
涉及的知识点主要是图论和数论。
写一下httpclient访问有验证码登陆网页的思路:
1. 首先需要获取验证码页面;
2. 把它以图片的格式保证下来;
3. 识别图片,获取图片的内容
4. 把跟踪到的cookie保存下来。cookie必须要跟抓包工具看到的cookie一致。
5. 提交登录页面所需要的字段和验证码,字段尽可能全部提交上去。
其中,难度都在第三步的识别图片。
想要识别图片,我们需要很多额外的工作和处理,一些参考:
识别图片内容主要流程
l 预处理:检测是正确的图像格式,转换到合适的格式,压缩,剪切出ROI,去除噪音,灰度化,转换色彩空间。
l 检测:找出文字所在的主要区域;
l 前处理:验证码识别,“一般”要做文字的切割。
l 训练:通过各种模式识别,机器学习算法,来挑选和训练合适数量的训练集。不是训练的样本越多越好。过学习,泛化能力差的问题可能在这里出现。这一步不是必须的,有些识别算法是不需要训练的。
l 识别:输入待识别的处理后的图片,转换成分类器需要的输入格式,然后通过输出的类和置信度,来判断大概可能是哪个字母。识别本质上就是分类。
关键概念
图像处理:一般指针对数字图像的某种数学处理。比如投影,钝化,锐化,细化,边缘检测,二值化,压缩,各种数据变换等等。
1.二值化:一般图片都是彩色的,按照逼真程度,可能很多级别。为了降低计算复杂度,方便后续的处理,如果在不损失关键信息的情况下,能将图片处理成黑白两种颜色,那就最好不过了。
2.细化:找出图像的骨架,图像线条可能是很宽的,通过细化将宽度将为1,某些地方可能大于1。不同的细化算法,可能有不同的差异,比如是否更靠近线条中间,比如是否保持联通行等。
3.边缘检测:主要是理解边缘的概念。边缘实际上是图像中图像像素属性变化剧烈的地方。可能通过一个固定的门限值来判断,也可能是自适应的。门限可能是图像全局的,也可能是局部的。不能说那个就一定好,不过大部分时候,自适应的局部的门限可能要好点。被分析的,可能是颜色,也可能是灰度图像的灰度。
机器视觉:利用计算机来模式实现人的视觉。比如物体检测,定位,识别。按照对图像理解的层次的差别,分高阶和低阶的理解。
模式识别:对事物或者现象的某种表示方式(数值,文字,我们这里主要想说的是数值),通过一些处理和分析,来描述,归类,理解,解释这些事物,现象及其某种抽象。
人工智能:这种概念比较宽,上面这些都属于人工智能这个大的方向。简单点不要过分学院派的理解就是,把人类的很“智能”的东西给模拟出来协助生物的人来处理问题,特别是在计算机里面。
经验: 目前这方面的技术难点主要在于验证吗图片的分割方面,对于识别的匹配,OCR技术已经很成熟了,完全可用于验证码图片的识别,但是复杂的验证码图片大多粘连,分割处理比较麻烦
httpClient模拟浏览器登陆之谜