java實現網絡爬蟲
爬取單一頁面
package liuwenwu.test; import java.io.*; import java.net.*; public class UrlDemo { public static void main(String[] args) { //確定爬取的網頁地址 String strurl="http://search.dangdang.com/?key=%BB%FA%D0%B5%B1%ED&act=input"; //建立url爬取核心對象 try { URL url = new URL(strurl); //通過url建立與網頁的連接 URLConnection conn = url.openConnection(); //通過鏈接取得網頁返回的數據 InputStream is=conn.getInputStream(); System.out.println(conn.getContentEncoding()); //一般按行讀取網頁數據,並進行內容分析 //因此用BufferedReader和InputStreamReader把字節流轉化為字符流的緩沖流 //進行轉換時,需要處理編碼格式問題 BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8")); //按行讀取並打印 String line=null; while((line=br.readLine())!=null) { System.out.println(line); } br.close(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
結果:
下面嘗試將這個網頁的源代碼保存成為本地的一個文本文件,以便后續做離線分析。
將爬取到時數據保存到F:/papapa/目錄下
package liuwenwu.test; import java.io.*; import java.net.*; import java.util.*; import java.util.regex.*; /** * 讀取當當網下首頁圖書的數據,並進行分析 * 爬取深度為2 * 爬去數據存儲到F:/papapa/目錄下,需自行創建 * @author ASUS * */ public class UrlDemo2 { //提取的數據存放到該目錄下 private static String savepath="F:/papapa/"; //等待爬取的url private static List<String> allwaiturl=new ArrayList<>(); //爬取過得url private static Set<String> alloverurl=new HashSet<>(); //記錄所有url的深度進行爬取判斷 private static Map<String, Integer> allurldepth=new HashMap<>(); //爬取的深度 private static int maxdepth=2; public static void main(String[] args) { //確定爬取的網址 String strurl="http://book.dangdang.com/"; workurl(strurl, 1); } public static void workurl(String strurl,int depth) { //判斷當前url是否爬取過 if(!(alloverurl.contains(strurl)||depth>maxdepth)) { //建立url爬取核心對象 try { URL url = new URL(strurl); //通過url建立與網頁的連接 URLConnection conn = url.openConnection(); //通過鏈接取得網頁返回的數據 InputStream is=conn.getInputStream(); System.out.println(conn.getContentEncoding()); //一般按行讀取網頁數據,並進行內容分析 //因此用BufferedReader和InputStreamReader把字節流轉化為字符流的緩沖流 //進行轉換時,需要處理編碼格式問題 BufferedReader br=new BufferedReader(new InputStreamReader(is,"GB2312")); //按行讀取並打印 String line=null; //正則表達式的匹配規則提取該網頁的鏈接 Pattern p=Pattern.compile("<a .*href=.+</a>"); //建立一個輸出流,用於保存文件,文件名為執行時間,以防重復 PrintWriter pw=new PrintWriter(new File(savepath+System.currentTimeMillis()+".txt")); while((line=br.readLine())!=null) { //編寫正則,匹配超鏈接地址 pw.println(line); Matcher m=p.matcher(line); while(m.find()) { String href=m.group(); //找到超鏈接地址並截取字符串 //有無引號 href=href.substring(href.indexOf("href=")); if(href.charAt(5)=='\"') { href=href.substring(6); } else { href=href.substring(5); } //截取到引號或者空格或者到">"結束 try { href=href.substring(0,href.indexOf("\"")); } catch (Exception e) { try { href=href.substring(0,href.indexOf(" ")); } catch (Exception e2) { href=href.substring(0,href.indexOf(">")); } } if(href.startsWith("http:")||href.startsWith("https:")){ //將url地址放到隊列中 allwaiturl.add(href); allurldepth.put(href,depth+1); } } } pw.close(); br.close(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } //將當前url歸列到alloverurl中 alloverurl.add(strurl); System.out.println(strurl+"網頁爬取完成,以爬取數量:"+alloverurl.size()+",剩余爬取數量:"+allwaiturl.size()); } //用遞歸的方法繼續爬取其他鏈接 String nexturl=allwaiturl.get(0); allwaiturl.remove(0); workurl(nexturl, allurldepth.get(nexturl)); } }
控制台:
本地目錄
如果想提高爬蟲性能,那么我們就需要使用多線程來處理,例如:准備好5個線程來同時進行爬蟲
操作。
這些線程需要標注出當前狀態,是在等待,還是在爬取。
如果是等待狀態,那么就需要取得集合中的一個連接,來完成爬蟲操作。
如果是爬取狀態,則在爬完以后,需要變為等待狀態。
多線程中如果想設置等待狀態,有一個方法可以實現:wait(),如果想從等待狀態喚醒,則可以使用notify()。
因此在多個線程中間我們需要一個對象來幫助我們進行線程之間的通信,以便喚醒其它線程。
多線程同時處理時,容易出現線程不安全的問題,導致數據出現錯誤。
為了保證線程的安全,就需要使用同步關鍵字,來對取得連接和放入連接操作加鎖。
多線程爬蟲實現
需要先自定義一個線程的操作類,在這個操作類中判斷不同的狀態,並且根據狀態來決定是進行wait()等待,還是取得一個新的url進行處理。
package liuwenwu.test; import java.io.*; import java.net.*; import java.util.*; import java.util.regex.*; /** * 讀取當當網下首頁圖書的數據,並進行分析 * 爬取深度為2 * 爬去數據存儲到F:\papapa2目錄下,需自行創建 * @author ASUS * */ public class URLDemo3 { //提取的數據存放到該目錄下 private static String savepath="F:/papapa2/"; //等待爬取的url private static List<String> allwaiturl=new ArrayList<>(); //爬取過的url private static Set<String> alloverurl=new HashSet<>(); //記錄所有url的深度進行爬取判斷 private static Map<String,Integer> allurldepth=new HashMap<>(); //爬取得深度 private static int maxdepth=2; //生命對象,幫助進行線程的等待操作 private static Object obj=new Object(); //記錄總線程數5條 private static int MAX_THREAD=5; //記錄空閑的線程數 private static int count=0; public static void main(String args[]){ //確定爬取網址 String strurl="http://book.dangdang.com/"; addurl(strurl,0); for(int i=0;i<MAX_THREAD;i++){ new URLDemo3().new MyThread().start(); } } /** * 網頁數據爬取 * @param strurl * @param depth */ public static void workurl(String strurl,int depth){ //判斷當前url是否爬取過 if(!(alloverurl.contains(strurl)||depth>maxdepth)){ //檢測線程是否執行 System.out.println("當前執行:"+Thread.currentThread().getName()+" 爬取線程處理爬取:"+strurl); //建立url爬取核心對象 try { URL url=new URL(strurl); //通過url建立與網頁的連接 URLConnection conn=url.openConnection(); //通過鏈接取得網頁返回的數據 InputStream is=conn.getInputStream(); //提取text類型的數據 if(conn.getContentType().startsWith("text")){ } System.out.println(conn.getContentEncoding()); //一般按行讀取網頁數據,並進行內容分析 //因此用BufferedReader和InputStreamReader把字節流轉化為字符流的緩沖流 //進行轉換時,需要處理編碼格式問題 BufferedReader br=new BufferedReader(new InputStreamReader(is,"GB2312")); //按行讀取並打印 String line=null; //正則表達式的匹配規則提取該網頁的鏈接 Pattern p=Pattern.compile("<a .*href=.+</a>"); //建立一個輸出流,用於保存文件,文件名為執行時間,以防重復 PrintWriter pw=new PrintWriter(new File(savepath+System.currentTimeMillis()+".txt")); while((line=br.readLine())!=null){ //編寫正則,匹配超鏈接地址 pw.println(line); Matcher m=p.matcher(line); while(m.find()){ String href=m.group(); //找到超鏈接地址並截取字符串 //有無引號 href=href.substring(href.indexOf("href=")); if(href.charAt(5)=='\"'){ href=href.substring(6); }else{ href=href.substring(5); } //截取到引號或者空格或者到">"結束 try{ href=href.substring(0,href.indexOf("\"")); }catch(Exception e){ try{ href=href.substring(0,href.indexOf(" ")); }catch(Exception e1){ href=href.substring(0,href.indexOf(">")); } } if(href.startsWith("http:")||href.startsWith("https:")){ //調用addurl方法 addurl(href,depth); } } } pw.close(); br.close(); } catch (Exception e) { //e.printStackTrace(); } //將當前url歸列到alloverurl中 alloverurl.add(strurl); System.out.println(strurl+"網頁爬取完成,已爬取數量:"+alloverurl.size()+",剩余爬取數量:"+allwaiturl.size()); } if(allwaiturl.size()>0){ synchronized(obj){ obj.notify(); } }else{ System.out.println("爬取結束......."); } } /** * 將獲取的url放入等待隊列中,同時判斷是否已經放過 * @param href * @param depth */ public static synchronized void addurl(String href,int depth){ //將url放到隊列中 allwaiturl.add(href); //判斷url是否放過 if(!allurldepth.containsKey(href)){ allurldepth.put(href, depth+1); } } /** * 移除爬取完成的url,獲取下一個未爬取得url * @return */ public static synchronized String geturl(){ String nexturl=allwaiturl.get(0); allwaiturl.remove(0); return nexturl; } /** * 線程分配任務 */ public class MyThread extends Thread{ @Override public void run(){ //設定一個死循環,讓線程一直存在 while(true){ //判斷是否新鏈接,有則獲取 if(allwaiturl.size()>0){ //獲取url進行處理 String url=geturl(); //調用workurl方法爬取 workurl(url,allurldepth.get(url)); }else{ System.out.println("當前線程准備就緒,等待連接爬取:"+this.getName()); count++; //建立一個對象,讓線程進入等待狀態,即wait() synchronized(obj){ try{ obj.wait(); }catch(Exception e){ } } count--; } } } } }
控制台:
本地目錄:
總結:
1、給出一個網頁鏈接,創建一個本地目錄;
2、用URL類本地連接,用字符流進行讀取,並寫入到本地;
3、利用正則表達式在按行讀取時獲取該網頁所存在的所有鏈接,以便進行深度+1的數據收集;
4、利用遞歸的方法,借助容器list,Set,Map來對鏈接進行爬取和未爬取得划分;
5、每次爬取一個網頁時,所獲得的所有鏈接在當前基礎上深度+1,並且從未爬取隊列中移除,加入到已爬取隊列中;
6、為提升性能,在進行遞歸的時候,可以利用線程,復寫Thread的run()方法,用多線程進行網頁數據爬取;
7、直到爬取得網頁深度達到你期望的深度時,爬取結束,此時可以查看本地目錄生成的文件;
8、后續對本地生成的文件進行數據分析,即可獲取你想要的信息。
借此,我們就可以對這些數據進行歸約,分析,處理,來獲取我們想要的信息。
這也是大數據數據收集的一個基礎。