news 2026/8/28 10:22:53

Java爬虫实战:HttpClient模拟登录绕过验证,Cookie与Token会话管理详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java爬虫实战:HttpClient模拟登录绕过验证,Cookie与Token会话管理详解

1. 项目缘起:当爬虫遇到登录墙

做数据采集的朋友,十有八九都遇到过这个坎儿:目标网站的数据就在那里,清晰可见,但中间横着一道登录墙。浏览器里手动登录一下,一切畅通无阻;可一旦换成程序去抓取,返回的不是401 Unauthorized就是403 Forbidden,再不然就是一堆让你验证身份的CookieSession。这堵“墙”的本质,是网站为了保护用户数据和服务器资源,在特定页面(尤其是用户个人中心、后台管理、付费内容等)前设置的身份验证关卡。

Java写爬虫绕过登录,听起来像是个“灰色”操作,但其实在合法合规的范围内,比如爬取自己公司内部需要登录才能查看的报表、自动化测试需要登录状态的页面、或者研究公开API的调用流程时,这是一个非常实际且高频的技术需求。它考验的不是“黑科技”,而是你对HTTP协议、会话管理以及目标网站验证逻辑的透彻理解。很多人一上来就找Selenium模拟浏览器,虽然省事,但资源消耗大、速度慢,容易被反爬策略识别。而更优雅、更接近本质的做法,是直接使用HttpClientOkHttp这类库,去模拟一次完整的登录过程,拿到那个代表“已登录”状态的令牌(通常是CookieToken),然后用这个令牌去请求后续的数据接口。

今天,我就以一个资深Java开发者的视角,抛开那些花哨的框架和复杂的封装,带你从HTTP协议最底层开始,一步步拆解如何用Java实现绕过登录界面,直接获取目标数据。我们会聚焦于最核心、最通用的技术方案,并分享我在实际项目中踩过的坑和总结的经验,让你不仅能写出能跑的代码,更能理解其背后的每一个“为什么”。

2. 核心原理:会话、Cookie与Token的攻防战

在动手写代码之前,我们必须把“登录”这件事在HTTP层面的运作机制彻底搞明白。HTTP协议本身是无状态的,这意味着服务器无法自动区分两次请求是否来自同一个用户。登录机制,就是为了在无状态的HTTP之上,构建出一个“有状态”的会话。

2.1 经典会话管理:Cookie-Session机制

这是最传统、也最普遍的方案。其流程可以概括为“一问一答一凭证”:

  1. 客户端发起登录请求:用户在前端页面输入用户名和密码,点击登录。浏览器会向服务器的登录接口(例如/login)发送一个POST请求,请求体中携带账号密码等凭证。
  2. 服务器验证并创建会话:服务器验证凭证通过后,会在内存或数据库中创建一个Session对象,用于存储该用户的登录状态和信息(如用户ID、权限)。这个Session有一个全局唯一的ID,称为Session ID
  3. 服务器下发凭证:服务器在返回的HTTP响应头中,通过Set-Cookie字段,将这个Session ID发送给客户端。最常见的Cookie名就是JSESSIONID(Java EE标准)或PHPSESSID(PHP)等。
  4. 客户端携带凭证:浏览器收到这个Cookie后,会将其保存起来。之后,在向同一域名下的任何页面或接口发起请求时,浏览器都会自动在请求头中通过Cookie字段,将这个Session ID回传给服务器。
  5. 服务器识别用户:服务器收到请求后,从Cookie中取出Session ID,去查找对应的Session对象。如果找到且未过期,就认为该请求来自已登录的用户,允许访问受保护资源。

注意:这里有一个关键点,爬虫程序要模拟的,就是第4步。我们不需要关心Session在服务器端如何存储,只需要在代码中,像浏览器一样,妥善保存登录成功后服务器下发的Cookie,并在后续请求中主动、正确地携带它。

2.2 现代认证方案:Token机制(如JWT)

