文档章节

如何使用HttpClient和Jsoup抓取网页及分析网页

M
 Miracle_lucy
发布于 2014/06/10 08:33
字数 1372
阅读 89
收藏 1
点赞 0
评论 0

最近在写一个小爬虫,准备爬一部分网页数据,来做模型训练,在考虑如何抓取网页及分析网页时,参考了OSC站中一些项目,特别是@黄亿华写的《webmagic的设计机制及原理-如何开发一个Java爬虫》这篇文章给了不少启发,webmagic是一个垂直的爬虫,而我要写的是一个比较通用的爬虫,主要爬起中文的网站的内容,对于HTTP协议及报文的处理,没有比HttpClient组件更好的选择了,对于HTML代码的解析,在比较HTMLParser和Jsoup后,后者在API的使用上优势明显,简洁易懂。所使用的开源组件定下来后,接着开始思考如何抓取和解析这两个基本功能。

对于我的爬虫抓取这部分功能来说,只要根据网页的URL抓取HTML代码,再从HTML代码中的解析出链接和<p>标签的文本即可以,因此解析的结果可以用一个Page类来表示,这个类纯粹是一个POJO,那么如何使用HttpClient和Jsoup直接解析成Page对象?

在HttpClient 4.2中,提供了ResponseHandler<T>这个接口来负责处理HttpResponse,因此,借助实现这个接口,可以所返回的HTML代码解析成所需要的Page对象,主要的思路是先把HttpResponse中的数据读出来,变换成HTML代码,然后再用jsoup解析<p>标签和<a>标签。代码如下,

01 public class PageResponseHandler implements ResponseHandler<Page> {
02  
03     private Page page;
04  
05     public PageResponseHandler(Page page) {
06     this.page = page;
07     }
08  
09     public void setPage(Page page) {
10     this.page = page;
11     }
12  
13     public Page getPage() {
14     return page;
15     }
16  
17     @Override
18     public Page handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
19  
20     StatusLine statusLine = response.getStatusLine();
21     HttpEntity entity = response.getEntity();
22  
23     if (statusLine.getStatusCode() >= 300) {
24         EntityUtils.consume(entity);
25         throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
26     }
27  
28     if (entity == null)
29         return null;
30  
31     // 利用HTTPClient自带的EntityUtils把当前HttpResponse中的HttpEntity转化成HTML代码
32     String html = EntityUtils.toString(entity);
33  
34  
35     Document document = Jsoup.parse(html);
36     Elements links = document.getElementsByTag("a");
37  
38     for (int i = 0; i < links.size(); i++) {
39         Element link = links.get(i);
40         page.addAnchor(link.attr("href"), link.text());
41     }
42  
43     // parse context of plain text from HTML code,
44     Elements paragraphs = document.getElementsByTag("p");
45     StringBuffer plainText = new StringBuffer(html.length() / 2);
46     for (int i = 0; i < paragraphs.size(); i++) {
47         Element paragraph = paragraphs.get(i);
48         plainText.append(paragraph.text()).append("\n");
49     }
50     page.setPlainText(plainText.toString());
51  
52     return page;
53     }
54  
55 }
代码不超过40行,非常简单,现在可以直接返回Page对象了,写一个测试类,来测试这个PageResponseHandler.测试这个类的功能也不需要复杂的代码。
01 public class PageResponseHandlerTest {
02  
03     HttpClient httpclient;
04  
05     PageResponseHandler pageResponseHandler;
06  
07     final String url = "http://news.163.com/13/0903/11/97RHS2NS0001121M.html";
08  
09     Page page = new Page(url);
10  
11     @Before
12     public void setUp() throws Exception {
13     httpclient = new DefaultHttpClient();
14     HttpGet httpget = new HttpGet(url);
15     pageResponseHandler = new PageResponseHandler(page);
16     httpclient.execute(httpget, pageResponseHandler);
17     }
18  
19     @After
20     public void tearDown() throws Exception {
21     httpclient.getConnectionManager().shutdown();
22     }
23  
24     @Test
25     public void test() {
26     System.out.println(page.getPlainText());
27     assertTrue(page.getPlainText().length() > 0);
28     assertTrue(page.getAnchors().size() > 0);
29     }
30 }
到目前为止,这个爬虫中的抓取和分析功能部分都工作的很好,对于中文,也能很好的解析,比较细心的读者会看到,这些代码中并没有设置字符集,也没有进行字符集进行转换,稍后会讲到HttpClient 4.2组件中字符集的处理。

