program story

크롤러를 작성하는 방법?

inputbox 2020. 11. 29. 10:26
반응형

크롤러를 작성하는 방법?


나는 우리 NPO의 웹 사이트와 콘텐츠에 대한 결과 목록을 크롤링하고 생성 할 수있는 간단한 크롤러를 작성하려고 생각했습니다.

아무도 이것을하는 방법에 대한 생각이 있습니까? 시작하기 위해 크롤러를 어디로 지정합니까? 검색 결과를 어떻게 되돌리고 계속 크롤링합니까? 무엇을 발견했는지 등을 어떻게 알 수 있습니까?


확실히 당신은 바퀴를 재발 명 할 것입니다. 하지만 기본 사항은 다음과 같습니다.

  • 방문하지 않은 URL 목록-하나 이상의 시작 페이지로 시드
  • 방문한 URL 목록-빙빙 돌지 않도록
  • 관심이없는 URL에 대한 일련의 규칙-전체 인터넷 색인을 생성하지 않습니다.

이를 영구 저장소에 넣으면 상태 손실없이 크롤러를 중지하고 시작할 수 있습니다.

알고리즘은 다음과 같습니다.

while(list of unvisited URLs is not empty) {
    take URL from list
    remove it from the unvisited list and add it to the visited list
    fetch content
    record whatever it is you want to about the content
    if content is HTML {
        parse out URLs from links
        foreach URL {
           if it matches your rules
              and it's not already in either the visited or unvisited list
              add it to the unvisited list
        }
    }
}

크롤러의 복잡한 부분은 엄청난 수의 웹 사이트 / 요청으로 확장하려는 경우입니다. 이 상황에서는 다음과 같은 몇 가지 문제를 처리해야합니다.

  • 정보를 모두 하나의 데이터베이스에 보관할 수 없습니다.

  • 대용량 인덱스를 처리하기에 RAM이 충분하지 않습니다.

  • 다중 스레드 성능 및 동시성

  • 크롤러 트랩 (URL, 캘린더, 세션 ID 등을 변경하여 생성 된 무한 루프) 및 중복 콘텐츠.

  • 둘 이상의 컴퓨터에서 크롤링

  • 잘못된 HTML 코드

  • 서버의 지속적인 http 오류

  • 압축이없는 데이터베이스는 약 8 배 더 큰 공간을 필요로합니다.

  • 루틴 및 우선 순위를 다시 크롤링합니다.

  • 압축 (Deflate / gzip)과 함께 요청을 사용합니다 (모든 종류의 크롤러에 적합).

그리고 몇 가지 중요한 것

  • robots.txt 존중

  • 그리고 웹 서버를 질식시키지 않기 위해 각 요청에 대해 크롤러가 지연됩니다.


다중 스레드 웹 크롤러

대형 웹 사이트를 크롤링하려면 멀티 스레드 크롤러를 작성해야합니다. 크롤링 된 정보를 파일 / 데이터베이스에 연결, 가져 오기 및 쓰기-크롤링의 세 단계이지만 단일 스레드를 사용하는 경우 CPU 및 네트워크 사용률이 쏟아집니다.

다중 스레드 웹 크롤러에는 두 개의 데이터 구조가 필요합니다. linksVisited (해시 맵 또는 trai로 구현되어야 함) 및 linksToBeVisited (이것은 대기열입니다).

웹 크롤러는 BFS를 사용하여 월드 와이드 웹을 탐색합니다.

기본 웹 크롤러의 알고리즘 :-

  1. linksToBeVisited에 하나 이상의 시드 URL을 추가합니다. linksToBeVisited에 URL을 추가하는 방법은 동기화되어야합니다.
  2. linksToBeVisited에서 요소를 팝하고이 요소를 linksVisited에 추가합니다. linksToBeVisited에서 URL을 팝하는이 팝 메서드는 동기화되어야합니다.
  3. 인터넷에서 페이지를 가져옵니다.
  4. 파일을 구문 분석하고 페이지에있는 지금까지 방문하지 않은 링크를 linksToBeVisited에 추가하십시오. 필요한 경우 URL을 필터링 할 수 있습니다. 사용자는 검색 할 URL을 필터링하는 일련의 규칙을 제공 할 수 있습니다.
  5. 페이지에서 찾은 필요한 정보는 데이터베이스 또는 파일에 저장됩니다.
  6. 대기열이 linksToBeVisited가 비어있을 때까지 2 ~ 5 단계를 반복합니다.

    다음은 스레드를 동기화하는 방법에 대한 코드 스 니펫입니다 ....

     public void add(String site) {
       synchronized (this) {
       if (!linksVisited.contains(site)) {
         linksToBeVisited.add(site);
         }
       }
     }
    
     public String next() {
        if (linksToBeVisited.size() == 0) {
        return null;
        }
           synchronized (this) {
            // Need to check again if size has changed
           if (linksToBeVisited.size() > 0) {
              String s = linksToBeVisited.get(0);
              linksToBeVisited.remove(0);
              linksVisited.add(s);
              return s;
           }
         return null;
         }
      }
    


크롤러는 개념 상 간단합니다.

HTTP GET을 통해 루트 페이지를 가져 와서 파싱하여 URL을 찾은 다음 이미 파싱되지 않은 경우 대기열에 넣습니다 (따라서 이미 파싱 한 페이지의 글로벌 레코드가 필요합니다).

콘텐츠 유형 헤더를 사용하여 콘텐츠 유형을 확인하고 크롤러를 HTML 유형 만 구문 분석하도록 제한 할 수 있습니다.

