[번역] Client-side rendering VS. Server-sde rendering

해당 포스팅은 Adam Zerner - Client-side rendering vs. server-side rendering의 글을 번역하였습니다.

초기에, 웹 프레임워크들은 서버(Server)에서 렌더링된 뷰를 갖고있었습니다. 현재는 클라이언트(Client)에서도 렌더링된 뷰를 가집니다. 지금부터 각각의 장점과 단점에 대해 알아보겠습니다.

성능(Performance)

**서버 측(Server-side)에서 렌더링**을 할 경우, 새로운 웹 페이지를 보고 싶을 때마다 다음과 같이 새로운 페이지 요청이 필요합니다.

서버 측 렌더링 작동 방식 다이어그램

이것은 먹고 싶은 것이 있을 때마다 슈퍼마켓에 가는것과 비슷합니다.

그러나 **클라이언트 측(Client-side) 렌더링**을 사용할 경우, 슈퍼마켓에 한 번 방문하고 좀 더 시간을 들여 꽤 오랜 기간동안 먹을 음식을 구매합니다. 그런 다음, 먹고 싶은 것이 있을 때마다 슈퍼마켓에 가지 않고 냉장고에서 찾게됩니다.

클라이언트 측 렌더링 작동 방식 다이어그램

각 접근법에서는 성능면에서 장점과 단점이 있습니다.

  • 클라이언트 측 렌더링을 사용하면 초기 페이지로드가 느려집니다. 네트워크를 통한 통신이 느리므로 사용자에게 콘텐츠를 표시하기 전에 서버를 두 번 왕복해야합니다. 그러나 그 후에는 이후의 모든 페이지로드가 엄청나게 빠릅니다.
  • 서버 쪽 렌더링을 사용하면 초기 페이지로드가 크게 느려지지 않습니다. 그렇다고 크게 빠르지는 않을 것입니다. 그리고 이후의 다른 요청도 마찬가지입니다.

보다 구체적으로 말하자면, 클라이언트 측 렌더링을 사용하면 초기 페이지는 다음과 같이 보입니다.

1
2
3
4
5
6
7
8
9
<html>
<head>
<script src="client-side-framework.js"></script>
<script src="app.js"></script>
</head>
<body>
<div class="container"></div>
</body>
</html>

app.js는 JavaScript의 모든 HTML 페이지를 다음과 같이 문자열로 유지합니다.

1
2
3
4
5
var pages = {
'/': '<html> ... </html>',
'/foo': '<html> ... </html>',
'/bar': '<html> ... </html>',
};

그런 다음 페이지가 로드되면 프레임워크는 URL 표시줄을 보고 [ ‘/‘] 페이지에서 문자열을 가져 와서 div class = "container"> </ div>에 삽입합니다. 또한 링크를 클릭하면 프레임워크가 이벤트를 가로 채고 컨테이너에 새 문자열 (예 : 페이지 [ ‘/ foo’])을 삽입하고 브라우저가 정상적으로하는 것처럼 HTTP 요청을 실행하지 못하게 합니다.

검색 엔진 최적화(SEO)

웹 크롤러reddit.com 을 요청하기 시작했다고 가정해봅시다.

1
2
3
4
5
6
7
8
9
10
var request = require('request');
request.get('reddit.com', function (error, response, body) {
// body looks something like this:
// <html>
// <head> ... </head>
// <body>
// <a href="espn.com">ESPN</a>
// <a href="news.ycombinator.com">Hacker News</a>
// ... other <a> tags ...
});

그러면 크롤러는 응답 본문에있는 <a href> 항목을 사용해서 새 요청을 생성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
var request = require('request');
request.get('reddit.com', function (error, response, body) {
// body looks something like this:
// <html>
// <head> ... </head>
// <body>
// <a href="espn.com">ESPN</a>
// <a href="news.ycombinator.com">Hacker News</a>
// ... other <a> tags ...

request.get('espn.com', function () { ... });
request.get('news.ycombinator.com', function () { ... });
});

그 후 크롤러는 espn.com 및 _news.ycombinator.com_의 링크를 사용하여 크롤링을 계속함으로써 프로세스를 계속 진행합니다.

결국 다음과 같은 재귀 코드처럼 동작합니다.

1
2
3
4
5
6
7
8
9
10
var request = require('request');
function crawlUrl(url) {
request.get(url, function (error, response, body) {
var linkUrls = getLinkUrls(body);
linkUrls.forEach(function (linkUrl) {
crawlUrl(linkUrl);
});
});
}
crawlUrl('reddit.com');

그렇다면 만약 요청에 의한 응답이 다음과 같은경우는 어떻게 될까요?

1
2
3
4
5
6
7
8
9
<html>
<head>
<script src="client-side-framework.js"></script>
<script src="app.js"></script>
</head>
<body>
<div class="container"></div>
</body>
</html>

위의 코드는 <a href> 태그가 없습니다. 또한 웹 페이지의 내용이 없기 때문에 검색 결과를 표시 할 때 우선순위를 지정하지 않을 것입니다.

크롤러는 거의 알지 못하지만, 클라이언트 측 프레임워크는 멋진 콘텐츠로 <div class = "container"> </div>"를 채우려합니다.

이러한 이유가 클라이언트 측 렌더링이 SEO에 좋지 않은 이유입니다.


사전 렌더링(Prerendering)

2009년에 Google은 이 문제를 해결할 수 있는 방법을 소개했습니다.

크롤러가 www.example.com/page?query#!mystate 를 방문하면 www.example.com/page?query&_escaped_fragment_=mystate 로 변환됩니다. 이렇게하면 서버가 _escaped_fragment_를 사용하여 요청을 받으면 사람이 아닌 크롤러에서 요청을 받는다는 것을 알 수 있습니다.

그렇기때문에 요청이 크롤러에서 온 경우 <div class = "container"> ... </ div>를 제공할 수 있습니다.
일반적인 요청 인 경우 <div class = "container"> </ div>를 제공하고 JavaScript가 내용을 내부에 삽입하도록 할 수 있습니다.

그러나 문제가 있습니다. 서버가 <div class = "container"> </ div>안에 무엇이 들어가는지 알지 못하기 때문입니다. 내부에 무엇이 들어가는지 파악하려면 JavaScript를 실행하고 DOM을 만들고 DOM을 조작해야합니다. 전통적인 웹 서버는 이를 수행하는 방법을 모르기 때문에 Headless Browser로 알려진 서비스를 사용합니다.

