Java簡單實現爬蟲技術,抓取整個網站所有連結+圖片+檔案(思路+程式碼)
阿新 • • 發佈:2019-02-13
寫這個純屬個人愛好,前兩天想玩爬蟲,但是百度了一大圈也沒發現有好一點的帖子,所以就自己研究了下,親測小點的網站還是能隨隨便便爬完的,由於是單執行緒所以速度嘛~~你懂的
(多執行緒沒學好,後期再慢慢加上多執行緒吧)
先上幾張效果圖
##需要用到的知識點
- 網路請求(至於用哪個嘛,看個人喜好,文章用的是okhttp)
- File檔案讀寫
- Jsoup框架(html解析器)
##需要的jar包
注意:okhttp內部依賴okio,別忘了同時匯入okio
難點
- 如圖(隨便弄了個草圖)
- 要說技術難點的話,無非就是如何遍歷整個網站了,首先,要考慮到的是抓取到一個連結後,這個連結裡面肯定還有好幾十甚至上百個連結,接下來這幾十個連結裡面又有連結,連結裡面有連結一層一層巢狀,該如何去獲取這些連結?
##實現思路
- 1.連結儲存
- 使用檔案操作儲存所有連結,至於為什麼不用集合儲存,據博主瞭解,寫爬蟲基本都不用集合去儲存資料,原因在於連結多了之後會報記憶體溢位錯誤。也就是集合裡面存太多東西了,然後還要對它進行查詢操作,所以不推薦使用集合進行儲存。
- 2.連結讀取
- 將每次讀到的連結存入.txt文字檔案中,這裡要注意的是每次存入連結的時候要在後面加上\r\n(換行),也就是讓每個連結各佔一行,這樣有利於後期以行的形式讀取連結。
- 3.連結遍歷
- ①、獲取首頁連結中的子連結,存入檔案中,已行為單位儲存。
- ②、定義一個變數num(預設為-1),用於記錄當前讀的是第幾條連結,每次遍歷完一條連結後 判斷如果(num<連結檔案行數 )則 num++。
- ③、遍歷解析連結的方法,每一次遍歷的目標連結等於 檔案內的第num行
這樣基本就實現了連結的遍歷
舉個栗子
假設index.html頁面內有5個子連結分別對應 ae.html,解析index.html頁面後將該頁面中的5個連結存入檔案中,num++(此時num=0),檔案中的15行就分別對應這5個連結,第二次呼叫讀取方法的時候用到的連結就是檔案中的第num行,也就是a.html。
接著解析a.html,將a.html中的所有超連結追加進檔案中。
上圖:
圖中的遍歷方式似乎有點像一個橫放著的wifi訊號
接下來貼程式碼:
- 首先建立兩個類
- HttpUtil.java (網路請求類,用於獲取網頁原始碼)
- Spider.java (爬蟲主程式碼)
###HttpUtil.java 類
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* Created by XieTiansheng on 2018/3/7.
*/
public class HttpUtil {
private static OkHttpClient okHttpClient;
private static int num = 0;
static{
okHttpClient = new OkHttpClient.Builder()
.readTimeout(1, TimeUnit.SECONDS)
.connectTimeout(1, TimeUnit.SECONDS)
.build();
}
public static String get(String path){
//建立連線客戶端
Request request = new Request.Builder()
.url(path)
.build();
//建立"呼叫" 物件
Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();//執行
if (response.isSuccessful()) {
return response.body().string();
}
} catch (IOException e) {
System.out.println("連結格式有誤:"+path);
}
return null;
}
}
這個就不多寫註釋了 百度有一大堆okhttp教程
Spider.java 類
首先定義要爬的網站首頁與儲存連結的檔案物件
:
public static String path = "http://www.yada.com.cn/"; //雅達公司官網
public static int num = -1,sum = 0;
/**
* 定義四個檔案類(連結儲存,圖片儲存,檔案儲存,錯誤連結儲存)
*/
public static File aLinkFile,imgLinkFile,docLinkFile,errorLinkFile;
解析html頁面的方法
:
/**
*
* @param path 目標地址
*/
public static void getAllLinks(String path){
Document doc = null;
try{
doc = Jsoup.parse(HttpUtil.get(path));
}catch (Exception e){
//解析出的錯誤連結(404頁面)
writeTxtFile(errorLinkFile, path+"\r\n"); //寫入錯誤連結收集檔案
num++;
if(sum>num){ //如果檔案總數(sum)大於num(當前讀取位置)則繼續遍歷
getAllLinks(getFileLine(aLinkFile, num));
}
return;
}
//獲取html程式碼中所有帶有href屬性的a標籤,和圖片
Elements aLinks = doc.select("a[href]");
Elements imgLinks = doc.select("img[src]");
System.out.println("本次抓取的連結:"+path);
for(Element element:aLinks){
String url =element.attr("href");
//判斷連結是否包含這兩個頭
if(!url.contains("http://")&&!url.contains("https://")){
//不是則加上 例:<a href="xitongshow.php?cid=67&id=113" />
//則需要加上字首 http://www.yada.com.cn/xitongshow.php?cid=67&id=113
//否則下次解析該連結的時候會報404錯誤
url = Spider.path+url;//網站首頁加上該連結
}
//如果檔案中沒有這個連結,而且連結中不包含javascript:則繼續(因為有的是用js語法跳轉)
if(!readTxtFile(aLinkFile).contains(url)
&&!url.contains("javascript")){
//路徑必須包含網頁主連結--->防止爬入別的網站
if(url.contains(Spider.path)){
//判斷該a標籤的內容是檔案還是子連結
if(url.contains(".doc")||url.contains(".exl")
||url.contains(".exe")||url.contains(".apk")
||url.contains(".mp3")||url.contains(".mp4")){
//寫入檔案中,檔名+檔案連結
writeTxtFile(docLinkFile, element.text()+"\r\n\t"+url+"\r\n");
}else{
//將連結寫入檔案
writeTxtFile(aLinkFile, url+"\r\n");
sum++; //連結總數+1
}
System.out.println("\t"+element.text()+":\t"+url);
}
}
}
//同時抓取該頁面圖片連結
for(Element element:imgLinks){
String srcStr = element.attr("src");
if(!srcStr.contains("http://")&&!srcStr.contains("https://")){//沒有這兩個頭
srcStr = Spider.path+srcStr;
}
if(!readTxtFile(imgLinkFile).contains(srcStr)){
//將圖片連結寫進檔案中
writeTxtFile(imgLinkFile, srcStr+"\r\n");
}
}
num++;
if(sum>num){ //如果檔案總數(sum)大於num(當前讀取位置)則繼續遍歷
getAllLinks(getFileLine(aLinkFile, num));
}
}
該方法用於解析html頁面,取到所有連結,存入檔案
兩個操作檔案的方法(讀/取)
:
/**
* 讀取檔案
* @param file 檔案類
* @return 檔案內容
*/
public static String readTxtFile(File file){
String result = ""; //讀取結果
String thisLine = ""; //每次讀取的行
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
try {
while((thisLine=reader.readLine())!=null){
result += thisLine+"\n";
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return result;
}
/**
* 寫入內容
* @param file 檔案類
* @param urlStr 要寫入的文字
*/
public static void writeTxtFile(File file,String urlStr){
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(file,true));
writer.write(urlStr);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
簡單的檔案操作方法,用於儲存每次解析出來的連結
獲取檔案中的指定行內容
:
/**
* 獲取檔案指定行數的資料,用於爬蟲獲取當前要爬的連結
* @param file 目標檔案
* @param num 指定的行數
*/
public static String getFileLine(File file,int num){
String thisLine = "";
int thisNum = 0 ;
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
while((thisLine = reader.readLine())!=null){
if(num == thisNum){
return thisLine;
}
thisNum++;
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
這個方法很重要,用於獲取檔案中的第幾條連結
下面是這個類的完整程式碼
:
package com.xietiansheng.shangmao.cn;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import okio.ForwardingTimeout;
public class Spider {
public static String path = "http://www.yada.com.cn/"; //雅達公司官網
public static int num = -1,sum = 0;
/**
* 定義四個檔案類(連結儲存,圖片儲存,檔案儲存,錯誤連結儲存)
*/
public static File aLinkFile,imgLinkFile,docLinkFile,errorLinkFile;
/**
*
* @param path 目標地址
*/
public static void getAllLinks(String path){
Document doc = null;
try{
doc = Jsoup.parse(HttpUtil.get(path));
}catch (Exception e){
//接收到錯誤連結(404頁面)
writeTxtFile(errorLinkFile, path+"\r\n"); //寫入錯誤連結收集檔案
num++;
if(sum>num){ //如果檔案總數(sum)大於num(當前座標)則繼續遍歷
getAllLinks(getFileLine(aLinkFile, num));
}
return;
}
Elements aLinks = doc.select("a[href]");
Elements imgLinks = doc.select("img[src]");
System.out.println("開始連結:"+path);
for(Element element:aLinks){
String url =element.attr("href");
//判斷連結是否包含這兩個頭
if(!url.contains("http://")&&!url.contains("https://")){
//不是則加上 例:<a href="xitongshow.php?cid=67&id=113" />
//則需要加上字首 http://www.yada.com.cn/xitongshow.php?cid=67&id=113
//否則404
url = Spider.path+url;
}
//如果檔案中沒有這個連結,而且連結中不包含javascript:則繼續(因為有的是用js語法跳轉)
if(!readTxtFile(aLinkFile).contains(url)
&&!url.contains("javascript")){
//路徑必須包含網頁主連結--->防止爬入別的網站
if(url.contains(Spider.path)){
//判斷該a標籤的內容是檔案還是子連結
if(url.contains(".doc")||url.contains(".exl")
||url.contains(".exe")||url.contains(".apk")
||url.contains(".mp3")||url.contains(".mp4")){
//寫入檔案中,檔名+檔案連結
writeTxtFile(docLinkFile, element.text()+"\r\n\t"+url+"\r\n");
}else{
//將連結寫入檔案
writeTxtFile(aLinkFile, url+"\r\n");
sum++; //連結總數+1
}
System.out.println("\t"+element.text()+":\t"+url);
}
}
}
//同時抓取該頁面圖片連結
for(Element element:imgLinks){
String srcStr = element.attr("src");
if(!srcStr.contains("http://")&&!srcStr.contains("https://")){//沒有這兩個頭
srcStr = Spider.path+srcStr;
}
if(!readTxtFile(imgLinkFile).contains(srcStr)){
//將圖片連結寫進檔案中
writeTxtFile(imgLinkFile, srcStr+"\r\n");
}
}
num++;
if(sum>num){
getAllLinks(getFileLine(aLinkFile, num));
}
}
/**
* 讀取檔案內容
* @param file 檔案類
* @return 檔案內容
*/
public static String readTxtFile(File file){
String result = ""; //讀取結果
String thisLine = ""; //每次讀取的行
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
try {
while((thisLine=reader.readLine())!=null){
result += thisLine+"\n";
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return result;
}
/**
* 寫入內容
* @param file 檔案類
* @param urlStr 要寫入的文字
*/
public static void writeTxtFile(File file,String urlStr){
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(file,true));
writer.write(urlStr);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 獲取檔案指定行數的資料,用於爬蟲獲取當前要爬的連結
* @param file 目標檔案
* @param num 指定的行數
*/
public static String getFileLine(File file,int num){
String thisLine = "";
int thisNum = 0 ;
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
while((thisLine = reader.readLine())!=null){
if(num == thisNum){
return thisLine;
}
thisNum++;
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 獲取檔案總行數(有多少連結)
* @param file 檔案類
* @return 總行數
*/
public static int getFileCount(File file){
int count = 0;
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
while(reader.readLine()!=null){ //遍歷檔案行
count++;
}
} catch (Exception e) {
e.printStackTrace();
}
return count;
}
public static void main(String[] args) {
aLinkFile = new File("D:/Spider/ALinks.txt");
imgLinkFile = new File("D:/Spider/ImgLinks.txt");
docLinkFile = new File("D:/Spider/DocLinks.txt");
errorLinkFile = new File("D:/Spider/ErrorLinks.txt");
//用陣列儲存四個檔案物件,方便進行相同操作
File[] files = new File[]{aLinkFile,imgLinkFile,docLinkFile,errorLinkFile};
try {
for(File file: files){
if(file.exists()) //如果檔案存在
file.delete(); //則先刪除
file.createNewFile(); //再建立
}
} catch (IOException e) {
e.printStackTrace();
}
long startTime = System.currentTimeMillis(); //獲取開始時間
Spider.getAllLinks(path); //開始爬取目標內容
System.out.println(""
+ "——————————————————爬取結束——————————————————"
+ "\n目標網址:"+path
+ "\n連結總數:"+sum+"條"
+ "\n圖片總數:"+getFileCount(imgLinkFile)+"張"
+ "\n檔案總數:"+getFileCount(docLinkFile)+"份");
writeTxtFile(aLinkFile, "連結總數:"+getFileCount(aLinkFile)+"條");
writeTxtFile(imgLinkFile, "圖片總數:"+getFileCount(imgLinkFile)+"張");
writeTxtFile(docLinkFile, "檔案總數:"+getFileCount(docLinkFile)+"份");
writeTxtFile(errorLinkFile, "問題連結總數:"+getFileCount(errorLinkFile)+"條");
long endTime = System.currentTimeMillis(); //獲取結束時間
System.out.println("\n程式執行時間:" + (endTime - startTime) + "ms"); //輸出程式執行時間
}
}
結束
程式碼比較初級
爬爬小網站就可以了
純屬娛樂而已
有問題可以給我留言或者在下面評論
可以用於服務端於安卓客戶端結合達到想要的效果