用Java抓取網頁

URI與URL

URI是通用資源標識符,由三部分組成
1. 訪問資源命名機制
2. 存放資源的主機名
3. 資源本身的名稱

而URL是URI的子集,稱爲統一資源定位符,由三部分組成
1. 協議
2. 主機IP地址
3. 主機資源的具體地址,如目錄與文件名

爬蟲最主要的處理對象就是URL。

抓取網頁的工具

Java語言是爲網絡而生的語言,Java將網絡資源看成一種文件,使對網絡資源的訪問呢與獲取像對文件操作一樣簡單。java.net.*包下有關於Java網絡操作的工具類,但是由於使用原生API代碼量較大,這裏選擇使用Apache的HttpClient包來抓取內容,HttpClient顧名思義就是客戶端,用其訪問網絡資源本質就是模擬IE客戶端行爲。

需要注意的是,Apache的HttpClient在4.x版本變成HttpComponent下面的子工具包,與原來的3.x在API上有較大區別。

抓取網頁示例

下面是我使用HttpClient3.x和4.x兩個版本API寫的一段訪問指定站點,獲取其狀態碼,並簡單抓取和保存網頁內容的代碼,供比較參考。

  • HttpClient3.x
package me.zzx.example;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;

public class RetrivePage {
    private static HttpClient httpClient = new HttpClient();

    //設置代理
    @SuppressWarnings("unused")
    private static void setProxy(String host, int port) {
        httpClient.getHostConfiguration().setProxy(host, port);
    }

    public static boolean downloadPage(String path) throws IOException {

        InputStream input = null;
        OutputStream output = null;
        //得到post方法
        PostMethod post = new PostMethod(path);
        //設置post方法的參數
        NameValuePair[] postData = new NameValuePair[2];
        postData[0] = new NameValuePair("username", "zzx");
        postData[1] = new NameValuePair("password", "*******");
        post.addParameters(postData);

        //執行並返回狀態碼
        int statusCode = httpClient.executeMethod(post);
        System.out.println(statusCode);

        //針對狀態碼進行處理
        if(statusCode == HttpStatus.SC_OK) {
            input = post.getResponseBodyAsStream();
            //得到文件名
            String filename = path.substring(path.lastIndexOf('/') + 1) + "test1.html";
            //獲得文件輸出流
            output = new FileOutputStream(filename);

            //輸出文件
            int tempByte = -1;
            while(((tempByte = input.read()) >= 0)) {
                output.write(tempByte);
            }
            //關閉輸入輸出流
            input.close();
            input = null;
            output.close();
            output = null;
            return true;
        //3XX狀態碼時
        } else if(statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY
                || statusCode == HttpStatus.SC_SEE_OTHER || statusCode == HttpStatus.SC_TEMPORARY_REDIRECT) {
            //讀取新的URL地址
            Header header = post.getResponseHeader("location");
            if(header != null) {
                String newUrl = header.getValue();
                if(newUrl == null || newUrl.equals("")) {
                    newUrl="/";
                    //使用post轉向,發送請求進一步處理
                    downloadPage(newUrl);
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        //抓取阿里巴巴網址,輸出
        try {
            RetrivePage.downloadPage("https://www.alibaba.com/");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • HttpClient4.x
package me.zzx.example;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

public class RetrivePage {
    private static HttpClient httpClient = new DefaultHttpClient();

    //設置代理
    @SuppressWarnings("unused")
    private static void setProxy(String host, int port) {
        HttpHost proxy = new HttpHost(host, port);
        httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
    }

    public static boolean downloadPage(String path) throws IOException {
        InputStream input = null;
        OutputStream output = null;
        //得到post方法
        HttpPost post = new HttpPost(path);
        //設置post方法的參數
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("username", "zzx"));
        params.add(new BasicNameValuePair("password", "******"));
        post.setEntity(new UrlEncodedFormEntity(params, "utf-8"));

        //執行並返回狀態碼
        HttpResponse response = httpClient.execute(post);
        int statusCode = response.getStatusLine().getStatusCode();

        //針對狀態碼進行處理
        if(statusCode == HttpStatus.SC_OK) {
            input = response.getEntity().getContent();
            //得到文件名
            String filename = path.substring(path.lastIndexOf('/') + 1) + "test2.html";
            //獲得文件輸出流
            output = new FileOutputStream(filename);

            //輸出文件
            int tempByte = -1;
            while(((tempByte = input.read()) >= 0)) {
                output.write(tempByte);
            }
            //關閉輸入輸出流
            input.close();
            input = null;
            output.close();
            output = null;
            return true;
        } else if(statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY
                || statusCode == HttpStatus.SC_SEE_OTHER || statusCode == HttpStatus.SC_TEMPORARY_REDIRECT) {
            //讀取新的URL地址
            Header header = post.getLastHeader("location");
            if(header != null) {
                String newUrl = header.getValue();
                if(newUrl == null || newUrl.equals("")) {
                    newUrl="/";
                    //使用post轉向,發送請求進一步處理
                    downloadPage(newUrl);
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        //抓取阿里巴巴網址,輸出
        try {
            RetrivePage.downloadPage("https://www.alibaba.com/");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

HTTP狀態碼

注意到,在代碼中對狀態碼分成兩種處理。第一個條件語句處理的是狀態碼爲2XX的情況,此時成功訪問網頁,則開始抓取網頁內容;第二個條件語句處理的是狀態碼爲3XX的情況,此時網頁可能發生了重定向,網絡資源仍有可能訪問到,故進行了進一步處理。另外還有常見的404之類的4XX和5XX狀態碼,是典型的訪問失敗狀態碼,故最後return false。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章