1. 程式人生 > >簡單的java爬蟲實現

簡單的java爬蟲實現

去年中旬開始接觸爬蟲一直都是淺顯帶過 期間也寫過 知乎爬蟲和科技網站定向抓取及爬取整個網際網路的爬蟲

今天和大家分享一下第三個 及其實現方式和程式碼 早期的實現想法 附程式碼

關於爬蟲其實理論上很簡單 就是通過網際網路上的超連結導航實現頁面的調轉與抓取 網際網路的網也因此而來 

我也會一步一步的將實現方式和想法展現出來 方便大家能夠明白每一步要做什麼應該怎麼做

爬蟲可以分為6個部分:

1.下載器 ——實現爬蟲的基礎

2.連結解析器——獲取文件超連結

3.連結佇列——負責管理連結(分為兩部分 1已經抓取的,2待抓取(實現去重))

4.頁面分析器——負責將有用資訊剝離出來

5.儲存器——將頁面資訊進行儲存(這裡為了方便展示選擇了生成html檔案,同樣也可以持久化資訊)

6.任務分發器——負責以上模組的協作

1.下載器我們選擇了apache提供的httpClient(還有其他一些也不錯,自由選擇)

  1. package com.search.sprider;
  2. import java.io.IOException;
  3. import org.apache.http.HttpEntity;
  4. import org.apache.http.HttpStatus;
  5. import org.apache.http.ParseException;
  6. import org.apache.http.client.ClientProtocolException;
  7. import org.apache.http.client.config.RequestConfig;
  8. import org.apache.http.client.methods.CloseableHttpResponse;
  9. import org.apache.http.client.methods.HttpGet;
  10. import org.apache.http.impl.client.CloseableHttpClient;
  11. import org.apache.http.impl.client.HttpClients;
  12. import org.apache.http.util.EntityUtils;
  13. /**
  14.  * @see 爬取網頁內容
  15.  * @author zhuGe
  16.  *
  17.  */
  18. public class Sprider {
  19. public static String get(String url) {
  20. CloseableHttpClient httpClient = HttpClients.createDefault();
  21. // 建立httpget
  22. HttpGet httpGet;
  23. try {
  24. httpGet = new HttpGet(url);
  25. } catch (Exception e1) {
  26. return null;
  27. }
  28. // 設定表頭
  29. httpHeader(httpGet);
  30. //設定超時
  31. RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();//設定請求和傳輸超時時間
  32. httpGet.setConfig(requestConfig);
  33. String download = null;
  34. try {
  35. // 執行get請求.
  36. CloseableHttpResponse response = httpClient.execute(httpGet);
  37. // 獲取響應實體
  38. HttpEntity entity = response.getEntity();
  39. //System.out.println(httpGet.getURI());
  40. //// 列印響應狀態
  41. //System.out.println(response.getStatusLine());
  42. //System.out.println("--------------------------------------");
  43. /**
  44.  * 爬蟲
  45.  */
  46. if(entity != null){
  47. if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
  48. download = EntityUtils.toString(entity);
  49. }
  50. }
  51. // if (entity != null) {
  52. // // 列印響應內容長度
  53. // System.out.println("Response content length: " +
  54. // entity.getContentLength());
  55.  // 列印響應內容
  56. // System.out.println(download);
  57. } catch (ClientProtocolException e) {
  58. // TODO Auto-generated catch block
  59. e.printStackTrace();
  60. return null;
  61. } catch (ParseException e) {
  62. // TODO Auto-generated catch block
  63. e.printStackTrace();
  64. return null;
  65. } catch (IOException e) {
  66. // TODO Auto-generated catch block
  67. new Exception("ioe");
  68. return null;
  69. }finally {
  70. // 關閉連線,釋放資源
  71. try {
  72. httpClient.close();
  73. } catch (IOException e) {
  74. e.printStackTrace();
  75. return null;
  76. }
  77. }
  78. return download;
  79. }
  80. //設定表頭
  81. public static void httpHeader(HttpGet httpGet){
  82. httpGet.setHeader("Accept", "Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
  83. httpGet.setHeader("Accept-Charset", "GB2312,utf-8;q=0.7,*;q=0.7");
  84. httpGet.setHeader("Accept-Encoding", "gzip, deflate");
  85. httpGet.setHeader("Accept-Language", "zh-cn,zh;q=0.5");
  86. httpGet.setHeader("Connection", "keep-alive");
  87. //httpGet.setHeader("Cookie", "__utma=226521935.73826752.1323672782.1325068020.1328770420.6;");
  88. //httpGet.setHeader("Host", "www.cnblogs.com");
  89. httpGet.setHeader("refer",
  90. "http://www.baidu.com/s?tn=monline_5_dg&bs=httpclient4+MultiThreadedHttpConnectionManager");
  91. httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:6.0.2) Gecko/20100101 Firefox/6.0.2");
  92. //System.out.println("Accept-Charset: " + httpGet.getFirstHeader("Accept-Charset"));
  93. }
  94. }

