티스토리 API를 사용하여 모든 글 목록 가져오기 방법

티스토리도 다른 프로그램과 마찬가지로 오픈 API를 제공합니다. 이번 주제는 티스토리 API를 사용하여 블로그에 있는 모든 글 목록을 가져오기를 해보겠습니다. 원래 목적은 모든 글 목록과 구글 서치 콘솔에 등록된 색인을 비교하여 색인이 누락된 게시글을 찾는 것이었으나, 이번 게시물은 티스토리 API를 어떻게 사용하는지에 대해 중점적으로 다루어 보도록 하겠습니다. (색인 비교는 다음 시간에 만나요~)

티스토리 오픈 API

티스토리 오픈 API는 OAuth(Open Authorization) 표준 프로토콜을 사용합니다. 해당 기능은 별도의 비밀번호를 제공하지 않고 외부에서 인증하고 사용 권한을 위임 받습니다. OAuth 2.0에 관한 개념과 간단한 블로거 포스트 예제 자료는 블로그 검색을 부탁드립니다. 검색은 하단에 있습니다.

티스토리 오픈 API 사용하기


티스토리 우측하단으로 이동하면 오픈 API에 관한 정보를 볼 수 있습니다. 오픈 API를 선택합니다.

티스토리 API 앱 등록하기

앱 등록 절차는 그렇게 어렵지 않습니다. 서비스명과 설명 및 URL 등을 간단히 입력하면 바로 등록이 가능합니다. 그리고 앱등록, 앱관리 우측상단에는 오픈 API 가이드가 있습니다. 앱 아이디와 키는 노출이 되지 않도록 주의하셔야 합니다.

오픈 API 흐름

API 흐름

티스토리 오픈 API 흐름은 다음과 같이 진행하겠습니다.

  • 오픈 API 인증 받기
  • API 인증코드 받기
  • 인증코드를 이용하여 액세스 토큰 발급 받기
  • 티스토리에서 모든 글 목록 가져오기

오픈 API 인증 받기

API 인증 받기

애플리케이션 기능을 사용하려면 접근 허가를 통해 인증을 받아야 합니다. 허가하기를 눌러 주세요. 다음은 인증을 받기 위해 티스토리 OAuth로 이동하는 프로그램입니다.

void BeomSang1_Authorize()
{
    //초기화
    m_code = string.Empty;
    m_accessToken = string.Empty;

    string state = CoreBeomSang.GetRandomDataBase64url(32);

    string requestString = string.Format("{0}?client_id={1}&redirect_uri={2}&response_type=code&state={3}",
        m_authorizationEndpoint,
        m_clientId,
        Uri.EscapeDataString(m_redirectUri),
        state);

    Process.Start(requestString);

    //var httpListener = new HttpListener();
    //httpListener.Prefixes.Add(m_redirectUri);
    //httpListener.Start();

    //HttpListenerContext context = await httpListener.GetContextAsync();

    //var response = context.Response;
    //string responseString = $"<html><head><meta http-equiv='refresh' content='10;url=beomsang.tistory.com' charset='UTF-8'></head><body><p>인증절차를 종료하였습니다.</p><p>애플리케이션으로 돌아가 주세요!</p><p>감사합니다.</p><p>범상 드림.</p></body></html>";
    //var buffer = Encoding.UTF8.GetBytes(responseString);
    //response.ContentLength64 = buffer.Length;
    //var responseOutput = response.OutputStream;
    //Task responseTask = responseOutput.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>
    //{
    //    responseOutput.Close();
    //    httpListener.Stop();
    //    Debug.WriteLine("HTTP 리스너 중단");
    //});

    //if (context.Request.QueryString.Get("error") != null)
    //{
    //    Debug.WriteLine($"오류 발생 : {context.Request.QueryString.Get("error")}");
    //    return;
    //}

    //if (context.Request.QueryString.Get("code") == null)
    //{
    //    Debug.WriteLine($"잘못된 응답 양식 (code가 없음) : {context.Request.QueryString}");
    //    return;
    //}

    //if (state != context.Request.QueryString.Get("state"))
    //{
    //    Debug.WriteLine($"(선택옵션)사이트간 요청 위조가 발생한 것 같습니다. 요청state : {state}, 응답state : {context.Request.QueryString.Get("state")}");
    //    return;
    //}

    //m_code = context.Request.QueryString.Get("code");
}
  • m_authorizationEndpoint 변수는 https://www.tistory.com/oauth/authorize 입니다. 다음은 오픈 API 가이드 중 일부입니다.
  • m_clientId 변수는 등록된 앱 아이디 입니다.
  • m_redirectUri 변수는 등록된 앱 정보의 CallBack 입니다. 콜백 주소를 임의로 입력하는 경우 오류가 발생합니다.
https://www.tistory.com/oauth/authorize?
  client_id={client-id}
  &redirect_uri={redirect-uri}
  &response_type=code
  &state={state-param}

오픈 API 인증 코드 받기