随着前后端分离和分布式架构的流行,基于Token(尤其是JWT)的认证方式越来越普遍。它与Cookie-Session的核心区别在于状态存储的位置。

  1. 客户端发起登录请求:同样发送账号密码到登录接口。
  2. 服务器验证并签发Token:服务器验证通过后,不再创建服务器端的Session,而是使用密钥生成一个Token(通常是一个加密的字符串,如JWT),其中直接编码了用户身份信息(Payload)和签名。然后将这个Token放在响应Response Body)中返回给客户端。
  3. 客户端保存Token:客户端(前端或我们的爬虫程序)需要自己保存这个Token,通常放在内存或本地存储。
  4. 客户端携带Token:在后续请求中,客户端需要手动Token添加到请求头中,最常见的字段是Authorization: Bearer <your_token>
  5. 服务器验证Token:服务器收到请求后,从Authorization头中取出Token,验证其签名和有效期。验证通过即认为用户已登录。

实操心得:对于爬虫开发者,识别目标网站使用哪种机制至关重要。最直接的方法是使用浏览器的开发者工具(F12):

  • 观察登录请求的响应头:如果有Set-Cookie,且后续请求的请求头中有对应的Cookie,那就是Cookie-Session
  • 观察登录请求的响应体:如果返回了一个长长的、由点号分隔的三段式字符串(xxxxx.yyyyy.zzzzz),那很可能是JWT。后续请求的请求头中会看到Authorization: Bearer ...
  • 有些网站会混合使用,比如用Cookie做会话保持,用Token做API授权。我们的策略是:响应里给什么,我们就存什么;请求里要什么,我们就带什么。

3. 工具选型:为什么是HttpClient?

Java生态中有很多HTTP客户端库,比如原生的HttpURLConnectionApache HttpClientOkHttpSpringRestTemplate/WebClient。对于爬虫这种需要精细控制请求头、自动管理Cookie、处理重定向和连接池的场景,Apache HttpClient是经过时间检验的经典选择。

  • HttpURLConnectionJDK自带,但API较为底层和繁琐,缺少连接池、自动重试、Cookie管理等高级功能,不适合复杂的爬虫任务。
  • OkHttp:非常优秀、现代的HTTP客户端,API设计优雅,性能出色。如果你是新项目,OkHttp是绝佳选择。但其在Cookie的自动管理上,默认策略可能不如HttpClientCookieStore直观。
  • RestTemplate/WebClient:属于Spring生态,更适合在Spring项目中做服务间调用。用于爬虫时,封装层次较高,有时反而不如直接使用底层库灵活。
  • Apache HttpClient:功能全面且稳定,对HTTP协议的各种特性支持得非常细致(如各种认证方式、重定向策略、连接管理)。其内置的BasicCookieStore可以完美模拟浏览器的Cookie管理行为,这是实现“一次登录,多次访问”的关键。社区资料和解决方案也极其丰富。

因此,我们选择Apache HttpClient 4.x(目前最新是4.5.x系列)作为本次实战的核心工具。它的核心对象包括:

  • CloseableHttpClient: 可关闭的HTTP客户端实例,代表一个连接池。
  • HttpPost/HttpGet: 代表具体的POST/GET请求。
  • BasicCookieStore: 用于存储和管理Cookie的容器。
  • RequestConfig: 用于配置请求超时、重定向等行为。

4. 实战演练:五步攻克登录关卡

下面,我们以一个假设的网站https://example.com为例,它使用经典的Cookie-Session机制。我们将用HttpClient完成从登录到获取数据的过程。

4.1 第一步:环境准备与依赖引入

首先,在你的Maven项目的pom.xml中添加HttpClient依赖。我们使用较新的HttpComponents Client 5系列,它模块化更清晰。

<dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <!-- 如果需要使用经典的 Fluent API,可以添加此模块 --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5-fluent</artifactId> <version>5.2.1</version> </dependency>

如果你仍在使用HttpComponents 4.x,依赖如下:

