溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

利用java怎么實現(xiàn)一個網(wǎng)頁爬蟲功能

發(fā)布時間:2020-12-02 14:54:00 來源:億速云 閱讀:173 作者:Leah 欄目:編程語言

利用java怎么實現(xiàn)一個網(wǎng)頁爬蟲功能?針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

爬蟲實現(xiàn)原理

網(wǎng)絡(luò)爬蟲基本技術(shù)處理

網(wǎng)絡(luò)爬蟲是數(shù)據(jù)采集的一種方法,實際項目開發(fā)中,通過爬蟲做數(shù)據(jù)采集一般只有以下幾種情況:

1) 搜索引擎

2) 競品調(diào)研

3) 輿情監(jiān)控

4) 市場分析

網(wǎng)絡(luò)爬蟲的整體執(zhí)行流程:

1) 確定一個(多個)種子網(wǎng)頁

2) 進行數(shù)據(jù)的內(nèi)容提取

3) 將網(wǎng)頁中的關(guān)聯(lián)網(wǎng)頁連接提取出來

4) 將尚未爬取的關(guān)聯(lián)網(wǎng)頁內(nèi)容放到一個隊列中

5) 從隊列中取出一個待爬取的頁面,判斷之前是否爬過。

6) 把沒有爬過的進行爬取,并進行之前的重復(fù)操作。

7) 直到隊列中沒有新的內(nèi)容,爬蟲執(zhí)行結(jié)束。

這樣完成爬蟲時,會有一些概念必須知道的:

1) 深度(depth):一般來說,表示從種子頁到當(dāng)前頁的打開連接數(shù),一般建議不要超過5層。

2) 廣度(寬度)優(yōu)先和深度優(yōu)先:表示爬取時的優(yōu)先級。建議使用廣度優(yōu)先,按深度的層級來順序爬取。

Ⅰ  在進行網(wǎng)頁爬蟲前,我們先針對一個飛機事故失事的文檔進行數(shù)據(jù)提取的練習(xí),主要是溫習(xí)一下上一篇的java知識,也是為了下面爬蟲實現(xiàn)作一個熱身準(zhǔn)備。

 首先分析這個文檔,

利用java怎么實現(xiàn)一個網(wǎng)頁爬蟲功能,關(guān)于美國歷來每次飛機失事的數(shù)據(jù),包含時間地點、駕駛員、死亡人數(shù)、總?cè)藬?shù)、事件描述,一共有12列,第一列是標(biāo)題,下面一共有5268條數(shù)據(jù)。

 現(xiàn)在我要對這個文件進行數(shù)據(jù)提取,并實現(xiàn)一下分析:  

根據(jù)飛機事故的數(shù)據(jù)文檔來進行簡單數(shù)據(jù)統(tǒng)計。

1) 哪年出事故次數(shù)最多

2) 哪個時間段(上午 8 12,下午 12 18,晚上 18 24,凌晨 0 – 8 )事故出現(xiàn)次數(shù)最多。

3) 哪年死亡人數(shù)最多

4)哪條數(shù)據(jù)的幸存率最高。

利用java怎么實現(xiàn)一個網(wǎng)頁爬蟲功能

利用java怎么實現(xiàn)一個網(wǎng)頁爬蟲功能

