网络爬虫--如何抓取html页面和httpClient的使用

一、写在前面

上篇文章以网易微博爬虫为例,给出了一个很简单的微博爬虫的爬取过程,大概说明了网络爬虫其实也就这么回事,或许初次看到这个例子觉得有些复杂,不过没有关系,上篇文章给的例子只是让大家对爬虫过程有所了解。接下来的系列里,将一步一步地剖析每个过程。

爬虫总体流程在上篇文章已经说得很清楚了,没有看过的朋友可以去看下:【网络爬虫】[java]微博爬虫(一):网易微博爬虫(自定义关键字爬取微博信息数据)

现在再回顾下爬虫过程:

step1: 通过请求url得到html的string,用httpClient-4.3.1工具,同时设置socket超时和连接超时connectTimeout,本文将详解此步骤。

step2: 对于上步得到的html,验证是否为合法HTML,判断是否为有效搜索页面,因为有些请求的html页面不存在。

step3: 把html这个string存放到本地,写入txt文件;

step4: 从txt文件解析微博数据:userid,timestamp……解析过程才是重点,对于不同网页结构的分析及特征提取,将在系列三中详细讲解。

step5: 解析出来的数据放入txt和xml中,这里主要jsoup解析html,dom4j工具读写xml,将在系列四中讲解。

然后在系列五中会给出一些防止被墙的方法,使用代理IP访问或解析本地IP数据库(前提是你有存放的IP数据库),后面再说。

二、HttpClient工具包

搞过web开发的朋友对这个应该很熟悉了,不需要再多说,这是个很基本的工具包,一个代码级Http客户端工具,可以使用其模拟浏览器向http服务器发送请求。HttpClient是HttpComponents(简称hc)项目其中的一部分,可以直接下载组件。使用HttpClient还需要HttpCore,后者包括Http请求与Http响应代码封装。它使客户端发送http请求变得容易,同时也会更加深入理解http协议。

在这里可以下载HttpComponents组件:http://hc.apache.org/,下载后目录结构:

首先要注意的有以下几点:

1.httpclient链接后释放问题很重要,就跟用database connection要释放资源一样。

2.https网站使用ssl加密传输,证书导入要注意。

3.对于http协议要有基本的了解,比如http的200,301,302,400,404,500等返回代码时什么意思(这个是最基本的),还有cookie和session机制(这个在之后的python爬虫系列三“模拟登录”的方法需要抓取数据包分析,主要就是看cookie这些东西,要学会分析数据包)

4.httpclient的redirect(重定向)状态默认是自动的,这在很大程度上给开发者很大的方便(如一些授权获得的cookie),但有时需要手动设置,比如有时会遇到CircularRedictException异常,出现这样的情况是因为返回的头文件中location值指向之前重复地址(端口号可以不同),导致可能会出现死循环递归重定向,此时可以手动关闭:method.setFollowRedirects(false)。

5.模拟浏览器登录,这个对于爬虫来说相当重要,有的网站会先判别用户的请求是否来自浏览器,如果不是直接拒绝访问,这个直接伪装成浏览器访问就好了,好用httpclient抓取信息时在头部加入一些信息:header.put(“User-Agent”, “Mozilla/5.0 (Windows NT 6.1)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36)”);

6.当post请求提交数据时要改变默认编码,不然提交上去的数据会出现乱码。重写postMethod的setContentCharSet()方法就可以了。

下面给几个例子:

(1)发post请求访问本地应用并根据传递参数不同返回不同结果

