gimmesilver's blog

Agbird.egloos.com

포토로그



하스켈로 웹크롤러 구현하기...3 하스켈 스프링노트

하스켈로 웹 크롤러 구현하기...1 에 올린 소스 내용을 하나씩 설명하도록 하겠습니다.
이 웹크롤러는 다음과 같은 방식으로 동작합니다.

1. 먼저 실행 인자로 최대 큐에 저장가능한 URL 갯수와 시작 지점이 될 seed URL 리스트를 받습니다.
2. 1에서 받은 실행 인자들을 이용해 URL리스트를 저장할 큐를 만듭니다.
3. 큐에서 URL을 하나 꺼내 HTTP GET 명령을 통해 해당 URL의 HTML 데이터를 가져옵니다.
4. HTML 데이터를 파싱해서 <a href='...'> 에 있는 link URL 들을 추출합니다.
5. 4에서 추출된 link URL 리스트를 큐에 저장합니다.
6. 4에서 큐에 URL 리스트를 저장할 때 총 저장 갯수가 최초 설정한 최대 크기를 초과하거나 혹은 큐가 텅비게 되면 프로그램을 종료합니다. 그렇지 않으면 3번 과정으로 돌아가 다시 위 동작들을 반복합니다.

그러면 소스를 살펴보겠습니다.

우선 크롤러에서 사용할 라이브러리들을 지정합니다. 하스켈에서는 자바와 유사하게, 자기 모듈 이름을 먼저 지정하고(생략 가능), 사용될 외부 모듈을 기술한 후에 구현 소스가 이어집니다.

import Network.HTTP  -- HTTP 통신을 위한 각종 함수들이 정의되어 있습니다. 하스켈로 웹크롤러 구현하기...2 참조
import Network.URI    -- URI 파싱 및 상대 URL을 절대 URL로 변환하기와 같은 URL 파싱 관련 함수들이 정의된 모듈
import System.Environment (getArgs) -- 실행 인자 처리를 위한 모듈
import System.IO  -- readFile, writeFile 등과 같은 입출력 관련 함수들 정의 모듈
import Text.ParserCombinators.Parsec -- 문자열 파싱을 위해 사용되는 파싱 모듈, 여기서는 HTML 파싱을 위해 사용됨

그 다음에는 최대 사이즈가 정해진 큐 데이터 구조를 정의합니다.

data LimitUrlQueue = LimitUrlQueue (Int, [String])

이 큐는 최대 사이즈를 나타내는 정수형 변수와 URL 리스트를 저장하는 String list의 쌍으로 이루어진 간단한 자료형입니다.
이제 큐에 데이터를 저장하고 빼는 함수인 pushQueue와 popQueue를 구현합니다.

pushQueue :: [String] -> LimitUrlQueue -> Maybe LimitUrlQueue
pushQueue e (LimitUrlQueue (i, u)) =
    if (length u + length e > i)
        then Nothing
        else Just $ LimitUrlQueue (i, u ++ e)
       
pushQueue 는 저장 대상 e 와 저장할 공간인 큐를 파라미터로 받습니다. 만약 현재 저장된 URL 리스트와 저장할 URL 리스트의 총 길이가 큐 최대 사이즈인 i를 초과하면 Nothing을 반환하고 그렇지 않으면 큐에 저장 대상 e를 추가한 큐를 가진 Just 생성자를 반환합니다.

popQueue :: LimitUrlQueue -> Maybe (String, LimitUrlQueue)
popQueue (LimitUrlQueue (i, u)) =
    if (length u == 0)
        then Nothing
        else Just (head u, LimitUrlQueue (i, tail u))
   
popQueue는 큐에서 가장 앞에있는 원소를 하나 꺼냅니다. 이 때 큐에 데이터가 하나도 없으면 Nothing 을 반환하며 데이터가 있으면 큐의 맨 앞에 있는 URL (head u)과 이 URL을 삭제한 큐(LimitUrlQueue (i, tail u))의 쌍에 대한 Just 생성자를 반환합니다.

pushQueue와 popQueue는 데이터가 큐에 하나도 없는 상황에서 원소를 꺼내려고 하거나 최대 큐 사이즈를 초과하는 양을 저장하려고 할 때와 같은 비정상적인 요청에 대한 처리가 필요합니다. 하스켈에서는 이처럼 예외 상황에 대한 처리를 위한 방법 중 하나로 Maybe 타입을 사용합니다. pushQueue, popQueue 역시 반환값으로 Maybe 타입을 사용합니다. 