더 똑똑해진 크롤러

6년 후, Google은 크롤러가 한층 더 똑똑해 졌다고 발표했습니다. Crawler 2.0에서 <script> 태그를 볼 때 웹 브라우저처럼 실제로 요청을하고 코드를 실행하고 DOM을 조작한다는 것입니다.

그래서 다음과 같은 코드가

1
<div class="container"></div>

이제는 이렇게 보이는 것입니다.

1
2
3
4
5
6
7
<div class="container">
...
...
...
...
...
</div>

Fetch as Google를 사용하여 Google 크롤러가 특정 URL을 방문했을 때 어떤 내용을 볼지 결정할 수 있습니다.

관련된 발표문의 내용 일부를 첨부합니다.

당시 우리 시스템은 자바 스크립트를 사용하여 사용자에게 콘텐츠를 제공하는 페이지를 렌더링하고 이해할 수 없었습니다. 크롤러는 동적으로 생성 된 콘텐츠를 볼 수 없었기 때문에 웹 마스터가 AJAX 기반 애플리케이션을 검색 엔진으로 인덱싱 할 수 있도록 일련의 방법을 제안했습니다.

시대가 바뀌 었습니다. 현재 Googlebot이 자바 스크립트 또는 CSS 파일을 크롤링하는 것을 차단하지 않는 한 일반적으로 최신 브라우저와 같이 웹 페이지를 렌더링하고 이해할 수 있습니다.

덜 똑똑한 크롤러

불행히도 Google 만이 유일한 검색 엔진이 아닙니다. Bing, Yahho, Duck Duck Go, Baidu 등도 있으며 실제로 사람들은 이러한 검색 엔진도 빈번하게 사용합니다.

다른 검색 엔진은 JavaScript를 잘 처리하지 못합니다. 다음 글을 참고해보세요. SEO vs. React: Web Crawlers are Smarter Than You Think

두 세계의 장점

두 세계(서버 측 렌더링, 클라이언트 측 렌더링)의 장점을 최대한 활용하려면 다음의 방법이 있습니다.

  1. 첫 번째 페이지 로드에는 서버 측 렌더링을 사용.
  2. 그 후 모든 후속 페이지 로드에는 클라이언트 측 렌더링을 사용.

이것이 의미하는 바를 생각해보세요.

  • 첫 번째 페이지 로드의 경우 사용자가 콘텐츠를 보기 전에 두 번 왕복하지 않습니다.
  • 후속 페이지 로드가 빨라집니다.
  • 크롤러는 간단한 HTML을 얻습니다. 옛날처럼 JavaScript를 실행하거나 _escaped_fragment_를 처리할 필요가 없습니다.

그러나 이를 위한 설정을 하기위해서는 서버에서 약간의 작업이 필요합니다. Angular, React 및 Ember 모두 이 접근 방식으로 변경했습니다.

토론

먼저 고려해야 할 몇 가지 사항은 다음과 같습니다.

  • 약 2%의 사용자가 JavaScript를 사용할 수 없게 설정되어 있는 경우 클라이언트 측 렌더링이 전혀 작동하지 않습니다.
  • 웹 검색의 약 1/4은 Google 이외의 엔진으로 수행됩니다.
  • 모두가 빠른 인터넷 연결을 사용하는 것은 아닙니다.
  • 휴대 전화 사용자는 대개 빠른 인터넷 연결이 필요하지 않습니다.
  • 너무 빠른 UI는 혼란 스러울 수 있습니다. 사용자가 링크를 클릭한다고 가정 해보세요. 앱에서 새로운 뷰로 이동합니다. 그러나 새로운 뷰는 이전의 뷰와 미묘하게 다릅니다. 그리고 변경 사항은 즉시 발생했습니다 (클라이언트 측 렌더링의 장점). 새로운 뷰가 실제로 로드 된 것을 사용자가 알지 못할 수도 있습니다. 또는 사용자가 주의를 기울 였지만 상대적으로 미묘하기 때문에 사용자는 전환이 실제로 발생했는지 여부를 감지하기 위해 약간의 노력을 기울여야합니다. 때로는 약간의 로딩 스피너와 전체 페이지 재 렌더링을 하는 것이 좋습니다.
  • 캐싱이 중요합니다. 따라서 서버 측 렌더링을 사용하면 실제로 사용자가 실제로 모든 것을 서버로 가져갈 필요가 없습니다. 때로는 바다 건너편의 “공식”서버가 아닌 근처의 서버에 가면됩니다.
  • 실제로 성과와 관련하여 때로는 중요하지 않습니다. 때로는 속도가 좋고 속도가 약간 올라가더라도 삶이 더 좋아지지는 않습니다.

대부분의 사용자는 인터넷 연결 상태가 좋으며 충분히 빠릅니다. 특히 Macbook Pro로 yuppies를 타겟팅하는 경우. 초기로드 시간이 너무 길어서 사용자를 잃을 염려가 없습니다. 사용자가 링크를 클릭 할 때 실제로 새 페이지가 로드된다는 사실을 사용자가 알지 못하는 사용성 문제에 대해 걱정할 필요가 없습니다.

그러나 초기 페이지 로드시 서버 측 렌더링을 사용하는 클라이언트 측 렌더링을위한 사용 사례는 확실합니다. 큰 회사의 경우 #perfMatters, 인터넷 연결 속도가 느린 사용자가 있고 최적화에 충분한 시간을 할애 할 수있는 충분한 엔지니어링 팀이있는 경우가 종종 있습니다.

앞으로 이 같은 형태의 웹 프레임 워크 (초기 페이지 로드시 서버 쪽 렌더링을 사용하고 후에는 클라이언트 측 렌더링을 수행)가 보다 안정되고 사용하기 쉬워지기를 기대합니다. 이 시점에서 추가 된 복잡성은 최소화 될 것입니다. 그러나 오늘날,이 모든 것은 매우 새롭고, 많은 추상화가있을 것으로 기대합니다. 앞으로 더 나아가 클라이언트 측 렌더링이 필요하지 않은 곳에 인터넷 연결이 충분해지기 때문에 추세가 다시 서버 측 렌더링으로 되돌아 갈 것으로 예상됩니다.

5) AWS Lambda 사용하기

Index

  1. AWS Lambda란?
  2. CloudWatch와 Lambda 구성하기
  3. 정리하며

