IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
한국 developerWorks   >   Open developerWorks  > developerworks

웹 스크래핑으로 웹 컨텐츠 입맛대로 꾸미기



박지인박지인 tisphie@gmail.com

화학공학도이자 루비스트다. 블로그는 http://tisphie.net.




난이도 : 초급
2007년 7월24일


[오픈 디벨로퍼웍스]는 여러분이 직접 필자로 참가하는 코너입니다. 이번에는 박지인 님이 웹에 있는 정보를 각자의 필요에 따라 가공할 수 있는 기술인 웹 스크래핑에 대해 소개합니다.

웹 스크래핑이란

위키백과에 나온 정의를 인용하면 다음과 같다.

  

Web scraping generically describes any of various means to extract content from a website 
over HTTP for the purpose of transforming that content into another format suitable for 
use in another context.


쉽게 말하자면 HTTP를 통해 웹 사이트에서 내용을 긁어다가 원하는 형태로 가공한다는 뜻이다. 검색엔진에서 사용하는 웹 크롤러, 게시판 스팸봇 따위도 웹 스크래핑 활용 사례 중 한 가지라 볼 수 있다. 요새는 사이트들마다 RSS라든지 XML-RPC 같이 접근할 수 있는 여러 가지 수단을 제공하지만, 그렇지 않은 사이트들에서 비슷한 기능을 구현하고자 할 때도 활용할 수 있다. 야후 파이프 같은 것이 그런 예가 되겠다.

   소셜 북마크

   mar.gar.in mar.gar.in
    digg Digg
    del.icio.us del.icio.us
    Slashdot Slashdot

여기서 얘기할 것들

간단한 예제를 통해 웹 스크래핑 애플리케이션을 만드는 것이 얼마나 간단한지 알아보자. 소스를 보면 허탈할 정도로 간단해 말이 안 나올지도 모르지만, 그것도 알아야 쓰는 거니까 속는 셈치고 한번 보자.


사용하는 도구들



위로



일러두기

  • 여기서 설명하는 예제는 리눅스에서 작성했지만 윈도우에서도 특별한 문제는 없을 것이다.
  • 이 글은 루비를 어느 정도 아는 독자들을 대상으로 한다. 루비를 한번도 접해보지 않은 사람은 배열, 해시 정도를 다루는 가벼운 루비 튜토리얼을 먼저 보길 바란다.


필요한 도구 설치

루비와 루비젬(gem)은 설치되어 있다고 가정한다. 혹시 루비를 아직 설치하지 않은 독자들은 황대산 님의 ‘루비 FAQ – 루비 설치하기’ 문서를 참고하기 바란다. 루비젬을 설치했다면 위에서 얘기한 라이브러리들은 다음과 같이 간단하게 설치할 수 있다.

  

 gem install hpricot mechanize -y



이것저것 읽어보자

구글 검색

구글 검색 결과를 간단하게 파싱해 루비 객체로 얻어보자. 방법은 간단하다. GET 메서드로 인자를 넘겨 제출(submit)한 후에 결과를 파싱해 배열로 저장하면 된다. 우선 코드의 뼈대부터 만들어 보자.

  

require 'mechanize'
agent = WWW::Mechanize.new
agent.user_agent_alias='Linux Mozilla'
agent.get "http://www.google.com/ncr"


agent는 메커나이즈 클래스의 인스턴스인데, 이제부터 이 agent를 조종해 웹 브라우저처럼 쓸 것이다. 그래서 user-agent가 웹 브라우저 흉내를 낼 것이다. 그리고 구글의 메인 페이지를 가져왔다.

여기까지 했으면 다음은 검색창의 폼을 채울 차례다. 그런데 어떻게 해야 할까? 폼을 찾아 값을 채우려면 이름이든 뭐든 실마리가 있어야 한다.

소스를 살펴보자. 파이어폭스 브라우저(소스를 볼 수 있다면 다른 웹 브라우저도 괜찮다)에서 http://www.google.com/ncr을 열고 소스보기 기능을 이용해 살펴보면 다음과 같은 부분을 찾을 수 있을 것이다. 간단하게 form 키워드로 찾으면 된다.

  

<form action="/search" name=f><table cellpadding=0 cellspacing=0><tr valign=top>
<td width=25%>&nbsp;</td><td align=center nowrap><input name=hl type=hidden value=en>
<input maxlength=2048 name=q size=55 title="Google Search" value=""><br>
<input name=btnG type=submit value="Google Search">
<input name=btnI type=submit value="I'm Feeling Lucky"></td>
<td nowrap width=25%><font size=-2>&nbsp;&nbsp;
<a href=/advanced_search?hl=en>Advanced Search</a><br>&nbsp;&nbsp;
<a href=/preferences?hl=en>Preferences</a><br>&nbsp;&nbsp;
<a href=/language_tools?hl=en>Language Tools</a></font></td></tr></table></form>


