[Python] 네이버 블로그 xml-rpc 클라이언트 만들기 – 1

xml-rpc 클라이언트 만들기

여러 개의 블로그 (최근 들어 마이크로블로깅 도구들도 쓰고 있으니)를 운영하다보면 가장 큰 문제 중 하나가 글이 여기 저기로 분산되다보니 한 곳에서 보아 볼 수 있는 방법이 없을까 하는 생각이 들었다. (또한 아내가 네이버 블로그를 시작한 것도 있고 해서) 네이버라는 플랫폼 자체는 그닥 마음에 들지는 않지만 아내는 사실 오픈된 블로깅보다는 이웃공개 등의 커뮤니티성 서비스를 원하는 것도 있고 또 네이버 블로그 자체가 유입량은 상당히 많다보니 다른 곳에 써 온 글을 네이버로 옮기는 작업을 해야 한다.

물론 수동으로 하나하나 복사해서 옮겨 넣을 수도 있는데, 이게 워낙 노가다로 해야하는 작업이다 보니 글의 양이 많거나 하면 만만한 일은 아니라는게 문제. 그런데 찾아보니 네이버블로그도 xmlrpc를 통해서 metablog API를 지원하고 있었다. 자 그럼 여기에 글을 모아다가 쭉쭉 올리는 걸 뭐든 만들어보면 되겠네… 아 그런데 xmlprc가 원격 블로깅도구 (Windows Live Writer 같이 블로그에 직접 브라우저로 접속하지 않고 글을 보내는 앱들)에서 쓴다는 건 알겠는데 어떤 건지를 모르겠다. 네이버 개발자 문서에서는 metaWeblog API를 지원한다고 하니 이걸 사용할 수 있는 방법을 찾으면 되겠다.

암튼 약간의 삽질을 거쳐서 얻은 결론은

  1. xmlrpc는 원격에서 http 혹은 https 접속을 통해서 서버의 특정 메소드를 호출하는 기능을 의미하며
  2. 데이터(파라미터)의 전달은 xml 포맷에 맞게 구성하면 된다.
  3. 이 때 xml 포맷의 구성 – 즉 파라미터 구성 -을 정의하고 있는 규격은 MetaWeblog API이다.

그래서 정확하게 말하자면 내가 만들어야 하는 것은 metaWeblog 클라이언트이고 이 것이 xmlrpc client로 구현되는 것이었다. 사실 xmlrpc는 상당히 오래전에 만들어진 규격이고, 그 역사를 자랑하는 만큼… 파이썬에는 xmlrpclib 이라는 라이브러리가 이미 존재하고 있다.

xmlrpclib를 사용하여 xmlrpc client를 만들자

그런데 상당히 골치아플 것 같은 부분이 있다. 바로 metaWeblog API에서 정의한 저 많은 파라미터들을 모두 XML로 포맷팅하여 만들어야 하는 것이다. 오… 그런데 이것은 그냥 기우였을 뿐, xmlrpclib은 서버 프록시를 사용하여 이걸 정말 쉽게 만들어 주고 있다. 즉 하나의 서버 프록시 객체를 만들고, metaWeblog API에 정의된 메소드를 마치 이 서버 프록시에 정의되 있는 것처럼 호출하기만 하면 그 사이의 과정 (데이터를 XML로 만들어서 실제 네이버 서버에 전송하고 다시 그 결과값을 XML로 받아와 파이썬 데이터 포맷으로 돌려주는 일)을 알아서 처리해 준다. 그래서 파이썬 코드는 결국 엄청 단순하더라.

metaWeblog API

MetaWeblog API는 크게 세 가지 포인트가 있다. 1) 신규 포스팅을 전송하고 2) 기존 포스팅을 업데이트하며 3) 포스팅의 내용을 가져오는 것이다. 그 외에 카테고리 정보를 가져오거나 하는 추가적인 메소드들이 있는데, 이는 MeatWeblog API 문서를 참고하면 되겠다.

메타블로그 API에서는 단순 문자열이 아닌 struct의 형태로 데이터를 주거나 받게 되는데, 이는 C에서 말하는 구조체는 아니고 적절하게 포맷팅된 xml 형태의 정보이다. 이 정보들은 골치아프게 파싱할 필요없이 xmlrpclib의 서버 프록시를 통하면서 일종의 사전의 형태로 변환된다. (흔히 말하는 json과 유사하다.)

먼저 테스트 삼아 블로그의 카테고리 정보를 얻어와서 출력해보도록 하자. 메타웹로그 API에서 카테고리 정보를 가져오는 메소드는 다음과 같이 정의되어 있다.

metaWeblog.getCategories (blogid, username, password) returns struct

네이버 블로그에서 blogid과 username은 동일하게 네이버 로그인 ID이며, password는 로그인 비밀번호가 아닌 api암호이다. (이는 블로그 설정 화면에서 얻을 수 있다.) 다음은 파이썬 대화형 셸에서 카테고리 정보를 호출해 본 내용.

>>> import xmlrpclib
>>> naver = xmlrpclib.ServerProxy('https://api.blog.naver.com/xmlrpc')
>>> cats = naver.metaWeblog.getCategories('######','######','40022e8f1509a3a4880eca8141a6a6a2')
>>> for c in cats:
    for (k,v) in c.viewitems():
        print "%s :::\n%s\n\n" % (k,v)

title :::
게시판

htmlUrl :::
http://blog.naver.com/PostList.nhn?blogId=######&categoryNo=4

