gimmesilver's blog

Agbird.egloos.com

포토로그



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

간만에 하스켈 관련 글을 올립니다...

이번에는 간단한 웹 크롤러를 하스켈로 구현해 보겠습니다. 제가 지금 다니고 있는 회사에 입사할 때 받았던 지원 과제가 바로 '간단한 웹 크롤러 구현하기'였습니다. (정확하게는 웹 크롤러와 인덱스 생성기 두 개인데 이제는 과제가 바뀌었습니다.)

그 때는 C++로 구현했었고 C++에서는 플랫폼 독립적으로 사용할 수 있는 마땅한 라이브러리가 없어서 HTTP 프로토콜, HTML 파서, URL 파서, 쿠키 관리자 등을 직접 구현했었습니다.

(참고로 제가 구현한 C++ 웹 크롤러는 http://irgroup.org/zb5/?sid=45&article_srl=4304 에서 보실 수 있습니다. 윈도우와 리눅스에서 다 실행할 수 있는데 리눅스에서는 멀티 쓰레드 관련해서 버그가 있습니다. 제가 리눅스에 익숙치 않다보니...^^;)

하지만 하스켈 컴파일러인 GHC 에서는 이와 관련된 라이브러리들을 잘 지원해주고 있으므로 이것들을 최대한 활용해서 그 때 과제 정도 수준으로 간단하게 구현해 보도록 하겠습니다.


우선 첫 번째 단계로 실행 인자로 주어진 seed url 리스트의 웹 페이지를 가져와 파일로 저장하는 소스입니다.

이 글은 계속 수정하면서 전체 소스를 올리도록 하고 각 부분에 대한 세부적인 설명은 다른 글을 통해 언급하도록 하겠습니다.


import Network.HTTP
import Network.URI
import System.Environment (getArgs)
import System.IO
import Text.ParserCombinators.Parsec
data LimitUrlQueue = LimitUrlQueue (Int, [String])

pushQueue :: [String] -> LimitUrlQueue -> Maybe LimitUrlQueue
pushQueue e (LimitUrlQueue (i, u)) =
    if (length u > i)
        then Nothing
        else Just $ LimitUrlQueue (i, u ++ e)
       
popQueue :: LimitUrlQueue -> Maybe (String, LimitUrlQueue)
popQueue (LimitUrlQueue (i, u)) =
    if (length u == 0)
        then Nothing
        else Just (head u, LimitUrlQueue (i, tail u))
   
main = do
    (qSize : seedList) <- getArgs
    simpleCrawler 0 $ LimitUrlQueue (read qSize, seedList)
    putStrLn "done!!!"
   
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
               
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);
           
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 = "" }
                      
saveContents idx body = writeFile (show idx ++ ".html") body >> return (idx+1)
       
extractLinks baseUrl body = extractAnchor body >>= extractLinkSrc baseUrl
   
