일본어 RSS 번역해서 보기 - AWS Lambda 이용

일본어 RSS 번역해서 보기 - AWS Lambda 이용

저는 요즘 일본어 글을 보는 일이 많아졌습니다. 일본어라고는 히라가나 철자를 발음하는 수준이 전부지만 일본어로 된 좋은 글들이 많기 때문입니다. 다행히 일본어-한국어 번역기는 품질이 괜찮은 편이라 구글 번역기를 이용해서 내용을 이해하는 데 큰 무리가 없고 크롬을 이용하면 일본어 사이트인 경우 자동으로 한국어로 번역하도록 설정해서 볼 수 있지만 문제는 모바일입니다.

모바일 기기에서도 크롬을 사용할 수 있지만 사용하는 RSS 리더인 Feedly(, iOS, 안드로이드)는 모바일웹용 RSS는 제공하지 않고, 번역을 제공하는 다른 RSS 서비스나 앱을 찾을 수 없었습니다. 이러한 불편함을 해결하고자 일본어 RSS를 읽어서 제목만이라도 한국어로 번역해주면 RSS 목록에서 제목을 보고 관심 있는 글은 크롬으로 열어서 번역으로 보면 괜찮겠다고 생각했습니다. 제목만 한국어로 바꾼 새로운 RSS 파일을 생성하면 RSS 리더 서비스나 앱이 지원하지 않아도 번역된 내용을 볼 수 있는 장점이 있습니다.

RSS 번역 구조도

RSS 번역 구조도

일본어 RSS를 번역 후 새로운 RSS 파일을 만드는 건 어렵지 않은 일입니다. 번역 API를 사용해서 얻은 값을 파일에 다시 쓰기만 하면 되는 거니 까요. 문제는 서버를 어떻게 관리할 것 인지였습니다. 이런 간단한 프로그램을 위해 서버를 운영하는 건 비용이나 운영에 들어가는 시간이 아까웠습니다. 제가 원하는 조건은 다음과 같았습니다.

  • 서버 비용이 저렴 할 것
  • 따로 신경쓰지 않아도 잘 동작 할 것

서버 비용은 집에 남는 서버를 사용하거나 기존에 운영 중인 개인 서버를 사용하면 됩니다. 하지만 따로 신경 쓰지 않아도 잘 동작하는 서버라는 건 없었죠. 아무리 신경 쓰지 않는다고 해도 새로운 서버로 업그레이드하거나 정전, OS 업그레이드 등 다양한 상황이 발생할 수 있습니다.

그러던 와중에 생각난 것이 AWS Lambda입니다. Lambda는 소스코드만 올려두면 서버 설정 없이 AWS가 코드를 실행해주는 서비스입니다. 소스코드에 이상만 없으면 서버 관리할 필요 없이 알아서 동작하고, 비용도 실제 실행시간만큼만(100ms 단위) 지불되므로 제가 원했던 것처럼 몇 초 안에 실행이 끝나는 경우 별도의 서버를 운영하는 것보다 저렴합니다.

Lambda는 다양한 방법으로 코드를 실행할 수 있습니다. S3, API Gateway 요청, AWS IoT 이벤트 그리고 정해진 시간에 실행하는 방법 등이 있습니다. 정해진 시간에 코드가 실행되는 건 CloudWatch – Schedule을 선택 후 cron 문법에 맞는 문자열만 적으면 정해진 시간에 실행됩니다. CloudWatch가 뭔지 몰라도 상관없는 거죠.

Lambda 이벤트 소스 종류

Lambda 이벤트 소스 종류

Lambda CloudWatch - Schedule

Lambda CloudWatch – Schedule

Lambda Cron 표현식

Lambda Cron 표현식

제가 만든 RSS 번역하는 소스코드는 다음과 같습니다. 참고로 Lambda가 지원하는 개발 환경은 Node.js(v0.10.36), Java(Java 8), Python(Python 2.7) 인데 Java(자바)는 그냥 하기 싫었고 Python(파이썬)은 3이 아니라서 한 번도 해본 적 없는 Node.js로 만들었습니다. Node.js는 처음이라 제가 잘못 작성한 부분도 많겠지만 참고 봐주세요 ^^;

