【教程】模拟登陆百度之Java代码版
本帖最后由 crifan 于 2013-09-17 20:40 编辑原发于:
【教程】模拟登陆百度之Java代码版
【背景】
之前已经写了教程,分析模拟登陆百度的逻辑:
【教程】手把手教你如何利用工具(IE9的F12)去分析模拟登陆网站(百度首页)的内部逻辑过程
然后又去用不同的语言:
Python的:
【教程】模拟登陆网站 之 Python版(内含两种版本的完整的可运行的代码)
C#的:
【教程】模拟登陆网站 之 C#版(内含两种版本的完整的可运行的代码)
去实现对应逻辑。
此处,继续尝试,用Java代码,实现这套,模拟登陆百度,的逻辑。
【折腾过程】
1.之前已经整理了一些Java代码:
http://code.google.com/p/crifanlib/source/browse/trunk/java/crifanLib.java
现在是:
继续去丰富此套库。
然后把模拟登陆百度的逻辑模拟出来。
2.期间,想要实现Java的函数的默认参数,结果却发现不支持:
【已解决】Java中函数的默认参数
3.然后就是一点点去写代码,去调试了。
4.期间,出错了:
【基本解决】java中没法new:Cannot instantiate the type HttpParams
5.再去搞懂参数配置:
【整理】关于Java中的httpClient中可以传入的参数
6.再继续写代码,期间遇到各种问题,基本都解决了:
【已解决】Java代码中new List时出错:Cannot instantiate the type List<NameValuePair>
【已解决】实现Java中控制台中输入字符串
【无需解决】Java代码new BasicNameValuePair时出错:The constructor BasicNameValuePair(String, boolean) is undefined
【已解决】Java中实现{String, boolean}类型的字典Dict变量
【已解决】Eclipse中用java代码去new Date结果出错:The constructor Date(String) is deprecated
【暂未解决】Eclipse中调试Java代码期间如何修改值
【已解决】Java中的new Date所得的年份异常:传入2043年结果却是3943年
7.最终,完成了,主体代码为:
/**
*
* EmulateLoginBaidu.java
*
*
* Use Java code to emulate login baidu
*
* 【教程】模拟登陆百度之Java代码版
* http://www.crifan.com/emulate_login_baidu_use_java_code
*
*
* v1.0, 2013-09-17
*
*
* 1. need add apache http lib:
* 【已解决】Eclipse的java代码出错:The import org.apache cannot be resolved
* http://www.crifan.com/java_eclipse_the_import_org_apache_cannot_be_resolved/
* 2.need crifanLib.java
* http://code.google.com/p/crifanlib/source/browse/trunk/java/crifanLib.java
*
*
*
* 1. initial version, finally successfully emulate login baidu using java code.
*/
//import java.io.IOException;
import java.util.ArrayList;
//import java.util.Calendar;
//import java.util.Date;
//import java.util.GregorianCalendar;
import java.util.HashMap;
//import java.util.Hashtable;
import java.util.List;
//import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.cookie.Cookie;
//import org.apache.http.impl.cookie.BasicClientCookie;
//import org.apache.http.impl.cookie.BasicClientCookie2;
import org.apache.http.message.BasicNameValuePair;
//import crifanLib;
/**
* @author CLi
*
*/
public class EmulateLoginBaidu {
static crifanLib crl;
/**
* @param args
*/
public static void main(String[] args) {
crl = new crifanLib();
// TODO Auto-generated method stub
EmulateLoginBaiduUsingJava();
}
// emulate login baidu using java code
public static void EmulateLoginBaiduUsingJava()
{
System.out.println("============ 程序说明 ============");
System.out.println("功能:本程序是用来演示使用Java代码去实现模拟登陆百度");
System.out.println("注意事项:部分百度账户,在登陆时会出现:");
System.out.println("1.部分百度账户,在登陆时会出现:");
System.out.println("系统检测到您的帐号疑似被盗,存在安全风险。请尽快修改密码。");
System.out.println("此时,本程序,无法成功模拟登陆,请自行按照提示去修改密码后,就可以了。");
boolean bLoginBaiduOk = false;
List<Cookie> curCookieList;
//step1: login baidu, got cookie BAIDUID
System.out.println("====== 步骤1:获得BAIDUID的Cookie ======");
String strTokenValue = "";
boolean bGotCookieBaiduid = false;
String strBaiduUrl = "http://www.baidu.com/";
HttpResponse baiduResp = crl.getUrlResponse(strBaiduUrl);
curCookieList =crl.getCurCookieStore().getCookies();
crl.dbgPrintCookies(curCookieList, strBaiduUrl);
for(Cookie ck : curCookieList)
{
String cookieName = ck.getName();
if(cookieName.equals("BAIDUID"))
{
bGotCookieBaiduid = true;
}
}
if (bGotCookieBaiduid)
{
System.out.println("正确:已找到cookie BAIDUID");
}
else
{
System.out.println("错误:没有找到cookie BAIDUID !");
}
//step2: login, pass paras, extract resp cookie
System.out.println("====== 步骤2:提取login_token ======");
boolean bExtractTokenValueOK = false;
if(bGotCookieBaiduid)
{
//https://passport.baidu.com/v2/api/?getapi&class=login&tpl=mn&tangram=true
String getapiUrl = "https://passport.baidu.com/v2/api/?getapi&class=login&tpl=mn&tangram=true";
String getApiRespHtml = crl.getUrlRespHtml(getapiUrl);
curCookieList =crl.getCurCookieStore().getCookies();
crl.dbgPrintCookies(curCookieList, getapiUrl);
//bdPass.api.params.login_token='3cf421493884e0fe9080593d05f4744f';
Pattern tokenValP = Pattern.compile("bdPass\\.api\\.params\\.login_token='(?<tokenVal>\\w+)';");
Matcher tokenValMatcher = tokenValP.matcher(getApiRespHtml);
//boolean foundTokenValue = tokenValMatcher.matches(); // will not match, but can search to find it
boolean foundTokenValue = tokenValMatcher.find();
if(foundTokenValue)
{
strTokenValue = tokenValMatcher.group("tokenVal"); //3cf421493884e0fe9080593d05f4744f
System.out.println("正确:找到 bdPass.api.params.login_token=" + strTokenValue);
bExtractTokenValueOK = true;
}
else
{
System.out.println("错误:没找到bdPass.api.params.login_token !");
}
}
//step3: verify returned cookies
if (bGotCookieBaiduid && bExtractTokenValueOK)
{
System.out.println("======步骤3:登陆百度并检验返回的Cookie ======");
/*
//Note:
//here, has verify, not manually update some cookie's domain and expiry
//also can emulate baidu successfully
//do some workaround to makesure here cookie H_PS_PSSID not expire
//
//Thu Sep 17 14:22:08 CST 2043
//Date newExpiryDate = new Date(2043, 9, 17);
Date newExpiryDate = new Date(143, 9, 17);
//Calendar newExpiryCalendar = new GregorianCalendar(2043, 9, 17, 14, 22, 8);
BasicClientCookie hPsPssidCookie = null;
BasicClientCookie dbsvrtmCookie = null;
//int hPsPssidCookieIdx = 0;
curCookieList = crl.getCurCookieList();
for(Cookie ck : curCookieList)
{
if(ck.getName().equalsIgnoreCase("H_PS_PSSID"))
{
//hPsPssidCookieIdx = curCookieList.indexOf(ck);
hPsPssidCookie = (BasicClientCookie) ck;
hPsPssidCookie.setExpiryDate(newExpiryDate);
ck = hPsPssidCookie;
//break;
}
if(ck.getName().equalsIgnoreCase("BDSVRTM"))
{
dbsvrtmCookie = (BasicClientCookie) ck;
dbsvrtmCookie.setDomain(".baidu.com");
dbsvrtmCookie.setExpiryDate(newExpiryDate);
ck = dbsvrtmCookie;
//break;
}
}
crl.setCurCookieList(curCookieList);
*/
String staticPageUrl = "http://www.baidu.com/cache/user/html/jump.html";
List<NameValuePair> postDict = new ArrayList<NameValuePair>();
//ArrayList<NameValuePair> headerDict = new ArrayList<NameValuePair>();
//postDict.add(new BasicNameValuePair("ppui_logintime", ""));
postDict.add(new BasicNameValuePair("charset", "utf-8"));
//postDict.add(new BasicNameValuePair("codestring", ""));
postDict.add(new BasicNameValuePair("token", strTokenValue));
postDict.add(new BasicNameValuePair("isPhone", "false"));
postDict.add(new BasicNameValuePair("index", "0"));
//postDict.add(new BasicNameValuePair("u", ""));
//postDict.add(new BasicNameValuePair("safeflg", "0"));
postDict.add(new BasicNameValuePair("staticpage", staticPageUrl));
postDict.add(new BasicNameValuePair("loginType", "1"));
postDict.add(new BasicNameValuePair("tpl", "mn"));
postDict.add(new BasicNameValuePair("callback", "parent.bdPass.api.login._postCallback"));
//get input baidu username and password
String strBaiduUsername = "";
String strBaiduPassword = "";
Scanner inputReader = new Scanner(System.in);
System.out.println("Please Enter Your:" );
System.out.println("Baidu Username:" );
strBaiduUsername = inputReader.nextLine();
//System.out.println("You Entered Username=" + strBaiduUsername);
System.out.println("Baidu Password:" );
strBaiduPassword = inputReader.nextLine();
//System.out.println("You Entered Password=" + strBaiduPassword);
inputReader.close();
postDict.add(new BasicNameValuePair("username", strBaiduUsername));
postDict.add(new BasicNameValuePair("password", strBaiduPassword));
postDict.add(new BasicNameValuePair("verifycode", ""));
postDict.add(new BasicNameValuePair("mem_pass", "on"));
String baiduMainLoginUrl = "https://passport.baidu.com/v2/api/?login";
String loginBaiduRespHtml = crl.getUrlRespHtml(baiduMainLoginUrl, null, postDict);
//Map cookieNameDict = new Map();
//Map cookieNameDict = new Hashtable();
HashMap<Object, Boolean> cookieNameDict = new HashMap<Object, Boolean>();
cookieNameDict.put("BDUSS", false);
cookieNameDict.put("PTOKEN", false);
cookieNameDict.put("STOKEN", false);
//Set-Cookie: SAVEUSERID=deleted; expires=Mon, 17-Sep-2012 09:45:03 GMT; path=/; domain=passport.baidu.com; httponly,
//cookieNameDict.put("SAVEUSERID", false);
curCookieList = crl.getCurCookieList();
for(Object objCookieName : cookieNameDict.keySet().toArray())
{
String strCookieName = objCookieName.toString();
for(Cookie ck: curCookieList)
{
if(strCookieName.equalsIgnoreCase(ck.getName()))
{
cookieNameDict.put(strCookieName, true);
}
}
}
boolean bAllCookiesFound = true;
for (ObjectobjFoundCurCookie : cookieNameDict.values())
{
bAllCookiesFound = bAllCookiesFound && Boolean.parseBoolean(objFoundCurCookie.toString());
}
bLoginBaiduOk = bAllCookiesFound;
if (bLoginBaiduOk)
{
System.out.println("成功模拟登陆百度首页!" );
}
else
{
System.out.println("模拟登陆百度首页 失败!");
System.out.println("所返回的HTML源码为:" + loginBaiduRespHtml);
}
}
return;
}
}
正常,成功模拟登陆百度的输出为:
8.完整的Eclipse下面的代码下载:
注意:
1.使用此项目,需要导入org.apache.http的库。
详见:
【已解决】Eclipse的java代码出错:The import org.apache cannot be resolved
2.上面的贴出来的代码,用到了对应的我自己的java库,需要的先去下载,才能用上面代码:
crifanLib.java
3.部分百度账户,在登陆时会出现:
系统检测到您的帐号疑似被盗,存在安全风险。请尽快修改密码。
此时,本程序,无法成功模拟登陆,请自行按照提示去修改密码后,就可以了。
【总结】
java在处理http方面的库,相对来说,还是很不方便使用。
只能算是基本够用吧。
用HttpClient就方便许多 本帖最后由 crifan 于 2013-09-20 20:47 编辑
回复 2# rover12421
1.我本来就是用的HttpClient
2.不知道高手有何更好的建议,如何"更方便"?
1.第一次还真没看出你用了httpclient,原来是自己封装了一层
2.httpclient本身就维护了cookies,根本不需要自己处理
3.crifanLib里每次请求都new一个httpClient,根本就不合理,难怪你要自己维护cookies
4.建议你看看xmlrpc对httpclient的封装,那个才叫漂亮,你这样封装还不如不封装,用起来比原来还累.httpclient直接post String,在很多情况下不需要自己那么累的构建一个list,很多时候也使用者也不太愿意把一个post的string变成list来用
5.校验也可以不用从cookies里判断,既然已经的到了请求的html,为何不从html里找特征校验呢 回复 4# rover12421
1.第一次还真没看出你用了httpclient,原来是自己封装了一层
2.httpclient本身就维护了cookies,根本不需要自己处理
有空再去看看,httpClient是如何自动处理cookie的。
3.crifanLib里每次请求都new一个httpClient,根本就不合理,难怪你要自己维护cookies
谢谢批评,抽空去研究研究。
4.建议你看看xmlrpc对httpclient的封装,那个才叫漂亮,你这样封装还不如不封装,用起来比原来还累.httpclient直接post String,在很多情况下不需要自己那么累的构建一个list,很多时候也使用者也不太愿意把一个post的string变成list来用
post data,是list而不是处理后的xxx=yyy&xxx=yyy,目的就是减少用户手动处理。
“很多时候也使用者”
可否给出一些比较经典的用例,供偶参考看看?
5.校验也可以不用从cookies里判断,既然已经的到了请求的html,为何不从html里找特征校验呢
校验的思路,从html找特征和cookie中找cookie,理论上,都是可以的。
但是个人觉得cookie更靠谱。
另外,库中提供了get和set cookie的功能,目的是为了有些特殊情况下,需要用户手动修改cookie,比如更改domain,供访问另外一个url使用。
所以才有操作cookie的功能。
而此处,正好就可以用得上获得当前cookie的功能。
到底是用html还是cookie去校验,纯属个人偏好。
回复 5# crifan
4.很多时候都是先截包,但是截包工具能直接复制出来的肯定是String,如果需要修改的很多,肯定是希望直接使用string,而不是拆分成list.如果每一个都要参数都要该,那当然是list比较好,但是就封装而言.就算是需要list.那应该也要做到,用户直接给一个hashmap就能完成的动作,而不是让用户自己主动去做一个BasicNameValuePair的list,这样的封装意义不大.建议这样的直接传里一个hashmap.至于hashmap怎么变成一个BasicNameValuePair的list才是你封装要做的事.
怎么post一个string,给你一个例子:StringEntity entity = new StringEntity(string, charset);
entity.setContentType("application/x-www-form-urlencoded; charset=" + charset);
post.setEntity(entity);HttpEntity有很多继承类,可以多看看
---------------
httpclient的本身就支持cookies的设置和修改.而且我并没看出的你对cookies的封装比httpclient的好,对用户来说,从你的封装操作cookies和直接对httpclient上操作cookies步骤是一样的,而对httpclient来说,你却反而多了一步,并且你并没给用户带来直接对httpclient更方便和快捷的一面,这样的封装有必要吗?
如果要封装cookies操作,当然是要提供直接针对"key","vaule"的操作(添加,删除,修改),这是httpclient原本没有的,而且能大大方便用户为达到此目的代码复杂度.
--------------
封装固然是好,但是要先对封装的对象做一个比较深入一点的了解.不要为了封装而封装. 回复 6# rover12421
"很多时候都是先截包,但是截包工具能直接复制出来的肯定是String,如果需要修改的很多"
我之前所了解到的:
肯定不建议,去直接拷贝出来从抓包工具分析出来的post data的string
因为:
我所遇到的,更多的时候,key和value中,key虽然不变,
但是value往往和你当前的上下文相关。
所以,最好还是:
通过抓包工具分析该value是如何获得的
然后再写代码去模拟。
对于不建议直接拷贝出来的内容,之前还写了个,供之前有人直接拷贝粘贴cookie值或者其他值,但是程序不工作的人的一些提示:
【总结】静态网页抓取,动态网页抓取,模拟登陆的注意事项和心得
中的“写程序模拟执行过程时,不能直接拷贝分析而得的数据,而要自己用程序模拟出来”
"肯定是希望直接使用string,而不是拆分成list."
对此,我个人不是很赞同:
我倒是觉得,至少我个人觉得,对于post string的话
正常的用户的用法,都是:
通过key和value去设置对应的值
而设置完毕后,再调用
Python中的urllib.urlencode
C#中的HttpUtility.UrlPathEncode
java中的UrlEncodedFormEntity
go语言中的url.Values
去进行相关的encode,才得到对应post data的string的。
在这点上,个人认为,先拿到key,value的键对再去encode为post string,更加符合http领域,通用的做法。
否则,各种语言的http的库,就至少没有必要再提供相关的encode的了。
“用户直接给一个hashmap就能完成的动作,而不是让用户自己主动去做一个BasicNameValuePair的list,”
至于key,value的键对,
由于个人不是很熟悉java
虽然之前也用过hashmap,但是此处是参考别人的做法,觉得用上BasicNameValuePair的list。
此点,经过你这个提醒,我也觉得,用hashmap可能更合适。
其也是我的本意:
就像Python中的dict,本来就应该是字典类的变量,保存这样的信息,更合适。
“HttpEntity有很多继承类,可以多看看”
是的,抽空再好好研究。谢谢提醒。
“httpclient的本身就支持cookies的设置和修改.”
我后来才看懂你的意思:
你是说,我封装后,手动操作了cookie?
实际上不是,我只是:
提供给外接get或set cookie的接口
方便用户,如果有需要的话,去操作cookie而已。
并没有额外进行cookie的管理。
因为就像你说的,httpClient本身就已经管理了cookie了。
“封装固然是好,但是要先对封装的对象做一个比较深入一点的了解.不要为了封装而封装.”
关于你这个理念,完全赞同。
我不会去为了什么而什么的。
只是,至少以自己要是一个普通用户的角度出发,觉得有封装的必要,才会去封装。
目前来说,对于java版本的getUrlRespHtml等函数来说,
自己对自己的评价是:
还是值得封装的,只是个别做法,还有待商榷和优化而已。
无论如何,欢迎继续探讨。谢谢提醒。
本帖最后由 rover12421 于 2013-09-22 16:28 编辑
说实话,你的封装已经算不上封装了,因为你已经把原本httpclient提供的强大功能给禁锢起来只能适用你需要的情况了.
就因为你把原有的功能禁锢起来,才有了,为了提供原本已经有cookies获取和设置功能,你为了能的到这个功能不的不自己再写一个设置和获取的函数
封装很多时候是为了减少重复代码,精简代码而做的,比如你写的getUrlRespHtml,就想一步获取源码,downlodFile也是一样,想一步做到下载.但如果你熟悉httpclient你会封装的更好.
像设置header的地方.其实封装很简单,没必要让用户提供一个key-value的链,只要提供一个简单的设置头部key-value的方法既可.设置一个key-value是一行代码,往key-value的链里添加一行,也是一行代码,有时候还往往多出new这个链的一行代码.这样的封装就没必要.
而且前一种会更清晰,下一步的方法也少了一个参数.
我看你的crifanLib.java里原本打算使用外部传来的key-value的链的,不知道为后来又不用了呢? 本帖最后由 crifan 于 2013-09-22 16:53 编辑
回复 8# rover12421
"我看你的crifanLib.java里原本打算使用外部传来的key-value的链的,不知道为后来又不用了呢?"
你指的是“headerDict”?
简答:
是想要用到的,只是暂时没时间,没精力,去完善这个库函数而已。
详解:
1.我此处的,java版本的getUrlRespHtml(以及还没来得及写的getUrlResponse)
都还是未完成的版本,算是初级版本,从功能角度来说,只算0.2这种级别的,里1.0的完善的功能,差很多。
像getUrlRespHtml这样的函数,对于java版本,其实还需要添加太多功能
比如:
支持(多种格式类型的)压缩
代理
设置各种默认的常用的header参数:什么user-agent等等
和用户手动设置的header参数:除了常见之前的,运行用户手动添加别的
允许用户操作cookie(以便实现更改domain等动作,实现后续访问不同的url可以真正实现发送cookie等等)
支持手动设置charset或默认自动解码为unicode
支持各种timeout的设置
等等等等。。。。
等以后有空,会继续更新的。
2. 你要是感兴趣,可以去看看我的:
v8.5的crifanLib.cs
和:
v4.7的:crifanLib.py
里面都有类似的:
getUrlRespHtml
getUrlResponse
等函数的。
那些,是已经完善(经过N次修补bug,功能增加的)更新后的。
而且还是,又额外花了很多精力,去写教程,说明如何使用该库:
详解crifan的Python库:crifanLib.py
详解crifan的C#库:crifanLib.cs
3.除此之前,还有其他语言的各种库函数,以及C#,Python等出了crifanLib的函数,都早已放出:
crifanLib
总体算上,java版本的crifanLib.java
只算功能或代码量上的,5%左右(应该是不到10%)的。
而我折腾java,也只算业余的业余,但是尽量是,接触到了,就整理出来,我认为有用的功能。
更多功能,只能等我有空再更新。
其他我写的很多东西,比如:
crifanLibAws.cs
算属于:domain lib,属于某个特定领域的,这些东西,只有用到人,才知道其价值,才知道可以帮其省多少精力的。
4.总之:
(1)当前的java版本的getUrlRespHtml,很不完善,有待后续更新。
(2)估计,只有等我的crifanLib.java,发展到1.0之后,你才能明白,我为何要封装:
保证绝大多数人的,绝大多数复杂的请求,只需通过调用getUrlRespHtml或getUrlResponse,即可完成其目的(获得需要的html或response)。
话说前几天用java写了投票程序。
还是比之前用c++写快的多。。。
页:
[1]
2