前言

前几天刚刚接触了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/)
模拟登录大概有下面这几个步骤 :

  1. 首先模拟访问首页 , 首页会带有一些cookie的信息 , 保存这些cookie
  2. 拿到首页的cookie之后 , 接下来我们移步账号密码登录页面, 不要进错了 , 输入账号密码之后 , 发送登录ajax请求 ( 这一步请求结果的应该都是让用手机号验证 , 我在登录时 , 还没遇到不验手机号的情况 )
  3. 根据上一步ajax请求中响应体中JSON数据中的跳转链接 , 跳转到指定的验证页面
  4. 访问验证链接首先会进行一次302跳转 , 这一步阻止链接跳转 , 获取该链接携带的cookie , 获取该链接的响应头(Response Header)里的 location 的值
  5. 根据location进行一次模拟跳转访问 , 这次跳转之后是验证手机号页面
  6. 访问获取手机验证码链接 , 会带着之前的cookie发送" 发短信 "的请求 , 响应体中json数据返回 " 输入验证码 " 的页面的链接 ( 这个链接没有什么作用 )
  7. 收到验证码之后 , 用验证码补全核验验证码的链接 , 带着之前的cookie访问 , 之后获取Json数据中的跳转链接
  8. 访问上一步跳转链接, 接下来将进行4次302跳转, 阻止每一次跳转并获取每次的携带的cookie
  9. 最终跳转到微博主页(https://m.weibo.cn/)

为什么不直接在首页使用手机号验证登录 ?
直接的话要用滑块拼图进行验证 , 滑块拼图验证这个需要更加深入的研究一下怎么加密的, 咱也是刚刚接触这个, 所以就选择了账号密码登录

三. 小提示

  1. 浏览器接收到302状态码的请求时, 自动根据 Response Header里的location里的链接进行跳转
  2. http请求的Header的是不区分大小写的
  3. 微博的json数据里有retcode时 , 为100000是为本次请求结果是正确的
  4. 浏览器会默认的为每次请求添加一些header , 这些header可能作为服务器的判定条件 , 如果不了解的可以移步
    HTTP headers 文档 : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
  5. 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
  6. 微博的每天大约只能发3次码 , 所以想要测试的时候脑子要清晰一点

四. 准备工作

开始写代码之前我们需要准备一下需要的东西
首先先导入一下必须的jar包 , 点击组件名字就可以跳转到maven对应的坐标主页

五. 基本的方法&用法

如果你还不熟悉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

八. 结语

以上就是本篇文章的全部内容

欢迎大家在评论区提出意见和建议 !

如果你真的从这篇文章中学到了一些新东西,喜欢它,收藏它并与你的小伙伴分享。