```index.js
var http = require('http');
var xml = require('xml2js');
var DOMParser = require('xmldom').DOMParser;
var async = require('async');
var request = require('request');
var AWS = require('aws-sdk');
var s3 = new AWS.S3();

exports.handler = function(event, context) {
    var options = {
      hostname: 'b.hatena.ne.jp',
      path: '/hotentry/it.rss',
      headers: {
        'User-Agent': 'Mozilla/5.0'
      }
    }
    http.get(options, function(res) {
        res.setEncoding('utf8');
        var body = '';
        res.on("data", function(chunk) {
            body += chunk;
        });
        res.on('end', function() {
            var parser = new DOMParser({
                            locator:{},
                            errorHandler:function(level,msg){ }
                        })
            var doc = parser.parseFromString(body);
            var items = doc.getElementsByTagName('item');
            async.eachSeries(items, function iterator(item, callback) {
                var title = item.getElementsByTagName('title')[0]
                var form = {
                    q: title.textContent,
                    langpair: 'ja|ko',
                    de: 'myemailaddress'
                }
                request.post({ url:'http://api.mymemory.translated.net/get', form: form },
                    function (err, httpResponse, body) {
                        if (!err) {
                            var result = JSON.parse(body);
                            title.textContent = result.responseData.translatedText;
                        }
                        callback();
                    }
                )
            }, function done() {
                var xml = doc.toString();
                s3.putObject({
                    Bucket: 'seapy-static',
                    Key: 'rss/lambda_hatena_hotentry_it.xml',
                    Body: xml,
                    ACL: 'public-read',
                    ContentType: 'text/xml'
                }, function(err, data) {
                    if (err) console.log(err, err.stack);
                    else     console.log(data);
                    context.done(null, 'success');
                });
            });
        });
    }).on('error', function(e) {
        context.done('error', e);
    });
};
```

이 코드를 로컬 환경에서 실행하려면 좀 귀찮은데 이걸 해결하려면 lambda-local을 이용합니다.

번역 API는 구글 것을 사용하고 싶었지만, 유료라서 무료 범위에서 많은 단어를 제공하는 Mymemory를 사용했습니다. 필요한 모듈들은 npm을 이용해서 설치했고 해당 모듈들은 node_modules 폴더에 위치합니다.(aws-sdk는 로컬에서만 사용하고 실제 업로드 할 파일에서는 제외합니다. lambda 에서 이미 가지고 있거든요) 그리고 위의 코드와 node_modules 폴더를 하나의 압축파일로 만들어서 Lambda 서비스에 업로드 후 이벤트 소스를 CloudWatch – Schedule로 하고 20분에 한 번씩 실행하도록 하면 끝입니다. 진짜 끝이에요. 이제 20분 마다 일본어로 된 RSS를 한국어로 번역하고 S3에 저장합니다.

저는 서버가 어떻게 실행되는지 다운되지는 않았는지 업그레이드는 언제 할지 전혀 고민하지 않아도 되고 비용도 거의 무료로 사용합니다. AWS Lambda의 무료 범위가 꽤 넓거든요 ~

Lambda가 실행된 횟수와 실행시간은 Monitoring 탭에서 확인할 수 있고, 메모리 사용량이나 출력 등 자세한 로그는 CloudWatch에서 확인할 수 있습니다.

Lambda 모니터링

Lambda 모니터링

Lambda 로그

Lambda 로그

일본어 RSS의 제목을 번역한 새로운 RSS를 구독한 결과는 다음과 같습니다.

RSS 번역 결과 - Feedly 웹

RSS 번역 결과 – Feedly 웹

RSS 번역 결과 - Feedly 모바일

RSS 번역 결과 – Feedly 모바일

일부 번역이 안된 것들도 보이지만 주제를 아는 데 방해가 될 정도는 아닙니다. 문제는 “Mymemory Warning: You Used All Available Free Translations For Today.” 이라고 나오는 것들인데요. 제가 보는 하테나 IT 섹션 RSS의 업데이트 빈도가 너무 높아서 Mymemory 번역 API의 무료범위를 넘어가서 발생하는 문제입니다. 돈을 내면 쉽게 해결될 문제인데 저와 같은 목적으로 위의 RSS를 보고 싶은 사람들이 있다면 여러 명이 나눠서 내면 크게 비싸지 않을 것도 같습니다.

이번 개발을 하면서 느낀 건 2가지였습니다. 첫째는 Node.js 도 재미있다는 거였습니다. 이번 개발을 하기 전까지는 필요성을 못 느꼈는데 서버 운영하기 싫은 간단한 것들 만들 때 Lambda에 올릴 것들은 Node.js 배워서 해보면 재미있겠다는 생각이 들었습니다. 두 번째는 Serverless 트렌드가 일회성으로 끝나지 않고 앞으로 많은 비중을 차지하게 될 것이라고 느꼈습니다. 직접 해보니 특정 영역에서 확실한 장점이 보였습니다.

Lambda에 아쉬운 건 제가 좋아하는 Ruby(루비)를 아직 지원하지 않는다는 것입니다. 작년 AWS re:invent에서 Python 지원을 발표했으니 올해는 Ruby 지원을 발표하면 좋겠지만, Ruby는 아마 안될 거예요…

게시글의 아마존, 쿠팡, iTunes 링크들을 통해 구매를 하시면 제휴(Affiliate) 프로그램에 의해 저에게 일정 금액이 적립될 수 있습니다. ^_____^

Subscribe to Seapy Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe