- 참고 : Clojure 에 대한 기초적 설명(특히 문법)은 최대한 줄이지만, 핵심이 되는 사항들은 주석을 통해 언급을 하도록 한다.
- 참고 : (그랬으면 좋겠지만) 완성된 형태에서 조금씩 설명하는 형태는 아닐 것 같다. 나름대로 삽질을 해보고 좋은 방법을 적도록 하겠지만, 나중에 얼마든지 변경될 수는 있다.
- 참고 : 기본적인 흐름은 REPL(Read-Eval-Print-Loop) 형태에서 구현을 완료한 후 Java 를 이용해서 GUI 를 만들어 연결하는 순서로 만들 예정이다.
- 참고 : Clojure Box(emacs + SLIME) 환경에서 구현.
먼저 트위터는 누구나 사용하기 쉽게 API 가 다음과 같이 되어 있다.
- 공식 API : Twitter API Wiki
- 간단한 요약 팁 : 아리수's Textyle - 트위터 API(Twitter API) 사용하기
트위터는 기본적으로 http 를 통해 전송되고 있다. 따라서 트위터에서도 http 를 전송하는 방법만 알면 쉽게 사용할 수 있다는 것이다. 그래서 http 로 url 로 정보를 요청하면 대부분 혹은 전부 xml 형태로 정보를 받게 된다. 그 xml 정보를 파싱하면 끝(로그인 과정 제외).
어쨌거나, 일단 트위터를 사용하기 위해서 추상화를 해보자.
트윗 (Tweet)
하나의 트윗은 140 자의 unicode 형태의 글자로 이루어져있다(즉, 영어도 140자, 한글도 140자). 그리고 글쓴 사람의 정보가 필요할 것이고... 등등...
생각보다는 정보가 많다는 것을 느낄 것이다. 일단은 간단하게 해보자. 사용자의 이미지도 없고, 그냥 다음과 같이 map 형태로 축양한다.
- :user-id :user-name :user-account :text :time
Map 형태는 차후 얼마든지 데이타를 추가할 수 있다는 장점이 있다. 예를 들면, map 형태로 된 트윗 하나는 다음과 같은 형태를 띌 수 있다.
- {:user-id 12345 :user-name "ABCD" :user-account "ABCD" :text "Tweet" :time "아홉시!"}
물론 이건 아직 제대로 된 형태라고 하긴 좀 그렇다. 실제 트윗을 하나 생성하는건 좀 나중으로 미루고 실제 트위터 서버에 있는 정보로부터 아무거나 하나 얻어오는 함수를 만들도록 한다.
Java 에서 XML 정보 얻기
순수 clojure 함수만으로는 이 과정이 아마 불가능하고, 사실상 그럴 필요도 없을듯 하다. 일단 Java 의 코드를 참고 하고, 그것을 Clojure 에서 호출하는 형태로 구현하면 될듯 하다.
참고 : Java 로 RSS(XML) 를 파싱해보아요.
위의 출처의 코드를 트위터에 맞게 바꾸었다.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse( strURL );
NodeList status_list = doc.getElementsByTagName( "status" );
NodeList first_status_list = status_list.item(0).getChildNodes();
strURL 변수에 XML 형태를 담은 http 주소가 String 으로 저장되어 있다. 그리고 트위터의 XML 데이타에서 하나의 트윗은 status 로 묶여 있기 때문에(Tweet Entities 참고), status 의 NodeList 를 얻어온 후 첫번째 status 안의 NodeList 를 얻어오는 코드이다.
Clojure 로 변환하자. 아주 쉽다[1].
위의 Java 코드에서는 import 가 빠졌다. 먼저 Clojure 에서 다음과 같이 import 를 일일이 해주자.
(import 'javax.xml.parsers.DocumentBuilder)
(import 'javax.xml.parsers.DocumentBuilderFactory)
(import 'org.w3c.dom.Document)
(import 'org.w3c.dom.NodeList)
타임라인(Timeline)이란 특정 사용자에게 보이는 혹은 모든 사용자에게 보이는 트위터의 목록을 의미한다. 각 사용자는 자신만의 타임라인을 가지고 있고, 이 타임라인은 이론적으로는 자신에게만 보인다. 어쨌건, 일단 특정 사용자의 타임라인 보다는 모든 사용자의 타임라인을 한번 얻어오는 함수를 정의하자[2].
user> (defn global-timeline)
이 함수의 내부를 구현하기 위해서 일단 U특정 RL 로부터 XML 정보를 얻어오는 함수인 read-xml-from-url 를 만들자.
(defn read-xml-from-url
"Read XML data from URL"
[url]
(.parse (.. DocumentBuilderFactory newInstance newDocumentBuilder) url))
여기서 n 번째 "status" 를 가져오면 그게 바로 n 번째 트윗이 된다. 각 트윗 한개 한개가 XML 에서는 "status" 라는 묶음의 형태로 되어 있다는 것을 기억하도록 한다. 일단 처음엔 모든 트윗을 가져 올 것이기 때문에 global-timeline 함수를 다음과 같이 재정의한다.
(defn global-timeline
"global-timeline (status list)"
[]
(.getElementsByTagName (read-xml-from-url "http://twitter.com/statuses/public_timeline.xml") "status"))
아직 이 함수는 제대로 작동하지 않는다. 아니 작동은 하지만, 원하는 형태로 보여주진 않는다.
user> (global-timeline)
#<DeepNodeListImpl com.sun.org.apache.xerces.internal.dom.DeepNodeListImpl@b6421>
이 트윗 객체 자체가 아직은 우리가 원하는 map 형태의 tweet 이 아니라 실제로는 Java 의 NodeList 형태이다. 즉, 아직은 쓸모가 없다는 말이다. 어쨌건 여기서 (global-timeline) 함수가 마치 객체처럼 보일 수 있다는 점에 위안을 얻고, 이제 이 데이타를 제대로 쓸모 있는 데이타로 바꾸는 함수들을 만들자.
(defn to-text
""
([elm](.getTextContent elm))
([elm name] (.getTextContent (.item (.getElementsByTagName elm name) 0)))
([elm name n] (.getTextContent (.item (.getElementsByTagName elm name) n))))
이 함수는 어떤 Node 데이타가 있을때 그 데이타를 text 데이타로 바꿔주는 함수다. 이 함수가 없어도 되지만 코드가 너무 지저분해지기 때문에 만들었다.
(global-timeline) 의 데이타는 사실 Java 의 NodeList 데이타다. 이 노드들이 하나의 트윗과 대응 되기 때문에 간단하게 얻어오면 된다.
- (global-timeline) = 여러개의 "status" 리스트
- 하나의 status = 하나의 트윗 = 트윗의 여러가지 정보
- 하나의 status 의 XML 하위에는 "user" 정보가 들어 있다.
(defn nth-tweet
"n th tweet from tweet list"
[tweetlist n]
(tweet-from-nodelist (.getChildNodes (.item tweetlist n))))
아직 tweet-from-nodelist 함수가 정의 되지 않았지만, 일단 n 번째 트윗을 가져오는 함수는 위와 같이 생겼다. "status" 의 n 번째 객체를 가져오고 그것의 Child Nodes 를 가져오는 형태다. 결국은 (.getChildNodes (.item tweetlist n)) 이것까지도 역시 Java 의 NodeList 객체다.
하나의 트윗에 대응되는 NodeList 객체를 얻어왔으면, 이 객체를 tweet-from-nodelist 함수를 이용해서 실제 map 형태의 트윗 정보로 변환한다.
(defn tweet-from-nodelist
"tweet map data from NodeList"
[nodelist]
{:text (to-text nodelist "text")
:id (to-text nodelist "id")
:reply-tweet-id (to-text nodelist "in_reply_to_status_id")
:reply-user-id (to-text nodelist "in_reply_to_user_id")
:reply-user-account (to-text nodelist "in_reply_to_screen_name")
:time (to-text nodelist "created_at")})
아직 트윗을 쓴 유저 정보는 넣지 않았지만, 일단 이 함수까지 정의가 되면 실제 의미 있는 정보를 뽑아 볼 수 있다.
user> (nth-tweet (global-timeline) 0)
{:text "insisto que odio a justin bieber", :id "21552333000", :reply-tweet-id "", :reply-user-id "", :reply-user-account "", :time "Thu Aug 19 05:17:52 +0000 2010"}
누군지 몰라도 일단 영어권 사용자는 아니다. 기본적인 Clojure Box 환경에서는 아마 한글의 언어가 나온다면 글씨가 깨질텐데, 그것은 emacs 에서 유니코드 설정을 통해 해결할 수 있다(해결 방법은 다음 기회에...).
어쨌건 유저정보까지 포함하는 함수를 만들면 다음과 같다.
(defn user-info-from-nodelist
"User's information from NodeList"
[nodelist]
{:user-id (to-text nodelist "id")
:user-name (to-text nodelist "name")
:user-account (to-text nodelist "screen_name")
:profile-image-url (to-text nodelist "profile_background_image_url")})
(defn tweet-from-nodelist
"tweet map data from NodeList"
[nodelist]
{:text (to-text nodelist "text")
:id (to-text nodelist "id")
:reply-tweet-id (to-text nodelist "in_reply_to_status_id")
:reply-user-id (to-text nodelist "in_reply_to_user_id")
:reply-user-account (to-text nodelist "in_reply_to_screen_name")
:user-info (user-info-from-nodelist
(.getChildNodes (.item (.getElementsByTagName nodelist "user") 0)))
:time (to-text nodelist "created_at")})
여기까지 하면 제법 많은 정보가 보인다.
user> (nth-tweet (global-timeline) 0)
{:text "Had to have my funnel cake =D", :id "21552673000", :reply-tweet-id "", :reply-user-id "", :reply-user-account "", :user-info {:user-id "73518147", :user-name "Daysi Hernandez (:", :user-account "Daysi_Dgaf", :profile-image-url "http://a1.twimg.com/profile_background_images/125335754/shortcake.jpg"}, :time "Thu Aug 19 05:24:22 +0000 2010"}
하지만 아직 제대로 사용하려면 멀었다. Tweet Entities를 보면 알겠지만, 트위터 하나에는 제법 많은 정보들이 담겨 있다. 일단 당분간은 이렇게 쓰고 필요에따라 tweet-from-nodelist 함수와 user-info-from-nodelist 함수만 수정하면 얼마든지 데이타를 얻어내는 데는 문제가 없다.
아직은 트위터 하나만 얻어오는 것을 했기 때문에 앞으로 할일은 많다. 원래는 트위터의 대부분의 기능들을 만든 후 GUI 를 만들려고 했지만, 사실 Lisp(Clojure)의 장점을 살리려면(장점을 명확하게 보려면) GUI를 만들고, 그 안에서 트위터의 기능들을 계속 추가하는 형태도 괜찮을듯 하다.
아무튼~ 그럼 이만 다음 시간에~ ~.~







878835
471
576





댓글을 달아 주세요