웹 페이지 코드 긁어오기 - wget :: Athtar의 잡동사니

+ 다른 웹 페이지 코드 긁어오기 - wget

웹 프로그래밍을 하다보면 웹 상에서 다른 웹 페이지의 코드를 긁어와야 하는 상황이 자주 발생한다. 보통 크롬 상에서 ‘소스 보기’ 기능을 통해 이를 수행할 수 있는데, 만약 자신이 원하는 페이지의 코드를 얻기 위해 일일이 크롬으로 해당 페이지에 접속하여 ‘소스 보기’를 통해 소스를 긁어온 뒤 붙여넣어 작업하는 방식은 너무나도 비효율적일 것이다.

이러한 수고를 덜어주기 위해, Linux 상에서는 wget이라는 유용한 명령어가 있다.

예를 들어 서버 상에서 다음과 같이 입력하면 네이버(http://www.naver.com)의 메인화면(index.html)의 코드를 통째로 긁어올 수 있다.

$ wget http://www.naver.com

위 실행 결과 다음과 같이 index.html이라는 파일이 현재 디렉토리 안에 저장되었다. 이 파일을 열어보면 내부의 코드를 자유롭게 확인할 수 있다.

$ vi index.html

만약 저장되는 파일의 이름을 임의로 지정하고 싶다면, 다음과 같이 wget 뒤에 -O 파일이름 목표URL 옵션을 붙여주면 된다.

$ wget -O lion.html http://www.naver.com

+ Ruby 코드 상에서 wget 실행하기

wget 명령어를 Ruby 프로그램 안에서도 구현할 수 있다. wget 명령어가 Ruby 코드와 어우러지면 훨씬 효과적으로 웹 페이지의 코드를 긁어올 수 있다.

그 예로, 네이버의 ‘지식백과’(terms.naver.com) 페이지를 긁어와보도록 하자. 다음은 네이버 지식백과에서 ‘안드로이드’ 항목을 검색하여 클릭했을 때 나타나는 웹 페이지 URL과 페이지 화면이다.

http://terms.naver.com/entry.nhn?docId=1348050&cid=40942&categoryId=32848

위 웹 페이지의 모든 코드를 긁어오고자 한다. 이를 위해, 다음과 같은 Ruby 파일을 생성한다.

nav_terms.rb

`wget http://terms.naver.com/entry.nhn?docId=1348050&cid=40942&categoryId=32848`

1행의 양 끝에 있는 `은 잘 쓰지 않는 문자인데, 키보드의 esc 버튼 아래, 1버튼 왼쪽에 있는 버튼을 누르면 입력할 수 있다. 이렇게 Ruby 프로그램 상에서 Linux 명령어를 입력하고자 할 때는 `명령어`의 형태로 입력하면 된다.

`(back quote라 부른다)를 이용하면, Ruby 프로그램 안에서 Linux 명령어를 구현할 수 있다!

위와 같이 작성한 nav_terms.rb 파일을 실행하면 아래 그림과 같이 웹 페이지의 다운로드 작업이 진행되며, 웹 페이지의 html파일이 현재 디렉토리 안에 저장된다. 저장된 파일명은 entry.nhn?docId=1348050 이다.

wget을 실행할 때 게시물의 URL()에 주목하도록 하자.

http://terms.naver.com/entry.nhn?docId=1348050&cid=40942&categoryId=32848

중간쯤에 보면 docId=1348050라는 부분을 확인할 수 있다. 게시판 형태의 페이지에서 이렇게 높은 자릿수의 숫자가 나오는 부분이 보통 게시물 번호일 가능성이 높다. 이 부분의 숫자만 docId=1348051로 바꾼 뒤, 크롬 상에서 바뀐 URL로 접속해 보도록 하자. 종전과는 다른 게시물이 나올 것이다.

이러한 원리를 활용하여, 여러 게시물의 웹 페이지 코드를 한번에 긁어올 수도 있다. 다음의 예를 보자.

nav_terms1.rb

1348049.downto(1348040) do |x|
  `wget http://terms.naver.com/entry.nhn?docId=#{x}&cid=40942&categoryId=32848`
end

위에 제시된 코드에서도 확인할 수 있다시피, 위 파일을 실행하면 docId=1348050 게시물부터 (번호가 하나씩 내려가면서) docId=1348041 게시물까지 웹 페이지 다운로드 작업이 한번에 진행되며, 해당 웹 페이지들의 html파일들이 현재 디렉토리 안에 모두 저장된다. 저장된 파일명은 entry.nhn?docId=1348049 ~ entry.nhn?docId=1348040이다.

만약 한번에 너무 많이 다운로드받았다는 생각이 들면, 현재 디렉토리 상에서

$ rm -rf entry.nhn?docId=*

로 한번에 모두 삭제할 수 있다. rm -rf entry.nhn?docId=*은 “entry.nhn?docId=로 시작하는 파일명을 가진 모든 파일들을 삭제하라”는 의미이다.

웹 페이지를 다운로드받을 때 다음과 같이 -O 옵션을 이용하여 파일명을 지정하면 관리하기 보다 간편하게 받을 수 있다.

nav_terms2.rb

1348049.downto(1348040) do |x|
  `wget -O #{x}.html http://terms.naver.com/entry.nhn?docId=#{x}&cid=40942&categoryId=32848`
end

웹 페이지들이 제대로 다운로드되었는지 확인하기 위해, 몇 개는 직접 열어서 내용을 확인해보도록 하자.

$ vi 1348045.html

+ 고전 영화 50개 페이지 저장하기

‘네이버 영화’(movie.naver.com)에서는 영화를 다섯 자리 코드(code) 번호에 따라 분류하고 있는데, 코드 번호는 10001부터 시작되며 앞쪽 코드 번호들은 주로 고전 명작 영화들에 부여되어 있다. 예를 들어, 코드 번호 10001에 해당하는 영화는 ‘시네마 천국’으로, 웹 페이지 URL과 페이지 화면은 다음과 같다.

http://movie.naver.com/movie/bi/mi/basic.nhn?code=10001

코드 번호 10001부터 10050에 대응되는 고전 영화 페이지들을 긁어와 저장하는 프로그램을 작성해 보자. 저장되는 파일명은 코드번호.html로 한다. 다음과 같이 하면 된다.

oldmovies.rb

10001.upto(10050) do |x|
  `wget -O #{x}.html http://movie.naver.com/movie/bi/mi/basic.nhn?code=#{x}`
end

웹 페이지들이 제대로 다운로드되었는지 확인하기 위해, 몇 개는 직접 열어서 내용을 확인해보도록 하자.

$ vi 10031.html

웹 페이지의 코드가 제대로 보인다!

7. 이메일 전송 프로그램 작성하기

+ 오픈 소스 라이브러리의 활용

과거와 비교할 때, 오늘날 프로그래밍을 하기에 매우 편해진 것 중 하나는 오픈 소스(open source) 라이브러리가 여기저기에 공개되어 있어서 쉽게 구할 수 있다는 점이다. 오픈 소스 라이브러리란 외부로 코드가 공개되어 있는 프로그램을 말하는데, 이들을 이용하면 보다 쉽고 빠르게, 보다 강력한 웹 서비스를 구현할 수 있다.

앞서도 언급한 적이 있지만, Ruby에서 쓰이는 오픈 소스 라이브러리를 gem(젬)이라고 부른다. 이들 gem은 소스 코드를 모두 볼 수 있도록 구성되어있기 때문에, 사용자의 입맛에 맞게 사용할 수 있다는 장점이 있다.

오픈 소스 라이브러리를 활용한 첫 번째 예로, 먼저 이메일 전송 프로그램을 작성하여 사용해보도록 하자.

+ Ruby로 이메일 전송 프로그램 작성하기

Ruby로 이메일을 전송하는 프로그램을 작성해보도록 하자. 우리는 gmail이라는 오픈 소스 라이브러리를 사용할 것이다:

https://github.com/nu7hatch/gmail

gmail 라이브러리를 이용하면, 기존에 가지고 있던 자신의 Gmail 계정을 사용하여 매우 쉽고 빠르게 이메일을 보낼 수 있다. 본 라이브러리의 사용법에 대한 자세한 설명과 다양한 기능들은 위 링크를 따라가면 확인할 수 있다. 한 번쯤 읽어볼만한 내용이긴 하다. 하지만 지금은 이메일을 보내기 위한 가장 핵심적인 부분에 대해서만 살펴보는 것으로도 충분할 것이다.

먼저 gmail gem의 설치가 필요하므로, 홈 디렉토리 상에서 다음과 같이 입력하고 엔터를 누른다.

$ gem install gmail

gmail gem의 설치가 완료되었으면, mail.rb 파일을 생성하여 다음 프로그램을 작성한다.

mail.rb

require 'gmail'
gmail = Gmail.connect("자신의 Gmail 계정 주소","자신의 Gmail 계정 비밀번호")
puts gmail.logged_in?
gmail.deliver do
  to "받는 사람 이메일 주소"
  subject "이메일 제목"
  text_part do
    body  "이메일 내용"
  end
end
gmail.logout

위 코드의 문법 자체는 Ruby의 기본 문법과는 크게 상관이 없을 수 있다. gmail gem의 제공자가 임의로 정의한 문법이기 때문이다. 위 프로그램의 코드를 부분별로 살펴보도록 하자.

(1행)

require 'gmail'

맨 먼저 1행에서 require ‘gmail’로 gmail gem을 불러들였다. 특정한 gem을 Ruby 파일 상에서 사용하기 위해서는 이렇게 서두에 require ‘gem이름’을 반드시 명시해줘야 한다.

(3~4행)

gmail = Gmail.connect("자신의 Gmail 계정 주소","자신의 Gmail 계정 비밀번호")
puts gmail.logged_in?

3행에서 Gmail.connect로 자신의 Gmail 계정으로 로그인할 수 있다. 이 때 자신의 Gmail 계정 주소와 해당 계정의 비밀번호를 문자열 형태로 입력한다. 예를 들어, 자신의 Gmail 계정 주소가 ‘lionuser@gmail.com’, 비밀번호가 ‘q!1w@2e#3r$4’라면, 다음과 같이 입력하면 된다.

gmail = Gmail.connect("lionuser@gmail.com","q!1w@2e#3r$4")

한편 4행의 puts gmail.logged_in?는, 3행에서 입력한 Gmail 계정 정보로 제대로 로그인되었는지 확인하기 위해 추가한 것이다. 로그인이 정상적으로 이루어진 경우 화면에 true값이 출력될 것이고, 그렇지 않은 경우 화면에 false값이 출력될 것이다.

(5~11행) gmail.deliver do

gmail.deliver do
  to "받는 사람 이메일 주소"
  subject "이메일 제목"
  text_part do
    body  "이메일 내용"
  end
end

5행에서 gmail.deliver do로 이메일에 들어갈 정보를 입력하는 부분이 시작되며, 이는 11행의 end에서 마무리된다. 6행의 to 다음에는 받는 사람의 이메일 주소를 문자열 형태로 입력하고, 7행의 subject 다음에는 이메일의 제목을 문자열 형태로 입력한다.

한편 8~10행은 이메일의 내용을 입력하는 부분이다. 9행의 body 다음부터 이메일의 내용을 문자열 형태로 입력하면 된다.

5~11행을 올바르게 입력한 예를 보이자면, 다음과 같은 것이다:

gmail.deliver do
  to "codelion.master@gmail.com"
  subject "This is a test email"
  text_part do
    body  'Hello world, Grrrr!"
  end
end

(13행)

gmail.logout

이메일을 전송한 뒤, 맨 마지막 13행의 gmail.logout가 실행되면서 Gmail 계정의 로그아웃이 이루어진다.

위 프로그램을 실행하면 다음의 결과를 출력하게 된다. 만약 Gmail 계정에 정상적으로 로그인하여 이메일 전송에 성공한 경우, 화면에 true 값이 표시되는 것을 확인할 수 있다.

받는 사람의 이메일 수신함을 확인해보면 이메일이 제대로 도착한 것을 확인할 수 있다.


* ruby mail.rb 실행 결과 화면에 false 값이 표시된 경우

Gmail 계정에 정상적으로 로그인이 되지 않은 것이다. 원인은 여러 가지일 수 있으나, Gmail 측에서 의심스러운 로그인으로 간주하여 이를 차단하였을 가능성이 가장 높다.

이러한 경우, 크롬을 실행하여 로그인하고자 했던 Gmail 계정으로 직접 로그인하도록 하자. 받은 편지함에 아래 그림과 같은 이메일이 도착해 있으면, 100%다. 해당 이메일 내용을 확인해보도록 하자.

우리의 Gmail 계정 정보를 사용하여 로그인을 시도한 시각과 위치, IP 주소 등이 나와있다. 특히 AWS 서버 상에서 mail.rb 프로그램을 실행한 경우, 서버의 실제 위치가 해외에 있기 때문에 ‘의심스러운 로그인’으로 간주되어 차단되었을 가능성이 매우 높다. 의심스러운 로그인이 아님을 입증하기 위해, 화면 아래쪽에 있는 링크를 클릭한다.

장황한 설명이 나와있는 페이지가 등장한다. 화면 중간에 있는 링크를 클릭한다.

‘내 Google 계정에 대한 액세스 허용’ 페이지가 등장한다. 계속 버튼을 클릭한다.

‘계정 액세스 사용 설정됨’ 페이지가 등장한다. 이제 터미널로 돌아가 mail.rb 프로그램을 다시 실행해보자. 이번에는 화면에 true라고 표시될 것이다.

+ Ruby로 단체 메일 보내기

앞서 작성했던 프로그램 상에서 약간의 Ruby 코드를 이용하면, 한번에 여러 주소로 쉽고 빠르게 단체 메일을 보낼 수 있다. 다음의 프로그램을 작성해보자.

multi_mail0.rb

require 'gmail'
def send_mail(address)
  gmail = Gmail.connect("자신의 Gmail 계정 주소","자신의 Gmail 계정 비밀번호")
  puts gmail.logged_in?
  gmail.deliver do
    to address
    subject "This is the first email from Ruby"
    text_part do
      body  "Hello world, Grrrr!"
    end
  end
  gmail.logout
end
address_list = ["첫번째 이메일 주소", "두번째 이메일 주소", "세번째 이메일 주소"]
address_list.each do |addr|
  send_mail(addr)
end

위 프로그램의 코드를 핵심적인 부분 위주로 살펴보도록 하자.

(3~15행) def send_mail(address) 메서드

def send_mail(address)
  gmail = Gmail.connect("자신의 Gmail 계정 주소","자신의 Gmail 계정 비밀번호")
  puts gmail.logged_in?
  gmail.deliver do
    to address
    subject "This is the first email from Ruby"
    text_part do
      body  "Hello world, Grrrr!"
    end
  end
  gmail.logout
end

3행의 def send_mail(address)에서 send_mail이라는 메서드를 정의하였으며, 이는 15행의 end에서 마무리된다. send_mail 메서드는 address라는 1개의 (문자열)인자를 받는다. 메서드의 내부에는 앞서 사용했던 프로그램의 코드가 그대로 들어갔다.

(17행)

address_list = ["첫번째 이메일 주소", "두번째 이메일 주소", "세번째 이메일 주소"]

17행에서 address_list라는 배열을 정의한다. 여기에는 우리가 보내는 이메일을 받는 사람들의 주소가 문자열 형태로 성분으로 포함된다.

(18~20행) address_list.each do |addr| 반복문

address_list.each do |addr|
  send_mail(addr)
end

18~20행에서는, address_list의 각각의 성분을 인자로 하여 send_mail 메서드를 호출한다.


위 프로그램을 실행해보자. 만약 Gmail 계정에 정상적으로 로그인하여 이메일 전송에 성공한 경우, 화면에 true 값이 총 3회 표시되는 것을 확인할 수 있다. 받는 사람들의 이메일 수신함을 확인해보면 모든 이들에게 이메일이 제대로 도착한 것을 확인할 수 있다.

프로그램을 조금만 더 발전시켜보자. 이번에는 send_mail 메서드에 받는 사람의 이름을 인자로 추가할 것이다. 이 때 배열 대신 해쉬를 이용하여 이메일 주소록을 만들고 여기에 send_mail 메서드를 적용하면 더 편하게 할 수 있다. 다음의 프로그램을 작성해보자.

multi_mail1.rb

require 'gmail'
def send_mail(address,name)
  gmail = Gmail.connect("자신의 Gmail 계정 주소","자신의 Gmail 계정 비밀번호")
  puts gmail.logged_in?
  gmail.deliver do
    to address
    subject "This is only for you, #{name}"
    text_part do
      body  "I miss you so bad, #{name}.."
    end
  end
  gmail.logout
end

address_list = {:Charles => "첫번째 이메일 주소", :John => "두번째 이메일 주소", :Miranda => "세번째 이메일 주소"}
address_list.each do |name,addr|
  send_mail(addr,name)
end

위 프로그램의 코드를 핵심적인 부분 위주로 살펴보도록 하자.


(3~15행) def send_mail(address,name) 메서드

def send_mail(address,name)
  gmail = Gmail.connect("자신의 Gmail 계정 주소","자신의 Gmail 계정 비밀번호")
  puts gmail.logged_in?
  gmail.deliver do
    to address
    subject "This is only for you, #{name}"
    text_part do
      body  "I miss you so bad, #{name}.."
    end
  end
  gmail.logout
end

3행의 def send_mail(address,name)에서 send_mail이라는 메서드를 정의하였으며, 이는 15행의 end에서 마무리된다. send_mail 메서드는 address라는 (문자열)인자와, name이라는 (문자열)인자를 받는다.

send_mail 메서드의 내부에는 앞서 사용했던 프로그램의 코드가 거의 그대로 들어갔다. 달라진 점이 있다면, subject와 body 옆의 문자열 안에 #{name}이 추가되었다는 점이다.

(17행)

address_list = {:Charles => "첫번째 이메일 주소", :John => "두번째 이메일 주소", :Miranda => "세번째 이메일 주소"}

17행에서 address_list라는 해쉬를 정의한다. 해쉬의 키에는 이메일을 받는 사람들의 이름이, 값에는 이메일 주소가 문자열 형태로 포함된다.

(18~20행) address_list.each do |name,addr| 반복문

address_list.each do |name,addr|
  send_mail(addr,name)
end

18~20행에서는, address_list 해쉬의 각각의 성분의 (키, 값)을 인자로 하여 send_mail 메서드를 호출한다.

이렇게 특별히 해쉬에 대해서 .each 반복문을 사용할 때는, 반복 횟수마다 변화하는 변수가 |키,값|의 형태로 명시된다. 즉 18~20행의 경우, send_mail 메서드를 호출할 때, address_list 해쉬의 키는 name이란 변수로, 값은 addr이란 변수로 대체되어 각각 send_mail 메서드의 인자로 들어간 것이다.


위 프로그램을 실행해보자. 만약 Gmail 계정에 정상적으로 로그인하여 이메일 전송에 성공한 경우, 화면에 true 값이 총 3회 표시되는 것을 확인할 수 있다. 받는 사람들의 이메일 수신함을 확인해보면 모든 이들에게 이메일이 제대로 도착한 것을 확인할 수 있다.

8. 파싱 프로그램 작성하기

+ Ruby로 파싱 프로그램 작성하기

학교 내, 혹은 직장 내 구내식당 메뉴를 일목요연하게 정리하여 보여주는 페이지를 만드는 경우를 가정해 보자. 식당 메뉴는 매일 매 끼니 때마다 바뀔 것이고, 이러한 내용은 구내식당 홈페이지 상의 식단표에 매번 게시된다고 가정하자.

매일 매 끼니 때마다 직접 식당 홈페이지의 식단표를 일일이 확인한 뒤, 필요한 부분별로 일일이 복사해서 내 페이지에 수동으로 업데이트하는 건 결코 쉬운 일이 아니다. 분명 엄청난 노가다(!)가 될 것이다. 파싱(parsing)은 이와 같은 반복적인 수고를 줄여주는 핵심적인 기술이다.

프로그래밍에서의 파싱이란, 어떤 구문 혹은 코드를 정해진 규칙에 따라 나누어 가공하는 기술을 의미한다. 파싱을 이용하면 다른 문서 혹은 웹 페이지에서 원하는 부분의 문자열을 추출하여 내 페이지에 불러오는 것이 가능해진다.

지금부터 파싱을 수행하는 오픈 소스 라이브러리를 이용하여, Ruby에서 파싱을 직접 해 보도록 하자.

+ 저장된 파일로부터 파싱하기

우리가 학습할 파싱으로는 크게 두 가지 방법이 있다. 내 서버에 저장된 파일로부터 파싱하는 방법, 다른 웹 페이지의 URL로부터 파싱하는 방법이다. 그 중에서 먼저 내 서버에 저장된 파일로부터 파싱하는 방법을 학습해보도록 하자.

우리는 Ruby에서 파싱을 수행하는 오픈 소스 라이브러리로 Nokogiri를 사용할 것이다. Nokogiri를 활용하면 파싱을 매우 쉽고 간편하게 할 수 있다:

http://nokogiri.org

앞선 42장에서 ‘네이버 영화’의 고전영화 50개 페이지를 wget으로 저장했던 것을 기억할 것이다. 이번에는 고전영화 웹 페이지들에 포함되어 있는 영화의 제목을 파싱을 통해 가져오는 작업을 할 것이다.

먼저 nokogiri gem의 설치가 필요하므로, 홈 디렉토리 상에서 다음과 같이 입력하고 엔터를 누른다. 설치 속도를 높이기 위해 —no-ri —no-rdoc 옵션을 추가하였다.

$ gem install nokogiri --no-ri --no-rdoc

nokogiri gem의 설치가 완료되었으면, 파싱 프로그램을 작성할 준비가 되었다.

연습을 위해, 먼저 코드 번호가 10001인 웹 페이지에 대해서만 파싱을 수행하는 프로그램을 작성할 것인데, 그 전에 먼저 목표로 하는 페이지의 코드 구성을 살펴볼 필요가 있다.

앞선 42장에서 긁어왔던 10001.html ~ 10050.html이 들어있는 디렉토리로 이동하자. 만약 그 때 긁어왔던 파일을 삭제해 버렸다면, 42장으로 돌아가 oldmovies.rb 프로그램을 다시 만든 뒤 이를 실행하여 필요한 파일들을 다시 다운로드받도록 하자.

준비가 되었으면, 10001.html 파일을 열어 그 내용을 살펴본다.

$ vi 10001.html

파싱을 할 페이지의 코드를 잘 보면, 29행에 ‘시네마 천국 : 네이버 영화’라는 문자열이 들어가 있다. 우리가 애초에 목표로 했던 것은 영화의 제목이기 때문에, 해당 문자열 앞쪽의 ‘시네마 천국’을 가져와야 할 것이다. 이 문자열은 title 태그에 둘러싸여 있는데, 이 점을 잘 기억하도록 하자. 이와 같이, 파싱에서 가장 중요한 것은 먼저 파싱하고자 하는 페이지의 소스를 확인해보아 내가 필요로 하는 문자열이 어느 태그 안의 어느 위치에 있는지 찾아보는 작업이 우선되어야 한다는 점이다.

이제 parsing_file.rb 파일을 생성하고, 다음 프로그램을 작성한다.

parsing_file0.rb

require 'nokogiri'
file = File.open("10001.html","r")
page = Nokogiri::HTML(file)
file.close
title = page.search("title").inner_html
title_edit = title.split(':')[0]
puts "10001, #{title_edit}"

위 코드의 일부 문법 자체는 Ruby의 기본 문법과는 크게 상관이 없을 수 있다. nokogiri gem의 제공자가 임의로 정의한 문법이기 때문이다. 위 프로그램의 코드를 부분별로 살펴보도록 하자.


(1행)

require 'nokogiri'

맨 먼저 1행에서 require ‘nokogiri’로 nokogiri gem을 불러들였다.

(3~5행)

file = File.open("10001.html","r")
page = Nokogiri::HTML(file)
file.close

3행의 file = File.open(“10001.html”,”r”)에서, 10001.html 파일을 읽어들여 이를 file이라는 변수로 대체하였다.

4행의 page = Nokogiri::HTML(file)은 ‘file 안에 저장된 내용을 html문서로서 읽어들이고, 해당 html문서의 코드를 page 변수에 파싱해서 저장한다’는 Nokogiri만의 기본 문법이다. 5행에서는 file.close로 파일을 닫았다.

(7행)

title = page.search("title").inner_html

분석하고자 하는 웹 페이지를 page에 저장하였으므로, 이에 대한 본격적인 파싱이 들어가는 부분이 7행의 title = page.search(“title”).inner_html이다. 앞서 우리가 필요로 했던 ‘시네마 천국 : 네이버 영화’라는 문자열이 title 태그에 둘러싸여 있었으므로, page.search(“title”)로 title 태그에 해당하는 부분만 추출할 수 있다.

즉, page.search(“title”)의 결과 부분만 추출된 것이다. 여기에 .inner_html 메서드를 적용시키면, 정확히 사이의 문자열인 시네마 천국 : 네이버 영화만을 얻을 수 있다. 이렇게 얻어진 “시네마 천국 : 네이버 영화” 문자열을 최종적으로 title이라는 변수에 저장하고 있다.

(9~10행)

title_edit = title.split(':')[0]
puts "10001, #{title_edit}"

9행에서는 자잘한 작업이 좀 더 들어간다. title_edit = title.split(‘:’)[0]에서, title 변수의 문자열을 :를 기준으로 분리하여, 각각의 분리된 문자열들을 성분으로 하는 배열로 만든 뒤, 그것의 첫 번째 성분을 [0]으로 호출하고 있다. 이렇게 어떤 문자열을 특정한 문자(여기에서는 :)를 기준으로 분리하고자 할 때, .split(‘:’) 메서드를 사용한다.

irb를 통해 직접 실험해보면 9행에서 어떤 과정을 거쳤는지 쉽게 확인할 수 있다.


위 프로그램을 실행하면 다음의 결과를 출력하게 된다.

이제 위에 적용한 원리를 코드 번호 10001부터 10050까지의 고전영화 웹 페이지 모두에 적용해보자. parsing_file1.rb 파일을 생성하고, 다음 프로그램을 작성한다.

parsing_file1.rb

require 'nokogiri'
10001.upto(10050) do |x|
  file = File.open("#{x}.html","r")
  page = Nokogiri::HTML(file)
  file.close
  title = page.search("title").inner_html
  title_edit = title.split(':')[0]
  puts "#{x}, #{title_edit}"
end

앞서 작성했던 parsing_file0.rb의 코드와 대동소이하다. 달라진 점이 있다면, 원래의 코드 일부가 3행의 10001.upto(10050) do |x|과 12행의 end 안으로 들어왔다는 것이다. 이 때, 각각의 코드 번호는 x라는 변수로 표시된다. 즉, upto 반복문을 사용하여 똑같은 과정을 코드 번호 10001부터 10050까지의 고전영화 웹 페이지 모두에 적용하는 것이다.

위 프로그램을 실행하면 다음의 결과를 출력하게 된다. 터미널 창 크기의 한계로 뒷쪽의 25개 결과만 한번에 표시되었다.

+ 웹 페이지 URL로부터 파싱하기

이번에는 다른 웹 페이지의 URL로부터 파싱하는 방법을 학습할 차례이다. 원래 내 서버에 있는 파일을 파싱하는 것도 유용하지만, 사실 진짜 많이 쓰이는 것은 다른 웹 페이지의 URL로부터 파싱하는 방법이다.

앞선 42장에서 ‘네이버 영화’의 고전영화 50개 페이지의 경우, 굳이 wget으로 저장한 뒤 파싱할 필요는 없다. 고전영화 페이지의 URL만 안다면, 해당 URL로부터 필요한 웹 페이지 코드를 곧바로 가져올 수 있다.

고전영화 페이지의 URL은 다음과 같은 형태였다.

http://movie.naver.com/movie/bi/mi/basic.nhn?code=10001

URL로부터 웹 페이지를 곧바로 가져올 때, 우리는 nokogiri 외에 open-uri라는 gem을 추가로 사용할 것이다. 이번에는 parsing_url0.rb 파일을 생성하고, 다음 프로그램을 작성한다.

parsing_url0.rb

require 'open-uri'
require 'nokogiri'
url = "http://movie.naver.com/movie/bi/mi/basic.nhn?code=10001"
page = Nokogiri::HTML(open(url))
title = page.search("title").inner_html
title_edit = title.split(':')[0]
puts "10001, #{title_edit}"

위 코드의 일부 문법 자체는 Ruby의 기본 문법과는 크게 상관이 없을 수 있다. nokogiri gem과 open-uri gem의 제공자가 임의로 정의한 문법이기 때문이다. 위 프로그램의 코드를 부분별로 살펴보도록 하자.


(1~2행)

require 'open-uri'
require 'nokogiri'

맨 먼저 1행에서 require ‘open-uri’, 2행에서 require ‘nokogiri’로 각각 open-uri와 nokogiri gem을 불러들였다.

(4~5행)

url = "http://movie.naver.com/movie/bi/mi/basic.nhn?code=10001"
page = Nokogiri::HTML(open(url))

4행의 url = “http://movie.naver.com/movie/bi/mi/basic.nhn?code=10001"에서, url 변수에 파싱을 수행하고자 하는 웹 페이지의 주소가 들어간다.

5행의 open(url)의 경우 ‘url값에 해당하는 URL의 웹 페이지를 가져온다’는 open-uri만의 기본 문법이다. 또, page = Nokogiri::HTML(open(url))는 open(url)를 html문서로서 읽어들이고, 해당 html문서의 코드를 page 변수에 파싱해서 저장한다’는 Nokogiri만의 기본 문법이다.

(7~10행)

title = page.search("title").inner_html
title_edit = title.split(':')[0]
puts "10001, #{title_edit}"

나머지 7~10행의 경우, 앞서 작성했던 parsing_file0.rb 프로그램의 내용과 동일하다.


위 프로그램을 실행하면 다음의 결과를 출력하게 된다.

앞서 저장된 파일로부터 파싱했을 때와 완전히 똑같은 결과를 내지만, 시간은 미세하게 좀 더 오래 걸린다. 이는 URL로부터 웹 페이지를 곧바로 가져오는 과정에 기인한 것이다.

이제 위에 적용한 원리를 코드 번호 10001부터 10050까지의 고전영화 웹 페이지 모두에 적용해보자. parsing_url1.rb 파일을 생성하고, 다음 프로그램을 작성한다.

parsing_url1.rb

require 'nokogiri'
require 'open-uri'
10001.upto(10050) do |x|
  url = "http://movie.naver.com/movie/bi/mi/basic.nhn?code=#{x}"
  page = Nokogiri::HTML(open(url))
  title = page.search("title").inner_html
  title_edit = title.split(':')[0]
  puts "#{x}, #{title_edit}"
end

앞서 작성했던 parsing_url0.rb의 코드와 대동소이하다. 달라진 점이 있다면, 원래의 코드 일부가 4행의 10001.upto(10050) do |x|과 12행의 end 안으로 들어왔다는 것이다. 이 때, 각각의 코드 번호는 x라는 변수로 표시된다. 즉, upto 반복문을 사용하여 똑같은 과정을 코드 번호 10001부터 10050까지의 고전영화 웹 페이지 모두에 적용하는 것이다.

위 프로그램을 실행하면 다음의 결과를 출력하게 된다. 터미널 창 크기의 한계로 뒷쪽의 25개 결과만 한번에 표시되었다.

역시 이번에도 앞서 저장된 파일로부터 파싱했을 때와 완전히 똑같은 결과를 내지만, 시간은 더 오래 걸린다. 이 역시 URL로부터 웹 페이지를 곧바로 가져오는 과정에 기인한 것이다.

출처 : http://db.pknu.ac.kr/zbxe/

+ Recent posts