代碼實現(xiàn):(一切知識從源碼獲?。。?/strong>

package com.plane;

import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
 * 飛機事故統(tǒng)計
 * @author k04
 *sunwengang  
 *2017-08-11
 */
public class planeaccident {
    //數(shù)據(jù)獲取存取鏈表
    private static List<String> alldata=new ArrayList<>();
    
    public static void main(String args[]){      
      getData("飛行事故數(shù)據(jù)統(tǒng)計_Since_1908.csv");
      alldata.remove(0);
      //System.out.println(alldata.size());
      //死亡人數(shù)最多的年份
      MaxDeadYear();
      //事故發(fā)生次數(shù)最多的年份
      MaxAccidentsYear();
      //事故各個時間段發(fā)生的次數(shù)
      FrequencyPeriod();
      //幸村率最高的一條數(shù)據(jù)
       MaximumSurvival();    
    }
    
    /**
     * 從源文件爬取數(shù)據(jù)
     * getData(String filepath)
     * @param filepath
     */
    public static void getData(String filepath){
      File f=new File(filepath);
      //行讀取數(shù)據(jù)
      try{
        BufferedReader br=new BufferedReader(new FileReader(f));
        String line=null;
        while((line=(br.readLine()))!=null){
          alldata.add(line);
        }
        br.close();
      }catch(Exception e){
        e.printStackTrace();
      }
    }
    /**
     * 記錄每年對應(yīng)的死亡人數(shù)
     * @throws 
     * 并輸出死亡人數(shù)最多的年份,及該年死亡人數(shù)
     */
    public static void MaxDeadYear(){
      //記錄年份對應(yīng)死亡人數(shù)
      Map<Integer,Integer> map=new HashMap<>();
      //時間用date顯示
      SimpleDateFormat sdf=new SimpleDateFormat("MM/dd/YYYY");
      //循環(huán)所有數(shù)據(jù)
      for(String data:alldata){
        //用逗號將數(shù)據(jù)分離,第一個是年份,第11個是死亡人數(shù)
        String[] strs=data.split(",");
        if(strs[0]!=null){
          //獲取年份
          try {
            Date date=sdf.parse(strs[0]);
            int year=date.getYear();
            //判斷map中是否記錄過這個數(shù)據(jù)
            if(map.containsKey(year)){
              //已存在,則記錄數(shù)+該年死亡人數(shù)
              map.put(year, map.get(year)+Integer.parseInt(strs[10]));
            }else{
              map.put(year, Integer.parseInt(strs[10]));
            }
            
          } catch (Exception e) {
            // TODO Auto-generated catch block
            
          }
          
        }
      }
      //System.out.println(map);
      
      //記錄死亡人數(shù)最多的年份
      int max_year=-1;
      //記錄死亡人數(shù)
      int dead_count=0;
      //用set無序獲取map中的key值,即年份
      Set<Integer> keyset=map.keySet();
      //
      for(int year:keyset){
        //當(dāng)前年事故死亡最多的年份,記錄年和次數(shù)
        if(map.get(year)>dead_count&&map.get(year)<10000){
          max_year=year;
          dead_count=map.get(year);
        }
      }
      
      System.out.println("死亡人數(shù)最多的年份:"+(max_year+1901)+"  死亡人數(shù):"+dead_count);
    }
    /**
     * 記錄事故次數(shù)最多的年份
     * 輸出該年及事故次數(shù)
     */
    public static void MaxAccidentsYear(){
      //存放年份,該年的事故次數(shù)
      Map<Integer,Integer> map=new HashMap<>();
      SimpleDateFormat sdf =new SimpleDateFormat("MM/dd/YYYY");
      //循環(huán)所有數(shù)據(jù)
      for(String data:alldata){
        String[] strs=data.split(",");
        if(strs[0]!=null){
          try {
            Date date=sdf.parse(strs[0]);
            //獲取年份
            int year=date.getYear();
            //判斷是否存在記錄
            if(map.containsKey(year)){
              //已存在記錄,+1
              map.put(year, map.get(year)+1);
            }else{
              map.put(year, 1);
            }
          } catch (Exception e) {
            // TODO Auto-generated catch block            
          }                        
        }
      }
      //記錄事故次數(shù)最多的年份
      int max_year=0;
      //該年事故發(fā)生次數(shù)
      int acc_count=0;
      //循環(huán)所有數(shù)據(jù),獲取事故次數(shù)最多的年份
      Set<Integer> keyset=map.keySet();
      for(int year:keyset){
        if(map.get(year)>acc_count){
          max_year=year;
          acc_count=map.get(year);
        }
      }
      //輸出結(jié)果
      System.out.println("事故次數(shù)最多的年份"+(max_year+1901)+" 該年事故發(fā)生次數(shù):"+acc_count);
    }
    /**
     * FrequencyPeriod()
     * 各個時間段發(fā)生事故的次數(shù)
     */
    public static void FrequencyPeriod(){
      //key為時間段,value為發(fā)生事故次數(shù)
      Map<String,Integer> map=new HashMap<>();
      //String數(shù)組存放時間段
      String[] strsTime={"上午(6:00~12:00)","下午(12:00~18:00)","晚上(18:00~24:00)","凌晨(0:00~6:00)"};
      //小時:分鐘
      SimpleDateFormat sdf=new SimpleDateFormat("HH:mm");
      
      for(String data:alldata){
        String[] strs=data.split(",");
        //判斷時間是否記錄,未記錄則忽略
        if(strs[1]!=null){
          try {
            Date date=sdf.parse(strs[1]);
            //取得小時數(shù)
            int hour=date.getHours();
            //判斷小時數(shù)在哪個范圍中
            int index=0;
            if(hour>=12&&hour<18){
              index=1;
            }else if(hour>=18){
              index=2;
            }else if(hour<6){
              index=3;
            }
            //記錄到map中
            if(map.containsKey(strsTime[index])){
              map.put(strsTime[index], map.get(strsTime[index])+1);
            }else{
              map.put(strsTime[index], 1);
            }                              
          } catch (ParseException e) {            
          }        
        }
        
      }
      /*
      System.out.println("各時間段發(fā)生事故次數(shù):");
      for(int i=0;i<strsTime.length;i++){    
      System.out.println(strsTime[i]+" : "+map.get(strsTime[i]));
      }    
      */
      // 記錄出事故最多的時間范圍
      String maxTime = null;
      // 記錄出事故最多的次數(shù)
      int maxCount = 0;

      Set<String> keySet = map.keySet();
      for (String timeScope : keySet) {
        if (map.get(timeScope) > maxCount) {
          // 當(dāng)前年就是出事故最多的年份,記錄下年和次數(shù)
          maxTime = timeScope;
          maxCount = map.get(timeScope);
        }
      }
      System.out.println("發(fā)生事故次數(shù)最多的時間段:");
      System.out.println(maxTime+" : "+maxCount);              
    }
    /**
     * 獲取幸村率最高的一條數(shù)據(jù)的內(nèi)容
     * 返回該內(nèi)容及幸存率
     */
    public static void MaximumSurvival(){
      //存放事故信息以及該事故的幸村率
      Map<String,Float> map=new HashMap<>();
      //SimpleDateFormat sdf =new SimpleDateFormat("MM/dd/YYYY");
      //事故幸存率=1-死亡率,第十一個是死亡人數(shù),第十個是總?cè)藬?shù)
      float survial=0;    
      //循環(huán)所有數(shù)據(jù)
      for(String data:alldata){
        try{
        String[] strs=data.split(",");
        //計算幸存率
        float m=Float.parseFloat(strs[10]);
        float n=Float.parseFloat(strs[9]);
        survial=1-m/n;
        map.put(data, survial);
        }catch(Exception e){
          
        }
      }
      //記錄事故次數(shù)最多的年份
      float max_survial=0;  
      //幸存率最高的數(shù)據(jù)信息
      String this_data="null";
      //循環(huán)所有數(shù)據(jù),獲取事故次數(shù)最多的年份
      Set<String> keyset=map.keySet();
      for(String data:keyset){
        if(map.get(data)>max_survial){
          this_data=data;
          max_survial=map.get(data);
        }
      }
      System.out.println("幸存率最高的事故是:"+this_data);
      System.out.println("幸存率為:"+survial);
    }  
}

Ⅱ  接下來我們就可以在網(wǎng)頁的數(shù)據(jù)上下手了。

下面先實現(xiàn)一個單網(wǎng)頁數(shù)據(jù)提取的功能。

使用的技術(shù)可以有以下幾類

1) 原生代碼實現(xiàn):

  a) URL

