httpclient 抓取网页(如何开发一个Java爬虫(的设计机制及原理))
优采云 发布时间: 2022-04-07 09:29httpclient 抓取网页(如何开发一个Java爬虫(的设计机制及原理))
最近在写一个小爬虫,准备爬取一部分网页数据进行模型训练。在考虑如何爬取和分析网页的时候,参考了OSC站的一些项目,特别是@黄一华写的webmagic的设计机制和原理——如何开发Java爬虫”文章给了很多启发,webmagic是一个垂直爬虫,而我想写的是一个比较通用的爬虫,主要是爬取中文网站对于HTTP协议和消息处理,没有比HttpClient组件更好的选择。对于HTML代码解析,后对比HTMLParser和Jsoup,后者在API的使用上优势明显,简洁易懂,在确定使用的开源组件后,
我的爬虫要爬取这部分功能,我只需要根据网页的URL爬取HTML代码,然后从HTML代码中解析出链接和HTML代码。
可以使用标签的文本,所以解析的结果可以用一个Page类来表示,纯粹是一个POJO,那么如何使用HttpClient和Jsoup直接解析成Page对象呢?
在HttpClient4.2中,提供了ResponseHandler接口,负责处理HttpResponse。因此,通过实现该接口,可以将返回的 HTML 代码解析为所需的 Page 对象。主要思路是先将HttpResponse中的数据读出来,转换成HTML代码,然后用jsoup解析
标签和标签。代码显示如下,
公共类 PageResponseHandler 实现 ResponseHandler
{
private Page page;
public PageResponseHandler(Page page) {
this.page = page;
}
public void setPage(Page page) {
this.page = page;
}
public Page getPage() {
return page;
}
@Override
public Page handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
StatusLine statusLine = response.getStatusLine();
HttpEntity entity = response.getEntity();
if (statusLine.getStatusCode() >= 300) {
EntityUtils.consume(entity);
throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
}
if (entity == null)
return null;
// 利用HTTPClient自带的EntityUtils把当前HttpResponse中的HttpEntity转化成HTML代码
String html = EntityUtils.toString(entity);
Document document = Jsoup.parse(html);
Elements links = document.getElementsByTag("a");
for (int i = 0; i < links.size(); i++) {
Element link = links.get(i);
page.addAnchor(link.attr("href"), link.text());
}
// parse context of plain text from HTML code,
Elements paragraphs = document.getElementsByTag("p");
StringBuffer plainText = new StringBuffer(html.length() / 2);
for (int i = 0; i < paragraphs.size(); i++) {
Element paragraph = paragraphs.get(i);
plainText.append(paragraph.text()).append("\n");
}
page.setPlainText(plainText.toString());
return page;
}
}
代码不超过40行,非常简单。现在可以直接返回Page对象,编写一个测试类来测试这个PageResponseHandler。测试这个类的功能不需要复杂的代码。
公共类 PageResponseHandlerTest {
HttpClient httpclient;
PageResponseHandler pageResponseHandler;
final String url = "http://news.163.com/13/0903/11/97RHS2NS0001121M.html";
Page page = new Page(url);
@Before
public void setUp() throws Exception {
httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet(url);
pageResponseHandler = new PageResponseHandler(page);
httpclient.execute(httpget, pageResponseHandler);
}
@After
public void tearDown() throws Exception {
httpclient.getConnectionManager().shutdown();
}
@Test
public void test() {
System.out.println(page.getPlainText());
assertTrue(page.getPlainText().length() > 0);
assertTrue(page.getAnchors().size() > 0);
}
}
到目前为止,该爬虫中的爬取和分析功能运行良好。对于中文,它们也可以很好地解析。比较细心的读者会看到,这些代码中并没有设置字符集,也没有对字符集进行转换,后面会讲到HttpClient4.2组件中的字符集处理。
让我们首先回顾一下 Content-Type 在 HTTP 协议的 RFC 规范中的作用。它指示发送给接收方 HttpEntity 的内容的媒体类型。对于text-type HttpEntity,通常采用如下形式,指定HttpEntity的媒体类型,使用另外,RFC规范还规定在Content-Type不指定字符集的情况下,ISO-8859-默认使用 1 个字符集来编码 Http Entity
1
内容类型:文本/html;字符集=UTF-8
说到这里,你应该可以猜到 HttpClient 4.2 是如何正确编码的——即使用 Content-Type 标头中收录的字符集作为输入源进行编码。具体代码见EntityUtils类第212行开始的代码。EntityUtils 首先从 HttpEntity 对象中获取 Content-Type。如果 Content-Type 的字符集不为空,则使用 Content-Type 对象中指定的字符集进行编码,否则使用开发者指定的字符集进行编码,如果开发者未指定字符集,默认字符集 iso-8859-1 用于编码。当然,编码实现是调用JDK的Reader类。
ContentType contentType = ContentType.getOrDefault(entity);
字符集 charset = contentType.getCharset();
if (charset == null) {
字符集 = 默认字符集;
}
if (charset == null) {