<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency>

本文示例将基于5.x版本编写,其API4.x有较大变化,但核心逻辑一致。4.x的用户可以参考思路,调整具体类名和方法。

4.2 第二步:分析目标登录请求

这是最关键的一步,决定了后续代码的成败。打开浏览器开发者工具(F12),切换到Network(网络)选项卡,并勾选Preserve log(保留日志)。

  1. 在登录页面输入账号密码,点击登录按钮。
  2. 在网络列表中,找到类型为documentxhr/fetch的登录请求。通常它的URL包含loginsigninauth等关键词。
  3. 点击这个请求,查看其详细信息:
    • Headers(请求头):
      • Request URL: 登录接口的完整地址。(最重要)
      • Request Method: 通常是POST
      • Content-Type: 通常是application/x-www-form-urlencodedapplication/json。这决定了我们提交参数的方式。
      • 其他可能需要的头,如User-AgentRefererOrigin
    • Payload/Form Data/Request Body(请求体):
      • 查看提交了哪些参数。除了明文的usernamepassword务必注意是否有隐藏字段,如csrf_tokenltexecution等。这些是服务器用于防止跨站请求伪造(CSRF)或维持登录流程状态的,必须原样获取并提交。
    • Response Headers(响应头):
      • 重点关注Set-Cookie字段。这里会包含登录成功后服务器下发的会话Cookie
    • Response(响应体):
      • 登录成功后的跳转信息,或者返回的Token

假设我们分析得到如下信息:

  • 登录URL:https://example.com/api/login
  • 方法:POST
  • Content-Type:application/x-www-form-urlencoded
  • 请求体参数:username=your_name&password=your_pass&csrf_token=abc123
  • 响应头:Set-Cookie: JSESSIONID=xxxxxx; Path=/; HttpOnly

4.3 第三步:构建HttpClient与Cookie管理器

我们需要一个能自动保存和发送CookieHttpClient实例。

