前言
前几天刚刚接触了Java这边的关于HTTP的一个工具包—HttpClient , 那么就想借此机会练练手, 用这个工具进行对微博的模拟登录, 简单的获取一下微博的数据 , 但是大家可以不必执着于这个框架的学习 , 还可以选择其他的 , 就在写博客的时候发现了更多的网络框架 , 比如okHttp , Retrofit , OpenFeign , WebMagic , 可以着重考虑使用上面的框架来实现微博模拟登录 , 重点更加的熟悉http请求的运用方式
本人学识尚浅 , 有什么不对的地方烦请指出 .
本次演示模拟 , 仅仅为学习交流 ! 不得用于其他非法用途 !
一. HTTPClient
那么首先我们是先介绍一下HttpClient , 套一下百度百科的话嘿嘿
HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议 .
HttpClient的首页地址: https://hc.apache.org/httpclient-legacy/
二. 基本流程
本次学习测试的对象为微博网页版(https://m.weibo.cn/)
模拟登录大概有下面这几个步骤 :
- 首先模拟访问首页 , 首页会带有一些cookie的信息 , 保存这些cookie
- 拿到首页的cookie之后 , 接下来我们移步账号密码登录页面, 不要进错了 , 输入账号密码之后 , 发送登录ajax请求 ( 这一步请求结果的应该都是让用手机号验证 , 我在登录时 , 还没遇到不验手机号的情况 )
- 根据上一步ajax请求中响应体中JSON数据中的跳转链接 , 跳转到指定的验证页面
- 访问验证链接首先会进行一次302跳转 , 这一步阻止链接跳转 , 获取该链接携带的cookie , 获取该链接的响应头(Response Header)里的 location 的值
- 根据location进行一次模拟跳转访问 , 这次跳转之后是验证手机号页面
- 访问获取手机验证码链接 , 会带着之前的cookie发送" 发短信 "的请求 , 响应体中json数据返回 " 输入验证码 " 的页面的链接 ( 这个链接没有什么作用 )
- 收到验证码之后 , 用验证码补全核验验证码的链接 , 带着之前的cookie访问 , 之后获取Json数据中的跳转链接
- 访问上一步跳转链接, 接下来将进行4次302跳转, 阻止每一次跳转并获取每次的携带的cookie
- 最终跳转到微博主页(https://m.weibo.cn/)
为什么不直接在首页使用手机号验证登录 ?
直接的话要用滑块拼图进行验证 , 滑块拼图验证这个需要更加深入的研究一下怎么加密的, 咱也是刚刚接触这个, 所以就选择了账号密码登录
三. 小提示
- 浏览器接收到302状态码的请求时, 自动根据 Response Header里的location里的链接进行跳转
- http请求的Header的是不区分大小写的
- 微博的json数据里有retcode时 , 为100000是为本次请求结果是正确的
- 浏览器会默认的为每次请求添加一些header , 这些header可能作为服务器的判定条件 , 如果不了解的可以移步
HTTP headers 文档 : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers - cookie中有一些参数 , 有些参数决定的cookie在浏览器中该怎么使用 , 如果不了解 , 移步
HTTP header cookies 文档 : https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cookie
HTTP set-Cookie 文档: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
HTTP cookies 文档 : https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies - 微博的每天大约只能发3次码 , 所以想要测试的时候脑子要清晰一点
四. 准备工作
开始写代码之前我们需要准备一下需要的东西
首先先导入一下必须的jar包 , 点击组件名字就可以跳转到maven对应的坐标主页
- HttpClient工具包 , 一共有3个组件 , httpclient4.5.13 , httpcore4.4.14 , httpmime4.5.13
- jackson工具包 , 一共3个组件 , jackson-core2.12.4 , jackson-databind2.12.4 , jackson-annotations2.12.4
五. 基本的方法&用法
如果你还不熟悉HttpClient , 那么可以先移步这里了解一下HttpClient基本使用方法
为了避免写代码过程中频繁的获取cookie,设置请求头
我自己根据HttpClient自己封装了一个Http执行器和cookie容器 , 在文章的最后介绍里面的方法
我们先看一下以下的信息 , 了解一下问题的起源
问题场景 :
cookie在浏览器中的保存方式
cookie的domain属性 , 现实生活中 , 在浏览器中进行登录时 , 浏览器会自动将cookie信息保存到浏览器中 , 这个保存并不是简单的保存到列表中 , 而是根据每一个网站的域名建立一个容器 , 当我们再次访问时该网站时 , 会自动把对应容器内的cookie带入到请求中 .
HttpClient没有类似浏览器的cookie储存方式
经过翻阅资料 , 我发现HttpClient储存cookie的方式是把每一次请求之后的cookie放入到CookieStore对象中 , 翻看源码 , 发现CookieStore内部是由一个TreeSet存储Cookie , CookieStore只提供了一个获取全部cookie的方法 , 显然不符合浏览器Cookie存取规则
Chrome浏览器会自动给每个请求加个一些Header
当我们在Chrome浏览器中调试网页时, 我们会发现 , 每次请求都会携带一些默认的Header , 如Accept ,
Accept-* , sec-fetch-* , sec-ch-* 等一系列与网页或者与我们的操作相关的header , 用来给后端服务器提供更多信息或者提升web安全
为了避免这些繁琐的步骤
六. 具体流程
首先新建一个Java项目 , 将准备工作中的Jar包导入到该项目中 ,
由于我的Clients执行器会自动根据访问的链接将header设置好 , 自动的在访问成功之后将cookie保存到指定域名的容器内, 接下来的流程中 , 将不会手动的写保存cookie的代码和设置header的代码
将json转化为对象和将cookie写入到本地 使用的是 Jackson工具包
ObjectMapper中的 readValue方法可以将JSON字符串解析为对象
ObjectMapper中的 writeValue方法可以将对象解析成JOSN字符串写入到本地文件
一. 模拟访问首页 , 访问登录页面
final String loginName = "12345678900";
final String password = "xxxxxxxxxx";
final String weibo = "https://m.weibo.cn/";
final String weiboPassportHost = "https://passport.weibo.cn";
final String loginURL = "https://passport.weibo.cn/sso/login";
//创建执行器
final Clients clients = new Clients();
//首先模拟访问首页,拿到cookie,然后发送登录请求
Request login = clients.createGet(weibo).next("POST",loginURL);
//将登录用到的参数转化为请求参数格式的字符串,放入请求体, param如下
//username,password,savestate,r,ec,pagerefer,entry,wentry,loginfrom,
//client_id,code,qq,mainpageflag,hff,hfp
login.setReqParams( new LoginParam(loginName,password).toString() );
//执行login,如果不成功抛出异常
if (login.execute() != 200) throw new RuntimeException("login.execute() != 200");
//将返回的json转化为对象
BasicData loginData = genBasicData(login.getRespText());
//判断json中retcode是否正确,50050011表示需要验证,50011002表示密码错误
if (loginData.getRetcode() != 50050011)
System.out.println("loginInfo.getMsg():"+loginData.getMsg());
if (loginData.getRetcode() == 50011002)
throw new RuntimeException(loginData.getMsg());
//如果是需要验证则进入验证环节,获取json中的验证链接
String verifyURL = (String)loginData.getData().get("errurl");
//示例结果展示:
{
"retcode": 50050011,
"msg": "请完成验证",
"data": {
"username": "12345678900",
"errurl": "https://passport.weibo.cn/verify/index?id=2YmRhIPz8AARDo_NnaoN07O40OLGtda8KBWxvZ2lu&showmenu=0&r=https%3A%2F%2Fm.weibo.cn%2F",
"errline": 727
}
}
二. 跳转验证链接 , 向手机号发送短信
final String queryCaptchaURL = "https://passport.weibo.cn/signin/secondverify/ajsend?number=1&mask_mobile=";
//把手机号的3到9位变成星号12345678900 --> 12********00
String hiddenPhone = StringUtil.hiddenString(loginName, 2, 9);
//访问验证页面的链接,会302跳转,获取cookie , 然后发送短信
Request verify = clients.createGet(verifyURL)
.next(queryCaptchaURL+ hiddenPhone +"&msg_type=sms");
//执行请求,如果不成功抛出异常
if (verify.execute() != 200) throw new RuntimeException("verify.execute() != 200");
//将结果转化为对象然后判断请求是否成功
BasicData verifyData = genBasicData(verify.getRespText());
if (verifyData.getRetcode() != 100000)
throw new RuntimeException(verifyData.getMsg());
//验证手机验证码的页面链接,这个链接不用
String verifyCaptchaJumpURL = weiboPassportHost + verifyData.getData().get("url");
三. 输入手机验证码 , 发送验证请求
final String verifyCaptchaPageURL = "https://passport.weibo.cn/signin/secondverify/check";
final String verifyCaptchaURL = "https://passport.weibo.cn/signin/secondverify/ajcheck?msg_type=sms&code=https://passport.weibo.cn/signin/secondverify/ajcheck?msg_type=sms&code=";
//输入验证码
Scanner in = new Scanner(System.in);
String captcha = "";
System.out.print("输入验证码: ");
//isCaptcha判断验证码输入的是否正确(6位纯数字)
while(!isCaptcha(captcha = in.next()))
System.out.print("输入有误,请重新输入: ");
// 验证验证码
HttpGet verifyCaptcha = clients.createGet(verifyCaptchaURL + captcha)
.setReferer(verifyCaptchaPageURL);
// 执行请求,如果不成功抛出异常
if (verifyCaptcha.execute() != 200) throw new RuntimeException("verifyCaptcha.execute() != 200");
//将结果转化为对象然后判断请求是否成功
BasicData verifyCaptchaData = genBasicData(verifyCaptcha.getRespText());
if (verifyCaptchaData.getRetcode() != 100000)
throw new RuntimeException(verifyCaptchaData.getMsg());
//拿到最后的跳转链接
String endURL = (String)verifyCaptchaData.getData().get("url");
四. 进行4次302跳转 , 之后保存cookie到本地
final String filePath = "src/main/resources/";
//进行4次302跳转,并保存每一次跳转的cookie,这里自动跳转收集了
clients.createGet(endURL).execute();
//保存cookie信息到本地
String path = filePath + "weiboCoookies.json";
JsonUtils.writeValueAsFile(new File(path),clients.getWebCookies().toArray());
五. 查看模拟登录后的Cookie
至此模拟登录就完成了 , 我们带着最后的cookie就可以游历微博了
最后如果正常结束 , 我们大概能够得到21条cookie , cookie的数量还是挺多的
名字 | 域名 | 名字 | 域名 |
---|---|---|---|
SUB | .sina.com.cn | SSOLoginState | .weibo.com |
SUBP | .sina.com.cn | ALC | login.sina.com.cn |
ALF | .sina.com.cn | LT | login.sina.com.cn |
SRF | .passport.weibo.com | SSOLoginState | .weibo.cn |
SRT | .passport.weibo.com | MLOGIN | .weibo.cn |
XSRF-TOKEN | m.weibo.cn | SUB | .weibo.cn |
FID | .passport.weibo.cn | _T_WM | .weibo.cn |
SUBP | .sina.cn | WEIBOCN_FROM | .weibo.cn |
SUB | .sina.cn | SUBP | .weibo.cn |
SUBP | .weibo.com | M_WEIBOCN_PARAMS | .weibo.cn |
SUB | .weibo.com |
可以看到登录之后微博所给的cookie还是挺全的 , 覆盖微博的各个根域 , 这个cookie虽然是从手机页面上获取的 , 但是cookie域仍然不影响我们在PC网页上使用他
七. 封装的HttpClient
我们看完了整个代码流程 , 可能对某些方法感到不解 , 在这里我会介绍一下其中的部分方法 , 想要看更多的方法的话 , 还是推荐下载下来看 , 代码里写的基本上都有中文的javadoc注释 , 代码可能存在bug , 毕竟是刚写的哈 , 发现了也见谅
资源的链接 :
虽然说是封装的HttpClient , 但是跟HttpClient耦合度还是比较低的 , 可以对接其他的HTTP框架 , 并且内容也比较简单 , 大家都可以尝试封装 , 在封装的过程中 , 都将会学习更多的与HTTP协议相关的知识
Request — 请求对象
我封装的地方使用都是我自己定义的Request接口 ,
个人觉得使用一个Request对象即可 , 有需要时将Request强转至对应的类型即可
创建HttpGet类继承HttpClient的HttpGet类 , 然后实现Request接口 , HttpPost也是如此
方法 | 描述 |
---|---|
int status() | 获取本次请求的状态码 , 初始值为0 , 执行后刷新 |
int execute() | 执行本次请求 , 并返回状态码 |
String getRespText() | 获取响应体的内容 |
Request next(String url) | 默认执行自身 , 并返回一个根据URL创建的请求 适用于自身执行之后不需要获取响应体的请求调用 , 下一个请求的方法默认自身的一样 |
Request next(String method,String url) | 指定了方法的next方法 , 同上 |
Request next(String method,String url,boolean executeCurrentReq) | 指定方法并且指定是否执行本身 , true为执行 |
URL getURL() | 获取请求的URL对象(是我定义的 , 非JDK的) |
String getMethod() | 获取请求的方法 |
Response getResponse() | 获取请求的结果对象(自定义的) |
HttpResponse getSrcResponse() | 获取HttpClient的结果结果对象 |
void setCookie(Cookie cookie) | 设置Cookie到cookie容器中 |
void setTempCookie(Cookie cookie) | 设置一些临时的cookie ,执行的会带上这些cookie , 优先级大于在cookie容器中的Cookie |
Collection<Cookie> getTempCookies() | 获取设置的临时的Cookie |
Request setReqParams(String reqParams) | 设置请求参数 |
String getReqParams() | 获取设置的请求参数 |
Request setReferer(String url) | 设置referer , 之后会根据referer重新设置一些header |
Request setReferer(URL url) | 同上 |
Request setIgnoreCookies(String… name) | 设置本次请求中不需要的携带的cookie |
String[] getIgnoreCookies() | 获取设置的不需要的携带的cookie |
Cookie[] getResponseCookies() | 获取结果的中的cookie对象 , 这些cookie对象来源于cookie容器,修改这些cookie将影响在容器中的存值 |
增删改Header的方法 | 之后是一些增删改Header的方法与HttpMessage中包含的方法一致,不在这展示了 |
Clients — Http请求执行器
方法 | 描述 |
---|---|
HttpGet createGet(String url) | 创建一个HttpGet对象 , 为其设置默认的Header |
HttpGet createPost(String url) | 创建一个HttpPost对象 , 为其设置默认的Header |
Request createRequest(String method,String url) | 创建一个Request对象 , 为其设置默认的Header |
Response execute(Request req) | 执行请求 , 并自动收集cookie |
八. 结语
以上就是本篇文章的全部内容
**本次项目的完整源码CSDN链接 : ** https://download.csdn.net/download/m0_46344146/21712800
欢迎大家在评论区提出意见和建议 !
如果你真的从这篇文章中学到了一些新东西,喜欢它,收藏它并与你的小伙伴分享。🤗最后,不要忘了❤或📑支持一下哦。