Markdown-Pod

 view release on metacpan or  search on metacpan

t/mkd/2011-12-12.mkd  view on Meta::CPAN

다음은 `manaba.yml` 설정 파일에서 관리하는 `webtoon` 항목의 내용 중 일부입니다.

    #!yaml
    webtoon:
      dieter:
        name: 다이어터
        site: daum
        code: 10362
        image: http://i1.cartoon.daumcdn.net/svc/image/U03/cartoon/U620854C4D5B251707
      noblesse:
        name: 노블레스
        site: naver
        code: 25455
        image: http://imgcomic.naver.com/webtoon/25455/thumbnail/title_thumbnail_20100614120245_t125x101.jpg
      kudu:
        name: 구두
        site: nate
        code: 31337
        image: http://crayondata.cyworld.com/upload/series/31337_m.gif

이제 다음과 네이버, 네이트 웹툰이라면 얼마든지 관리하고(보고?)
싶은 만큼 설정파일에 추가하면 됩니다.
각각의 만화는 자신만의 고유 ID 하부에 `name`, `site`, `code`, `image` 항목을 가집니다.
`site`와 `code` 부분은 정확하게 기입해야지만 Manaba가 제대로
처리를 해줄 수 있습니다.  `name`과 `image`는 화면에 보이기 위한
부분으로 잘못 입력한다고 해도 실행에 문제는 없지만 웹툰 이름이
제대로 보이지 않는다던가 또는 웹툰 대표 이미지가 제대로 보이지 않는
문제가 있을 수 있습니다.
설정파일 자체가 간결한 만큼 특별한 설명이 더 필요할 것 같지는 않습니다.

작성한 `manaba.yml` 설정 파일을 읽어 들이려면
[CPAN의 YAML::Tiny 모듈][cpan-yaml-tiny]을 사용합니다.

    #!perl
    sub load_manaba {
        my $yaml = YAML::Tiny->read( config->{manaba} );
        $CONFIG  = $yaml->[0];
    }


마구 긁어오기!
---------------

웹 페이지의 정보를 긁어올 수 있는 라이브러리나 Perl 모듈은 무척 많습니다.
하지만 웹 포탈의 경우 디자인과 HTML 구조가 수시로 변하기 때문에
가능하면 손쉽게 원하는 HTML 요소의 값을 추출할 수 있는 방법을 사용하는 것이 유리합니다.
[CPAN의 Web::Scraper 모듈][cpan-web-scraper]을 이용하면 CSS 셀렉터 방식이나
XPath 방식을 이용해서 HTML 특정 요소를 쉽게 찾을 수 있습니다.
정규 표현식을 이용하는 것보다는 상대적으로 많이 느리지만
사이트의 변화에 따라 발빠르게 대응할 수 있다는 것이 매력입니다.
또한 웹을 긁어올 때 일부러 해당 사이트에 과부하를 주지 않기 위해
지연 시간을 주기도(`sleep $time`) 하는데 `Web::Scraper`의 속도 자체가
느리기 때문에 아무래도 긁는 입장에서는 약간의 지연이 발생하므로
조금 안심되는 면도 있습니다.

각각의 포탈 사이트 별로 `Web::Scraper` 모듈을 이용해서 페이지를 긁은 후
회차 관련 정보를 추출하도록 합니다.
네이트 웹툰에 대해 처리하는 코드는 다음과 같습니다.

    #!perl
    sub update_nate_link {
        my ( $id, @links ) = @_;
        ...
        my @chapters = sort {
            my $page_no_a = 0;
            my $page_no_b = 0;
    
            $page_no_a = $1 if $a =~ m/^(\d+)$/;
            $page_no_b = $1 if $b =~ m/^(\d+)$/;

            $page_no_a <=> $page_no_b;
        } map {
            m{viewer/(\d+)$};
        } @links;
        ...
    }

네이버 웹툰과  다음 웹툰도 기본적인 형식은 비슷하지만,
`map`을 이용해서 링크 주소에서 회차 정보를 긁어오는
정규표현식 부분만 조금씩 다릅니다.



Let's Dance!
-------------

원하는 웹툰의 첫 주소도, 최신 주소도 알았고
이제 남은 것은 화면에 뿌려주기만 하면 됩니다.
아무래도 간단하게 만들 때는 웹 어플리케이션으로 만드는 것이
UI를 수정한다거나 Perl과 연동하기도 좋은 것 같습니다.
그렇다면 Perl의 마이크로 웹 프레임워크인
[Dancer][dancer-home]를 사용해서 UI 구현을 마무리하죠.