고맙게도 form에 이름이 붙어있다. 중간쯤에 보면 유일하게 hidden이 아닌 input 태그도 찾아볼 수 있다. 이제 코드로 써보자.

  

form = agent.page.form 'f'
form.q = 'ruby'


폼에 값을 채웠다. 검색을 시작해 보자.

  

form.submit


메커나이즈는 HTTP요청을 하고 값을 받아온다. 이것으로 절반이 끝났다. 남은 반은 결과를 원하는 형태로 가공하는 것이다. 여기서는 결과를 해시의 배열로 만들어 보겠다.

위와 마찬가지로 결과를 파싱하려면 HTML이 어떻게 구성되어 있는지 알아내야 한다. 브라우저로 동일한 검색어를 넣고 검색을 해서, 소스보기를 해보자.

소스보기 화면
그림 1. 소스보기 화면

그런데 소스를 보니 상상 이상으로 HTML이 지저분하다. 구글에서 스크래핑을 못하게 하려고 일부러 그런 것일까! 먼저 이 지저분한 코드를 어떻게든 알아내야겠는데, 어떻게 하면 좋을까. 소스코드를 들여쓰기 해서 깨끗하게 해 놓고 보면 좀 나을지도 모르지만 엄청나게 길어질 것이다. 여기서는 인간답게 도구를 써보자. 분명히 DOM을 보기 편하게 해주는 무언가가 있을 것이다.

파이어폭스 부가 기능 중에 firebug라는 것이 있다. 설치하고 나서 구글 검색 결과 페이지에서 오른쪽 아래의 아이콘을 눌러 활성화시켜 보자.

그림 2
그림 2. 초록색 동그라미 바탕의 체크 표시가 되어있으면 firebug가 활성화되어 있다는 표시. 만약 회색 동그라미라면 눌러서 활성화시켜주면 된다.

벌레 모양 아이콘 옆에 보면 Inspect라는 버튼이 있다. 이걸 누르니 아래 창에 약간 변화가 생겼다. 마우스를 이리저리 옮겨보자. 노드가 열렸다 닫혔다 한다. 첫 번째 검색결과 링크로 가서 클릭해 보자.

Inspect 버튼
그림 3. Inspect 버튼

이제 아래쪽 반전된 곳에 가서 마우스 오른쪽 버튼을 클릭하면 팝업 메뉴에 Copy XPath란 항목이 있다. 이걸 선택하고 아무 데나 한번 복사해 보자.

Xpath 복사
그림 4. Xpath 복사

  

/html/body/div[4]/div/div/h2/a


이건 뭘까. 척 보면 HTML 태그의 계층 구조란 걸 알 수 있다. 이제 코드를 완성할 수 있게 되었다.

  

results = (agent.page/"/html/body/div[4]/div/div/h2/a").map 
{|a|
  { :title => CGI.unescapeHTML(a.inner_text), :link => a.attributes['href'] }
}


겨우 이것뿐이다! HTML에서 기호를 표기하는 인코딩을 디코드하는 코드를 제외하면 실제로 파싱하는 부분은 강조한 부분이 전부다. 이게 어떻게 된 것일까?

앞서 저 문자열을 XPath라고 했던 것을 기억하는가? XML을 다뤄본 사람이라면 이미 알고 있을지도 모른다. 그렇다. XML을 파싱하는 것과 비슷하게 HTML을 파싱할 수 있다.



위로



Hpricot을 좀더 자세히

Hpricot은 why the lucky stiff가 만든 HTML 파싱 라이브러리다. 스캐너로 Ragel이란 C기반 스캐너를 사용하기 때문에 꽤 빠르다(파이썬의 beatifulsoup보다 빠르다!). Hpricot만 사용하고 싶을 땐 어떻게 하면 좋을까? 다음은 문자열로 저장된 HTML 문서에서 anchor 태그를 찾아내는 코드다.

  

html =<<EOF
<html><body><a href="/somewhere_in_the_rainbow'>
</body>
</html>
EOF
doc = Hpricot(html)
doc/"//a"


Hpricot은 클래스 이름이지만, 메서드로 사용할 때는 Kernel#Hpricot이 된다. 이것은 Kernel#Integer 같은 관례와 마찬가지이므로 너무 놀랄 필요는 없다. Hpricot은 Xpath 외에도 CSS에서 사용하는 식으로도 노드를 검색할 수 있다. 자세한 것은 why the lucky stiff의 문서를 참조하자.


간단한 소스포지 다운로더 만들기

소스포지에서 필요한 소프트웨어를 다운로드 하려면 프로젝트 페이지로 들어가 수 차례 클릭을 해야 겨우 받을 수 있다. 이걸 좀 편하게 코드로 짜보자. 과정은 구글 검색과 크게 다르지 않다. 먼저 http://sf.net 메인 페이지를 열어 폼을 채우고 검색 페이지로 이동한다.

  

