본문 바로가기
장인으로의 여정/유용한 TIP

크롤러(crawler) 만들기(주의 : 실사이트 테스트하면 안됨)

by 유기농프로그래밍 2021. 9. 18.
반응형

크롤링(Crawling)이란?

크롤링 혹은 스크래핑은 웹 페이지를 그대로 가져와서 데이터를 추출하는 행위를 의미한다.

 

이번 프로그램은 요청 페이지들 안에 있는 url들을 print하는 것이다.

예를 들어 A라는 페이지를 열면, 그 안에는 다양한 url들을 호출하는 페이지가 있다.

네이버나 다음 메인페이지를 누르게 되면, 수많은 요청문과 함께 이미지 파일들과 css파일, json파일등등이 쏟아져 받아진다.

robot.txt 라는 파일을 보고 가져올지 말지를 정해야하는데, 그건 나중에 따로 개발해야한다.

 

구성방식

1. 먼저 실행시에 크롤링할 IP를 입력 받는다.

2. 입력받은 IP가 ssl인지 여부를 확인 후 크롤러를 실행시킨다.

3. crawled_urls 함수를 이용해 crawl을 실행한다.

4. url 데이터를 보고 html인지 여부를 판단하여, html 파싱을 한다.

5. parse된 html 안에 있는 href를 이용해 link들을 모두 가져온다.

6. 추출된 url들은 urlseen으로 저장해놓고, 다시 추출된 url로 검색한다.

7. 다 끝나게 되면 urlseen에 저장된 내용을 출력한다.

※ 참고로 ignored_exts 는 굳이 가져와서 html 확인할 필요가 없는 확장자들이다.


파일이름은 software_craftsman_crawler.py 으로 저장한다.

from parser import HtmlParser
from urllib.parse import urlparse

import os, sys
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
from urllib.parse import urljoin
import ssl

ignored_exts = [
    # images
    'mng', 'pct', 'bmp', 'gif', 'jpg', 'jpeg', 'png', 'pst', 'psp', 'tif',
    'tiff', 'ai', 'drw', 'dxf', 'eps', 'svg',

    # audio
    'mp3', 'wma', 'ogg', 'wav', 'ra', 'aac', 'mid', 'au', 'aiff',

    # video
    '3gp', 'asf', 'asx', 'avi', 'mov', 'mp4', 'mpg', 'qt', 'rm', 'swf', 'wmv',
    'm4a',

    # other
    'css', 'pdf', 'doc', 'exe', 'bin', 'rss', 'zip', 'rar', 'gz', 'tar', 'ova', 'pptx'
]

class Crawler(object):

    def __init__(self, seedurl, ssl_site):
        self.ssl_site = ssl_site
        self.seedurl = seedurl
        self.urlseen = set()  # store URLs

        # parse seed url to get domain.
        # crawler does not support external domain.
        urlparsed = urlparse(seedurl)
        self.domain = urlparsed.netloc

    def get_links(self, html):
        """
        Parse return link in html contents
        by finding href attribute in a tag.
        """

        hrefs = set()
        parser = HtmlParser(html)

        # get href tags from parsed results
        for href in parser.hrefs:
            u_parse = urlparse(href)

            # check whether href content is same domain with seed url
            if u_parse.netloc == '' or u_parse.netloc == self.domain:
                hrefs.add(href)
        return hrefs

    def fetch(self, url):
        """
        return fetch HTML content from url
        return empty string if response raise an HTTPError (not found, 500...)
        """

        try:
            for ignore_ext in ignored_exts:
                if ignore_ext in url:
                    return ''
            if self.ssl_site:
                context = ssl._create_unverified_context()
                res = urlopen(url, context=context)
                return res.read().decode('utf-8', 'ignore')
            else:
                req = Request(url)
                res = urlopen(req)
                return res.read().decode('utf-8', 'ignore')

        except HTTPError as e:
            print('ERROR: %s \t  %s' % (url, e.code))
            return ''
        except URLError as e:
            print('Reason: ', e.reason)
            return ''

    def crawl(self):
        # add seed url to url frontier
        # URL frontier is the list which stores found URL but not yet crawled.
        # it works like a queue.
        url_frontier = list()
        url_frontier.append(self.seedurl)

        while url_frontier:
            url = url_frontier.pop()  # get url from frontier

            # do not crawl twice the same page
            if url not in self.urlseen:
                html = self.fetch(url)

                if html:  # if reponse has html content
                    print('Crawl: ', url)
                    self.urlseen.add(url)

                for href in self.get_links(html):
                    # join seed url and href, to get url
                    joinlink = urljoin(self.seedurl, href)
                    url_frontier.append(joinlink)

    @property
    def crawled_urls(self):
        self.crawl()
        return self.urlseen

def main(argc, argv):
    if argc != 2:
        print("Input host")
        print("ex) python3 software_craftsman_crawler.py http://IP")
        return -1

    seedurl = argv[1]
    ssl_site = False
    if 'https' in seedurl:
        ssl_site = True
    crawler = Crawler(seedurl, ssl_site)
    # crawler.crawl()
    for url in crawler.crawled_urls:
        print('>>>', url)

if __name__ == "__main__":
    sys.exit(main(len(sys.argv), sys.argv))

 

파일이름은 parser.py 으로 저장한다.

(참고로 BeautifulSoup를 사용하기 위해 bs4가 설치 되어 있어야한다.)

from bs4 import BeautifulSoup

class HtmlParser(object):

    def __init__(self, html):
        self.soup = BeautifulSoup(html, 'html5lib')

    @property
    def hrefs(self):
        # find all a tag which contains href attribute
        a_tags = self.soup.find_all("a", {"href": True})
        for tag in a_tags:
            yield tag['href']

    @property
    def script_text(self):
        # find all script tag
        # return a list of text content in <script> tag.
        # for example: <script>alert(1)</script>
        # will return alert(1)
        scripts = self.soup.find_all('script')
        return [script.text for script in scripts]

같은 폴더에 두 파일을 넣고 

아래와 같은 예시로 실행시키면 된다.

python3 software_craftsman_crawler.py http://1.1.1.1

 

반응형

댓글