2) 使用第三方的URL

  a) HttpClient

3) 開源爬蟲框架

  a) Heritrix

  b) Nutch

【一】

先使用URL,來將當(dāng)當(dāng)網(wǎng)下搜索機械表的內(nèi)容提取出來。

利用java怎么實現(xiàn)一個網(wǎng)頁爬蟲功能

package com.exe1;
/**
 * 讀取當(dāng)當(dāng)網(wǎng)下機械表的數(shù)據(jù),并進行分析
 * sunwengang  2017-08-13 20:00
 */
import java.io.*;
import java.net.*;

public class URLDemo {
  public static void main(String args[]){
    //確定爬取的網(wǎng)頁地址,此處為當(dāng)當(dāng)網(wǎng)搜機械表顯示的網(wǎng)頁
    //網(wǎng)址為    http://search.dangdang.com/&#63;key=%BB%FA%D0%B5%B1%ED&act=input
    String strurl="http://search.dangdang.com/&#63;key=%BB%FA%D0%B5%B1%ED&act=input";
    //建立url爬取核心對象
    try {
      URL url=new URL(strurl);
      //通過url建立與網(wǎng)頁的連接
      URLConnection conn=url.openConnection();
      //通過鏈接取得網(wǎng)頁返回的數(shù)據(jù)
      InputStream is=conn.getInputStream();
      
      System.out.println(conn.getContentEncoding());
      //一般按行讀取網(wǎng)頁數(shù)據(jù),并進行內(nèi)容分析
      //因此用BufferedReader和InputStreamReader把字節(jié)流轉(zhuǎn)化為字符流的緩沖流
      //進行轉(zhuǎn)換時,需要處理編碼格式問題
      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 Auto-generated catch block
      e.printStackTrace();
    }
    
  }
}