HTML 태그를 제거하여 일반 텍스트를 얻을 수 있으며, 텍스트 분석을 수행 할 수 있습니다 (태그 등을 얻기 위해 페이지의 고기). 고급을 얻은 경우 이미지의 alt / title 태그에서도 그렇게 할 수 있습니다.

그리고 백그라운드에서 큐에서 URL을 먹고 ​​동일한 작업을 수행하는 스레드 풀을 가질 수 있습니다. 물론 스레드 수를 제한하고 싶습니다.


NPO의 사이트가 상대적으로 크거나 복잡하다면 ( '다음 날'링크가있는 캘린더와 같은 '블랙홀'을 효과적으로 생성하는 동적 페이지가있는 경우) Heritrix 와 같은 실제 웹 크롤러를 사용하는 것이 좋습니다 .

사이트의 총 페이지 수가 적은 경우 curl 또는 wget 또는 직접 사용하여 벗어날 수 있습니다. 그들이 커지기 시작하거나 실제 크롤러를 사용하기 위해 스크립트를 더 복잡하게 만들기 시작했는지 또는 적어도 소스를보고 그들이 무엇을하고 왜하는지 확인하기 시작했는지 기억하십시오.

일부 문제 (더 있음) :

  • 블랙홀 (설명 된대로)
  • 재시도 (500을 받으면 어떻게 되나요?)
  • 리디렉션
  • 흐름 제어 (그렇지 않으면 사이트에 부담이 될 수 있음)
  • robots.txt 구현

Wikipedia에는 많은 알고리즘과 고려 사항을 다루는 웹 크롤러 에 대한 좋은 기사가 있습니다.

그러나 나는 내 크롤러를 작성하는 것을 귀찮게하지 않을 것입니다. 많은 작업이 필요하며 "간단한 크롤러"만 필요하기 때문에 실제로 필요한 것은 기성품 크롤러 뿐이라고 생각합니다 . 거의 모든 작업을 수행하지 않고 필요한 모든 작업을 수행 할 수있는 무료 및 오픈 소스 크롤러가 많이 있습니다.


단어 목록을 만들고 Google에서 검색된 각 단어에 대한 스레드를 만들 수 있습니다.
그런 다음 각 스레드는 페이지에서 찾은 각 링크에 대해 새 스레드를 만듭니다.
각 스레드는 데이터베이스에서 찾은 내용을 작성해야합니다. 각 스레드가 페이지 읽기를 마치면 종료됩니다.
그리고 거기에는 데이터베이스에 매우 큰 링크 데이터베이스가 있습니다.


내 회사 내부 검색을 위해 개방형 검색 서버를 사용 하고 있습니다 . http://open-search-server.com 도 개방형 소스입니다.


wget을 사용하고 모든 파일을 하드 드라이브에 덤프하는 재귀 웹을 수행 한 다음 다른 스크립트를 작성하여 모든 다운로드 파일을 살펴보고 분석합니다.

편집 : 또는 wget 대신 컬일 수도 있지만 curl에 익숙하지 않습니다. wget과 같은 재귀 다운로드를 수행하는지 모르겠습니다.


.net에서 반응 확장을 사용하여 간단한 웹 크롤러를 만들었습니다.

https://github.com/Misterhex/WebCrawler

public class Crawler
    {
    class ReceivingCrawledUri : ObservableBase<Uri>
    {
        public int _numberOfLinksLeft = 0;

        private ReplaySubject<Uri> _subject = new ReplaySubject<Uri>();
        private Uri _rootUri;
        private IEnumerable<IUriFilter> _filters;

        public ReceivingCrawledUri(Uri uri)
            : this(uri, Enumerable.Empty<IUriFilter>().ToArray())
        { }

        public ReceivingCrawledUri(Uri uri, params IUriFilter[] filters)
        {
            _filters = filters;

            CrawlAsync(uri).Start();
        }

        protected override IDisposable SubscribeCore(IObserver<Uri> observer)
        {
            return _subject.Subscribe(observer);
        }

        private async Task CrawlAsync(Uri uri)
        {
            using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromMinutes(1) })
            {
                IEnumerable<Uri> result = new List<Uri>();

                try
                {
                    string html = await client.GetStringAsync(uri);
                    result = CQ.Create(html)["a"].Select(i => i.Attributes["href"]).SafeSelect(i => new Uri(i));
                    result = Filter(result, _filters.ToArray());

                    result.ToList().ForEach(async i =>
                    {
                        Interlocked.Increment(ref _numberOfLinksLeft);
                        _subject.OnNext(i);
                        await CrawlAsync(i);
                    });
                }
                catch
                { }

                if (Interlocked.Decrement(ref _numberOfLinksLeft) == 0)
                    _subject.OnCompleted();
            }
        }

        private static List<Uri> Filter(IEnumerable<Uri> uris, params IUriFilter[] filters)
        {
            var filtered = uris.ToList();
            foreach (var filter in filters.ToList())
            {
                filtered = filter.Filter(filtered);
            }
            return filtered;
        }
    }

    public IObservable<Uri> Crawl(Uri uri)
    {
        return new ReceivingCrawledUri(uri, new ExcludeRootUriFilter(uri), new ExternalUriFilter(uri), new AlreadyVisitedUriFilter());
    }

    public IObservable<Uri> Crawl(Uri uri, params IUriFilter[] filters)
    {
        return new ReceivingCrawledUri(uri, filters);
    }
}

다음과 같이 사용할 수 있습니다.

Crawler crawler = new Crawler();
IObservable observable = crawler.Crawl(new Uri("http://www.codinghorror.com/"));
observable.Subscribe(onNext: Console.WriteLine, 
onCompleted: () => Console.WriteLine("Crawling completed"));

참고 URL : https://stackoverflow.com/questions/102631/how-to-write-a-crawler

반응형