欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用java實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲

 更新時(shí)間:2020年07月31日 11:56:17   作者:sunwengang  
這篇文章主要介紹了使用java實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

接著上面一篇對(duì)爬蟲需要的java知識(shí),這一篇目的就是在于網(wǎng)絡(luò)爬蟲的實(shí)現(xiàn),對(duì)數(shù)據(jù)的獲取,以便分析。----->

爬蟲實(shí)現(xiàn)原理

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

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

1)搜索引擎

2)競品調(diào)研

3)輿情監(jiān)控

4)市場分析

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

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

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

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

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

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

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

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

這樣完成爬蟲時(shí),會(huì)有一些概念必須知道的:

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

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

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

 首先分析這個(gè)文檔,

,關(guān)于美國歷來每次飛機(jī)失事的數(shù)據(jù),包含時(shí)間地點(diǎn)、駕駛員、死亡人數(shù)、總?cè)藬?shù)、事件描述,一共有12列,第一列是標(biāo)題,下面一共有5268條數(shù)據(jù)。

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

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

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

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

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

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

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

package com.plane;

import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
 * 飛機(jī)事故統(tǒng)計(jì)
 * @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)計(jì)_Since_1908.csv");
      alldata.remove(0);
      //System.out.println(alldata.size());
      //死亡人數(shù)最多的年份
      MaxDeadYear();
      //事故發(fā)生次數(shù)最多的年份
      MaxAccidentsYear();
      //事故各個(gè)時(shí)間段發(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();
      }
    }
    /**
     * 記錄每年對(duì)應(yīng)的死亡人數(shù)
     * @throws
     * 并輸出死亡人數(shù)最多的年份,及該年死亡人數(shù)
     */
    public static void MaxDeadYear(){
      //記錄年份對(duì)應(yīng)死亡人數(shù)
      Map<Integer,Integer> map=new HashMap<>();
      //時(shí)間用date顯示
      SimpleDateFormat sdf=new SimpleDateFormat("MM/dd/YYYY");
      //循環(huán)所有數(shù)據(jù)
      for(String data:alldata){
        //用逗號(hào)將數(shù)據(jù)分離,第一個(gè)是年份,第11個(gè)是死亡人數(shù)
        String[] strs=data.split(",");
        if(strs[0]!=null){
          //獲取年份
          try {
            Date date=sdf.parse(strs[0]);
            int year=date.getYear();
            //判斷map中是否記錄過這個(gè)數(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()
     * 各個(gè)時(shí)間段發(fā)生事故的次數(shù)
     */
    public static void FrequencyPeriod(){
      //key為時(shí)間段,value為發(fā)生事故次數(shù)
      Map<String,Integer> map=new HashMap<>();
      //String數(shù)組存放時(shí)間段
      String[] strsTime={"上午(6:00~12:00)","下午(12:00~18:00)","晚上(18:00~24:00)","凌晨(0:00~6:00)"};
      //小時(shí):分鐘
      SimpleDateFormat sdf=new SimpleDateFormat("HH:mm");

      for(String data:alldata){
        String[] strs=data.split(",");
        //判斷時(shí)間是否記錄,未記錄則忽略
        if(strs[1]!=null){
          try {
            Date date=sdf.parse(strs[1]);
            //取得小時(shí)數(shù)
            int hour=date.getHours();
            //判斷小時(shí)數(shù)在哪個(gè)范圍中
            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("各時(shí)間段發(fā)生事故次數(shù):");
      for(int i=0;i<strsTime.length;i++){
      System.out.println(strsTime[i]+" : "+map.get(strsTime[i]));
      }
      */
      // 記錄出事故最多的時(shí)間范圍
      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ù)最多的時(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-死亡率,第十一個(gè)是死亡人數(shù),第十個(gè)是總?cè)藬?shù)
      float survial=0;
      //循環(huán)所有數(shù)據(jù)
      for(String data:alldata){
        try{
        String[] strs=data.split(",");
        //計(jì)算幸存率
        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ù)上下手了。

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

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

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

  a)URL類

2)使用第三方的URL庫

  a)HttpClient庫

3)開源爬蟲框架

  a)Heritrix

  b)Nutch

【一】

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

package com.exe1;
/**
 * 讀取當(dāng)當(dāng)網(wǎng)下機(jī)械表的數(shù)據(jù),并進(jìn)行分析
 * 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)搜機(jī)械表顯示的網(wǎng)頁
    //網(wǎng)址為    http://search.dangdang.com/?key=%BB%FA%D0%B5%B1%ED&act=input
    String strurl="http://search.dangdang.com/?key=%BB%FA%D0%B5%B1%ED&act=input";
    //建立url爬取核心對(duì)象
    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ù),并進(jìn)行內(nèi)容分析
      //因此用BufferedReader和InputStreamReader把字節(jié)流轉(zhuǎn)化為字符流的緩沖流
      //進(jìn)行轉(zhuǎn)換時(shí),需要處理編碼格式問題
      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é)果顯示:

【二】

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

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

正則表達(dá)式

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

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

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

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

1)內(nèi)容匹配

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

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

  c).:任意字符

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

2)數(shù)量匹配

  a)+:1個(gè)或以上

  b)*:0個(gè)或以上

  c)?:0或1次

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

匹配手機(jī)電話號(hào)碼:

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

匹配郵件地址:

規(guī)則:\\w+@\\w+.\\w+(\\.\\w+)?

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

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

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='手機(jī)號(hào)碼17610866588估價(jià)評(píng)估_值多少錢_歸屬地查詢_測吉兇_數(shù)字含義_求購轉(zhuǎn)讓信息' class='lj44'>17610866588</a>由 張?jiān)讫?300元轉(zhuǎn)讓,聯(lián)系電話:17610866588</div><div class='jg666'>[轉(zhuǎn)讓]<a href='/17777351513' title='手機(jī)號(hào)碼17777351513估價(jià)評(píng)估_值多少錢_歸屬地查詢_測吉兇_數(shù)字含義_求購轉(zhuǎn)讓信息' class='lj44'>17777351513</a>由 胡俊宏 888元轉(zhuǎn)讓,QQ:762670775,聯(lián)系電話:17777351513,可以小砍價(jià)..</div><div class='jg666'>[求購]<a href='/15019890606' title='手機(jī)號(hào)碼15019890606估價(jià)評(píng)估_值多少錢_歸屬地查詢_測吉兇_數(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方法來進(jìn)行查找和匹配
    while (m.find()) {
      String value = m.group();
      set.add(value);
    }
    System.out.println(set);
  }
}

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

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

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

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

規(guī)則:

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

廣度優(yōu)先遍歷

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

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

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

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

package com.exe1;
/**
 * 讀取當(dāng)當(dāng)網(wǎng)下首頁圖書的數(shù)據(jù),并進(jìn)行分析
 * 爬取深度為2
 * 爬去數(shù)據(jù)存儲(chǔ)到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的深度進(jìn)行爬取判斷
  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)首頁上的圖書分類進(jìn)去的網(wǎng)頁
    //網(wǎng)址為    http://book.dangdang.com/
//    String strurl="http://search.dangdang.com/?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爬取核心對(duì)象
    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ù),并進(jìn)行內(nèi)容分析
      //因此用BufferedReader和InputStreamReader把字節(jié)流轉(zhuǎn)化為字符流的緩沖流
      //進(jìn)行轉(zhuǎn)換時(shí),需要處理編碼格式問題
      BufferedReader br=new BufferedReader(new InputStreamReader(is,"GB2312"));

      //按行讀取并打印
      String line=null;
      //正則表達(dá)式的匹配規(guī)則提取該網(wǎng)頁的鏈接
      Pattern p=Pattern.compile("<a .*href=.+</a>");
      //建立一個(gè)輸出流,用于保存文件,文件名為執(zhí)行時(shí)間,以防重復(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();
          //找到超鏈接地址并截取字符串
          //有無引號(hào)
          href=href.substring(href.indexOf("href="));
          if(href.charAt(5)=='\"'){
            href=href.substring(6);
          }else{
            href=href.substring(5);
          }
          //截取到引號(hào)或者空格或者到">"結(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地址放到隊(duì)列中
          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));
    }
}

控制臺(tái)顯示:

本地目錄顯示:

但是,僅是深度為2的也運(yùn)行不短地時(shí)間,

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

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

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

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

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

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

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

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

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

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

package com.exe1;
/**
 * 讀取當(dāng)當(dāng)網(wǎng)下首頁圖書的數(shù)據(jù),并進(jìn)行分析
 * 爬取深度為2
 * 爬去數(shù)據(jù)存儲(chǔ)到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的深度進(jìn)行爬取判斷
  private static Map<String,Integer> allurldepth=new HashMap<>();
  //爬取得深度
  private static int maxdepth=2;
  //生命對(duì)象,幫助進(jìn)行線程的等待操作
  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)首頁上的圖書分類進(jìn)去的網(wǎng)頁
    //網(wǎng)址為    http://book.dangdang.com/
//    String strurl="http://search.dangdang.com/?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()+" 爬取線程處理爬取:"+strurl);
    //建立url爬取核心對(duì)象
    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ù),并進(jìn)行內(nèi)容分析
      //因此用BufferedReader和InputStreamReader把字節(jié)流轉(zhuǎn)化為字符流的緩沖流
      //進(jìn)行轉(zhuǎn)換時(shí),需要處理編碼格式問題
      BufferedReader br=new BufferedReader(new InputStreamReader(is,"GB2312"));

      //按行讀取并打印
      String line=null;
      //正則表達(dá)式的匹配規(guī)則提取該網(wǎng)頁的鏈接
      Pattern p=Pattern.compile("<a .*href=.+</a>");
      //建立一個(gè)輸出流,用于保存文件,文件名為執(zhí)行時(shí)間,以防重復(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();
          //找到超鏈接地址并截取字符串
          //有無引號(hào)
          href=href.substring(href.indexOf("href="));
          if(href.charAt(5)=='\"'){
            href=href.substring(6);
          }else{
            href=href.substring(5);
          }
          //截取到引號(hào)或者空格或者到">"結(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地址放到隊(duì)列中
          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放入等待隊(duì)列中,同時(shí)判斷是否已經(jīng)放過
   * @param href
   * @param depth
   */
  public static synchronized void addurl(String href,int depth){
    //將url放到隊(duì)列中
    allwaiturl.add(href);
    //判斷url是否放過
    if(!allurldepth.containsKey(href)){
      allurldepth.put(href, depth+1);
    }
  }
  /**
   * 移除爬取完成的url,獲取下一個(gè)未爬取得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è)定一個(gè)死循環(huán),讓線程一直存在
      while(true){
        //判斷是否新鏈接,有則獲取
        if(allwaiturl.size()>0){
          //獲取url進(jìn)行處理
          String url=geturl();
          //調(diào)用workurl方法爬取
          workurl(url,allurldepth.get(url));
        }else{
          System.out.println("當(dāng)前線程準(zhǔn)備就緒,等待連接爬?。?+this.getName());
          count++;
          //建立一個(gè)對(duì)象,讓線程進(jìn)入等待狀態(tài),即wait()
          synchronized(obj){
            try{
              obj.wait();
            }catch(Exception e){

            }
          }
          count--;
        }
      }
    }

  }
}

控制臺(tái)顯示:

本地目錄顯示:

總結(jié):

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

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

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

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

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

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

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

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

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

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

這也是大數(shù)據(jù)數(shù)據(jù)收集的一個(gè)基礎(chǔ)。

        到此這篇關(guān)于使用java實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲的文章就介紹到這了,更多相關(guān)java實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot使用Swagger范例講解

    SpringBoot使用Swagger范例講解

    Swagger是一個(gè)規(guī)范和完整的框架,用于生成、描述、調(diào)用和可視化 Restful 風(fēng)格的 Web 服務(wù)??傮w目標(biāo)是使客戶端和文件系統(tǒng)作為服務(wù)器以同樣的速度來更新。文件的方法、參數(shù)和模型緊密集成到服務(wù)器端的代碼,允許API來始終保持同步
    2022-07-07
  • Java生成UUID的常用方式示例代碼

    Java生成UUID的常用方式示例代碼

    UUID保證對(duì)在同一時(shí)空中的所有機(jī)器都是唯一的,通常平臺(tái)會(huì)提供生成的API,按照開放軟件基金會(huì)(OSF)制定的標(biāo)準(zhǔn)計(jì)算,用到了以太網(wǎng)卡地址、納秒級(jí)時(shí)間、芯片ID碼和許多可能的數(shù)字,下面這篇文章主要給大家介紹了關(guān)于Java生成UUID的常用方式,需要的朋友可以參考下
    2023-05-05
  • Spring?@Cacheable注解類內(nèi)部調(diào)用失效的解決方案

    Spring?@Cacheable注解類內(nèi)部調(diào)用失效的解決方案

    這篇文章主要介紹了Spring?@Cacheable注解類內(nèi)部調(diào)用失效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Springboot項(xiàng)目快速實(shí)現(xiàn)攔截器功能

    Springboot項(xiàng)目快速實(shí)現(xiàn)攔截器功能

    上一篇文章介紹了Springboot項(xiàng)目如何快速實(shí)現(xiàn)過濾器功能,本篇文章接著來盤一盤攔截器,仔細(xì)研究后會(huì)發(fā)現(xiàn),其實(shí)攔截器和過濾器的功能非常類似,可以理解為面向切面編程的一種具體實(shí)現(xiàn)。感興趣的小伙伴可以參考閱讀
    2023-03-03
  • Spring Boot實(shí)現(xiàn)通用的接口參數(shù)校驗(yàn)

    Spring Boot實(shí)現(xiàn)通用的接口參數(shù)校驗(yàn)

    本文介紹基于 Spring Boot 和 JDK8 編寫一個(gè) AOP ,結(jié)合自定義注解實(shí)現(xiàn)通用的接口參數(shù)校驗(yàn)。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • Springmvc如何返回xml及json格式數(shù)據(jù)

    Springmvc如何返回xml及json格式數(shù)據(jù)

    這篇文章主要介紹了Springmvc如何返回xml及json格式數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • jedis連接池對(duì)commons-pool的封裝示例詳解

    jedis連接池對(duì)commons-pool的封裝示例詳解

    這篇文章主要為大家介紹了jedis連接池對(duì)commons-pool的封裝示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Mybatisplus創(chuàng)建Spring?Boot工程打包錯(cuò)誤的解決方式

    Mybatisplus創(chuàng)建Spring?Boot工程打包錯(cuò)誤的解決方式

    最近在實(shí)戰(zhàn)springboot遇到了一些坑,記錄一下,下面這篇文章主要給大家介紹了關(guān)于Mybatisplus創(chuàng)建Spring?Boot工程打包錯(cuò)誤的解決方式,文中通過圖文介紹的介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03
  • SpringMVC處理器映射器HandlerMapping詳解

    SpringMVC處理器映射器HandlerMapping詳解

    這篇文章主要介紹了SpringMVC處理器映射器HandlerMapping詳解,在SpringMVC中會(huì)有很多請求,每個(gè)請求都需要一個(gè)HandlerAdapter處理,具體接收到一個(gè)請求之后使用哪個(gè)HandlerAdapter進(jìn)行處理呢,他們的過程是什么,需要的朋友可以參考下
    2023-09-09
  • springboot+spring?data?jpa實(shí)現(xiàn)新增及批量新增方式

    springboot+spring?data?jpa實(shí)現(xiàn)新增及批量新增方式

    這篇文章主要介紹了springboot+spring?data?jpa實(shí)現(xiàn)新增及批量新增方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11

最新評(píng)論