description :::
게시판

title :::
내그림

htmlUrl :::
http://blog.naver.com/PostList.nhn?blogId=######&categoryNo=2

description :::
내그림

title :::
photolog

htmlUrl :::
http://blog.naver.com/PostList.nhn?blogId=######&categoryNo=5

description :::
photolog

title :::
나의 포토이야기

htmlUrl :::
http://blog.naver.com/PostList.nhn?blogId=######&categoryNo=6

description :::
나의 포토이야기

title :::
친구들과 한컷

htmlUrl :::
http://blog.naver.com/PostList.nhn?blogId=######&categoryNo=7

description :::
친구들과 한컷

title :::
여행 스케치

htmlUrl :::
http://blog.naver.com/PostList.nhn?blogId=######&categoryNo=8

description :::
여행 스케치

title :::
videolog

htmlUrl :::
http://blog.naver.com/PostList.nhn?blogId=######&categoryNo=9

description :::
videolog

오오오오오오오 성공!

이번에는 글을 등록하는 과정을 살펴보자.

metaWeblog.newPost(blogid, userid, password, struct, publish)

newPost 메소드의 파라미터 중 주목할 부분은 struct 이다. 이구조는 blogger API로부터 정의되었다. 실제로는 xml 포맷으로 전달되나, 여기서는 ‘사전’의 형식으로 데이터를 구성한다. (이를 xml 형태로 만들어주는 작업은 서버 프록시가 대신 한다.)

이제 편집기를 열고 코딩을 시작한다. 아래 코드에서 id와 password 부분은 #######로 처리했으니, 자신의 네이버 ID와 api 암호로 치환하면 된다. 참고로 윈도 환경에서는 한글을 읽어와서 뿌리는데 좀 문제가 있어서 encoding 모듈이 필요하다. sublime2의 콘솔에서 출력을 위해 cp949로 다시 인코딩해서 출력한다. 윈도의 경우에는 한글로 된 컨텐츠가 제대로 표시가 안되거나 아예 에러가 나면서 표시가 안될 수 있으니, 테스트는 idle을 사용할 것을 추천.

#! c:/python27/python.exe
#-*-coding:utf-8

import xmlrpclib
import encodings

class Naver:
    def __init__(self, userid, password):
        self.server = xmlrpclib.ServerProxy('https://api.blog.naver.com/xmlrpc')
        self.userid = userid
        self.blogid = userid
        self.password = password
        self.categories = []

    def getCategories(self):
        if self.userid and self.blogid and self.password:
            struct_category = self.server.metaWeblog.getCategories(self.blogid, self.userid, self.password)
            for cat in struct_category:
                self.categories.append(cat['title'])
        return len(self.categories)

    def newPost(self, title, description, category, shouldPublish):
        struct = {}
        struct['title'] = title
        struct['description'] = description
        if type(category) == int:
            self.getCategories()
            struct['category'] = self.categories[category]
        else:
            struct['category'] = category
        newPostID = self.server.metaWeblog.newPost(self.blogid, self.userid, self.password, struct, shouldPublish)
        return newPostID

    def getPost(self, postID):
        struct = self.server.metaWeblog.getPost(postID, self.userid, self.password)
        return struct

def main():
    n = Naver('#######', '#################################')
    p = n.newPost('테스트','이 글은 테스트 용입니다',0,True)
    print "New Post ID : %s" % p
    newlyPosted = n.getPost(p)
    print newlyPosted['title'].encode('cp949')
    print newlyPosted['description'].encode('cp949')

if __name__ == '__main__':
    main()

발로 만든 코드이므로 문제점들은 차차 수정하도록 하고, 기본적인 아이디어는 다음과 같이 정리한다.

1. 네이버 블로그 프록시를 담당할 클래스를 하나 만든다. 이 클래스는 서버 프록시를 가지고 있으며, 카테고리 정보와 게시물 정보 등을 얻어오거나 신규 게시물을 올릴 수 있는 기능을 간단하게 가지고 있다.

2.이를 통해 가장 기본적인 처리들은 할 수 있지만 실제로 복잡한 구조를 가지고 전달되는 리턴값들을 좀 더 편리하게 살펴보려면 몇 가지 트릭이 필요하다.

3. 또한 이 프로젝트의 목적은 텀블러에 등록되어 있는 포스팅을 가져와서 네이버 블로그로 옮기는 등 일종의 일괄 처리를 위한 것이므로 각각의 포스트를 클래스로 정의해서 취급하는 게 좋다. (나중에 보면 알겠지만, 이게 훨씬 수월함)

4. sturct는 파이썬 데이터형으로는 사전과 리스트가 혼합된 형태인데, 포맷만 보노라면 json과 거의 동일하다. 어쨌든 이걸 보다 일반적으로 활용하기 위해서는 이런 중첩된 사전에서 정보를 좀 더 수월하게 뽑을 수 있는 방법이 필요하다. 이 역시 별도의 클래스로 정의해 보고자 한다. 특히 struct의 파싱된 형태는 json 포맷과 거의 동일하다는 점에서 추후 분석할 tumblr api의 데이터 구조와 유사성이 많다.

일단 네이버 블로그에 xmlrpc를 통해 글을 등록하는 테스트까지는 성공. 다음 편에서는 텀블러 API를 통해서 포스트를 받아오는 방법을 살펴보고, 받아온 포스트를 다시 네이버에 올리는 법에 대해서도 알아보도록 해야겠다.