2.連結解析器選擇了jsoup 配合正則(通過dom樹更方便獲取,可以選擇單純使用正則或jsoup>_< 早期寫程式碼失誤了下版升級優化)

  1. package com.search.split;
  2. import java.util.HashSet;
  3. import java.util.Set;
  4. import java.util.regex.Matcher;
  5. import java.util.regex.Pattern;
  6. import org.jsoup.nodes.Document;
  7. import org.jsoup.nodes.Element;
  8. import org.jsoup.select.Elements;
  9. /**
  10.  * 
  11.  * @author zhuGe
  12.  * @see 連結獲取器
  13.  */
  14. public class HrefOfPage {
  15. /**
  16.  * 
  17.  * @see 獲取所有符合要求的連結
  18.  * @param doc
  19.  * @return 所有的http://的a連結裡面的href屬性值
  20.  * 
  21.  */
  22. @SuppressWarnings({ "rawtypes", "unchecked" })
  23. public static Set<String> printHref(Document  doc){
  24. Set aHref = null;
  25. if(aHref==null){
  26. aHref = new HashSet<String>();
  27. }
  28. aHref.clear();
  29. //獲取所有的a元素
  30. Elements aS = doc.getElementsByTag("a");
  31. for (Element element : aS) {
  32. //正則匹配
  33. //獲取屬性href裡面滿足條件的內容
  34. String href = (element.attr("href"));
  35. String regex ="(http://.+)";
  36. Pattern p = Pattern.compile(regex);
  37. Matcher m = p.matcher(href);
  38. //獲取遍歷所有滿足條件的標籤並獲取連結
  39. while(m.find()){
  40. String a = m.group(0);
  41. aHref .add(a);
  42. }
  43. }
  44. //System.out.println("頁面連結數量:"+aHref.size());
  45. return aHref;
  46. }
  47. }

3.連結佇列 待抓取佇列 選擇了LinkedList的集合(佇列(queue)方便管理)

  1. package com.search.url;
  2. import java.util.LinkedList;
  3. public class UrlQueue {
  4.  /**超連結佇列*/
  5.     public static LinkedList<String> urlQueue = new LinkedList<String>();  
  6.     /**佇列中對應最多的超連結數量*/
  7.     public static final int MAX_SIZE = 10000;  
  8.     public synchronized static void addElem(String url)  
  9.     {  
  10.         urlQueue.add(url);  
  11.     }  
  12.     public synchronized static String outElem()  
  13.     {  
  14. String outUrl = urlQueue.removeFirst();
  15. //將查詢過的去除掉
  16. if(urlQueue.contains(outUrl)){
  17. urlQueue.remove(outUrl);
  18. System.out.println("faxxx");
  19. }
  20.         return outUrl;  
  21.     }
  22.     public synchronized static boolean isEmpty()  
  23.     {  
  24.         return urlQueue.isEmpty();  
  25.     }  
  26. }

3.連結佇列 以抓取佇列 選擇了set結婚(可以去重)

  1. package com.search.url;
  2. import java.util.HashSet;  
  3. /**  
  4. * 已訪問url佇列  
  5. * @author zhuGe
  6. *  
  7. */
  8. public class VisitedUrlQueue  
  9. {  
  10.     public static HashSet<String> visitedUrlQueue = new HashSet<String>();  
  11.     public synchronized static void addElem(String url)  
  12.     {  
  13.         visitedUrlQueue.add(url);  
  14.     }  
  15.     public synchronized static boolean isContains(String url)  
  16.     {  
  17.         return visitedUrlQueue.contains(url);  
  18.     }  
  19.     public synchronized static int size()  
  20.     {  
  21.         return visitedUrlQueue.size();  
  22.     }  
  23. }

4.頁面分析器同樣採用jsoup(2和4分開方便後期維護管理,只獲取了網站標題,可以定製)

  1. package com.search.split;
  2. import org.jsoup.nodes.Document;
  3. import org.jsoup.select.Elements;
  4. public class PageTitle {
  5. public static String printTitle(Document doc){
  6. Elements title = doc.getElementsByTag("title");
  7. return title.text();
  8. }
  9. }