컨트롤러는 단 두 개만 만들겠습니다.
기본 페이지를 의미하는 `/`, 즉 인덱스용 컨트롤러 하나와
강제로 사용자가 등록한 웹툰의 정보를 갱신(`Web::Scraper`를 이용해서)하는
`update` 컨트롤러를 생성합니다.

- /
- /update/:id?

`/` 컨트롤러는 뷰 단에 넘겨주기 위한 데이터를 생성하기 위한 처리를 수행합니다.

    #!perl
    get '/' => sub {
        my $webtoon = $CONFIG->{webtoon};
    
        my @items = map {
            my $item = $webtoon->{$_};
    
            $item->{id}    = $_;
            $item->{first} = q{} unless $item->{first};
            $item->{last}  = q{} unless $item->{last};
    
            $item;
        } sort keys %$webtoon;
    
        my $ptr = 0;
        my @rows;
        while ( $items[$ptr] ) {
            my @cols;
            for my $i ( 0 .. 9 ) {
                last unless $items[$ptr];
                push @cols, $items[$ptr];
                ++$ptr;
            }
            push @rows, \@cols;
        }
    
        template 'index' => {
            rows => \@rows,
        };
    };

`update` 컨트롤러는 `id`를 받아서 특정 회차만 갱신할 수도 있고
`id`를 넘겨주지 않는 경우 모든 웹툰의 회차 정보를 갱신합니다.

    #!perl
    get '/update/:id?' => sub {
        my $id = param('id');
    
        if ($id) {
            update($id);
        }
        else {
            update_all();
        }
    
        redirect '/';
    };

웹툰의 정보를 긁어오는 함수는 `update_all()`과 `update()` 함수입니다.
`update_all()` 함수는 내부적으로 `update()` 함수를 호출하므로
`update()` 함수를 간략하게 살펴보죠.

    #!perl
    sub update {
        my $id = shift;
    
        return unless $id;
    
        my $webtoon = $CONFIG->{webtoon};
        return unless $webtoon;
    
        my $site_name = $webtoon->{$id}{site};
        return unless $site_name;
    
        my $scraper = $SCRAPERS->{ $site_name };
        return unless $scraper;
    
        my $site = $CONFIG->{site};
        return unless $site;
    
        my $start_url = sprintf(
            $site->{ $site_name }{ 'start_url' },
            $webtoon->{$id}{ 'code' },
        );
    
        my $items = $scraper->scrape( URI->new( $start_url ) )->{items};
        my @links = map { $_->{link} } @$items;
    
        given ( $site_name ) {
            update_daum_link($id, @links)  when 'daum';
            update_naver_link($id, @links) when 'naver';
            update_nate_link($id, @links)  when 'nate';
        }
    }

`update()` 함수는 다시 각각의 사이트 별로 웹툰을 처리하기 위한 함수로 이동합니다.
각각의 함수에서는 앞에서 보았던 `Web::Scraper` 모듈을 이용해서
원하는 웹툰 회차 정보를 추출합니다.

`Web::Scraper`를 사용하기 때문에 적절하게 지연 시간을 주지 않으면
포탈 사이트로부터 사용하는 아이피가 블록 당할 수 있으므로 주의하도록 합니다.
필요하다면 `update_all()` 또는 `update()` 함수에
`sleep $time` 처럼 지연 시간을 적절하게 주도록 합니다.

현재 구현상 Manaba가 최초에 Dancer 웹 어플리케이션으로 실행될 때
모든 웹툰의 정보를 긁어옵니다.
웹툰의 양이 많으면 많을수록 정보를 긁어오는데 드는 비용이 커지므로
페이지가 갱신 될때마다 정보를 갱신하기 보다는 각각의 웹툰 별로
갱신할 수 있도록 `/update` 컨트롤러에서 `id`를 인자로 받고 있음을 유의해주세요.


네이버는 괴로워...
-------------------

거의 다 작업이 끝나갈 무렵 네이버의 웹툰 대표 이미지가 보이지 않기 시작했습니다.
(이런! 아까까지만 해도 잘 보였는데!!)
여러번의 검색 및 테스트 결과 네이버가 정책적으로 외부 주소에서
자신의 이미지를 열람하는 것을 막아 놓았다는 심증을 굳히게 됩니다.
아마도 트래픽의 폭증을 막기 위한 방안으로 생각되는데
국내 1위 대형 포탈임을 감안할때 생각보다 *쪼짜..* ... 한 것 같습니다.
IP 블럭 당하지 않은 것이 다행이군요...;;;

그래서 프로그램 상에서 이미지를 바로 보여주는 것이 아니라
다운로드 받아서 로컬 하드 디스크에 저장해서 보여주기로 정책을 선회합니다.



( run in 3.222 seconds using v1.01-cache-2.11-cpan-d8267643d1d )