자바 다 중 스 레 드 및 분포 식 파충류 구조 원리 분석

이것 은 자바 파충류 시리즈 박문 의 다섯 번 째 편 으로 전편자바 파충류 서버 차단 솔 루 션에서 우 리 는 반 파충류 전략 과 반 파충류 방법 에 대해 간단하게 이야기 하 는데 주로 IP 가 봉쇄 되 고 그 대응 방법 을 대상 으로 한다.앞의 몇 편의 글 에서 우 리 는 파충류 와 관련 된 기본 지식 을 거의 다 말 하지 못 했다.이 편 은 파충류 구조 와 관련 된 내용 을 이야기 해 보 자.
앞의 몇 장의 내용 은 우리 의 파충류 프로그램 이 모두 단일 라인 이다.우리 가 파충류 프로그램 을 디 버 깅 할 때 단일 라인 파충 류 는 아무런 문제 가 없 지만,우리 가 온라인 환경 에서 단일 라인 파충 류 프로그램 을 사용 하여 웹 페이지 를 채집 할 때 단일 라인 은 두 가지 치 명 적 인 문 제 를 드 러 냈 다.
4.567917.채집 효율 이 매우 느 리 고 단일 스 레 드 간 에 직렬 로 되 어 있 으 며 다음 실행 동작 은 이전 실행 이 끝나 야 실행 할 수 있 습 니 다
  • 서버 의 CUP 등 이 용 률 이 높 지 않 습 니 다.저희 서버 는 모두 8 핵 16G 이 고 32G 는 한 라인 만 달 리 는 것 이 너무 낭비 되 지 않 을 까 생각 합 니 다
  • 온라인 환경 은 우리 지역 테스트 처럼 채집 효율 에 신경 쓰 지 않 고 결 과 를 정확하게 추출 할 수 있 으 면 된다.이 시간 이 바로 돈의 시대 입 니 다.천천히 채집 할 시간 을 줄 수 없 기 때문에 단일 스 레 드 파충류 프로그램 은 통 하지 않 습 니 다.우 리 는 단일 스 레 드 를 다 중 스 레 드 모델 로 바 꾸 어 채집 효율 을 높이 고 컴퓨터 이 용 률 을 높 여야 합 니 다.
    다 중 스 레 드 의 파충류 프로 그래 밍 은 단일 스 레 드 보다 훨씬 복잡 하지만 다른 업무 가 높 고 데이터 안전 을 확보 해 야 하 는 것 과 달리 다 중 스 레 드 파충류 가 데이터 안전 에 있어 요구 가 그리 높 지 않다.모든 페이지 가 하나의 입체 로 볼 수 있 기 때문이다.많은 스 레 드 파충 류 를 하려 면 반드시 두 가 지 를 잘 해 야 한다.첫 번 째 는 채집 대기 URL 유지 이다.두 번 째 는 URL 의 무 게 를 없 애 는 것 이다.다음은 우리 가 간단하게 이 두 가 지 를 이야기 하 자.
    수집 할 URL 유지
    다 중 스 레 드 파충류 프로그램 은 단일 스 레 드 처럼 모든 스 레 드 가 자신의 채집 대기 URL 을 혼자 유지 할 수 없습니다.만약 그렇다면 모든 스 레 드 가 수집 한 웹 페이지 는 똑 같 을 것 입 니 다.이것 은 다 중 스 레 드 채집 이 아 닙 니 다.한 페이지 를 여러 번 수집 하 는 것 입 니 다.이 를 바탕 으로 저 희 는 수집 해 야 할 URL 을 통일 적 으로 유지 해 야 합 니 다.모든 스 레 드 는 통 일 된 URL 유지보수 에서 채집 URL 을 받 고 채집 작업 을 완성 해 야 합 니 다.페이지 에서 새로운 URL 링크 를 발견 하면 통 일 된 URL 유지보수 용기 에 추가 합 니 다.다음은 URL 유지 보수 에 적합 한 몇 가지 용기 입 니 다.
  • JDK 의 보안 대기 열,예 를 들 어 링크 드 BlockingQueue
  • 고성능 NoSQL,예 를 들 어 Redis,MongodbMQ 메시지 중간 부품URL 의 중복 제거
    URL 의 무 게 를 줄 이 는 것 도 다 중 스 레 드 채집 의 관건 적 인 단계 입 니 다.무 게 를 줄 이지 않 으 면 중복 되 는 URL 을 대량으로 수집 할 것 입 니 다.그러면 우리 의 채집 효율 을 향상 시 키 지 못 합 니 다.예 를 들 어 한 페이지 의 뉴스 목록 입 니 다.우 리 는 첫 페이지 를 채집 할 때 2,3,4,5 페이지 의 링크 를 얻 을 수 있 고 두 번 째 페이지 를 채집 할 때 1,3,4 를 얻 을 수 있 습 니 다.5 페이지 의 링크 입 니 다.수집 할 URL 대기 열 에 대량의 목록 페이지 링크 가 존재 합 니 다.그러면 반복 채집 을 하거나 순환 에 들 어가 기 때문에 URL 을 다시 해 야 합 니 다.URL 무 게 를 줄 이 는 방법 은 매우 많 습 니 다.다음은 자주 사용 하 는 URL 무 게 를 줄 이 는 방법 입 니 다.
  • URL 을 데이터베이스 에 저장 하여 리 디 스,MongoDB
  • 해시 표 에 URL 을 넣 고 무 게 를 제거 합 니 다.예 를 들 어 hashset
  • URL 을 MD5 를 거 친 후에 해시 표 에 저장 하면 위 에 있 는 것 보다 공간 을 절약 할 수 있 습 니 다
  • 4.567917.부 릉 필터(Bloom Filter)를 사용 하여 무 게 를 제거 하 는데 이런 방식 은 대량의 공간 을 절약 할 수 있 지만 정확 하지 않다다 중 스 레 드 파충류 에 관 한 두 가지 핵심 지식 을 우 리 는 모두 알 고 있다.아래 에 나 는 간단 한 다 중 스 레 드 파충류 구조 도 를 그 렸 다.다음 그림 과 같다.

    위 에서 우 리 는 주로 다 중 스 레 드 파충류 의 구조 디자인 을 알 게 되 었 습 니 다.그 다음 에 자바 다 중 스 레 드 파충 류 를 시도 해 보 겠 습 니 다.우 리 는 호랑이 뉴스 를 채집 하 는 것 을 예 로 들 어 자바 다 중 스 레 드 파충 류 를 실전 해 보 겠 습 니 다.자바 다 중 스 레 드 파충 류 는 채집 해 야 할 URL 의 유지 와 URL 을 무 겁 게 디자인 했 습 니 다.우 리 는 여기 서 시연 만 하기 때문에 JDK 에 내 장 된 용 기 를 사용 하여 완성 합 니 다.저 희 는 LinkedBlockingQueue 를 채집 대기 URL 유지보수 용기 로 사용 하고 HashSet 을 URL 로 재 활용 기 를 사용 합 니 다.다음은 자바 다 중 스 레 드 파충류 의 핵심 코드 입 니 다.자세 한 코드 는 GitHub 를 업로드 하고 주 소 는 글 끝 에 있 습 니 다.
    
    /**
     *      
     */
    public class ThreadCrawler implements Runnable {
      //       
      private final AtomicLong pageCount = new AtomicLong(0);
      //           
      public static final String URL_LIST = "https://voice.hupu.com/nba";
      protected Logger logger = LoggerFactory.getLogger(getClass());
      //       
      LinkedBlockingQueue<String> taskQueue;
      //         
      HashSet<String> visited;
      //    
      CountableThreadPool threadPool;
      /**
       *
       * @param url    
       * @param threadNum    
       * @throws InterruptedException
       */
      public ThreadCrawler(String url, int threadNum) throws InterruptedException {
        this.taskQueue = new LinkedBlockingQueue<>();
        this.threadPool = new CountableThreadPool(threadNum);
        this.visited = new HashSet<>();
        //              
        this.taskQueue.put(url);
      }
    
      @Override
      public void run() {
        logger.info("Spider started!");
        while (!Thread.currentThread().isInterrupted()) {
          //           URL
          final String request = taskQueue.poll();
          //      request   ,                 
          if (request == null) {
            if (threadPool.getThreadAlive() == 0) {
              break;
            }
          } else {
            //       
            threadPool.execute(new Runnable() {
              @Override
              public void run() {
                try {
                  processRequest(request);
                } catch (Exception e) {
                  logger.error("process request " + request + " error", e);
                } finally {
                  //      +1
                  pageCount.incrementAndGet();
                }
              }
            });
          }
        }
        threadPool.shutdown();
        logger.info("Spider closed! {} pages downloaded.", pageCount.get());
      }
    
      /**
       *       
       * @param url
       */
      protected void processRequest(String url) {
        //         
        if (url.matches(URL_LIST)) {
          //                  URL   
          processTaskQueue(url);
        } else {
          //     
          processPage(url);
        }
      }
      /**
       *       
       *      ,  url       
       *
       * @param url
       */
      protected void processTaskQueue(String url) {
        try {
          Document doc = Jsoup.connect(url).get();
          //      
          Elements elements = doc.select(" div.news-list > ul > li > div.list-hd > h4 > a");
          elements.stream().forEach((element -> {
            String request = element.attr("href");
            //                   set  ,          
            if (!visited.contains(request) && !taskQueue.contains(request)) {
              try {
                taskQueue.put(request);
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
            }
          }));
          //      
          Elements list_urls = doc.select("div.voice-paging > a");
          list_urls.stream().forEach((element -> {
            String request = element.absUrl("href");
            //                 
            if (request.matches(URL_LIST)) {
              //                   set  ,          
              if (!visited.contains(request) && !taskQueue.contains(request)) {
                try {
                  taskQueue.put(request);
                } catch (InterruptedException e) {
                  e.printStackTrace();
                }
              }
            }
          }));
    
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
      /**
       *     
       *
       * @param url
       */
      protected void processPage(String url) {
        try {
          Document doc = Jsoup.connect(url).get();
          String title = doc.select("body > div.hp-wrap > div.voice-main > div.artical-title > h1").first().ownText();
    
          System.out.println(Thread.currentThread().getName() + "   " + new Date() + "         " + title);
          //       url          set  
          visited.add(url);
    
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    
      public static void main(String[] args) {
    
        try {
          new ThreadCrawler("https://voice.hupu.com/nba", 5).run();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
    우 리 는 5 개의 스 레 드 로 호랑이 뉴스 목록 페이지 를 채집 하여 효 과 를 보 았 다.만약 에?이 프로그램 을 실행 하면 다음 과 같은 결 과 를 얻 을 수 있 습 니 다.
    다 중 스 레 드 채집 결과

    결 과 를 통 해 알 수 있 듯 이 우 리 는 5 개의 스 레 드 를 시작 하여 61 페이지 를 채 집 했 습 니 다.모두 2 초 걸 렸 습 니 다.효과 가 좋다 고 할 수 있 습 니 다.우 리 는 단일 스 레 드 와 비교 해서 차이 가 얼마나 큰 지 봅 시다.우 리 는 스 레 드 수 를 1 로 설정 하고 프로그램 을 다시 시작 하여 다음 과 같은 결 과 를 얻 을 수 있 습 니 다.
    단일 스 레 드 실행 결과

    이 를 통 해 알 수 있 듯 이 단일 스 레 드 에서 호랑이 가 61 개의 뉴스 를 수집 하 는 데 7 초 가 걸 렸 다.시간 차 이 는 다 중 스 레 드 의 4 배 에 불과 하 다.이것 은 61 페이지 에 불과 하 다.페이지 가 많 으 면 차이 가 점점 커지 기 때문에 다 중 스 레 드 파충류 의 효율 이 매우 높다.
    분산 식 파충류 구조
    분포 식 파충류 구 조 는 대형 채집 프로그램 이 사용 해 야 하 는 구조 이다.일반적인 상황 에서 단기 다 중 스 레 드 를 사용 하면 업무 수 요 를 해결 할 수 있다.어쨌든 나 는 분포 식 파충류 프로젝트 의 경험 이 없 기 때문에 이 부분 은 나 도 할 말 이 없다.그러나 우 리 는 기술자 로 서 기술 보존 열 도 를 필요 로 한다.비록 사용 하지 않 지만 이해 해도 무방 하 다.나 는 많은 자 료 를 찾 아 보고 다음 과 같은 결론 을 얻 었 다.
    분포 식 파충류 구 조 는 우리 의 다 중 스 레 드 파충류 구조 와 사고방식 에 있어 서 똑같다.우 리 는 다 중 스 레 드 를 바탕 으로 조금 만 개선 하면 간단 한 분포 식 파충류 구조 가 될 수 있다.분포 식 파충류 구조 에서 파충류 프로그램 은 서로 다른 기계 에 배치 되 어 있 기 때문에 우리 가 수집 해 야 할 URL 과 수집 한 URL 은 파충류 프로그램 기계 의 메모리 에 저장 할 수 없다.우 리 는 이 를 특정한 기계 에 통일 적 으로 유지 해 야 한다.예 를 들 어 Redis 나 MongoDB 에 저장 하고 모든 기 계 는 이 위 에서 채집 링크 를 가 져 와 야 한다.링크 드 BlockingQueue 와 같은 메모리 대기 열 에서 링크 를 찾 는 것 이 아니 라 간단 한 분포 식 파충류 구조 가 나 타 났 습 니 다.물론 이 안 에는 많은 세부 적 인 문제 가 있 을 것 입 니 다.저 는 분포 식 구조 경험 이 없 기 때문에 저도 말 할 수 없습니다.관심 이 있다 면 교 류 를 환영 합 니 다.
    소스 코드:소스 코드
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기