1. 程式人生 > >Java簡單實現爬蟲技術,抓取整個網站所有連結+圖片+檔案(思路+程式碼)

Java簡單實現爬蟲技術,抓取整個網站所有連結+圖片+檔案(思路+程式碼)

寫這個純屬個人愛好,前兩天想玩爬蟲,但是百度了一大圈也沒發現有好一點的帖子,所以就自己研究了下,親測小點的網站還是能隨隨便便爬完的,由於是單執行緒所以速度嘛~~你懂的
(多執行緒沒學好,後期再慢慢加上多執行緒吧)

先上幾張效果圖

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

##需要用到的知識點

  • 網路請求(至於用哪個嘛,看個人喜好,文章用的是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");    //輸出程式執行時間
	}
}

結束

程式碼比較初級
爬爬小網站就可以了
純屬娛樂而已
有問題可以給我留言或者在下面評論
可以用於服務端於安卓客戶端結合達到想要的效果