5.儲存器使用輸出流輸出資料生成html頁面 6.任務分發器配合多執行緒提升效率(加入和深度篩選 控制深度優先 )

  1. package com.search.tread;
  2. import java.io.BufferedWriter;
  3. import java.io.FileWriter;
  4. import java.io.IOException;
  5. import java.util.Set;
  6. import org.jsoup.Jsoup;
  7. import org.jsoup.nodes.Document;
  8. import com.search.split.HrefOfPage;
  9. import com.search.split.PageTitle;
  10. import com.search.sprider.Sprider;
  11. import com.search.url.UrlQueue;
  12. import com.search.url.VisitedUrlQueue;
  13. import com.search.util.Depth;
  14. /**
  15.  * @author zhuGe
  16.  * @data 2016年1月17日
  17.  */
  18. public class UrlTread implements Runnable{
  19. @Override
  20. public void run() {
  21. while(!UrlQueue.isEmpty()){
  22. String url = UrlQueue.outElem();
  23. System.out.println("移除"+url);
  24. String context = null;
  25. if(!VisitedUrlQueue.isContains(url)){
  26. context = Sprider.get(url);
  27. }
  28. if(context!=null){
  29. //訪問過的連結
  30. addHref(context,url);
  31. }
  32. VisitedUrlQueue.addElem(url);
  33. }
  34. }
  35. /**
  36.  * @see 獲取連結並輸出標題
  37.  * @param context
  38.  * @param url
  39.  */
  40. public  void addHref(String context,String url){
  41. Document doc = Jsoup.parse(context);
  42. //獲取所有連結
  43. Set<String> hrefSet = HrefOfPage.printHref(doc);
  44. //獲取網站標題
  45. String title = PageTitle.printTitle(doc);
  46. System.out.println(Thread.currentThread().getName());
  47. String html =("<li><a href='"+url+"'>"+title+"</a></li>\n");
  48. //新增檔案到輸出物件
  49. outFile(html);
  50. System.out.println(html);
  51. //進行深度篩選
  52. if(hrefSet!=null){
  53. hrefSet = Depth.depth(hrefSet, 1);
  54. }
  55. //將連結新增進待訪問佇列
  56. for (String string : hrefSet) {
  57. if(!VisitedUrlQueue.isContains(string)){//判斷是否已被訪問
  58. System.out.println("加入佇列"+string);
  59. UrlQueue.addElem(string);
  60. }else{
  61. System.out.println("重複"+string);
  62. }
  63. }
  64. }
  65. public void outFile(String html){
  66. try {
  67. @SuppressWarnings("resource")
  68. BufferedWriter out = new BufferedWriter(new FileWriter("d://test.html",true));
  69. out.write(html);
  70. out.flush();
  71. } catch (IOException e) {
  72. // TODO Auto-generated catch block
  73. e.printStackTrace();
  74. }
  75. }
  76. }

其他擴充套件 

深度控制器

  1. package com.search.util;
  2. import java.util.HashSet;
  3. import java.util.Set;
  4. /**
  5.  * @see 篩選連結的深度
  6.  * @author zhuGe
  7.  *
  8.  */
  9. public class Depth {
  10. /**
  11.  * 
  12.  * @param hrefSet 注入需要控制深度的連結
  13.  * @param depth 篩選滿足深度的連結
  14.  */
  15. public static Set<String> depth(Set<String> hrefSet,int depth){
  16. Set<String> deptahHrefSet=null;
  17. if(deptahHrefSet==null){
  18. deptahHrefSet = new HashSet<String>();
  19. }
  20. deptahHrefSet.clear();
  21. String[] str = null;
  22. for (String href : hrefSet) {
  23. str = href.split("/");
  24. //連結深度
  25. int idepth = str==null?0:str.length-2;
  26. //
  27. //System.out.println(href+" [深度:"+idepth+"]");
  28. if(idepth<=depth){
  29. //去除最後的反斜槓
  30. if(href.lastIndexOf("/")==href.length()-1){
  31. deptahHrefSet.add(href.substring(0, href.length()-1));
  32. }else{
  33. deptahHrefSet.add(href);
  34. }
  35. }
  36. }
  37. return deptahHrefSet;
  38. }
  39. }

啟動入口(‘加入睡眠防止開啟時連結數目過少導致執行緒沒有獲取任務“)

  1. package com.search.control;
  2. import com.search.tread.UrlTread;
  3. import com.search.url.UrlQueue;
  4. public class controlCentre {
  5. public static void main(String[] args) {
  6. UrlQueue.addElem("http://www.ifanr.com");
  7. UrlQueue.addElem("http://www.leiphone.com");
  8. UrlQueue.addElem("http://www.huxiu.com");
  9. UrlTread[] t = new UrlTread[8];
  10. for(int i=0;i<t.length;i++){
  11. t[i] = new UrlTread();
  12. try {
  13. Thread.sleep(2000);
  14. } catch (InterruptedException e) {
  15. // TODO Auto-generated catch block
  16. e.printStackTrace();
  17. }
  18. new Thread(t[i],"蜘蛛人:"+i+"號").start();
  19. }
  20. //
  21. }
  22. }
程式碼還有待優化(這只是簡單爬蟲實現的基礎,不過理論上他已經可以爬取整個網際網路了) ,原始碼下載可以郵箱留言