저번 포스팅에서 작성한 학식 메뉴 크롤링(학식 메뉴를 크롤링해 DB에 저장) 코드를 **AWS Labmda**를 사용해 일주일에 한번씩 실행되도록 스케쥴링을 해보겠습니다.

AWS(아마존 웹 서비스)에 대한 기본적인 내용은 생략합니다.

## AWS Lambda란?

어떠한 이벤트에 따라 Cron 프로세스를 구성하는데에는 다양한 방법이 존재합니다. 서버 인스턴스를 띄워놓고 일정 시간에 이벤트를 발생시키는 것이 일반적인 방법 중 하나이지만 이러한 방법으로 구성할 시에는 해당 시간에 Event를 발생시키는 일을 제외하고는 서버 인스턴스를 낭비하게 됩니다.

AWS Lambda이벤트에 응답하여 코드를 실행하고 자동으로 기본 컴퓨팅 리소스를 관리하는 서버 없는 컴퓨팅 서비스입니다. AWS Lambda를 사용하여 커스텀 로직으로 다른 AWS 서비스를 확장하거나, AWS 규모, 성능 및 보안으로 작동하는 자체 백엔드 서비스를 만들 수 있습니다. AWS Lambda는 Amazon S3 버킷의 객체에 대한 변경 또는 Amazon DynamoDB의 테이블 업데이트와 같은 다양한 이벤트에 대한 응답으로 코드를 자동 실행할 수 있습니다.

AWS Lambda는 코드가 실행되지 않을 때는 요금이 부과되지 않습니다. 즉, 서버 인스턴스를 계속 띄워놓는 것과 비교했을 때 더 저렴합니다. AWS Lambda의 자세한 요금 정책은 AWS 홈페이지에서 확인 가능합니다. (Lambda 함수가 다른 AWS 서비스를 사용하거나 데이터를 전송하는 경우 추가 요금이 부과될 수 있으므로 필히 확인바랍니다.)


## CloudWatch와 Lambda 구성하기
  1. AWS에 로그인 후 Lambda 서비스를 클릭합니다.

Lambda Function 생성하기 - 1

  1. 저와 같이 이미 생성한 Lambda 함수가 있는 경우는 아래와 같이 Create a Lambda Function 버튼을 클릭하고 처음으로 Lambda 함수를 생성하는 경우는 Get Started Now 버튼을 클릭합니다.

Lambda Function 생성하기 - 2

  1. Blueprint는 자주 사용되어지는 미리 준비된 Lambda 함수의 환경을 제공합니다. 저희는 Blank Function을 선택해서 직접 trigger(Event)와 환경을 설정합니다.

Lambda Function 생성하기 - 3

  1. Lambda를 일정 시간 규칙에 맞게 호출할 것이기 때문에 CloudWatch Events - Schedule를 선택합니다. CloudWatch는 AWS에서 실행되는 애플리케이션을 위한 모니터링 서비스 뿐만 아니라 Schedule도 제공합니다.
    (Lambda를 실행시키기 위한 trigger로 다양한 방법이 제공됩니다. 한가지 예시로 S3의 특정 Bucket에 이미지 파일이 업로드 될때마다 ReSizing을 하고싶다면 trigger로 S3를 설정한 후 Bucket과 Event type(Delete, Put, Post, Copy)을 설정하여 해당 Lambda를 수행하도록 할 수 있습니다.)

Lambda Function 생성하기 - 4

  1. CloudWatch Events - Schedule의 내용을 작성합니다. Rule nameRule description에는 해당 Schedule의 이름과 설명을 적고, Schedule expression에는 Event를 발생시키고자 하는 주기에 맞게 Cron 식을 작성합니다.
    Schedule expression에 작성한 Cron 식에 따라 Event가 발생하는 시간이 달라집니다. Cron 식을 처음 접하시는 분은 AWS 홈페이지1, AWS 홈페이지2를 참고해서 Cron 식을 작성합니다.

단, Cron 식에 사용되는 시간은 UTC를 기준으로 하기 때문에 유의해야 합니다. 제가 작성한 cron 식 (0 13 ? * SUN *)을 기준으로 하면 매주 일요일 22(13+9)시에 이벤트가 발생합니다.

Lambda Function 생성하기 - 5

6-1) 다음과 같이 Lambda function의 이름, 설명, 실행환경(포스팅 기준 Node.js 6.10 이 최신 버전입니다.) 을 작성합니다.

6-2) 다음으로 CloudWatch Events에 의해서 실행될 코드를 기재해야합니다. 코드를 기재하는 방식은 직접 작성, .ZIP 파일로 압축, S3를 이용하는 3가지 방식중 하나로 진행됩니다.
만약 작성한 코드가 aws-sdk를 제외한 외부 라이브러리를 사용하고 있다면 코드와 함께 라이브러리 파일(node_modules)을 압축하여 .ZIP 파일을 업로드 해야합니다.

6-3) 기존에 작성했던 코드에서 수정이 필요합니다. Lambda function의 코드가 에러없이 잘 완료되었는지 callback을 통해 결과를 반환해야합니다. 지난번에 작성했던 코드는 다음과 같이 수정됩니다. (예시를 들기위해 간단하게 작성했을 뿐, 실제 DB에 Insert 하는 부분은 생략되어 있습니다.)
29, 30번째 줄과, 34번째 줄에 추가된 코드에 주의하셔야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
exports.handler = (event, context, callback) => {
const request = require('request');
const cheerio = require('cheerio');

const url = "http://www.inu.ac.kr/com/cop/mainWork/foodList1.do?siteId=inu&id=inu_050110010000&command=week";

request(url, (error, response, body) => {
if (error) throw error;

let $ = cheerio.load(body);

try {
let krDay = '';
let corner = '';
let menu = '';

$('table').find('tr').each(function (index, elem) {
if (index % 6 === 0) {
krDay = $(this).find('th').text().trim();

console.log(`${krDay}`);
} else {
corner = $(this).find('th').text().trim();
menu = $(this).find('th').next().text().trim();

console.log(`${corner} -> ${menu}`);
}

callback(null);
context.succeed();
});
} catch (error) {
console.error(error);
callback(err);
}
});
};

Lambda Function 생성하기 - 6

6_4) 필요에 따라 환경변수(Environment variables)와 Advanced settings를 작성한 후 Role을 작성하고 다음단계로 넘어갑니다. (Handler에 index.handler 라고 작성했기 때문에 .ZIP파일로 압축할때 소스코드(.js)의 파일명은 index가 되어야 합니다)

