@gypark - gypark.pe.kr의 주인장. 홈페이지에 Perl에 대해 정리해두는 취미가 있고, Raymundo라는 닉을 사용하기도 한다.
트위터에서 제공하는 스트리밍 API를 사용해 타임라인을 감시하다가 어떤 조건에 맞는 트윗에 대해 원하는 작업을 수행하는 방법을 소개합니다.
2010 - 넷째 날: 선물 세 가지 :-D에서, CPAN의 Net::Twitter::Lite 모듈을 사용하여 펄로 트위터에 트윗을 올리는 것을 소개한 적이 있습니다. 이 모듈을 쓰면 트윗을 올리는 것 뿐 아니라 내 타임라인을 가져올 수도 있습니다. (물론 그 외에도 트위터 API에서 지원하는 모든 작업을 할 수 있습니다.) 그렇다면, 타임라인을 주기적으로 가져와 그 타임라인 안의 트윗을 분석해 어떤 유용한 일을 할 수 있을 것입니다. 예를 들어 특정한 문자열이 들어 있는 트윗들만 뽑아내어 저장한다거나 말이죠.
그런데 이 때 몇 가지 불편한 점이 있습니다. 트윗이 올라오자마자 처리하지 못하고, 다음 번 주기에 스크립트가 실행된 후에야 처리하게 됩니다. 그렇다고 반복 실행 주기를 매우 짧게 준다면, 트위터 서버에서 제한하는 API 호출 제한에 걸려서 일정 시간 동안 API 사용을 할 수 없게 됩니다. 타임라인을 한 번 가져오고 그 다음 번 가져오는 사이에 올라온 트윗 중에 일부를 놓치거나, 반대로 한 트윗을 두 번 이상 중복 처리하는 일을 막기 위해서는 매번 어느 트윗부터 어느 트윗까지 읽었는지를 기록해주어야 합니다.
다행히도, 트위터는 스트리밍 API를 따로 제공하기 시작했습니다. 이 API를 사용하는 프로그램은 트위터 서버에 계속 연결된 상태로 있으면서, 어떤 이벤트(새로운 트윗이 올라온다거나, 새 팔로워가 추가되거나, 다이렉트 메시지를 받는 것 등)가 발생할 때마다 그 내용을 통보받을 수 있습니다.
CPAN의 AnyEvent::Twitter::Stream 모듈을 이용해 펄에서 스트리밍 API를 활용하는 간단한 예제를 소개합니다.
필요한 모듈은 다음과 같습니다.
eval
로 대체 가능)직접 CPAN을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.
$ sudo cpan \ AnyEvent::Twitter::Stream \ Net::Twitter::Lite \ Try::Tiny;
사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 다음 명령을 이용해서 모듈을 설치합니다.
$ cpan \ AnyEvent::Twitter::Stream \ Net::Twitter::Lite \ Try::Tiny;
트위터 API를 사용하려면 자신만의 앱을 트위터 서버에 등록한 후, OAuth 인증을 해야 합니다. 트위터 앱 관리 서비스에 접속하여 Create New App 버튼을 눌러 진행하면 되며, 등록 과정에 나오는 항목에 대해서는 2010 - 넷째 날: 선물 세 가지 :-D를 참고하세요.
앱을 등록할 때 앱의 권한을 조절할 수 있는데, 관심글 등록 기능을 쓰려면 write 권한이 있어야 하고, DM 기능을 쓰려면 "Read, Write and Access direct messages"를 선택해야 합니다.
그림 1. 앱 권한 설정 (원본)
다음 코드는 실제로 동작하는 스크립트입니다. 코드의 각 부분에 대한 설명은 주석문으로 상세히 적어두었으니 참고하세요. :)
#!/usr/bin/env perl use v5.20; use strict; use warnings; use AnyEvent::Twitter::Stream; use Net::Twitter::Lite::WithAPIv1_1; use Try::Tiny; binmode STDOUT, ':encoding(UTF-8)'; my $done = AE::cv; # # http://dev.twitter.com 에서 자신의 앱을 등록한 후 받아오는 키와 토큰 # 각 항목은 자신이 받은 값으로 채웁니다. # my $oauth = { consumer_key => '****', consumer_secret => '****', access_token => '****', access_token_secret => '****', }; # # 내 트위터 ID - 디엠을 보낼 때 수신자 아이디에 넣기 위해 필요 # my $my_id = '****'; # # 트위터 REST API 클라이언트 # favorite, DM 등을 쓰기 위해 필요 # my $api = Net::Twitter::Lite::WithAPIv1_1->new( %{$oauth}, legacy_lists_api => 1, ssl => 1 ); # # 트위터 스트리밍 API 이벤트 처리기 # my $streamer = AnyEvent::Twitter::Stream->new( # oAuth 정보 consumer_key => $oauth->{consumer_key}, consumer_secret => $oauth->{consumer_secret}, token => $oauth->{access_token}, token_secret => $oauth->{access_token_secret}, # # 스트림 타입 # 세 가지 중 하나를 선택할 수 있는데, # 우리는 자신의 타임라인을 보고 싶은 것이므로 userstream 선택 # https://dev.twitter.com/streaming/overview # method => 'userstream', # # 1로 하면 종종 문제가 생김 # use_compression => 0, # # 여러 가지 이벤트에 대해 그 이벤트를 처리할 핸들러를 등록한다: # # 이벤트에 대한 설명은 다음 주소 참조: # https://dev.twitter.com/streaming/overview/messages-types # # # 새 트윗이 올라왔을 때 - 코드가 길어서 별도 함수로 분리 # on_tweet => \&on_tweet, # # 에러가 발생하면 에러 내용을 출력한 후 종료 # on_error => sub { my $error = shift; warn "ERROR: $error"; $done->send; }, # # EOF는 정상적인 상태라면 발생하지 않을 것으로 생각됨 # on_eof => sub { $done->send; }, # # 굳이 따로 처리하지 않아도 무방해 보이는 이벤트 # #on_connect => sub {}, #on_keepalive => sub {}, # # 다음 이벤트들은 따로 핸들러를 지정하지 않으면 on_tweet 핸들러가 불린다. # 따라서 명시적으로 빈 서브루틴을 지정하여 아무 일도 하지 않게 함 # on_direct_message => sub {}, on_event => sub {}, on_friends => sub {}, on_delete => sub {}, ); # # 스크립트는 이 지점에서 대기하면서 이벤트 발생을 기다리게 되고, # 이벤트가 발생하면 핸들러가 불리는 과정을 반복한다. # $done->send 가 호출되면 그 때 대기를 끝내고 종료 # $done->recv; exit 0; # # 새 트윗이 올라왔을 때 불릴 핸들러 # 인자로 트윗 객체가 익명 해시의 형태로 전달된다. # 그 해시의 키와 값은 다음 주소 참조 # https://dev.twitter.com/overview/api/tweets # sub on_tweet { my $tweet = shift; # 트윗 기본 정보 (트윗 아이디, 작성자 아이디 등) 추출 my $tweet_id = $tweet->{id}; my $tweet_userid = $tweet->{user}{screen_name}; my $tweet_username = $tweet->{user}{name}; my $tweet_text = $tweet->{text}; # URL이 포함된 트윗은 URL 추출 my @tweet_urls; if ( $tweet->{entities}{urls} ) { @tweet_urls = map { $_->{expanded_url} } @{$tweet->{entities}{urls}}; } unless ($tweet_id and $tweet_userid) { return; } # # 디버그용 출력 # print <<"EOF"; * id : $tweet_id * author : $tweet_username (\@$tweet_userid) * text : $tweet_text EOF foreach my $url ( @tweet_urls ) { print "* url : $url\n"; } # # 'perl' '펄'이 포함된 트윗에 대하여 특별 처리 # if ( $tweet_text =~ /perl|펄/i ) { # # Net::Twitter::Lite의 여러 메쏘드들은 실행 도중 에러가 나면 # 에러의 내용이 담긴 Net::Twitter::Lite::Error 객체를 던지며 die한다. # eval { ... } 로 감싸고 $@ 변수의 내용을 검사해도 되고, # 여기서는 Try::Tiny 를 사용하여 에러 핸들링을 함 # try { # # favorite 체크를 하거나 # $api->create_favorite( { id => $tweet_id } ); # # DM으로 자기 자신에게 그 내용을 보내거나 # $api->new_direct_message( { screen_name => $my_id, text => $tweet_text } ); } catch { warn "API error: $_"; }; } }
이 스크립트를 실행하면 조용히 있다가, 로그인한 계정이 팔로우하는 사람들이 트윗을 올릴 때마다 반응합니다. 위 코드에서는 디버그를 위해서 표준 출력으로도 간단한 정보를 출력하도록 했기 때문에 실행 결과는 다음과 같습니다. 테스트한 계정의 팔로잉의 개인적인 트윗은 가려두었고 테스트를 위해 @JEEN_LEE님께서 도와주셨습니다.
그림 2. 디버그용 출력 (원본)
트윗들 중에 "perl" 또는 "펄"이라는 문자열이 있으면, 그 트윗들은 자동으로 관심글 목록에 추가되어 있음을 확인할 수 있습니다.
그림 3. 관심글 목록 (원본)
더불어 쪽지함에도 내용이 들어가 있음을 확인할 수 있습니다.
그림 4. 쪽지함 (원본)
보다시피 "단순 문자열 검색"만으로는 의도하지 않은 내용도 같이 수집될 수 있다는 것을 알 수 있습니다. 너무 당연한가요? :-)
여기서는 단순히 관심글 목록에 담는 것으로 끝났지만, 좀 더 응용하면 내용과 링크를 추출하여 데이터베이스에 넣는다거나, 통계 처리를 한다거나, REST API를 같이 사용하여 마치 봇처럼 자동으로 특정 조건에 반응하게 할 수도 있을 것입니다. 재미있는 응용법이 많이 나오길 기대해봅니다. :)
X-mas tree
& Llama
ASCII Art by ASCII Art Farts.
Computer ASCII Art by Chris.com.
Font ASCII Art by TAAG.
Artwork by
Inkyung Park
& Keedi Kim.
Designed by
Hojung Youn
& Keedi Kim.
Articles by
Seoul Perl Mongers.
Edited by
Luzluna Park
& Keedi Kim.
Hosting generously sponsored by
Yuni Kim.
Sponsored by
SILEX.
.-''' __ __ / \/ \/ \ =-_- | \. -____- / \ // /|| '' //| //|| == = == ==