先回顾一下关于HTTP协议的RFC规范中Content-Type作用,它指明发送给接收者Http Entity内容的媒介类型,对于文本类型的HttpEntity而言,它通常是下面的形式,指定HttpEntity的媒介类型,使用了何种字符集进行编码的,此外RFC规范还规定了,在Content-Type没有指定字符集的情况,默认使用ISO-8859-1字符集对Http Entity进行编码

1 Content-Type: text/html; charset=UTF-8
说到这里,大家应该能猜到HttpClient 4.2是如何能正确的进行编码的----就是利用Content-Type头中所包含的字符集作为编码的输入源。具体的代码可以看EntityUtils这个类的第212行开始的代码。EntityUtils首先从HttpEntity对象中获取Content-Type, 如果Content-Type的字符集不为空,则使用Content-Type对象中指定的字符集进行编码,否则使用开发者指定的字符集进行编码,如果开发者也没有指定字符集,使用默认的字符集iso-8859-1进行编码,当然编码实现,还是调用JDK的Reader类。
01 ContentType contentType = ContentType.getOrDefault(entity);
02             Charset charset = contentType.getCharset();
03             if (charset == null) {
04                 charset = defaultCharset;
05             }
06             if (charset == null) {
07                 charset = HTTP.DEF_CONTENT_CHARSET;
08             }
09             Reader reader = new InputStreamReader(instream, charset);
10             CharArrayBuffer buffer = new CharArrayBuffer(i);
11             char[] tmp = new char[1024];
12             int l;
13             while((l = reader.read(tmp)) != -1) {
14                 buffer.append(tmp, 0, l);
15             }
16             return buffer.toString();

开放的互联网造就繁荣的网站的同时,也给非遵循HTML规范的网站一些机会,有一些中文网站,没有正确的设置HTTP请求相应头中的Content-Type属性,导致HttpClient用默认的iso-8859-1字符集对Http Entity内进行编码,因此为了防止HttpClient在爬取中文网站出现乱码的问题,可以指定一个默认的GBK字符集,对原有的PageResponseHandler中的那行转换字符串的代码修改成

1 String html = EntityUtils.toString(entity, Charset.forName("gbk"));
至此,爬虫的爬取和解析功能都完成了,再也不怕没有正确设置Content-Type头的中文网站。希望这个篇文章能对读者有用。

@仪山湖

本文转载自:http://www.oschina.net/question/564772_124359

共有 人打赏支持
M
粉丝 0
博文 5
码字总数 0
作品 0
桂林
关于HttpClient重试策略的研究

原文出处:kingszelda 一、背景 由于工作上的业务本人经常与第三方系统交互,所以经常会使用HttpClient与第三方进行通信。对于交易类的接口,订单状态是至关重要的。 这就牵扯到一系列问题:...

kingszelda ⋅ 04/25 ⋅ 0

聊聊spring cloud gateway的NettyConfiguration

序 本文主要研究下spring cloud gateway的NettyConfiguration NettyConfiguration spring cloud gateway使用的是reactor的httpclient,其通过nettyClientOptions这个bean来进行构造options,......

go4it ⋅ 06/03 ⋅ 0

大家都用什么工具发起http请求的呢?

我以前喜欢用apache的 httpClient ,公司用jersey。听经理说jersey的性能好,是真的吗?我在网上并没有搜到相关的性能比较,java9也带了一个httpClient,不知性能对比起来又如何?...

暗中观察 ⋅ 04/22 ⋅ 0

使用httpClient 调起https url接口