extractLinkSrc baseUrl = return . (filter ((/=0) . length)) . (map (escapeUrl . (filterUrl baseUrl) . (extractLinkSrc' baseUrl)))
extractLinkSrc' baseUrl anchor = case (parseRun parseAnchorLink anchor) of
    Left err -> Nothing
    Right link -> parseRelativeReference link >>= (`relativeTo` baseUrl)

isDuplicateUrl url1 url2 = if (uriScheme url1 == uriScheme url2
                               && uriAuthority url1 == uriAuthority url2
                               && uriPath url1 == uriPath url2
                               && uriQuery url1 == uriQuery url2) then True else False
                              
escapeUrl = escapeURIString isAllowedInURI
filterUrl _ Nothing = ""
filterUrl baseUrl (Just url) = if (isDuplicateUrl baseUrl url) then "" else show url

parseAnchorLink = parseStartLink
    >> (try parseEndLink1 <|> try parseEndLink2 <|> try parseEndLink3 <|> try parseEndLink4)

doubleQuote = char '\"'
singleQuote = char '\''
parseStartLink = string "a " >> manyTill anyChar (string "href=")
parseEndLink1 = doubleQuote >> manyTill anyChar doubleQuote
parseEndLink2 = singleQuote >> manyTill anyChar singleQuote
parseEndLink3 = manyTill anyChar space
parseEndLink4 = manyTill anyChar eof

extractAnchor html = extractTag html >>= (return . filter (checkTag check_a_href))

check_a_href = string "a " >> manyTill anyChar (string "href")
checkTag f tag = case (parseRun f tag) of
    Left _ -> False
    Right _ -> True
   
extractTag html = case (parseRun extractTag' html) of
    Left err -> print err >> return []
    Right tagList -> return tagList
   
extractTag' = do {tag <- tagParser
                ; tagList <- extractTag'
                ; return (tag:tagList)
                } <|> return [""]

parseRun p t = parse p "" t

tagParser = try parseComment <|> try parseScript <|> try parseTag <|> try parsePlain
parseComment = string "<!--" >> manyTill anyChar (string "-->") >> return ""
parseScript = string "<script" >> manyTill anyChar (string "</script>") >> return ""
parseTag = char '<' >> manyTill anyChar (char '>')
parsePlain = anyChar >> return ""


위 소스를 컴파일하고 실행한 화면입니다.


위 소스를 컴파일하기 위해서는 GHC가 기본으로 제공하는 라이브러리 외에 추가적으로 HTTP 라이브러리가 필요합니다.
HTTP 라이브러리 설치법 및 소스에 대한 자세한 설명은 다음에 하도록 하겠습니다.

핑백

  • gimmesilver's blog : 하스켈로 웹 크롤러 구현하기...2 2007-07-08 22:59:12 #

    ... 요즘은 블로그에 글 쓸 시간이 정말 부족하군요...그래도 마냥 손놓고 있을 수는 없어서 일단 약간이나마 글을 써보도록 하겠습니다.우선 백승우님이 이전 글에서 크롤러에 대해 물어 보셨기 때문에 웹 크롤러에 대해 간단하게 설명하겠습니다.웹 크롤러는 자동으로 웹문서를 수집하는 프로그램입니다. 웹 로봇, 스파이더 등 ... more

  • gimmesilver's blog : 하스켈로 웹크롤러 구현하기...3 2007-07-15 14:02:15 #

    ... 하스켈로 웹 크롤러 구현하기...1 에 올린 소스 내용을 하나씩 설명하도록 하겠습니다.이 웹크롤러는 다음과 같은 방식으로 동작합니다.1. 먼저 실행 인자로 최대 큐에 저장가능한 URL 갯수와 시작 ... more

  • gimmesilver's blog : 하스켈로 웹크롤러 구현하기...5 2007-07-17 19:06:47 #

    ... 들을 제거한 리스트를 반환합니다.이렇게 해서 웹문서에서 a href 태그의 링크들을 추출하는 소스까지 웹 크롤러 프로그램을 구현해 봤습니다. 전체 소스는 하스켈로 웹크롤러 구현하기...1 에 있습니다. 비록 단일 쓰레드 기반의 매우 단순한 크롤러 프로그램이지만 하스켈이 단순한 수학용 언어이다라는 편견을 깨는데 도움이 되었으면 좋겠습니다. ... more

  • gimmesilver's blog : 간단한 C++ 웹 크롤러 소스 2010-08-03 18:54:57 #

    ... 어떤 분이 http://agbird.egloos.com/3549472#12466031 에서 C++로 만든 웹 크롤러 소스 좀 올려달라고 하셔서... ... more

덧글

  • 백승우 2007/06/29 00:55 # 답글

    원리가 궁금해요..
    크롤러란게 사실 뭔지도 모르겠지만.
    리눅스에 wget이랑 비슷한건가요?
  • silverbird 2007/06/30 01:08 # 답글

    // 백승우
    웹 크롤러란 간단하게 말씀드리면 웹 문서들을 자동으로 수집하는 프로그램입니다. 프로그램에 대한 보다 자세한 설명은 따로 글을 올리도록 하겠습니다.
  • 대학생 2010/08/02 22:27 # 삭제 답글

    안녕하세요? C++로 간단한 웹크롤러를 만들어 보고자 하는 대학생 입니다...
    제가 하스켈이라는 언어는 잘 알지 못해서 C++로 만든 웹크롤러의 소스를 참고하고자 하는데
    위에 써 놓으신 링크(http://irgroup.org/zb5/?sid=45&article_srl=4304)에 가보니 페이지가 깨져있네요ㅠㅠ
    혹시 C++로 구현한 웹크롤러 소스 파일을 이 블로그에 올려주실 수 있으신지요?
    아니면 제 메일 주소를 남겨 놓으면 가능할까요?
  • silverbird 2010/08/03 18:55 #

    http://agbird.egloos.com/5326997 에 올려놨습니다.
  • 대학생 2010/08/04 14:14 # 삭제 답글

    좋은 자료 올려주셔서 감사합니다.
    감사히 잘 참고하겠습니다...
  • 직딩 2012/03/21 14:41 # 삭제 답글

    검색중 우연히 들어오게되었는데요

    JSP로 크롤러를 만들어보려는 직딩입니다

    갑자기 말씀드려 죄송합니다만 혹시 JSP로 구현된 크롤러 예제가 있을까요??

    JSP로 된 크롤러를 예제보고 만들려고하는데 안보이네요..ㅠ 관련 예제가 있으시다면 부탁드립니다.

    혹시몰라 메일주소 남깁니다.

    juminwan@gmail.com
댓글 입력 영역