Lambda Function 생성하기 - 6

  1. 지금까지 설정한 옵션을 확인하고 최종적으로 function을 생성합니다.

Lambda Function 생성하기 - 7

  1. 생성된 Lambda Function을 테스트할 수 있습니다. Actions -> Configure test event를 통해 테스트를 위한 이벤트를 설정합니다. trigger(Event)로 CloudWatch Events - Schedule를 설정했기 때문에 event template 목록 중 Schedule Event를 선택합니다.

Lambda Function 테스트 - 1
Lambda Function 테스트 - 2

  1. 테스트 후 CloudWatch의 로그를 통해서 lambda function 코드에서 출력했던 로그 및 실행결과를 확인할 수 있습니다.

Lambda Function 로그 확인 - 1
Lambda Function 로그 확인 - 2


## 정리하며

지금까지 진행한 작업은 다음의 이미지 한장으로 정리가 가능합니다. 이번기회에 lambda를 처음 사용해봤지만 앞으로 개인프로젝트에도 실무에서도 자주 사용하게될 것 같습니다.

AWS의 CloudWatch와 Lambda

4) 학식 메뉴 크롤링 하기

Index

  1. 크롤링이란?
  2. Scraping - 데이터 가져오기
  3. Parsing - 데이터 추출하기
  4. 크롤링 이후

인천대학교-에밀리에 처음 추가한 기능은 교내 식당의 메뉴를 알려주는 기능이였습니다. 학교 DB에 접근할 수 있는 권한이 없었기 때문에 하루마다 매일 갱신되는 식당의 메뉴를 알기 위해서는 학교 홈페이지에서 데이터를 참조하는 방법밖에 없었습니다. 이를 해결하기 위해 **크롤링**에 대해 공부하고 구현했던 방법에 대해 포스팅하려 합니다.

Node 버전은 6.6.0을 사용합니다.

## 크롤링이란?

우리가 흔히 부르는 **크롤링(crawling)**이란 **스크래핑(scraping)**이라고도 합니다. 즉 웹 페이지를 그대로 가져와 데이터를 추출해 내는 행위입니다. 크롤링 하는 소프트웨어를 크롤러(crawler)라고 부릅니다.

웹 크롤러(web crawler)는 조직적, 자동화된 방법으로 월드 와이드 웹(WWW)을 탐색하는 프로그램입니다. 검색 엔진과 같은 여러 사이트에서는 데이터의 최신 상태 유지를 위해 웹 크롤링을 합니다. 웹 크롤러는 대체로 방문한 사이트의 모든 페이지 복사본을 생성하는데 사용되며, 검색 엔진은 이렇게 생성된 페이지를 보다 빠른 검색을 위해 인덱싱합니다.

웹 크롤러는 시드(seeds)라고 불리는 URL 리스트에서부터 시작해서 페이지의 모든 하이퍼링크를 인식하여 URL 리스트를 갱신합니다. 갱신된 리스트를 재귀적으로 다시 방문합니다.

결국 웹 크롤러는 엄청난 분량의 웹문서를 사람이 직접 구별해서 모으는 일은 거의 불가능하기 때문에, 이를 자동으로 수행해 주는 것입니다.

웹은 기본적으로 HTML 형태로 되어 있습니다. 아래와 같이 **페이지 소스 보기**를 통해 확인할 수 있습니다.

페이지 소스 보기로 확인한 HTML 구조

위와 같은 HTML의 형태를 분석해서 원하는 정보만을 뽑아오는 것을 **웹 크롤링**작업이라고 합니다.

웹 사이트의 데이터를 수집하는데 있어서 2가지 단계만 기억하면 됩니다. 일반적으로는 이 2가지 과정을 합쳐 크롤링이라고 부르지만, 각각의 단계는 명확히 구분됩니다.

1.Scraping : 데이터를 어떻게 가져올 것인가?

2.Parsing : 가져온 데이터를 어떻게 추출할 것인가?


## Scraping - 데이터 가져오기

Javascript에서 웹 페이지 데이터를 가져오기 위해서 request 모듈을 사용합니다.

먼저 request 모듈을 설치합니다.

1
npm install request --save
1
2
3
4
5
6
7
8
9
const request = require('request');

const url = "http://www.inu.ac.kr/com/cop/mainWork/foodList1.do?siteId=inu&id=inu_050110010000&command=week";

request(url, (error, response, body) => {
if (error) throw error;

console.log(body);
});

위의 코드를 실행하면 아래와 같이 제가 얻고자 했던 학식 메뉴의 데이터를 포함한 웹 페이지 데이터를 가져옵니다. 해당 데이터는 크롬에서 **보기 - 개발자 정보 - 소스 보기**를 통해서도 확인이 가능합니다.

웹 페이지 Scraping


## Parsing - 데이터 추출하기

Scraping을 통해 가져온 데이터에서 원하는 데이터만을 추출(Parsing)하기 위해 cheerio 모듈을 사용합니다.

cheerio 모듈을 설치합니다.

1
npm install cheerio --save

cherrio 모듈을 사용하면 웹 클라이언트 자바스크립트 라이브러리인 jQuery 에서 사용하는 것 처럼 style을 통해 element를 선택할 수 있어, 기존의 선택자 방식을 그대로 사용할 수 있습니다.

따라서 cherrio를 사용하기 위해서는 Request 모듈을 통해 가져온 HTML 데이터에서 어떤 element가 우리가 원하는 데이터를 갖고 있는지 파악할 필요가 있습니다.

웹 페이지의 HTML 구조는 크롬의 **보기 - 개발자 정보 - 개발자 도구**를 통해 확인할 수 있습니다.

학식 메뉴 데이터를 포함하고 있는 table은 다음과 같은 구조를 갖고 있습니다.