遇到的问题* 1、ssl证书信任,解决方式信任所有证书 2、生成请求体调用httpPost.setEntity()时输出的参数格式有误,有多种Entity可以选择(常用 StringEntity、UrlEncodedFormEntity、FileE...

梦魂清风 ⋅ 05/10 ⋅ 0

聊聊spring cloud的PreserveHostHeaderGatewayFilter

序 本文主要研究下spring cloud gateway的PreserveHostHeaderGatewayFilter GatewayAutoConfiguration spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gatew......

go4it ⋅ 06/14 ⋅ 0

Httpclient报Timeout waiting for connection from pool 异常

下面这段代码在定时一分钟的任务里面执行: public String remoteJsonUTF8(String url) { HttpGet get = null; CloseableHttpResponse response = null; String json = null; CloseableHttpCl......

小小的小小snake ⋅ 05/17 ⋅ 0

聊聊spring cloud gateway的streaming-media-types属性

序 本文主要研究下spring cloud gateway的streaming-media-types属性 配置 配置说明 GatewayProperties spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gatew......

go4it ⋅ 06/07 ⋅ 0

聊聊spring cloud gateway的RemoveHopByHopHeadersFilter

序 本文主要研究一下spring cloud gateway的RemoveHopByHopHeadersFilter GatewayAutoConfiguration spring-cloud-gateway-core-2.0.0.RC1-sources.jar!/org/springframework/cloud/gateway......

go4it ⋅ 05/30 ⋅ 0

Atitit webdav大文件上传的解决方案

Atitit webdav大文件上传的解决方案 目录 1.1. 修改本地电脑注册表无效 1 1.2. 只好使用java lib模式了 jackrabbit.webdav 2 2. ref 4 Upload file thro webdav client java api [if !suppor...

ati艾龙AttilaxAkb ⋅ 06/13 ⋅ 0

关于apache httpclient4连接池的问题

现在需要通过http请求调用另一个项目的接口,采用apache的httpclient框架, 在配置连接池的时候,遇到问题。是否可以通过spring来配置连接池,或者直接在Java代码中配置连接池。 应该如何配置...

你的好友 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

一篇文章学懂Shell脚本

Shell脚本,就是利用Shell的命令解释的功能,对一个纯文本的文件进行解析,然后执行这些功能,也可以说Shell脚本就是一系列命令的集合。 Shell可以直接使用在win/Unix/Linux上面,并且可以调用...

Jake_xun ⋅ 26分钟前 ⋅ 0

大数据工程师需要精通算法吗,要达到一个什么程度呢?

机器学习是人工智能的一个重要分支,而机器学习下最重要的就是算法,本文讲述归纳了入门级的几个机器学习算法,加大数据学习群:716581014一起加入AI技术大本营。 1、监督学习算法 这个算法由...

董黎明 ⋅ 59分钟前 ⋅ 0

Kylin 对维度表的的要求

1.要具有数据一致性,主键值必须是唯一的;Kylin 会进行检查,如果有两行的主键值相同则会报错。 2.维度表越小越好,因为 Kylin 会将维度表加载到内存中供查询;过大的表不适合作为维度表,默...

无精疯 ⋅ 今天 ⋅ 0

58到家数据库30条军规解读

军规适用场景:并发量大、数据量大的互联网业务 军规:介绍内容 解读:讲解原因,解读比军规更重要 一、基础规范 (1)必须使用InnoDB存储引擎 解读:支持事务、行级锁、并发性能更好、CPU及...

kim_o ⋅ 今天 ⋅ 0

代码注释中顺序更改 文件读写换行

`package ssh; import com.xxx.common.log.LogFactory; import com.xxx.common.log.LoggerUtil; import org.apache.commons.lang3.StringUtils; import java.io.*; public class DirErgodic ......

林伟琨 ⋅ 今天 ⋅ 0

linux实用操作命令

参考 http://blog.csdn.net/qwe6112071/article/details/50806734 ls [选项] [目录名 | 列出相关目录下的所有目录和文件 -a 列出包括.a开头的隐藏文件的所有文件-A 同-a,但不列出"."和"...

简心 ⋅ 今天 ⋅ 0

preg_match处理中文符号 url编码方法

之前想过直接用符号来替换,但失败了,或者用其他方式,但有有些复杂,这个是一个新的思路,亲测可用 <?php$str='637朗逸·超速新风王(300)(白光)'; $str=iconv("UTF-8","GBK",$s...

大灰狼wow ⋅ 今天 ⋅ 0

DevOps 资讯 | PostgreSQL 的时代到来了吗 ?

PostgreSQL是对象-关系型数据库,BSD 许可证。拼读为"post-gress-Q-L"。 作者: Tony Baer 原文: Has the time finally come for PostgreSQL?(有删节) 近30年来 PostgreSQL 无疑是您从未听...

RiboseYim ⋅ 今天 ⋅ 0

github太慢

1:用浏览器访问 IPAddress.com or http://tool.chinaz.com 使用 IP Lookup 工具获得github.com和github.global.ssl.fastly.net域名的ip地址 2:/etc/hosts文件中添加如下格式(IP最好自己查一...

whoisliang ⋅ 今天 ⋅ 0

非阻塞同步之 CAS

为解决线程安全问题,互斥同步相当于以时间换空间。多线程情况下,只有一个线程可以访问同步代码。这种同步也叫阻塞同步(Blocking Synchronization). 这种同步属于一种悲观并发策略。认为只...

长安一梦 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部