1public void post() { 2 //创建默认httpClient实例 3 CloseableHttpClient httpclient = HttpClients.createDefault(); 4 //创建httpPost 5 HttpPost httppost = new HttpPost("http://localhost:8088/weibo/Ajax/service.action"); 6 //创建参数队列 7 List<keyvalue> formparams = new ArrayList<keyvalue>(); 8 formparams.add(new BasicKeyValue("name", "alice")); 9 UrlEncodeFormEntity uefEntity; 10 try { 11 uefEntity = new UrlEncodeFormEntity(formparams, "utf-8"); 12 httppost.setEntity(uefEntity); 13 System.out.println("executing request " + httppost.getURI()); 14 CloseableHttpResponse response = httpclient.execute(httppost); 15 try { 16 HttpEntity entity = response.getEntity(); 17 if(entity != null) { 18 System.out.println("Response content: " + EntityUtils.toString(entity, "utf-8")); 19 } 20 } finally { 21 response.close(); 22 } 23 } catch (ClientProtocolException e) { 24 e.printStackTrace(); 25 } catch (UnsupportedEncodingException e) { 26 e.printStackTrace(); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } finally { 30 //关闭连接,释放资源 31 try { 32 httpclient.close(); 33 } catch (IOException e) { 34 e.printStackTrace(); 35 } 36 } 37 } 38

(2)发get请求

1public void get() { 2 CloseableHttpClient httpclient = HttpClients.createDefault(); 3 try { 4 //创建httpget 5 HttpGet httpget = new HttpGet("http://www.baidu.com"); 6 System.out.println("executing request " + httpget.getURI()); 7 //执行get请求 8 CloseableHttpResponse response = httpclient.execute(httpget); 9 try { 10 //获取响应实体 11 HttpEntity entity = response.getEntity(); 12 //响应状态 13 System.out.println(response.getStatusLine()); 14 if(entity != null) { 15 //响应内容长度 16 System.out.println("response length: " + entity.getContentLength()); 17 //响应内容 18 System.out.println("response content: " + EntityUtils.toString(entity)); 19 } 20 } finally { 21 response.close(); 22 } 23 } catch (ClientProtocolException e) { 24 e.printStackTrace(); 25 } catch (ParseException e) { 26 e.printStackTrace(); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } finally { 30 //关闭链接,释放资源 31 try { 32 httpclient.close(); 33 } catch(IOException e) { 34 e.printStackTrace(); 35 } 36 } 37 } 38

(3)设置header

比如在百度搜索”httpclient”关键字,百度一下,发送请求,chrome里按F12开发者工具,在Network选项卡查看分析数据包,可以看到数据包相关信息,比如这里请求头Request Header里的信息。

有时需要模拟浏览器登录,把header设置一下就OK,照着这里改吧。

1public void header() { 2 HttpClient httpClient = new DefaultHttpClient(); 3 try { 4 HttpGet httpget = new HttpGet("http://www.baidu.com"); 5 httpget.setHeader("Accept", "text/html, */*; q=0.01"); 6 httpget.setHeader("Accept-Encoding", "gzip, deflate,sdch"); 7 httpget.setHeader("Accept-Language", "zh-CN,zh;q=0.8"); 8 httpget.setHeader("Connection", "keep-alive"); 9 httpget.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36)"); 10 11 HttpResponse response = httpClient.execute(httpget); 12 HttpEntity entity = response.getEntity(); 13 System.out.println(response.getStatusLine()); //状态码 14 if(entity != null) { 15 System.out.println(entity.getContentLength()); 16 System.out.println(entity.getContent()); 17 } 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 } 22

三、通过url得到html页面

前面说了这么多,都是些准备工作主要是HttpClient的一些基本使用,其实还有很多,网上其他资料更详细,也不是这里要讲的重点。下面来看如何通过url来得到html页面,其实方法已经在上一篇文章中说过了:【网络爬虫】[java]微博爬虫(一):网易微博爬虫(自定义关键字爬取微博信息数据)

新浪微博和网易微博:(这里尤其要注意地址及参数!)

新浪微博搜索话题地址:http://s.weibo.com/weibo/苹果手机&nodup=1&page=50

网易微博搜索话题地址:http://t.163.com/tag/苹果手机

这里参数&nodup和参数&page=50,表示从搜索结果返回的前50个html页面,从第50个页面开始爬取。也可以修改参数的值,爬取的页面个数不同。

在这里写了三个方法,分别设置用户cookie、默认一般的方法、代理IP方法,基本思路差不多,主要是在RequestConfig和CloseableHttpClient的custom()可以自定义配置。

1/** 2 * @note 三种连接url并获取html的方法(有一般方法,自定义cookie方法,代理IP方法) 3 * @author DianaCody 4 * @since 2014-09-26 16:03 5 * 6 */ 7 8import java.io.IOException; 9import java.io.UnsupportedEncodingException; 10import java.net.URISyntaxException; 11import java.text.ParseException; 12 13import org.apache.http.HttpEntity; 14import org.apache.http.HttpHost; 15import org.apache.http.HttpResponse; 16import org.apache.http.HttpStatus; 17import org.apache.http.client.ClientProtocolException; 18import org.apache.http.client.HttpClient; 19import org.apache.http.client.config.CookieSpecs; 20import org.apache.http.client.config.RequestConfig; 21import org.apache.http.client.methods.CloseableHttpResponse; 22import org.apache.http.client.methods.HttpGet; 23import org.apache.http.client.methods.HttpPost; 24import org.apache.http.config.Registry; 25import org.apache.http.config.RegistryBuilder; 26import org.apache.http.cookie.Cookie; 27import org.apache.http.cookie.CookieOrigin; 28import org.apache.http.cookie.CookieSpec; 29import org.apache.http.cookie.CookieSpecProvider; 30import org.apache.http.cookie.MalformedCookieException; 31import org.apache.http.impl.client.CloseableHttpClient; 32import org.apache.http.impl.client.HttpClients; 33import org.apache.http.impl.conn.DefaultProxyRoutePlanner; 34import org.apache.http.impl.cookie.BestMatchSpecFactory; 35import org.apache.http.impl.cookie.BrowserCompatSpec; 36import org.apache.http.impl.cookie.BrowserCompatSpecFactory; 37import org.apache.http.protocol.HttpContext; 38import org.apache.http.util.EntityUtils; 39 40public class HTML { 41 42 /**默认方法 */ 43 public String[] getHTML(String url) throws ClientProtocolException, IOException { 44 String[] html = new String[2]; 45 html[1] = "null"; 46 RequestConfig requestConfig = RequestConfig.custom() 47 .setSocketTimeout(5000) //socket超时 48 .setConnectTimeout(5000) //connect超时 49 .build(); 50 CloseableHttpClient httpClient = HttpClients.custom() 51 .setDefaultRequestConfig(requestConfig) 52 .build(); 53 HttpGet httpGet = new HttpGet(url); 54 try { 55 CloseableHttpResponse response = httpClient.execute(httpGet); 56 html[0] = String.valueOf(response.getStatusLine().getStatusCode()); 57 html[1] = EntityUtils.toString(response.getEntity(), "utf-8"); 58 //System.out.println(html); 59 } catch (IOException e) { 60 System.out.println("----------Connection timeout--------"); 61 } 62 return html; 63 } 64 65 /**cookie方法的getHTMl() 设置cookie策略,防止cookie rejected问题,拒绝写入cookie --重载,3参数:url, hostName, port */ 66 public String getHTML(String url, String hostName, int port) throws URISyntaxException, ClientProtocolException, IOException { 67 //采用用户自定义的cookie策略 68 HttpHost proxy = new HttpHost(hostName, port); 69 DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); 70 CookieSpecProvider cookieSpecProvider = new CookieSpecProvider() { 71 public CookieSpec create(HttpContext context) { 72 return new BrowserCompatSpec() { 73 @Override 74 public void validate(Cookie cookie, CookieOrigin origin) throws MalformedCookieException { 75 //Oh, I am easy... 76 } 77 }; 78 } 79 }; 80 Registry<CookieSpecProvider> r = RegistryBuilder 81 .<CookieSpecProvider> create() 82 .register(CookieSpecs.BEST_MATCH, new BestMatchSpecFactory()) 83 .register(CookieSpecs.BROWSER_COMPATIBILITY, new BrowserCompatSpecFactory()) 84 .register("easy", cookieSpecProvider) 85 .build(); 86 RequestConfig requestConfig = RequestConfig.custom() 87 .setCookieSpec("easy") 88 .setSocketTimeout(5000) //socket超时 89 .setConnectTimeout(5000) //connect超时 90 .build(); 91 CloseableHttpClient httpClient = HttpClients.custom() 92 .setDefaultCookieSpecRegistry(r) 93 .setRoutePlanner(routePlanner) 94 .build(); 95 HttpGet httpGet = new HttpGet(url); 96 httpGet.setConfig(requestConfig); 97 String html = "null"; //用于验证是否正常取到html 98 try { 99 CloseableHttpResponse response = httpClient.execute(httpGet); 100 html = EntityUtils.toString(response.getEntity(), "utf-8"); 101 } catch (IOException e) { 102 System.out.println("----Connection timeout----"); 103 } 104 return html; 105 } 106 107 /**proxy代理IP方法 */ 108 public String getHTMLbyProxy(String targetUrl, String hostName, int port) throws ClientProtocolException, IOException { 109 HttpHost proxy = new HttpHost(hostName, port); 110 String html = "null"; 111 DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); 112 RequestConfig requestConfig = RequestConfig.custom() 113 .setSocketTimeout(5000) //socket超时 114 .setConnectTimeout(5000) //connect超时 115 .build(); 116 CloseableHttpClient httpClient = HttpClients.custom() 117 .setRoutePlanner(routePlanner) 118 .setDefaultRequestConfig(requestConfig) 119 .build(); 120 HttpGet httpGet = new HttpGet(targetUrl); 121 try { 122 CloseableHttpResponse response = httpClient.execute(httpGet); 123 int statusCode = response.getStatusLine().getStatusCode(); 124 if(statusCode == HttpStatus.SC_OK) { //状态码200: OK 125 html = EntityUtils.toString(response.getEntity(), "gb2312"); 126 } 127 response.close(); 128 //System.out.println(html); //打印返回的html 129 } catch (IOException e) { 130 System.out.println("----Connection timeout----"); 131 } 132 return html; 133 } 134} 135

四、验证是否存在HTML页面

有时请求的html不存在,比如在上篇文章中提到的情况一样,这里加个判断函数。

1private boolean isExistHTML(String html) throws InterruptedException { 2 boolean isExist = false; 3 Pattern pNoResult = Pattern.compile("\\\\u6ca1\\\\u6709\\\\u627e\\\\u5230\\\\u76f8" 4 + "\\\\u5173\\\\u7684\\\\u5fae\\\\u535a\\\\u5462\\\\uff0c\\\\u6362\\\\u4e2a" 5 + "\\\\u5173\\\\u952e\\\\u8bcd\\\\u8bd5\\\\u5427\\\\uff01"); //没有找到相关的微博呢,换个关键词试试吧!(html页面上的信息) 6 Matcher mNoResult = pNoResult.matcher(html); 7 if(!mNoResult.find()) { 8 isExist = true; 9 } 10 return isExist; 11} 12

五、爬取微博返回的HTML字符串

把所有html写到本地txt文件里。

1/**把所有html写到本地txt文件存储 */ 2 public static void writeHTML2txt(String html, int num) throws IOException { 3 String savePath = "e:/weibo/weibohtml/" + num + ".txt"; 4 File f = new File(savePath); 5 FileWriter fw = new FileWriter(f); 6 BufferedWriter bw = new BufferedWriter(fw); 7 bw.write(html); 8 bw.close(); 9 } 10

爬下来的html: 

来看下每个html页面,头部一些数据:

微博正文数据信息,是个json格式,包含一些信息:

代码交流 2021