HTML 구조 - 개발자 도구

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<table>
<thead>
<tr>
<th class="bdlrNone">29(월)</th>
</tr>
</thead>
<tbody>
<tr>
<th>1코너</th>
<td>햄순두부찌개 / 고추장삼겹살볶음 / 훈제오리철판구이 (2인)</td>
<td></td>
<td class="bdlrNone"></td>
</tr>
<tr>
<th>2코너</th>
<td>등심돈까스&해쉬포테이토 / 떡갈비난자완스덮밥</td>
<td></td>
<td class="bdlrNone"></td>
</tr>
<tr>
<th>3코너</th>
<td>냉모밀&닭갈비덮밥</td>
<td></td>
<td class="bdlrNone"></td>
</tr>
<tr>
<th>4코너</th>
<td>삼겹살스테이크,치즈함박스테이크,고르곤졸라피자,치즈오븐스파게티(토마토),빠네파스타,도리아(불닭,불고기),불고기샐러드,닭가슴살샐러드,훈제연어샐러드,옛날통닭</td>
<td></td>
<td class="bdlrNone"></td>
</tr>
<tr>
<th>5코너</th>
<td>신라면,신라면정식,만두라면,쫄면,해물짬뽕라면,폭탄라면,떡라면,치즈라면,크림치즈김밥,참치김밥,즉석떡볶이,짜파게티,냄비우동</td>
<td></td>
<td class="bdlrNone"></td>
</tr>
</tbody>
<thead>...</thead>
<tbody>...</tbody>
<thead>...</thead>
<tbody>...</tbody>
<thead>...</thead>
<tbody>...</tbody>
<thead>...</thead>
<tbody>...</tbody>
<thead>...</thead>
<tbody>...</tbody>
<thead>...</thead>
<tbody>...</tbody>
</table>

제가 필요한 데이터는 1. 날짜, 2. 코너, 3. 메뉴 인데 이 데이터는 다음과 같은 구조로 존재합니다.

table 태그 안에 thead > tr > th 에는 날짜, tbody > tr > th 에는 코너, tbody > tr > td 에는 메뉴가 있습니다.

thead와 tbody 태그를 사용해서 데이터를 추출할 수 있지만 테이블이 규칙적으로 만들어져 있기 때문에 table 태그의 tr 태그만 추출한 후 규칙을 이용해서도 쉽게 데이터를 추출 할 수 있습니다.

table에서 하루의 식단은 6개의 tr 태그마다 반복 되고 있습니다.
1번째 tr 태그는 th 태그 안에 날짜 데이터를 갖고 있고,
2번째~6번째 tr 태그는 th 태그 안에는 코너 데이터를, td 태그 안에는 메뉴 데이터를 갖고 있습니다.

이를 이용해 다음과 같이 코드를 작성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const request = require('request');
const cheerio = require('cheerio');

const url = "http://www.inu.ac.kr/com/cop/mainWork/foodList1.do?siteId=inu&id=inu_050110010000&command=week";

request(url, (error, response, body) => {
if (error) throw error;

let $ = cheerio.load(body);

try {
let krDay = '';
let corner = '';
let menu = '';

$('table').find('tr').each(function (index, elem) {
if (index % 6 === 0) {
krDay = $(this).find('th').text().trim();

console.log(`${krDay}`);
} else {
corner = $(this).find('th').text().trim();
menu = $(this).find('th').next().text().trim();

console.log(`${corner} -> ${menu}`);
}
});
} catch (error) {
console.error(error);
}
});

결과는 다음과 같습니다.

parsing 결과


## 크롤링 이후

크롤링을 통해 원하는 데이터를 추출했다면 이를 활용해야 합니다. 에밀리에서는 사용자의 요청(학생식당, 카페테리아, 사범대식당, 기숙사식당, 교직원식당)에 따라 해당하는 식당의 메뉴를 제공합니다.

메뉴라는 데이터 특성상 실시간으로 변경되는 데이터가 아니기 때문에 한번 크롤링 하고 나면 사용자의 요청(식당)에 따라 다른 응답해주면 될 뿐 데이터 자체는 요일이 변경되지 않는이상 계속 유효합니다. 따라서 학교 사이트에서 메뉴를 크롤링하는 행위는 학교 메뉴가 갱신되는 일주일 주기마다 한번씩만 하고 크롤링한 데이터를 DB에 저장한다면 사용자의 요청마다 반복적인 크롤링을 하지 않을 수 있습니다.

이를 위한 해결 방법으로는 node-schedule 모듈을 사용해서 정해진 시간마다 실행되도록 하는 방법이 있습니다.

혹은 AWS-Lambda를 사용할 수도 있습니다.

저는 AWS-Lambda 를 사용해서 스케쥴링을 하였는데 이와 관련된 내용은 다음에 포스팅하도록 하겠습니다.

3) Express 프로젝트 생성하기

Index

  1. 들어가기 앞서
  2. Express 프로젝트 생성하기
  3. 옐로아이디 API 문서 살펴보기
  4. API 작성하기
  5. 서버 실행 & API 연동하기
  6. 마치며

## 들어가기 앞서 **옐로아이디 자동응답API** 를 만드는데에는 여러가지 방법이 있습니다. 저는 **`Express generator`**를 사용하여 생성한 **`Express`** 기반의 프로젝트로 **Emily**를 만들었고 이 방식으로 설명할 예정입니다. 그러나 간단한 기능만 구현하는 경우 AWS의 Lambda와 API GateWay를 사용해서도 Serverless하게 만들 수 있습니다. 해당 내용은 [박상권의 삽질블로그](http://gun0912.tistory.com/63)를 참고하면 좋을것 같습니다.
## Express 프로젝트 생성하기 >Node 버전은 **6.6.0**을 사용합니다. Node가 설치되어 있지 않다면 글을 작성하는 기준으로 LTS 버전인 **6.10.3**이상의 버전을 설치합니다. 이왕이면 nvm을 이용하여 설치하는것을 권장합니다. ([nvm 관련글 참고](https://jongmin92.github.io/2016/09/20/Node/nvm-(Node-Version-Manager)) npm, Express 에 대해서는 해당 글에서 자세하게 설명하지 않겠습니다. 더 좋은 글들이 많으니 참고하시기 바랍니다!!

이제 Express 프로젝트를 생성하겠습니다.

npm을 통해 **Express generator**를 먼저 설치합니다. (global로 추가하기 위해 -g 옵션을 사용합니다.)

1
$ npm install express-generator -g

Express generator를 사용해서 프로젝트를 생성합니다. 저는 Emily라는 이름으로 프로젝트를 생성했습니다.

1
$ express Emily

생성된 프로젝트 폴더로 이동하여 모듈을 설치합니다.

1
2
$ cd Emily
$ npm install

생성된 프로젝트는 다음과 같은 구조입니다. 이제 옐로아이디 API 문서를 참고해서 간단한 봇을 만들어보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.jade
├── index.jade
└── layout.jade

