顾名思义,httpclient就是http客户端的意思;在项目中前后台交互是通过HTTP/HTTPS请求、响应来进行的,而HTTP请求是由浏览器生成并发送给后台服务器的,服务器接收request请求处理过后返回前台response响应。HttpClient就是代替并模拟浏览器,由代码生成HTTPrequest请求;请求发送给各个大型网站的服务器,获取其网页内容可以实现网络爬虫的功能。
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
<version>4.5.13version>
dependency>
浏览器发送HTTP请求步骤:
使用HttpClient向网页发送请求本质就是用代码的方式实现了上面“浏览器发送HTTP请求步骤”,即代码代替手工(方便理解)。
查看HttpGet的源码可以发现HttpGet方法有三个构造函数,可以接受URI对象和String字符串。
代码如下(示例):
/**
* 使用httpclient发送get请求
* 并且通过URIBuilder设置带参数的请求(可以不设置,直接发送GET请求)
* @throws Exception
*/
@Test
public void test2() throws Exception{
/**
* CloseableHttpClient:
* 可以关闭的httpclient客户端,相当于打开一个浏览器
* 据说是在老版本中httpClient不能及时关闭,造成了资源的浪费,所以有了CloseableHttpClient类。
*/
CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
String urlStr = "https://www.huawei.com/cn/searchresult";
/**
* 通过URIBuilder设置带参数的get请求
* 如果有多个参数可以采用链式编程的方法设置多个键值对参数。
*/
URIBuilder uriBuilder = new URIBuilder(urlStr);
/**
* 最终成型样式打印:
* "GET /cn/searchresult?keywords=p50 HTTP/1.1[\r][\n]"
*/
uriBuilder.setParameter("keywords","p50");
/**
* 构造httpGet请求对象
* 打印httpGet:GET https://www.huawei.com/ HTTP/1.1
* 其构造方法可以接受字符串或者是URI实例对象;
* 即:如果是HttpGet httpGet = new HttpGet(urlStr);
* 的话是不带参数发送GET请求
*/
HttpGet httpGet = new HttpGet(uriBuilder.build());
/**
* 设置请求头,防止多次请求后被网页服务器认定为破坏者给封了
*/
httpGet.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36");
// 可以关闭的httpClient客户端的响应
CloseableHttpResponse closeableHttpResponse = null;
//相当于按下Enter发送请求,然后用closeableHttpResponse 接收返回的响应。
closeableHttpResponse = closeableHttpClient.execute(httpGet);
/**
* 获取status:HTTP/1.1 200 OK 获取状态码:
* 代码实现:closeableHttpResponse.getStatusLine().getStatusCode();
*/
/**
* HttpEntity:
* 发送请求后获取响应的结果,即获取返回的结果!!!
* HttpEntity不仅可以作为结果,也可以作为请求的实体参数,他有很多实现。
* 输出entity:org.apache.http.client.entity.DecompressingEntity@dd3b207
* HttpEntity表示http的request和resposne实体,它由消息头和消息体组成。
* 从HttpEntity中可以获取http请求头和回应头,也可以获取http请求体和回应体信息。
*/
HttpEntity entity = closeableHttpResponse.getEntity();
/**
* EntityUtils是对HttpEntity操作的工具类
* responseResult就是完整的响应
*/
String responseResult = EntityUtils.toString(entity,StandardCharsets.UTF_8);
//System.out.print("测试输出请求的内容:"+entity.getContentType().getValue());
System.out.print(responseResult);
//关闭流水
EntityUtils.consume(entity);
if(closeableHttpClient != null){
closeableHttpClient.close();
}
if(closeableHttpResponse != null){
closeableHttpResponse.close();
}
}
对网页手动发出带参数的请求右击查看网页源代码,和下面的对比发现一样。
执行代码,发出请求,打印结果
补充:请求主体的设置
//请求主体的设置,可加在代码中也可不加,记录一下使用方法。
//通过RequestConfig设置,通过httpGet.setConfig将设置添加到http请求中去
HttpGet httpGet = new HttpGet(urlStr);
RequestConfig requestConfig = RequestConfig
.custom()
.setConnectTimeout(10000) //设置链接超时时间为10秒钟,单位是毫秒
.setConnectionRequestTimeout(1000) //设置获取连接的最长时间
.setSocketTimeout(10000) //设置读取超时时间为十秒钟
.build();
httpGet.setConfig(requestConfig);
可以设置带参数也可以不带参数,这里直接设置的带参数的post请求,去掉设置请求参数的代码就是不带参数的post请求了;同样可以请求成功。
/**
* 实现:发送Post请求
* 步骤:
* 1.开启客户端
* 2.找到网址
* 3.发送GET/POST请求
* 4.收到response响应
* 5.处理响应
* 6.关闭网页,释放资源
*/
@Test
public void testPost() throws Exception{
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建httpPost对象,设置url访问地址
HttpPost httpPost = new HttpPost("https://www.huawei.com/cn/searchresult");
//封装表单中的额数据--->声明list集合
List<NameValuePair> paramsList = new ArrayList<NameValuePair>();
//设置请求地址是:https://www.huawei.com/cn/searchresult?keywords=p50,是post请求!
paramsList.add(new BasicNameValuePair("keywords","p50"));
//创建表单的Entitty的对象
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(paramsList,StandardCharsets.UTF_8);
//将表单对象加入到post请求中
httpPost.setEntity(urlEncodedFormEntity);
CloseableHttpResponse closeableHttpResponse = httpClient.execute(httpPost);
//判断是否请求成功,状态码是200说明得到了正确的响应
if(closeableHttpResponse.getStatusLine().getStatusCode() == 200){
HttpEntity entity = closeableHttpResponse.getEntity();
String s = EntityUtils.toString(entity);
System.out.print(s);
}
httpClient.close();
closeableHttpResponse.close();
}
/**
* 功能:测试连接池
* 问:为什么使用连接池?
* 答:每次使用HttpClient时,需要开启一个可关闭的httpclient客户端(实际是在内存中申请地址获取硬件资源),使用完后关闭时释放内存资源;
* 单次使用未有大问题,但是频繁开启httpclient客户端会造成资源的利用不合理。使用连接池可以提前申请好资源,不用频繁开启关闭,使得硬件资源合理利用。
*
* 步骤:
* 1.创建连接池管理器
* 2.配置连接池
* 3.使用连接池管理器发起请求
*
* 使用连接池和使用HttpClient发起请求的区别:
* 1.使用连接池管理器创建http请求时不需要使用 HttpClients.createDefault()的方法创建请求
* 2.请求发起并完成后逇链接不需要关闭,因为有连接池管理器在管理链接。
*/
@Test
public void connPoolMan() throws Exception{
//1.创建连接池
PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager();
//2.配置连接池
pool.setMaxTotal(100);//设置最大连接
pool.setDefaultMaxPerRoute(10);//设置每个主机的最大连接数
//3.使用连接池发起请求
conn(pool);
conn(pool);
}
public static void conn(PoolingHttpClientConnectionManager pool) throws Exception{
CloseableHttpClient closeableHttpClient = HttpClients.custom().setConnectionManager(pool).build();
/**
* 可以看到两次的请求地址是不一样的:
* HttpClient客户端地址:org.apache.http.impl.client.InternalHttpClient@19dc67c2
* HttpClient客户端地址:org.apache.http.impl.client.InternalHttpClient@1e127982
*/
System.out.println("HttpClient客户端地址:"+closeableHttpClient);
HttpGet httpGet = new HttpGet("https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=parser&fenlei=256&oq=Incompatible%2520types.%2520Found%253A%2520%2526%252339%253Bcom.minGe.SpringSecurityStudy.WeekDay%2526%252339%253B%252C%2520required&rsv_pq=89062b920009f034&rsv_t=c484X67GB9z1uE4WJcPgGer043fTPO5I83E1A7YpLoq%2Fw9NM4JZlWN94GOw&rqlang=cn&rsv_enter=1&rsv_dl=tb&rsv_btype=t&rsv_sug3=53&rsv_sug1=41&rsv_sug7=100&rsv_sug2=0&inputT=1201&rsv_sug4=2524");
CloseableHttpResponse execute = closeableHttpClient.execute(httpGet);
if(execute.getStatusLine().getStatusCode()==200){
HttpEntity string = execute.getEntity();
System.out.println("占用了多少字节:"+EntityUtils.toString(string).length());
}
/**
* 注意:
* closeableHttpClient.close()不需要,连接池管理资源,所以不需要再手动去关闭。
*/
execute.close();
}
从下图看到Jsoup.parse的构造函数可以接收多种类型的数据,并且其返回的结果只有一种形式,均是Document类型的结果。
@Test
/**
* 功能:使用jsoup解析url
* 注意:
* 从程序中可以看出使用jsoup可以代替HttpClient直接发起请求并且解析数据,而且简单,但是为何不用jsoup爬取数据?
* 因为在实际开发中需要使用到多线程、连接池、代理等等方式;而jsoup对这些支持不是很好,所以我们吧jsoup仅仅作为html解析工具使用。
*/
public void parseURL() throws Exception{
//直接解析url地址,返回的是一个Document对象
Document document = Jsoup.parse(new URL("https://www.huawei.com/cn/searchresult"), 10000);
//获取解析到的document对象中的第一个title标签下的文字内容
String span = document.getElementsByTag("title").first().text();
//打印结果:站内搜索 - 华为
System.out.println(span);
}
//解析字符串
String content = FileUtils.readFileToString(new File("C://Users//minge//Desktop//这就是一段简单html内容.txt"));
Document docu = Jsoup.parse(content);
System.out.println(docu.getElementsByTag("title").text());
使用FileUtils工具类要导commons-io包:
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.4version>
dependency>
//直接解析文件,是在Windows系统下解析的文件,Windows系统下的文件默认使用GB2312编码。
Document document = Jsoup.parse(new File("C://Users//minge//Desktop//这就是一段简单html内容.html"), "GB2312");
//打印结果对比: 我嫩爹
我嫩爹
System.out.println(document.getElementsByClass("wonendie").html());
System.out.println(document.getElementsByClass("wonendie").text());