main = do
    (qSize : seedList) <- getArgs
    simpleCrawler 0 $ LimitUrlQueue (read qSize, seedList)
    putStrLn "done!!!"

이 프로그램의 메인 함수입니다. 메인에서는 실질적인 크롤 작업을 수행하는 함수인 simpleCrawler 함수를 호출하고 함수 호출이 끝나면 "done!!!" 이라는 메시지를 출력하고 프로그램을 종료합니다.
getArgs 함수는 실행 인자를 문자열 리스트로 반환해주는 함수입니다. 첫번째 실행인자는 최대 큐사이즈인데 문자열로 되어 있으므로 정수형으로 변환하기 위해 read라는 함수를 사용했습니다.

simpleCrawler idx urlQ = do
    case popQueue urlQ of
        Nothing -> putStrLn "queue is empty!!!"
        Just (url, queue) -> do
            (nextIdx, links) <- crawl url idx
            case pushQueue links queue of
                Nothing -> putStrLn "queue is full!!!"
                Just nextQueue -> simpleCrawler nextIdx nextQueue

simpleCrawler 함수는
1) 주어진 url 큐에서 url을 하나 꺼냅니다. 큐가 비어 있으면 "queue is empty!!!"라는 메시지를 출력하고 종료하며 그렇지 않은 경우에는 크롤링을 수행합니다.
2) 크롤링 함수인 crawl함수는 작업이 성공하면 데이터를 '인덱스.html' 형태로 저장하고 다음 인덱스 값과 추출된 링크 리스트의 쌍을 반환합니다. 
3) 추출된 링크 리스트를 큐에 추가합니다. 만약 큐 최대 사이즈를 초과하면 "queue is full!!!"이라는 메시지를 출력하고 종료합니다. 그렇지 않은 경우에는 simpleCrawler 함수를 재귀적으로 호출하여 위 과정을 반복합니다.

crawl url idx = do
    putStr (url ++ " --> ")
    case parseURI url of
        Nothing -> putStrLn "Invalid URL" >> return (idx, [])
        Just u -> do
            contents <- get u
            case contents of
                Nothing -> return (idx, [])
                Just body -> do
                    nextIdx <- saveContents idx body
                    linkList <- extractLinks u body
                    putStrLn ("crawled!")
                    return (nextIdx, linkList)

crawl 함수는
1) 주어진 url을 먼저 parseURI 함수를 이용해 파싱합니다. parseURI 함수는 주어진 문자열을 분석해서 올바른 URI 형태를 가진 문자열이면 URI 타입으로 반환합니다.
2) get 함수를 이용해 HTTP GET 명령을 수행합니다. get 명령이 성공하면 서버에서 전달받은 HTTP body 데이터를 반환합니다.
3) 전달받은 웹 문서를 saveContents 함수를 이용해 '인덱스.html' 형태의 파일로 저장합니다. saveContents 함수는 저장에 성공하면 다음 인덱스 값을 반환합니다.
4) 웹 문서를 파싱해서 <a href='...'> 에 있는 링크 url 들을 추출합니다.
5) 추출된 링크 url 리스트와 다음 index 값을 반환합니다.

get url = do
    response <- simpleHTTP $ request url
    case response of
        Left error -> return Nothing
        Right repData -> return $ Just $ rspBody repData

request uri = Request{ rqURI = uri,
                       rqMethod = GET,
                       rqHeaders = [],
                       rqBody = "" }

get 함수는 말그대로 HTTP GET 명령을 수행하고 성공 시 서버에서 전달받은 데이터를 반환합니다. (하스켈로 웹크롤러 구현하기...2 참조)

saveContents idx body = writeFile (show idx ++ ".html") body >> return (idx+1)

saveContents 함수는 서버에서 전달받은 웹 문서를, 주어진 idx 숫자를 파일이름으로 하는 html 파일로 만듭니다. writeFile은 System.IO 모듈에 정의된 파일 쓰기 함수입니다.

다음에는 하스켈 파싱 라이브러리인 Parsec을 이용해서 HTML 문서에서 링크를 추출하는 기능을 설명하겠습니다.


덧글

댓글 입력 영역