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。