## API 문서 살펴보기 카카오톡 이용자가 플러스친구, 옐로아이디에게 보낸 메세지를 카카오톡 서버를 통해 저희가 전달 받은 후 다시 카카오톡 서버로 응답 메시지를 전달해야하기 때문에 카카오톡에서 정의하고 있는 API 문서에 맞게 코드를 작성해야 합니다.

지금부터 옐로아이디 API 문서를 살펴보겠습니다. 본 포스팅에서는 **[이용에 대한 참고사항, 개인정보 수집 및 이용에 대한 주의사항]**에 대해서는 따로 설명하지 않습니다. 그러나 개발전에 한번쯤은 꼭 읽어보셔야합니다.

1. 용어

먼저 API 문서에서 사용되는 용어에 대해서 살펴보겠습니다.

(1) 수신 API
카카오톡 이용자가 플러스친구, 옐로아이디에게 보낸 메시지를 전달 받은 후 응답을 할 수 있는 API 입니다.
http(s) restful api를 통하여 카카오 API 서버 -> 파트너 서버를 호출합니다.

(2) app_key
플러스친구/옐로아이디에서 자동응답을 위한 앱 등록시 프로필별로 발급되는 고유 키 값입니다.
자동응답 기능만 이용하시는 경우 사용되지 않으며, 일부 app_secret을 통한 별도 인증이 필요한 일부 프로필에만 사용됩니다.

(3) app_secret
인증을 위해 app_key와 조합하여 사용되는 키 값입니다.
자동응답 기능만 이용하시는 경우 사용되지 않으며, 일부 app_secret을 통한 별도 인증이 필요한 일부 프로필에만 사용됩니다.

(4) user_key
특정 카카오톡 이용자를 구분하기 위한 key 입니다. 카카오에서는 이용자의 개인정보를 외부에 제공하지 않으므로, 외부 파트너사에서 카카오톡 이용자를 구분하기 위해서는 카카오로부터 API를 통해 user_key를 response로 받아야 합니다.
user_key는 특정 카카오톡 이용자에 대해 프로필별로 각기 다르게 발급됩니다. 따라서 user_key는 해당 프로필에 대해서만 유효합니다.
카카오톡 이용자가 프로필을 차단했다가 다시 추가한 경우에는 user_key가 갱신되지 않으며, 이용자가 카카오톡 탈퇴 후 재가입한 경우 갱신됩니다.

2. API

카카오에서 제공하고 있는 옐로아이디 API는 아래와 같이 5개 입니다. 각 API에 대한 자세한 내용은 문서를 참고합니다.

Action Method URL 설명
키보드 GET /keyboard 처음에 사용자가 채팅방에 들어왔을때 호출되는 API. 사용자에게 키보드에 보여질 내용을 리턴
메시지 POST /message 사용자가 보내는 메세지/사진/동영상 을 받아 처리해 응답
친구추가 POST /friend 사용자가 옐로아이디를 친구추가했을때 호출되는 API
친구차단(삭제) DELETE /friend 사용자가 옐로아이디를 친구차단(삭제)했을때 호출되는 API
채팅방 나가기(삭제) DELETE /chat_room 사용자가 채팅방 나가기(삭제)했을때 호출되는 API

## API 작성하기 API 문서를 참고하여 코드를 작성하겠습니다. 작성하는 코드는 사용자가 옐로아이디를 친구 추가 후 채팅방에서 입력하는 메시지를 받아 그대로 다시 전달해주는 코드입니다.

routes 폴더 아래에 **api.js**를 추가 후 다음 코드를 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const express = require('express');
const router = express.Router();

// 처음에 사용자가 채팅방에 들어왔을때 호출되는 API. 사용자에게 키보드에 보여질 내용을 리턴
router.get('/keyboard', function (req, res) {
res.status(200).json(
{
type: 'text'
}
);
});

// 사용자가 보내는 메세지/사진/동영상 을 받아 처리해 응답
router.post('/message', function (req, res) {
const { user_key, type, content } = req.body;

console.log(`"${user_key}"가 "${content}" 메시지 입력`);

res.status(200).json({
message: {
content
}
});
});

// 사용자가 옐로아이디를 친구추가했을때 호출되는 API
router.post('/friend', function (req, res) {
const userKey = req.body.user_key;

console.log(`"${userKey}"가 옐로아이디 친구 추가`);

res.status(200).json();
});

// 사용자가 옐로아이디를 친구차단(삭제)했을때 호출되는 API
router.delete('/friend/:userKey', function (req, res) {
const userKey = req.body.userKey;

console.log(`"${userKey}"가 옐로아이디 친구 삭제`);

res.status(200).json();
});

// 사용자가 채팅방 나가기(삭제)했을때 호출되는 API
router.delete('/chat_room/:userKey', function (req, res) {
const userKey = req.body.user_key;

console.log(`"${userKey}"가 옐로아이디 채팅방 삭제`);

res.status(200).json();
});

module.exports = router;

작성한 router를 **app.js**에 추가합니다.

1
2
const api = require('./routes/api');
app.use('/api', api);

## 서버 실행 & API 연동하기 서버는 해당 프로젝트 경로에서 **npm start** 혹은 **node bin/www** 로 실행할 수 있습니다. 그러나 해당 서버는 외부에서 접근 가능해야 합니다. 즉, AWS의 EC2와 같은 클라우드 자원을 사용하거나(추천), 본인의 컴퓨터 혹은 노트북에서 서버를 실행한다면 포트포워딩을 통해 **외부에서 접근할 수 있도록 설정이 필요합니다.**

외부에서 접근할 수 있도록 설정이 끝난 후에는 옐로아이디 홈페이지에서 해당 서버의 URL을 기재합니다.

옐로아이디 자동응답 API 등록-1

옐로아이디 자동응답 API 등록-2

앱 URL을 기재하고 API TEST 버튼을 누르면 카카오에서 해당 URL에 test request를 요청합니다. test request의 결과를 통해 잘 작동하는지 확인이 가능합니다.


## 마치며 이번 포스팅에서는 Express 프로젝트를 생성하고 API 문서를 토대로 간단한 봇을 만들어보았습니다. 해당 API를 토대로 앞으로 구현하고자 하는 기능을 구현하면 될것입니다.

다음번에는 제가 Emily를 만들며 구현하고자 했던 부분들을 어떤식으로 해결했는지에 대해 포스팅하겠습니다.

Sequelize 사용하기

ORM 이란?