結(jié)果顯示:

利用java怎么實現(xiàn)一個網(wǎng)頁爬蟲功能

【二】

下面嘗試將這個網(wǎng)頁的源代碼保存成為本地的一個文本文件,以便后續(xù)做離線分析。

如果想根據(jù)條件提取網(wǎng)頁中的內(nèi)容信息,那么就需要使用Java的正則表達式。

正則表達式

Java.util包下提供了PatternMatcher這兩個類,可以根據(jù)我們給定的條件來進行數(shù)據(jù)的匹配和提取。

通過Pattern類中提供的規(guī)則字符或字符串,我們需要自己拼湊出我們的匹配規(guī)則。

正則表達式最常用的地方是用來做表單提交的數(shù)據(jù)格式驗證的。

常用的正則表達式規(guī)則一般分為兩類:

1) 內(nèi)容匹配

  a) \d:是否是數(shù)字

  b) \w:匹配 字母、數(shù)字或下劃線

  c) .:任意字符

  d) [a-z]:字符是否在給定范圍內(nèi)。

2) 數(shù)量匹配

  a) +:1個或以上

  b) *:0個或以上

  c) &#63;:0或1次

  d) {n,m}:n-m次

匹配手機電話號碼:

規(guī)則:1\\d{10}

匹配郵件地址:

規(guī)則:\\w+@\\w+.\\w+(\\.\\w+)&#63;

通過PatternMatcher的配合,我們可以把一段內(nèi)容中匹配我們要求的文字提取出來,方便我們來處理。

例如:將一段內(nèi)容中的電話號碼提取出來。

public class PatternDemo {

  public static void main(String[] args) {
    Pattern p = Pattern.compile("1\\d{10}");

    String content = "<div><div class='jg666'>[轉(zhuǎn)讓]<a href='/17610866588' title='手機號碼17610866588估價評估_值多少錢_歸屬地查詢_測吉兇_數(shù)字含義_求購轉(zhuǎn)讓信息' class='lj44'>17610866588</a>由 張云龍 300元轉(zhuǎn)讓,聯(lián)系電話:17610866588</div><div class='jg666'>[轉(zhuǎn)讓]<a href='/17777351513' title='手機號碼17777351513估價評估_值多少錢_歸屬地查詢_測吉兇_數(shù)字含義_求購轉(zhuǎn)讓信息' class='lj44'>17777351513</a>由 胡俊宏 888元轉(zhuǎn)讓,QQ:762670775,聯(lián)系電話:17777351513,可以小砍價..</div><div class='jg666'>[求購]<a href='/15019890606' title='手機號碼15019890606估價評估_值多少錢_歸屬地查詢_測吉兇_數(shù)字含義_求購轉(zhuǎn)讓信息' class='lj44'>15019890606</a>由 張寶紅 600元求購,聯(lián)系電話:15026815169</div><div class='jg666'>";

    Matcher m = p.matcher(content);
    // System.out.println(p.matcher("sf@sina").matches());
    Set<String> set = new HashSet<>();
    // 通過Matcher類的group方法和find方法來進行查找和匹配
    while (m.find()) {
      String value = m.group();
      set.add(value);
    }
    System.out.println(set);
  }
}

通過正則表達式完成超連接的連接匹配和提取

對爬取的HTML頁面來說,如果想提取連接地址,就必須找到所有超連接的標(biāo)簽和對應(yīng)的屬性。

超連接標(biāo)簽是<a></a>,保存連接的屬性是:href。

<a href=”…”>…</a>

規(guī)則:

<a .*href=.+</a>

廣度優(yōu)先遍歷

需要有一個隊列(這里直接使用ArrayList來作為隊列)保存所有等待爬取的連接。

還需要一個Set集合記錄下所有已經(jīng)爬取過的連接。

還需要一個深度值,記錄當(dāng)前爬取的網(wǎng)頁深度,判斷是否滿足要求

此時對當(dāng)當(dāng)網(wǎng)首頁分類里的圖書進行深度為2的網(wǎng)頁爬取,參照上述對機械表單網(wǎng)頁的爬取,利用遞歸的方式進行數(shù)據(jù)獲取存到E:/dangdang_book/目錄下:

package com.exe1;
/**
 * 讀取當(dāng)當(dāng)網(wǎng)下首頁圖書的數(shù)據(jù),并進行分析
 * 爬取深度為2
 * 爬去數(shù)據(jù)存儲到E:/dangdang_book/目錄下,需自行創(chuàng)建
 * sunwengang  2017-08-13 20:00
 */
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.*;

public class URLDemo {
  //提取的數(shù)據(jù)存放到該目錄下
  private static String savepath="E:/dangdang_book/";
  //等待爬取的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[]){
    //確定爬取的網(wǎng)頁地址,此處為當(dāng)當(dāng)網(wǎng)首頁上的圖書分類進去的網(wǎng)頁
    //網(wǎng)址為    http://book.dangdang.com/
//    String strurl="http://search.dangdang.com/&#63;key=%BB%FA%D0%B5%B1%ED&act=input";
    String strurl="http://book.dangdang.com/";
    
    workurl(strurl,1);
    
  }
  public static void workurl(String strurl,int depth){
    //判斷當(dāng)前url是否爬取過
    if(!(alloverurl.contains(strurl)||depth>maxdepth)){
    //建立url爬取核心對象
    try {
      URL url=new URL(strurl);
      //通過url建立與網(wǎng)頁的連接
      URLConnection conn=url.openConnection();
      //通過鏈接取得網(wǎng)頁返回的數(shù)據(jù)
      InputStream is=conn.getInputStream();
      
      System.out.println(conn.getContentEncoding());
      //一般按行讀取網(wǎng)頁數(shù)據(jù),并進行內(nèi)容分析
      //因此用BufferedReader和InputStreamReader把字節(jié)流轉(zhuǎn)化為字符流的緩沖流
      //進行轉(zhuǎn)換時,需要處理編碼格式問題
      BufferedReader br=new BufferedReader(new InputStreamReader(is,"GB2312"));
    
      //按行讀取并打印
      String line=null;
      //正則表達式的匹配規(guī)則提取該網(wǎng)頁的鏈接
      Pattern p=Pattern.compile("<a .*href=.+</a>");
      //建立一個輸出流,用于保存文件,文件名為執(zhí)行時間,以防重復(fù)
      PrintWriter pw=new PrintWriter(new File(savepath+System.currentTimeMillis()+".txt"));
      
      while((line=br.readLine())!=null){
        //System.out.println(line);
        //編寫正則,匹配超鏈接地址
        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);
          }
          //截取到引號或者空格或者到">"結(jié)束
        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:")){
          //輸出該網(wǎng)頁存在的鏈接
          //System.out.println(href);
          //將url地址放到隊列中
          allwaiturl.add(href);
          allurldepth.put(href,depth+1);
            }
        
          }
        
        }
      pw.close();
      br.close();
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    //將當(dāng)前url歸列到alloverurl中
    alloverurl.add(strurl);
    System.out.println(strurl+"網(wǎng)頁爬取完成,已爬取數(shù)量:"+alloverurl.size()+",剩余爬取數(shù)量:"+allwaiturl.size());
    }
    //用遞歸的方法繼續(xù)爬取其他鏈接
    String nexturl=allwaiturl.get(0);
    allwaiturl.remove(0);
    workurl(nexturl,allurldepth.get(nexturl));        
    }
}

控制臺顯示:

本地目錄顯示:

利用java怎么實現(xiàn)一個網(wǎng)頁爬蟲功能

但是,僅是深度為2的也運行不短地時間,

如果想提高爬蟲性能,那么我們就需要使用多線程來處理,例如:準(zhǔn)備好5個線程來同時進行爬蟲操作。

這些線程需要標(biāo)注出當(dāng)前狀態(tài),是在等待,還是在爬取。

如果是等待狀態(tài),那么就需要取得集合中的一個連接,來完成爬蟲操作。

如果是爬取狀態(tài),則在爬完以后,需要變?yōu)榈却隣顟B(tài)。

多線程中如果想設(shè)置等待狀態(tài),有一個方法可以實現(xiàn):wait(),如果想從等待狀態(tài)喚醒,則可以使用notify()。

因此在多個線程中間我們需要一個對象來幫助我們進行線程之間的通信,以便喚醒其它線程。

多線程同時處理時,容易出現(xiàn)線程不安全的問題,導(dǎo)致數(shù)據(jù)出現(xiàn)錯誤。

為了保證線程的安全,就需要使用同步關(guān)鍵字,來對取得連接和放入連接操作加鎖。

多線程爬蟲實現(xiàn)

需要先自定義一個線程的操作類,在這個操作類中判斷不同的狀態(tài),并且根據(jù)狀態(tài)來決定是進行wait()等待,還是取得一個新的url進行處理

package com.exe1;
/**
 * 讀取當(dāng)當(dāng)網(wǎng)下首頁圖書的數(shù)據(jù),并進行分析
 * 爬取深度為2
 * 爬去數(shù)據(jù)存儲到E:/dangdang_book/目錄下,需自行創(chuàng)建
 * 孫文剛  2017-08-13 20:00
 */
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.*;

public class URLDemo {
  //提取的數(shù)據(jù)存放到該目錄下
  private static String savepath="E:/dangdang_book/";
  //等待爬取的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();
  //記錄總線程數(shù)5條
  private static int MAX_THREAD=5;
  //記錄空閑的線程數(shù)
  private static int count=0;
  
