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.  提交登录页面所需要的字段和验证码,字段尽可能全部提交上去。 

 

其中,难度都在第三步的识别图片。

想要识别图片,我们需要很多额外的工作和处理,一些参考:

识别图片内容主要流程

预处理:检测是正确的图像格式,转换到合适的格式,压缩,剪切出ROI,去除噪音,灰度化,转换色彩空间。

检测:找出文字所在的主要区域;

前处理:验证码识别,一般要做文字的切割。

训练:通过各种模式识别,机器学习算法,来挑选和训练合适数量的训练集。不是训练的样本越多越好。过学习,泛化能力差的问题可能在这里出现。这一步不是必须的,有些识别算法是不需要训练的。

识别:输入待识别的处理后的图片,转换成分类器需要的输入格式,然后通过输出的类和置信度,来判断大概可能是哪个字母。识别本质上就是分类。

关键概念

图像处理:一般指针对数字图像的某种数学处理。比如投影,钝化,锐化,细化,边缘检测,二值化,压缩,各种数据变换等等。

1.二值化:一般图片都是彩色的,按照逼真程度,可能很多级别。为了降低计算复杂度,方便后续的处理,如果在不损失关键信息的情况下,能将图片处理成黑白两种颜色,那就最好不过了。

2.细化:找出图像的骨架,图像线条可能是很宽的,通过细化将宽度将为1,某些地方可能大于1。不同的细化算法,可能有不同的差异,比如是否更靠近线条中间,比如是否保持联通行等。

3.边缘检测:主要是理解边缘的概念。边缘实际上是图像中图像像素属性变化剧烈的地方。可能通过一个固定的门限值来判断,也可能是自适应的。门限可能是图像全局的,也可能是局部的。不能说那个就一定好,不过大部分时候,自适应的局部的门限可能要好点。被分析的,可能是颜色,也可能是灰度图像的灰度。

机器视觉:利用计算机来模式实现人的视觉。比如物体检测,定位,识别。按照对图像理解的层次的差别,分高阶和低阶的理解。

模式识别:对事物或者现象的某种表示方式(数值,文字,我们这里主要想说的是数值),通过一些处理和分析,来描述,归类,理解,解释这些事物,现象及其某种抽象。

人工智能:这种概念比较宽,上面这些都属于人工智能这个大的方向。简单点不要过分学院派的理解就是,把人类的很智能的东西给模拟出来协助生物的人来处理问题,特别是在计算机里面。

经验: 目前这方面的技术难点主要在于验证吗图片的分割方面,对于识别的匹配,OCR技术已经很成熟了,完全可用于验证码图片的识别,但是复杂的验证码图片大多粘连,分割处理比较麻烦

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。