**관계형 데이터베이스(RDB)**를 사용할때 데이터베이스의 데이터 조작(CRUD)를 위해서는 SQL 문을 작성해야합니다. SQL 문은 비즈니스 로직을 구성하고 있는 코드와 함께 작성하게 되는데 이는 코드의 가독성을 떨어뜨릴뿐만 아니라, 사용하는 관계형 데이터베이스에 따라 조금씩의 차이가 존재하기 때문에 문제가 발생할 수 있습니다. 이를 해결하기 위해 ORM을 사용합니다.

**ORM(Object Relational Mapping)**은 객체(Object)와 관계(Relation)를 맵핑(Mapping)하여 비즈니스 로직에 집중할 수 있도록 데이터 처리 로직을 추상화시킵니다.

객체와 관계를 매핑한다는 것은 데이터베이스에 저장된 레코드를 객체로 바꿔표현한다는 의미하며, 비즈니스 로직에 집중할 수 있도록 데이터 처리 로직을 추상화한다는 것은 쿼리를 사용하지 않고도 데이터베이스를 사용할 수 있음을 뜻합니다.

ORM을 사용할 경우 특정 DBMS에 종속되지 않으며 생산성, 독립성, 가독성(SQL문이 코드에 들어가지 않기때문) 및 유지보수 측면에서의 장점이 있지만 반대로 RAW query에 비해 퍼포먼스가 떨어지고, query가 복잡해 질수록 오히려 생산성이 저하될 수 있다는 단점도 존재합니다.

ORM

Sequelize 설치하기

**Sequelize**는 Node에서 가장 많이 사용되는 ORM 입니다.

RDS로 PostgresSQL, MySQL, MariaDB, SQLite, MSSQL을 지원하고 transaction, read replication등 다양한 기능을 제공하고 있으며. 또한 Promise를 기본으로 동작하기 때문에 비동기 코드를 보기좋게 작성할 수 있습니다.

실습을 위해 express-generator를 통해 Express 프로젝트를 생성후 sequelizemysql module을 설치합니다. (Express 프로젝트를 생성하는 부분은 생략합니다.)

1
npm install --save sequelize mysql

**Sequelize Command Line Interface(CLI)**를 사용하기 위해서 sequelize-cli module을 설치합니다.

1
npm install -g sequelize-cli

Sequelize CLI 사용하기

sequelize cli를 통해서 **migration(마이그레이션), seeder(시더), model(모델)**의 초기 설정을 손쉽게 할 수 있습니다. 이번 포스팅에서는 model에 관해서 알아보겠습니다.

RDB의 테이블을 model로 정의를 하면 해당 model을 통해 데이터 처리가 가능하게 됩니다.
먼저 생성한 express 프로젝트에 sequelize cli 명령어를 통해 sequelize 설정 파일을 생성 후 model을 정의해 보겠습니다.

1
2
sequelize init:config --config config/sequelize.json
sequelize init:models

sequelize cli의 sequelize init:config라는 명령어로 sequelize관련 config 파일을 자동으로 생성할 수 있습니다. 아무런 옵션을 주지 않는다면 config/config.json 파일이 생성됩니다.

sequelize init:models 명령어를 통해서는 models 정의에 관련된 기본 구조를 생성할 수 있습니다.

위 2개의 명령어를 실행하면 다음과 같은 폴더와 파일이 생성됩니다.

1
2
3
4
├── config/
└── sequelize.json
├── models/
└── index.js

Sequelize config 설정하기

sequelize cli를 통해 생성한 config/sequelize.json파일에 데이터베이스에 관련된 설정 값을 입력합니다.

NODE_ENV 에 따라 각기 다른 값을 사용하기 때문에 상황에 맞게 설정할 수 있습니다. NODE_ENV에 대해 잘 알지 못한다면 이곳을 참고하세요.

데이터베이스를 사용한 프로젝트 경험이 있다면 대부분의 config 값은 입력할 수 있습니다. config 값중 dialect에는 사용하는 RDB 이름을 입력해야 합니다. diaect에 사용 가능한 값은 sequelize docs를 참고하세요. 현재 사용 가능한 RDB 로는 ‘mysql’, ‘sqlite’, ‘postgress’, ‘mariadb’가 있습니다.

추가적으로 커넥션 풀과 로깅 기능을 사용한다면 해당 값을 추가합니다. 추가적으로 필요한 옵션은 docs를 참고하세요.

1
2
3
4
5
6
"pool": {
"max": 20,
"min": 0,
"idle": 5000
},
"logging": true

# Model 정의하기

Model을 생성하기 전 sequelize cli를 통해 생성한 models/index.js파일을 살펴보겠습니다. index.js 의 역할은 config/sequelize.json의 설정값을 읽어 sequelize를 생성한 후 models 폴더 아래에 정의한 model 관련 js 파일을 모두 로딩하여 db 객체에 Model을 정의한 후 반환합니다.

sequelize config 관련 파일을 sequelize.json으로 생성하였다면 config 파일을 불러오는 require 부분의 경로를 수정해주어야 합니다.

이제 models 폴더 아래에 간단한 모델을 정의해 보겠습니다. **user.js**를 생성 후 다음의 코드를 입력합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module.exports = function (sequelize, DataTypes) {
const user = sequelize.define('User', {
userID: { field: 'user_id', type: DataTypes.STRING(50), unique: true, allowNull: false },
password: { field: 'password', type: DataTypes.STRING(30), allowNull: false },
}, {
// don't use camelcase for automatically added attributes but underscore style
// so updatedAt will be updated_at
underscored: true,

// disable the modification of tablenames; By default, sequelize will automatically
// transform all passed model names (first parameter of define) into plural.
// if you don't want that, set the following
freezeTableName: true,

// define the table's name
tableName: 'user'
});

return user;
};

/*
Sequelize 참고
DataTypes => http://docs.sequelizejs.com/en/v3/api/datatypes/
Associations => http://docs.sequelizejs.com/en/v3/api/associations/
Model Function => http://docs.sequelizejs.com/en/v3/api/model/
*/

Model을 생성하며 사용된 옵션은 주석과 docs를 참고합니다. 이제 모델에 대한 정의가 끝났습니다. 데이터베이스에 user라는 테이블은 User라는 Object로 매핑되었고 user_id, password라는 칼럼은 User Object의 속성으로 매핑되었습니다.

Sequelize Sync 사용하기