  public static void main(String args[]){
    //確定爬取的網(wǎng)頁地址,此處為當(dāng)當(dāng)網(wǎng)首頁上的圖書分類進去的網(wǎng)頁
    //網(wǎng)址為    http://book.dangdang.com/
//    String strurl="http://search.dangdang.com/&#63;key=%BB%FA%D0%B5%B1%ED&act=input";
    String strurl="http://book.dangdang.com/";
    
    //workurl(strurl,1);
    addurl(strurl,0);
    for(int i=0;i<MAX_THREAD;i++){
      new URLDemo().new MyThread().start();
    }
  }
  /**
   * 網(wǎng)頁數(shù)據(jù)爬取
   * @param strurl
   * @param depth
   */
  public static void workurl(String strurl,int depth){
    //判斷當(dāng)前url是否爬取過
    if(!(alloverurl.contains(strurl)||depth>maxdepth)){
      //檢測線程是否執(zhí)行
      System.out.println("當(dāng)前執(zhí)行:"+Thread.currentThread().getName()+" 爬取線程處理爬?。?quot;+strurl);
    //建立url爬取核心對象
    try {
      URL url=new URL(strurl);
      //通過url建立與網(wǎng)頁的連接
      URLConnection conn=url.openConnection();
      //通過鏈接取得網(wǎng)頁返回的數(shù)據(jù)
      InputStream is=conn.getInputStream();
      
      //提取text類型的數(shù)據(jù)
      if(conn.getContentType().startsWith("text")){
        
      }
      System.out.println(conn.getContentEncoding());
      //一般按行讀取網(wǎng)頁數(shù)據(jù),并進行內(nèi)容分析
      //因此用BufferedReader和InputStreamReader把字節(jié)流轉(zhuǎn)化為字符流的緩沖流
      //進行轉(zhuǎn)換時,需要處理編碼格式問題
      BufferedReader br=new BufferedReader(new InputStreamReader(is,"GB2312"));
    
      //按行讀取并打印
      String line=null;
      //正則表達式的匹配規(guī)則提取該網(wǎng)頁的鏈接
      Pattern p=Pattern.compile("<a .*href=.+</a>");
      //建立一個輸出流,用于保存文件,文件名為執(zhí)行時間,以防重復(fù)
      PrintWriter pw=new PrintWriter(new File(savepath+System.currentTimeMillis()+".txt"));
      
      while((line=br.readLine())!=null){
        //System.out.println(line);
        //編寫正則,匹配超鏈接地址
        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);
          }
          //截取到引號或者空格或者到">"結(jié)束
        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:")){
          /*
          //輸出該網(wǎng)頁存在的鏈接
          //System.out.println(href);
          //將url地址放到隊列中
          allwaiturl.add(href);
          allurldepth.put(href,depth+1);
          */
          //調(diào)用addurl方法
          addurl(href,depth);
            }
        
          }
        
        }
      pw.close();
      br.close();
    } catch (Exception e) {
      // TODO Auto-generated catch block
      //e.printStackTrace();
    }
    //將當(dāng)前url歸列到alloverurl中    
    alloverurl.add(strurl);    
    System.out.println(strurl+"網(wǎng)頁爬取完成,已爬取數(shù)量:"+alloverurl.size()+",剩余爬取數(shù)量:"+allwaiturl.size());
    }
    /*
    //用遞歸的方法繼續(xù)爬取其他鏈接
    String nexturl=allwaiturl.get(0);
    allwaiturl.remove(0);
    workurl(nexturl,allurldepth.get(nexturl));
    */
    if(allwaiturl.size()>0){
      synchronized(obj){
        obj.notify();
      }
    }else{
      System.out.println("爬取結(jié)束.......");
    }
        
    }
  /**
   * 將獲取的url放入等待隊列中,同時判斷是否已經(jīng)放過
   * @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;
  }
  /**
   * 線程分配任務(wù)
   */
  public class MyThread extends Thread{
    @Override
    public void run(){
      //設(shè)定一個死循環(huán),讓線程一直存在
      while(true){
        //判斷是否新鏈接,有則獲取
        if(allwaiturl.size()>0){
          //獲取url進行處理
          String url=geturl();
          //調(diào)用workurl方法爬取
          workurl(url,allurldepth.get(url));
        }else{
          System.out.println("當(dāng)前線程準(zhǔn)備就緒,等待連接爬?。?quot;+this.getName());
          count++;
          //建立一個對象,讓線程進入等待狀態(tài),即wait()
          synchronized(obj){
            try{
              obj.wait();
            }catch(Exception e){
              
            }
          }
          count--;
        }
      }
    }
    
  }
}

控制臺顯示:

本地目錄顯示:

利用java怎么實現(xiàn)一個網(wǎng)頁爬蟲功能

總結(jié):

對于網(wǎng)頁數(shù)據(jù)爬取,用到了線程,類集處理,繼承,正則表達式等各方面的知識,從一個網(wǎng)頁以深度為主,廣度為基本進行爬取,獲取每一個網(wǎng)頁的源代碼,并寫入到一個本地的目錄下。

1、給出一個網(wǎng)頁鏈接,創(chuàng)建一個本地目錄;

2、用URL類本地連接,用字符流進行讀取,并寫入到本地;

3、利用正則表達式在按行讀取時獲取該網(wǎng)頁所存在的所有鏈接,以便進行深度+1的數(shù)據(jù)收集;

4、利用遞歸的方法,借助容器list,Set,Map來對鏈接進行爬取和未爬取得劃分;

5、每次爬取一個網(wǎng)頁時,所獲得的所有鏈接在當(dāng)前基礎(chǔ)上深度+1,并且從未爬取隊列中移除,加入到已爬取隊列中;

6、為提升性能,在進行遞歸的時候,可以利用線程,復(fù)寫Thread的run()方法,用多線程進行網(wǎng)頁數(shù)據(jù)爬?。?/strong>

7、直到爬取得網(wǎng)頁深度達到你期望的深度時,爬取結(jié)束,此時可以查看本地目錄生成的文件;

8、后續(xù)對本地生成的文件進行數(shù)據(jù)分析,即可獲取你想要的信息。

借此,我們就可以對這些數(shù)據(jù)進行歸約,分析,處理,來獲取我們想要的信息。

關(guān)于利用java怎么實現(xiàn)一個網(wǎng)頁爬蟲功能問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI