c httpclient抓取网页(一下-Cookie的header信息解析信息的制作 )
优采云 发布时间: 2022-03-27 23:01c httpclient抓取网页(一下-Cookie的header信息解析信息的制作
)
在使用 HttpClient 抓取一些网页时,往往会保留从服务器发回的 cookie 信息,以便发起其他需要这些 cookie 的请求。在大多数情况下,我们使用内置的 cookie 策略,这使得获取这些 cookie 变得容易和直接。
下面一小段代码是访问并获取对应的cookie:
@Test
public void getCookie(){
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet get=new HttpGet("http://www.baidu.com");
HttpClientContext context = HttpClientContext.create();
try {
CloseableHttpResponse response = httpClient.execute(get, context);
try{
System.out.println(">>>>>>headers:");
Arrays.stream(response.getAllHeaders()).forEach(System.out::println);
System.out.println(">>>>>>cookies:");
context.getCookieStore().getCookies().forEach(System.out::println);
}
finally {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
打印结果
>>>>>>headers:
Server: bfe/1.0.8.18
Date: Tue, 12 Sep 2017 06:19:06 GMT
Content-Type: text/html
Last-Modified: Mon, 23 Jan 2017 13:28:24 GMT
Transfer-Encoding: chunked
Connection: Keep-Alive
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Pragma: no-cache
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
>>>>>>cookies:
[version: 0][name: BDORZ][value: 27315][domain: baidu.com][path: /][expiry: null]
但是,也有一些由 网站 返回的 cookie 不一定完全符合规范。例如,在下面的例子中,从打印的 header 中可以看出,这个 cookie 中的 Expires 属性是时间戳的形式,不符合标准时间。格式化,因此,httpclient对cookies的处理无效,最终无法获取cookie,并发出警告信息:“Invalid 'expires' attribute: 1505204523”
警告: Invalid cookie header: "Set-Cookie: yd_cookie=90236a64-8650-494b332a285dbd886e5981965fc4a93f023d; Expires=1505204523; Path=/; HttpOnly". Invalid 'expires' attribute: 1505204523
>>>>>>headers:
Date: Tue, 12 Sep 2017 06:22:03 GMT
Content-Type: text/html
Connection: keep-alive
Set-Cookie: yd_cookie=90236a64-8650-494b332a285dbd886e5981965fc4a93f023d; Expires=1505204523; Path=/; HttpOnly
Cache-Control: no-cache, no-store
Server: WAF/2.4-12.1
>>>>>>cookies:
虽然我们可以使用头部数据来重构一个cookie,而且很多人都这样做,但是这种方法并不优雅,那么如何解决这个问题呢?网上相关资料很少,只能从官方文档入手。在官方文档3.4的“自定义cookie策略”一节中,提到了允许自定义cookie策略。自定义方法是实现CookieSpec接口,通过CookieSpecProvider在httpclient中完成策略实例的初始化和注册。嗯,关键线索就在 CookieSpec 接口,我们来看一下它的源码:
public interface CookieSpec {
……
/**
* Parse the {@code "Set-Cookie"} Header into an array of Cookies.
*
* <p>This method will not perform the validation of the resultant
* {@link Cookie}s
*
* @see #validate
*
* @param header the {@code Set-Cookie} received from the server
* @param origin details of the cookie origin
* @return an array of {@code Cookie}s parsed from the header
* @throws MalformedCookieException if an exception occurs during parsing
*/
List parse(Header header, CookieOrigin origin) throws MalformedCookieException;
……
}</p>
在源码中找到了一个parse方法,看注释就知道是这个方法。它将 Set-Cookie 的头部信息解析成一个 Cookie 对象。自然,我们将了解 httplcient 中的默认实现 DefaultCookieSpec。由于篇幅限制,源代码没有贴出来。在默认实现中,DefaultCookieSpec的主要工作就是判断头部中Cookie规范的类型,然后调用具体的实现。像上面这样的 Cookie 最终由 NetscapeDraftSpec 的一个实例解析。在 NetscapeDraftSpec 的源代码中,默认的过期时间格式定义为“EEE, dd-MMM-yy HH:mm:ss z”
public class NetscapeDraftSpec extends CookieSpecBase {
protected static final String EXPIRES_PATTERN = "EEE, dd-MMM-yy HH:mm:ss z";
/** Default constructor */
public NetscapeDraftSpec(final String[] datepatterns) {
super(new BasicPathHandler(),
new NetscapeDomainHandler(),
new BasicSecureHandler(),
new BasicCommentHandler(),
new BasicExpiresHandler(
datepatterns != null ? datepatterns.clone() : new String[]{EXPIRES_PATTERN}));
}
NetscapeDraftSpec(final CommonCookieAttributeHandler... handlers) {
super(handlers);
}
public NetscapeDraftSpec() {
this((String[]) null);
}
……
}
至此,就比较清楚了,我们只需要将cookie中的expires时间转换成正确的格式,然后发送给默认的解析器即可。
解决方案:
自定义一个CookieSpec类,继承DefaultCookieSpec并重写解析器方法将cookie中的expires转换为正确的时间格式并调用默认解析方法
实现如下(网址不公开,已隐藏)
public class TestHttpClient {
String url = sth;
class MyCookieSpec extends DefaultCookieSpec {
@Override
public List parse(Header header, CookieOrigin cookieOrigin) throws MalformedCookieException {
String value = header.getValue();
String prefix = "Expires=";
if (value.contains(prefix)) {
String expires = value.substring(value.indexOf(prefix) + prefix.length());
expires = expires.substring(0, expires.indexOf(";"));
String date = DateUtils.formatDate(new Date(Long.parseLong(expires) * 1000L),"EEE, dd-MMM-yy HH:mm:ss z");
value = value.replaceAll(prefix + "\\d{10};", prefix + date + ";");
}
header = new BasicHeader(header.getName(), value);
return super.parse(header, cookieOrigin);
}
}
@Test
public void getCookie() {
CloseableHttpClient httpClient = HttpClients.createDefault();
Registry cookieSpecProviderRegistry = RegistryBuilder.create()
.register("myCookieSpec", context -> new MyCookieSpec()).build();//注册自定义CookieSpec
HttpClientContext context = HttpClientContext.create();
context.setCookieSpecRegistry(cookieSpecProviderRegistry);
HttpGet get = new HttpGet(url);
get.setConfig(RequestConfig.custom().setCookieSpec("myCookieSpec").build());
try {
CloseableHttpResponse response = httpClient.execute(get, context);
try{
System.out.println(">>>>>>headers:");
Arrays.stream(response.getAllHeaders()).forEach(System.out::println);
System.out.println(">>>>>>cookies:");
context.getCookieStore().getCookies().forEach(System.out::println);
}
finally {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
再次运行,顺利打印出正确的结果,完美!
>>>>>>headers:
Date: Tue, 12 Sep 2017 07:24:10 GMT
Content-Type: text/html
Connection: keep-alive
Set-Cookie: yd_cookie=9f521fc5-0248-4ab3ee650ca50b1c7abb1cd2526b830e620f; Expires=1505208250; Path=/; HttpOnly
Cache-Control: no-cache, no-store
Server: WAF/2.4-12.1
>>>>>>cookies:
[version: 0][name: yd_cookie][value: 9f521fc5-0248-4ab3ee650ca50b1c7abb1cd2526b830e620f][domain: www.sth.com][path: /][expiry: Tue Sep 12 17:24:10 CST 2017]