리디렉션 된 페이지의 URI를 보면 코드가 반환된 것을 확인할 수 있습니다. 이 코드를 통해 이후의 과정에서 액세스 토큰을 발급받을 것입니다. 코드를 다음과 같이 복사하겠습니다.

 void BeomSang2_GetAuthorizationCode()
{
    string uri = Clipboard.GetText();
    Match match = Regex.Match(uri, "\\?code=(?<code>.*)&");
    if (match.Success)
        m_code = match.Groups["code"].Value;
}

오픈 API 액세스 토큰 발급

액세스 토큰 가져오기

오픈 API를 실행하기 위해 액세스 토큰을 발급받을 차례입니다. 필요한 매개변수는 액세스토큰 앤드포인트, 등록한 앱 아이디, 시크릿 키, 콜백 URI, 그리고 아까 인증한 코드입니다. Http Web Request와 Http Web Response, 그리고 스트림리더로 액세스 토큰을 가져옵니다.

void BeomSang3_GetAccessToken()
{
    string requestString = string.Format("{0}?client_id={1}&client_secret={2}&redirect_uri={3}&code={4}&grant_type=authorization_code",
        m_accessTokenEndpoint,
        m_clientId,
        m_clientSecret,
        Uri.EscapeDataString(m_redirectUri),
        m_code);

    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestString);
    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
        using (Stream stream = response.GetResponseStream())
        {
            using (StreamReader reader = new StreamReader(stream))
            {
                m_accessToken = reader.ReadToEnd();
            }
        }
    }
}

오픈 API 모든 글 가져오기

이제 마지막으로 모든 글을 가져오는 예제입니다. 원하는 API 앤드포인트와 액세스 토큰을 통해 모든 글을 가져올 수 있습니다. 반복문을 통하여 1페이지부터 특정 페이지에 게시물이 없을 때까지 무한히 실행하도록 합니다. 혹시라도 API를 짧은 시간에 반복 호출하면 문제가 될까봐, 반복 한 번마다 0.1초의 대기시간을 주었습니다.

  • 모든 글 목록을 가져오려면 액세스 토큰과 출력 형식, 그리고 어떤 페이지의 정보를 읽을지 정합니다.
  • m_postListEndpoint = "https://www.tistory.com/apis/post/list"; 입니다.
  • 액세스 토큰은 이전 단계에서 발급받은 토큰 입니다.
  • 아웃풋은 JSON 형태로 설정하겠습니다.
  • m_blogName은 본인의 티스토리 이름입니다. 저의 경우에는 beomsang입니다. string m_blogName = "beomsang";
  • 역직렬화(역마샬링)는 newtonsoft JSON 및 구조체를 사용하였습니다. (해당 부분은 아래 코드에 있습니다.)

티스토리 모든 글 주소 가져오기

티스토리 API를 사용하여 모든 글 주소 가져오기 예제입니다. 주소를 모두 쉼표로 구분하여 메모장으로 저장하도록 했습니다. 예제 코드는 다음과 같습니다.

void BeomSang4_GetList()
{
    string requestString = string.Empty;
    string urls = string.Empty;
    List<JObject> objs = new List<JObject>();
    int i = 0;
    do
    {
        i++;
        requestString = string.Format("{0}?{1}&output={2}&blogName={3}&page={4}",
        m_postListEndpoint,
        m_accessToken,
        "json",
        m_blogName,
        i);

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestString);
        using (WebResponse response = request.GetResponse())
        {
            using (Stream stream = response.GetResponseStream())
            {
                using (StreamReader reader = new StreamReader(stream))
                {
                    string p = reader.ReadToEnd();
                    var op = JObject.Parse(p);
                    var li = op.ToObject<TPostList>();
                    if (li.tistory.item.posts == null)
                    {
                        break;
                    }
                    else
                    {
                        li.tistory.item.posts.ForEach(delegate (TPostList.Tistory.Item.Post _post)
                        {
                            urls += $",{_post.postUrl}";
                        });

                        Thread.Sleep(100);
                    }
                }
            }
        }
    } while (true);

    urls.TrimStart(',');

    using (StreamWriter writer = File.CreateText($"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}\\beomsang_beomstory_get_list.txt"))
    {
        writer.Write(urls);
    }
}

다음은 역직렬화에 사용한 구조체입니다. 언마샬링은 여러가지 방법이 있으나 저는 이 방법을 선호합니다. (역직렬화 종류는 다음 기회에 다루도록 하겠습니다.)

public struct TPostList
{
    public struct Tistory
    {
        public struct Item
        {
            public struct Post
            {
                public string id { get; set; }
                public string title { get; set; }
                public string postUrl { get; set; }
                public string visibility { get; set; }
                public string categoryId { get; set; }
                public string comments { get; set; }
                public string trackbacks { get; set; }
                public string date { get; set; }
            }
            public string url { get; set; }
            public string secondaryUrl { get; set; }
            public string page { get; set; }
            public string count { get; set; }
            public string totalCount { get; set; }
            public List<Post> posts { get; set; }
        }
        public string status { get; set; }
        public Item item { get; set; }
    }
    public Tistory tistory { get; set; }
}

댓글