Sequeliz에서는 **입력(INSERT), 수정(UPDATE), 조회(SELECT), 삭제(DELETE)**의 **데이터 조작(DML: Data Manipulation Language)**뿐만 아니라 데이터베이스의 스키마 객체를 생성(CREATE), 변경(ALERT), 제거(DROP) 할 수 있는 **데이터 정의(DDL: Data Definition Language)**도 지원합니다.
따라서 이미 만들어진 데이터베이스 테이블에 모델을 매핑할 수 있을 뿐만 아니라, 정의한 모델을 바탕으로 테이블을 생성할 수도 있습니다.(동기화)

해당 기능을 사용하기 위해서는 Sequelize의 sync 메서드를 사용합니다. **app.js**에 다음의 코드를 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
// connect To DB
const models = require('./models');
models.sequelize.sync()
.then(() => {
console.log('✓ DB connection success.');
console.log(' Press CTRL-C to stop\n');
})
.catch(err => {
console.error(err);
console.log('✗ DB connection error. Please make sure DB is running.';
process.exit();
});

sync 메서드를 호출하여 실패했을 경우에는 에러 메시지를 출력 후 프로세스를 종료합니다.

sync 메서드는 모델에서 정의한 이름의 테이블이 존재하지 않을 경우에만 동작합니다. 이미 테이블이 존재할 경우에는 models.sequelize.sync({force: true}) 과 같이 force 옵션을 주어 강제적으로 테이블을 제거 후 다시 생성이 가능하지만 매우 위험한 옵션이므로 주의를 기울여 사용해야 합니다.

Sequelize 예제 (SELECT)

이제 Sequelize를 사용하여 SELECT를 사용해보겠습니다. 유저 리스트를 가져오는 query는 다음과 같습니다.

1
2
3
4
5
6
7
models.User.findAll()
.then(results) {
res.json(results);
})
.catch(err => {
console.error(err);
});

User 테이블에 있는 모든 row를 가져오는 query입니다. Sequelize는 결과를 Promise로 리턴하기 때문에 findAll 메서드 역시 Promise를 리턴합니다. 따라서 query의 결과는 then에서 받고, catch문에서 상황에 맞게 error 처리(handling)를 하면됩니다.

findAll의 더 자세한 사용법은 Sequelize-model-findAll 설명을 참고합니다.

Sequelize 예제 (INSERT)

Sequelize를 사용하여 INSERT를 하는 방법은 다음과 같습니다.

1
2
3
4
5
6
7
models.User.create({userID: '유저ID', password: '유저PW'})
.then(result => {
res.json(result);
})
.catch(err => {
console.error(err);
});

create 메서드의 매개변수에 model에서 매핑한 내용을 토대로 데이터를 넣으면 query를 실행 후 insert된 row정보가 반환됩니다.

create의 더 자세한 사용법은 Sequelize-model-create 설명을 참고합니다.

Sequelize 예제 (UPDATE)

User 테이블의 데이터를 수정할때는 다음과 같이 사용합니다.

1
2
3
4
5
6
7
models.User.update({password: '새로운 유저PW'}, {where: {userID: '유저ID'}})
.then(result => {
res.json(result);
})
.catch(err => {
console.error(err);
});

update 메서드의 매개변수에는 update할 데이터를 입력합니다.

update 더 자세한 사용법은 Sequelize-model-update 설명을 참고합니다.

Sequelize 예제 (DELETE)

User 테이블의 데이터를 삭제할때는 다음과 같이 사용합니다.

1
2
3
4
5
6
7
models.User.destroy({where: {userID: '유저ID'}})
.then(result => {
res.json({});
})
.catch(err => {
console.error(err);
});

destroy 메서드의 매개변수에는 where 조건을 입력합니다.(where 조건을 입력하지 않을 경우 테이블의 모든 row가 삭제되기 때문에 주의해야 합니다.)

destroy 더 자세한 사용법은 Sequelize-model-update 설명을 참고합니다.

2) 카카오 옐로아이디 만들기

Index

  1. 옐로아이디란?
  2. 옐로아이디 만들기

## 옐로아이디란? [옐로아이디](https://yellowid.kakao.com/login)란 `고객과 소통할 수 있는 카카오톡 비즈니스 아이디입니다.` 옐로아이디를 무료로 만들어 비즈니스에 활용가능하며 옐로아이디는 별도의 전화번호가 필요없고, 한글 아이디 검색이 가능합니다.

옐로아이디 검색결과

옐로아이디는 API형 자동응답이 가능하기 때문에 사용자의 요청에 따라 자유롭게 답변 시나리오 및 이벤트를 구성할 수 있습니다.

카카오 뿐만 아니라 페이스북, 텔레그램 또한 API형 자동응답 기능을 제공하기 때문에 굳이 옐로아이디를 사용하지 않아도 자동응답 챗봇을 만들 수 있습니다.


## 옐로아이디 만들기 옐로아이디를 사용하기위해서는 먼저 정해진 절차에 따라 가입신청을 해야합니다. [Yellow ID 신규가입](https://yellowid.kakao.com/join)을 통해 옐로아이디 신청이 가능합니다. 모바일이라면 [Yellow ID App](https://play.google.com/store/apps/details?id=com.kakao.yellowid) 설치 후 신청이 가능합니다.

옐로아이디 API 문서는 공개되어 있기 때문에 옐로아이디 가입신청 후 기획이 되어있다며 바로 개발이 가능합니다. 가입신청이 완료되면 발급받은 API 키만 적용해주면 됩니다.

신청시 주의사항

  • 프로필 이름옐로아이디개설 후 변경이 불가능합니다.(위의 사진에서 인천대학교 에밀리가 프로필 이름, @인천대학교-에밀리가 옐로아이디를 의미합니다.
  • 주소는 입력하지 않아도 상관없습니다.
  • 홈페이지 주소를 입력하게 되는데 저는 처음 블로그 주소를 기재하였다가 옐로아이디의 목적과 맞지 않다는 이유로 반려되었습니다. 옐로아이디에 대한 설명이 있는 홈페이지가 없다면 페이스북 페이지를 생성한 후 간단히 옐로아이디에 대한 목적과 서비스에 대해 간단한 글을 남긴 후 홈페이지 주소에 페이스북 페이지를 기재하는 방법이 좋습니다.
  • 심사는 2~3일 정도의 시간이 소요됩니다. 심사에서 반려될 경우 해당 반려사유를 참고하여 수정 후 재심사를 요청해야합니다. 재심사도 2~3일 정도의 시간이 소요되기 때문에 처음 신청시 반려되는 불상사가 일어나지 않도록 공들여 작성해야합니다.