import org.apache.hc.client5.http.cookie.BasicCookieStore; import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.core5.http.io.entity.EntityUtils; public class LoginCrawler { public static void main(String[] args) throws Exception { // 1. 创建Cookie存储仓库 BasicCookieStore cookieStore = new BasicCookieStore(); // 2. 创建HttpClient,并绑定CookieStore CloseableHttpClient httpClient = HttpClients.custom() .setDefaultCookieStore(cookieStore) // 关键配置! .build(); // 3. 创建HTTP上下文,用于关联本次会话的Cookie等状态 HttpClientContext context = HttpClientContext.create(); // 上下文会自动使用上面创建的cookieStore // ... 后续登录和数据请求都使用这个httpClient和context } }

重要提示BasicCookieStore是一个内存中的Cookie容器。这意味着如果你重启程序,Cookie就会丢失。对于需要长期维持会话的爬虫,可以考虑将其序列化到文件或数据库中。此外,确保使用同一个HttpClient实例和关联的CookieStore/Context进行登录和后续请求,否则Cookie无法共享。

4.4 第四步:模拟登录请求

根据第二步分析的结果,构造登录请求。这里以application/x-www-form-urlencoded格式为例。

import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; // 接上面的main方法 try { // 4. 构建登录请求参数列表 List<NameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("username", "your_username")); params.add(new BasicNameValuePair("password", "your_password")); // 注意:csrf_token需要先从登录页面获取,这里假设我们已经通过另一个GET请求拿到了值“abc123” params.add(new BasicNameValuePair("csrf_token", "abc123")); // 5. 创建POST请求,并设置表单实体 ClassicHttpRequest loginPost = ClassicRequestBuilder .post("https://example.com/api/login") .setEntity(new UrlEncodedFormEntity(params)) .build(); // 6. 可选但重要:设置请求头,模仿浏览器 loginPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..."); loginPost.setHeader("Referer", "https://example.com/login"); // 登录页地址 loginPost.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); System.out.println("Executing login request: " + loginPost.getUri()); // 7. 执行登录请求,并将响应上下文关联到我们创建的context try (ClassicHttpResponse response = httpClient.execute(loginPost, context)) { HttpEntity entity = response.getEntity(); int statusCode = response.getCode(); System.out.println("Login Status: " + statusCode); // 打印响应内容,有助于调试 if (entity != null) { String responseBody = EntityUtils.toString(entity); System.out.println("Login Response: " + responseBody); EntityUtils.consume(entity); // 确保实体内容被完全消费,以便连接可复用 } // 8. 关键检查:登录是否成功? // 成功不一定是200。可能是302重定向到首页,也可能是200但返回了JSON {“code”: 0}。 // 需要根据你分析的实际响应来判断。 if (statusCode == 200 || statusCode == 302) { // 检查CookieStore里是否已经有了服务器下发的Cookie System.out.println("Cookies after login:"); cookieStore.getCookies().forEach(cookie -> System.out.println(" - " + cookie.getName() + "=" + cookie.getValue()) ); // 如果cookieStore里有关键的JSESSIONID,说明登录状态已被记录 boolean hasSession = cookieStore.getCookies().stream() .anyMatch(c -> "JSESSIONID".equalsIgnoreCase(c.getName())); if (hasSession) { System.out.println("Login appears successful (Session Cookie found)."); } else { System.out.println("Warning: No session cookie found. Login might have failed."); } } else { System.out.println("Login likely failed with status: " + statusCode); } } } catch (Exception e) { e.printStackTrace(); } finally { httpClient.close(); }

4.5 第五步:携带会话状态访问目标数据

登录成功后,cookieStore中已经保存了服务器颁发的Cookie。现在,使用同一个httpClientcontext去请求需要登录后才能访问的页面。

// 接在登录成功的代码块之后 if (/* 判断登录成功的条件 */) { try { // 9. 构建访问目标数据页面的请求 ClassicHttpRequest dataGet = ClassicRequestBuilder .get("https://example.com/user/profile") // 假设这是个人资料页 .build(); // 可以继续设置必要的请求头,如User-Agent dataGet.setHeader("User-Agent", "Mozilla/5.0 ..."); System.out.println("\nExecuting data request: " + dataGet.getUri()); // 10. 执行请求,注意这里依然传入了同一个context try (ClassicHttpResponse dataResponse = httpClient.execute(dataGet, context)) { HttpEntity dataEntity = dataResponse.getEntity(); int dataStatusCode = dataResponse.getCode(); System.out.println("Data Request Status: " + dataStatusCode); if (dataEntity != null) { String dataHtml = EntityUtils.toString(dataEntity); // 现在,dataHtml里应该就是登录后才能看到的页面内容了! System.out.println("Data Response Length: " + dataHtml.length()); // 这里可以开始你的数据解析工作(如用Jsoup解析HTML) // Document doc = Jsoup.parse(dataHtml); // String userName = doc.select(".user-name").first().text(); // System.out.println("Extracted User Name: " + userName); EntityUtils.consume(dataEntity); } // 判断是否真的拿到了数据 // 如果返回302重定向到登录页,或者返回401/403,说明Cookie无效或已过期 if (dataStatusCode == 200 && dataHtml != null && dataHtml.contains("个人中心")) { // 根据实际页面特征判断 System.out.println("Successfully accessed protected data!"); } else { System.out.println("Failed to access protected data. Might be redirected to login."); } } } catch (Exception e) { e.printStackTrace(); } }

5. 进阶挑战与深度避坑指南

上面的五步是理想情况下的流程。现实中的网站防御机制要复杂得多。下面是我在多年爬虫实践中总结的几个关键挑战和应对策略。

5.1 动态Token(如CSRF Token)的获取

很多网站在登录表单中会嵌入一个一次性的Token,用于防止CSRF攻击。这个Token通常在打开登录页时,隐藏在HTML的表单里(如<input type="hidden" name="csrf_token" value="动态值">)或者通过某个API接口返回。

应对策略:在发起登录POST请求前,先发起一个GET请求到登录页面,用HTML解析库(如Jsoup)把这个Token值提取出来,然后再用它构造登录参数。

// 1. 先GET登录页面,获取csrf_token ClassicHttpRequest getLoginPage = ClassicRequestBuilder.get("https://example.com/login").build(); String csrfToken = null; try (ClassicHttpResponse pageResponse = httpClient.execute(getLoginPage, context)) { String html = EntityUtils.toString(pageResponse.getEntity()); // 使用Jsoup解析 Document doc = Jsoup.parse(html); Element tokenInput = doc.select("input[name=csrf_token]").first(); if (tokenInput != null) { csrfToken = tokenInput.attr("value"); System.out.println("Fetched CSRF Token: " + csrfToken); } } // 2. 将获取到的csrfToken填入登录参数列表 params.add(new BasicNameValuePair("csrf_token", csrfToken));

5.2 验证码识别

这是绕不过去的坎。如果登录需要验证码,程序必须能“看到”并“识别”图片。

  1. 获取验证码图片:找到验证码图片的URL,用HttpClient下载到本地或内存。
  2. 识别
    • 简单验证码:可以使用开源库如Tesseract OCR(需训练字库)进行识别,但成功率有限。
    • 复杂验证码:考虑使用第三方打码平台(如超级鹰、图鉴等)的API,这是最稳定高效但需要付费的方案。
    • 机器学习:针对特定网站,可以训练一个简单的CNN模型,但成本较高。
  3. 填入并提交:将识别出的验证码文本作为参数,与其他登录信息一同提交。

核心技巧:对于需要验证码的网站,务必在代码中实现验证码图片的保存和人工查看的接口,便于调试。例如,将下载的验证码图片保存为文件,并暂停程序,等待你在控制台输入识别结果。

5.3 登录后的重定向处理

HttpClient默认会自动处理重定向(302,301等)。这通常是好事,因为它模拟了浏览器的行为。但在爬虫中,有时我们需要检查重定向的最终地址,以确认登录是否成功(例如,成功会重定向到/home,失败则留在/login?error)。

可以通过自定义HttpClient的配置来控制重定向行为,或者在执行请求后,从context中获取重定向的轨迹。

// 创建自定义配置,允许重定向但我们可以追踪 RequestConfig config = RequestConfig.custom() .setRedirectsEnabled(true) // 启用自动重定向 .build(); CloseableHttpClient httpClient = HttpClients.custom() .setDefaultCookieStore(cookieStore) .setDefaultRequestConfig(config) .build(); // 执行请求后,可以从context中获取重定向链 List<URI> redirectLocations = context.getRedirectLocations(); if (redirectLocations != null) { System.out.println("Redirect path:"); for (URI uri : redirectLocations) { System.out.println(" -> " + uri); } }

5.4 会话过期与心跳维持

服务器端的Session有存活时间(如30分钟)。如果爬虫长时间不活动,Cookie会失效。解决方案是:

  1. 定时心跳:定期(比如每15分钟)访问网站的一个轻量级接口(如/api/keepalive或首页),让服务器刷新Session的过期时间。
  2. 异常重登:在抓取数据的主循环中,捕获401/403状态码或发现被重定向到登录页。一旦检测到,立即触发重新登录流程,获取新的Cookie,然后重试刚才失败的请求。

5.5 应对反爬虫策略

绕过登录后,你依然可能触发网站的反爬虫机制。

  • User-Agent:务必设置一个常见的浏览器UA字符串。
  • 请求频率:在请求间添加随机延时(Thread.sleep(randomInterval)),模拟人类操作。
  • 请求头完整性:复制浏览器请求中的所有常见头,如Accept,Accept-Language,Accept-Encoding,Connection,Host等。
  • IP限制:这是最棘手的。如果IP被封锁,需要考虑使用代理IP池。HttpClient可以很方便地配置代理。
    HttpHost proxy = new HttpHost("proxy-host", 8080); RequestConfig config = RequestConfig.custom() .setProxy(proxy) .build(); // 然后将config设置到HttpClient或单个Request上
  • JavaScript渲染:如果目标数据由前端JavaScript动态加载,上述方法只能拿到初始HTML框架。这时就需要用到SeleniumPuppeteer(通过Java驱动)或HtmlUnit这类无头浏览器工具来模拟完整的浏览器环境。但这已属于另一个技术范畴,资源消耗会大很多。

6. 完整代码示例与关键调试技巧

将上述所有步骤整合,一个健壮的、带有基础错误处理和调试输出的爬虫骨架代码如下:

import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.cookie.BasicCookieStore; import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import java.util.ArrayList; import java.util.List; public class RobustLoginCrawler { private static final String LOGIN_URL = "https://example.com/api/login"; private static final String LOGIN_PAGE_URL = "https://example.com/login"; private static final String TARGET_DATA_URL = "https://example.com/user/profile"; public static void main(String[] args) { BasicCookieStore cookieStore = new BasicCookieStore(); HttpClientContext context = HttpClientContext.create(); try (CloseableHttpClient httpClient = HttpClients.custom() .setDefaultCookieStore(cookieStore) .build()) { // --- 阶段一:获取动态Token --- String csrfToken = fetchCsrfToken(httpClient, context, LOGIN_PAGE_URL); if (csrfToken == null || csrfToken.isEmpty()) { System.err.println("Failed to fetch CSRF token. Exiting."); return; } // --- 阶段二:执行登录 --- boolean loginSuccess = performLogin(httpClient, context, LOGIN_URL, "your_user", "your_pass", csrfToken); if (!loginSuccess) { System.err.println("Login failed. Exiting."); return; } System.out.println("Login successful. Current cookies:"); cookieStore.getCookies().forEach(c -> System.out.println(" " + c)); // 可选:短暂暂停,观察 Thread.sleep(1000); // --- 阶段三:访问受保护数据 --- String protectedData = fetchProtectedData(httpClient, context, TARGET_DATA_URL); if (protectedData != null) { System.out.println("\n=== Successfully Fetched Protected Data ==="); System.out.println("Data length: " + protectedData.length()); // 这里进行你的数据解析逻辑 // parseData(protectedData); } else { System.err.println("Failed to fetch protected data."); } } catch (Exception e) { e.printStackTrace(); } } private static String fetchCsrfToken(CloseableHttpClient client, HttpClientContext context, String url) throws Exception { HttpGet request = new HttpGet(url); // 模仿浏览器头 request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); try (CloseableHttpResponse response = client.execute(request, context)) { HttpEntity entity = response.getEntity(); if (entity != null) { String html = EntityUtils.toString(entity); Document doc = Jsoup.parse(html); // 假设token在name为‘csrf_token’的input标签里 Element tokenElem = doc.select("input[name=csrf_token]").first(); EntityUtils.consume(entity); return tokenElem != null ? tokenElem.attr("value") : null; } } return null; } private static boolean performLogin(CloseableHttpClient client, HttpClientContext context, String loginUrl, String user, String pass, String token) throws Exception { HttpPost request = new HttpPost(loginUrl); List<NameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("username", user)); params.add(new BasicNameValuePair("password", pass)); params.add(new BasicNameValuePair("csrf_token", token)); // 可能还有其他隐藏字段,根据实际分析添加 request.setEntity(new UrlEncodedFormEntity(params)); request.setHeader("User-Agent", "Mozilla/5.0 ..."); request.setHeader("Referer", LOGIN_PAGE_URL); request.setHeader("Content-Type", "application/x-www-form-urlencoded"); System.out.println("Posting to login URL..."); try (CloseableHttpResponse response = client.execute(request, context)) { int status = response.getCode(); HttpEntity entity = response.getEntity(); String body = entity != null ? EntityUtils.toString(entity) : ""; EntityUtils.consume(entity); System.out.println("Login HTTP Status: " + status); // 根据实际情况判断成功条件:可能是302重定向,也可能是200返回成功JSON boolean successByStatus = status == 302 || status == 200; // 进一步检查响应体或Cookie boolean successByCookie = context.getCookieStore().getCookies().stream() .anyMatch(c -> "JSESSIONID".equalsIgnoreCase(c.getName())); return successByStatus && successByCookie; } } private static String fetchProtectedData(CloseableHttpClient client, HttpClientContext context, String url) throws Exception { HttpGet request = new HttpGet(url); request.setHeader("User-Agent", "Mozilla/5.0 ..."); try (CloseableHttpResponse response = client.execute(request, context)) { int status = response.getCode(); HttpEntity entity = response.getEntity(); if (status == 200 && entity != null) { String data = EntityUtils.toString(entity); EntityUtils.consume(entity); // 简单检查内容是否包含登录后才有的特征,避免拿到的是登录页 if (data.contains("logout") || data.contains("个人中心")) { return data; } else { System.err.println("Got 200 but content seems not to be protected page."); return null; } } else { System.err.println("Failed to fetch data. Status: " + status); return null; } } } }

关键调试技巧

  1. 日志输出:像上面代码一样,打印关键步骤的URL、状态码、响应体片段和Cookie信息。
  2. 使用抓包工具:将FiddlerCharles设置为系统代理,让你的Java程序通过代理发送请求。这样你可以在图形化界面中清晰地看到程序发出的每一个请求和收到的每一个响应,方便与浏览器行为对比。
  3. 保存中间结果:将登录请求的响应HTML、获取到的Cookie等保存到文件,方便离线分析。
  4. 逐步验证:不要一次性写完所有代码。先写GET登录页面取Token的部分,验证能否拿到Token。再写登录部分,验证能否拿到Cookie。最后写数据抓取部分。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 10:14:42

MarkItDown 实战教程:把 20 余种文件转成 LLM 能读的 Markdown

MarkItDown 实战教程&#xff1a;把 20 余种文件转成 LLM 能读的 Markdown 【免费下载链接】markitdown Python tool for converting files and office documents to Markdown. 项目地址: https://gitcode.com/GitHub_Trending/ma/markitdown 给企业做文档问答系统时&am…

作者头像 李华
网站建设 2026/8/28 10:12:13

AI资本开支首超油气:开发者工程化转型的确定性方向

2026年AI资本开支达7650亿美元首超油气&#xff1a;这笔钱花在哪&#xff0c;开发者如何接住这波红利&#xff1f; 如果你过去半年一直在关注AI开发圈&#xff0c;应该能感受到一种明显的撕裂感&#xff1a;一边是身边不少团队还在为大模型API的账单纠结&#xff0c;一边是全球…

作者头像 李华
网站建设 2026/8/28 10:12:05

CTF竞赛实战:从Web渗透到Linux提权的完整攻击链解析

1. 赛题复盘与核心思路拆解 2022年的那场全国中职组网络安全国赛&#xff0c;现在回想起来&#xff0c;依然能感受到赛场上的紧张氛围和烧脑的快感。我拿到的这套赛题&#xff08;试题8&#xff09;&#xff0c;可以说是对选手综合能力的一次“压力测试”。它不像一些基础题那样…

作者头像 李华
网站建设 2026/8/28 10:11:50

从Mechanize到Playwright:Python浏览器自动化实战指南

最近一条科技圈消息让不少人把目光重新投向了一个熟悉又陌生的名字&#xff1a;Mechanize。据外媒报道&#xff0c;Google 正在与 Mechanize 洽谈一笔金额超过 15 亿美元的潜在合作项目&#xff0c;目前消息仍属于传闻阶段&#xff0c;最终是否落地还需要以官方披露为准。对于开…

作者头像 李华