require 'mechanize'
agent = WWW::Mechanize.new
agent.get "http://sf.net"
agent.keep_alive = false # keep-alive를 지원하지 않는 사이트에서 문제가 생기지 않도록 하기 위한 work-around
form = agent.page.form('searchform')  #
form.words = "pidgin"
form.submit


검색 결과를 보니 나오긴 나왔는데, 순서가 마음에 안 든다. activity 대신 rank 순으로 바꿔보자.

  

link = agent.page.links.text 'Rank'
link.click


프로젝트 페이지가 나타났다. 다운로드 페이지로 이동한다.

  

anchor = (agent.page/"//td[@class='project']/h3/a").first
project_name = anchor.inner_text
agent.get anchor.attributes['href']
link = agent.page.links.text /Download .*/
link.click


아까와 약간 다른 부분이 td[@class='project'] 부분이다. 이것은 XPath 표현 중 하나로, class 속성이 project인 노드를 선택하라는 뜻이다. 프로젝트 페이지로 이동하니 최근 다운로드 링크를 발견할 수 있다. Download 이름을 가진 링크를 클릭하면 될 것 같다. 일단 Download로 된 링크를 검색해 보자.

  

irb(main):109:0> agent.page.links.text(/Download/).length
=> 3


앗? 그런데 링크가 세 개다. 다시 페이지를 보니, 첫 번째 것은 페이지 위쪽의 다운로드 링크다. 원하는 게 이것이 아니므로 여기선 두 번째 링크를 선택하기로 한다.

  

agent.page.links.text(/Download/)[1].click


선택한 범주(여기서는 GTK+ for Windows)의 최근 파일들이 나타났다. 이것들의 링크를 알아내야 한다. firebug를 이용해 XPath를 알아내자.

  

dls = (agent.page/"//table/tbody[2]/tr/td[2]/a")[1..-1]


여기서도 좀 전과 마찬가지로, 첫 번째 나타난 것이 위쪽의 필요 없는 링크여서 제외했다. 이제 이것들을 다운로드 한다.

  

file = agent.get(dls.first.attributes['href'])
puts "saving #{file.filename}"
file.save


save 메서드는 Mechanize::File 클래스가 제공한다. 메커나이즈가 URL을 열다가, 해석할 수 없는 것을 만나면 파일로 간주하고 반환한다. 그것을 저장하는 기능도 물론 제공한다.

이것으로 다운로더 완성이다. 하지만 이 코드는 불완전하다. Pidgin 대신 사용자 입력을 받아 검색어를 지정할 수 있게 해주고, 여러 개의 파일들을 저장하는 기능들은 직접 구현해 보자.

힌트: IO#gets 메서드 등을 이용하면 사용자로부터 입력을 받을 수 있다. 또 ARGV 변수를 입력하면 명령행 옵션을 사용할 수 있다. 메커나이즈의 다른 유용한 메서드들도 살펴보자.


마치며

웹이 발전하면서 웹에 접근하는 방법들도 많이 늘어났다. 덕분에 문자열 비교함수만 가지고 웹에 접근하는 시대는 지났다. 코드 몇 줄만 가지고 웹 문서를 자유자재로 다룰 수 있게 된 것이다. 웹 기반으로 된 애플리케이션을 다른 형태로 가공해 편하게 사용하는 것은 여러분의 몫이다. 대학교 수강 신청이든, 블로그 댓글 달기든 원하는 대로 만들어 보라.






위로


참고자료





이제 전문가의 글을 단순히 ‘보는 것’에서, 직접 여러분이 developerWorks의 필자가 될 수 있습니다. IBM developerWorks를 통해 공유하고 싶은 지식이 있으신 분들은 원고 기획안을 접수해주세요. 채택되신 분께는 소정의 원고료를 드립니다.
Open developerWorks 신청하기   MS워드 아이콘   아래아한글 아이콘



[지난 Open dW 보기]
[이 기사에 대한 의견 쓰기 - 웹 개발 포럼]
[이 기사에 대한 의견 쓰기 - 오픈 소스 포럼]

사이트 여행

dW 커뮤니티
포럼 | 블로그 | Spaces
dW Student Community

로컬 컨텐츠

행사 및 세미나

기획 기사

개발자 입문

튜토리얼 및 교육

TOP 10 인기자료

SW 다운로드

RSS 피드

뉴스레터
  
자바스크립트가 작동이 중지되었습니다. 이 기능을 수행하시려면 브라우저에서 자바스크립스트를 작동시켜 주시거나 이곳을 클릭해주세요.
Special offers
IBM SOA Sandbox 시험판
dW Student Community
로보코드
코드 트레이닝


    IBM 소개 개인정보 보호정책 문의