이 블로그는 더 이상 업데이트되지 않습니다.

최신 내용을 확인하시려면 여기를 클릭해주세요.

Programming (77)


레진코믹스 RSS로 구독하기

  • 2015/04/17
  • Web

바쁜 사람들을 위한 한 줄 요약

Lezhin Comics Feed(https://lezhin-rss.update.sh/)에서 구독하시면 됩니다.

현재 동작하지 않습니다.

레진코믹스의 RSS를 제공하는 사이트를 만들었던 과정을 대략적으로 기록해 놓은 문서이다.

서론


레진코믹스(http://www.lezhin.com/)라는 웹툰 사이트가 있다. 여러 작가들을 데려와서 유/무료 웹툰을 서비스하거나, 이미 출판된 만화도 가져와서 볼 수 있게 했다.

레진코믹스 메인페이지. 세월호 1주기가 스크린샷을 촬영한 날이었기에 로고에 노란 리본이 달려있다.

네이버나 다음과 같은 포털에서 운영하는 웹툰 서비스와 비슷하지만, 적극적인 유료화 정책과, 나름 고수위(?)의 성인 컨텐츠를 제공하는 게 특징이다. 얼마 전에는 무슨 일이 있었는지 정확하게는 모르겠지만 레진코믹스 사이트를 방송통신위원회에서 차단하면서 이래저래 말이 많이 나온다. 나는 그거랑은 별개로, 레진코믹스에서 연재중인 레바툰이 이런 저런 커뮤니티 사이트들에 공유되면서 관심을 갖게 됬다.

나는 기본적으로 RSS 리더를 애용한다. 주로 들어가는 블로그나 사이트 게시판 등등을 가능하면 RSS로 구독하여 받아보고 있다. 물론, 네이버나 다음 웹툰도 그렇게 하고 있다. 다음 웹툰 서비스는 자체적으로 RSS 구독이 가능하고, 네이버 웹툰은 링크(홍민희라는 분이 만드셨다. 파이썬 행사에서 뵌적이 있는것 같은 느낌이…) 에서 구독할 수 있다. 그러나 레진코믹스는 RSS 구독을 지원하지 않았고, 다른 사람이 만든 구독 사이트도 없는 것 같았다.

Feed43(http://feed43.com/)과 같은 RSS 자동 생성 사이트를 이용할 수도 있었겠지만, 네이버 웹툰 구독 사이트처럼 한번 만들어 보기로 했다. 아마 시험기간이라 공부 말고 열심히 할 딴짓이 필요했던 것 같다.

지원하리라 마음먹은 대략적인 기능은 아래와 같다.

  • 모든 구독은 RSS 2.0과 Atom 1.0을 복수 지원한다.
    • 사실 별 차이점은 없지만, 그냥 둘 다 지원하기로 했다. 굳이 고르자면 Atom이 더 낫지만…
    • 레진코믹스의 API를 이용할 수 있다면 실시간 생성, 그렇지 않다면 매 시간마다 생성해서 캐
  • 개별 만화의 에피소드를 구독할 수 있다.
  • 새로 추가된 만화를 구독할 수 있다.
    • 왜냐면 새롭게 추가된 만화중에 보석같은 만화가 있을지도 모르니까?
  • 구독할 수 있는 만화들을 보여주는 웹페이지를 만든다.
    • 해당 페이지에서는 만화의 이름이나 작가의 이름으로 검색이 가능해야 한다.

현재는 위의 기능을 모두 구현했다. 만들어진 구독 사이트는 Lezhin Comics Feed(http://kb.update.sh/lezhin-rss/)에서 볼 수 있다. 레진코믹스 API를 사용할 수는 없었으므로, 매 시간 정각에 구독정보를 업데이트한다.

Lezhin Comics Feed 메인 페이지.

구현


만들기로 마음을 먹은 김에 바로 작업에 들어갔다. 일단 웹사이트를 만들어야 하는데, 내가 다뤄본 웹 프레임워크는 Python Flask밖에 없어서 선택권이 딱히 없었다. 어차피 빠른 개발을 하려면 결국 Python을 선택하긴 했을 것 같다

만화 및 에피소드 정보 가져오기

필요한 만화 및 에피소드 정보는 레진코믹스 웹사이트를 파싱해서 가져오면 될 것이라고 막연하게 생각했다. 그래서 레진코믹스 웹사이트를 켜서 이것 저것 봤는데… 분명히 동적으로 만화들을 처리하는데 ajax로 오고가는 데이터가 없었다.

가만히 보니, 처음 웹사이트의 html을 가져올 때, inline javascript로 만화나 에피소드 정보를 다 넣어서 받아오고 있었다! 이게 문제인게, 원래는 HTML을 파싱하거나, ajax call을 분석해서 API를 따라 쓰려고 했는데 이것이 불가능하다는 뜻이었다.

따라서, 적절하게 javascript를 파싱해서 만화 정보를 가져와야 했다. 우선은 requests 라이브러리를 이용하여 html을 가져와서, 이를 자체파싱-_-해서 사용했다. 이래저래 복잡하게 짤 수도 있겠지만, 딱히 퍼포먼스가 문제될 것 같지는 않았기에(물론 나중에 문제가 100% 생기지만) 내가 원하는 데이터의 시작 단어와 끝 단어를 기준으로 문자열을 잘라내어, json 처럼 취급했다.

html의 inline javacript로 정보가 담겨 있다.

그래서 이래저래 고생하긴 했지만, 깔끔하게 json을 뽑아낼 수 있었다. 뽑아낸 만화 및 에피소드 정보는 자체 API를 만들어 사용할 수 있게 했다. 이는 아래와 같다.

데이터 저장 및 업데이트

얻어온 정보는 sqlite를 이용하여 저장해놓고 사용하기로 했다. 이는 Flask-SQLAlchemy를 통해 ORM으로 단순하게 해결했다. 만화와 에피소드에 맞는 모델만 적절하게 정의해놓으면 끝이었다.

문제는 업데이트에 있었다. 그냥 반복문을 돌면서 전체 만화와 에피소드를 가져오는 것은 시간이 너무 오래 걸렸다. (이 당시 만화는 약 700개, 에피소드는 약 60,000화 이상이었다. 당연히 오래 걸린다.) 전체 만화와 에피소드 목록을 가져오는데 약 13분이 소요되었다.

대부분의 시간 소요는 네트워크 I/O 때문에 발생할 것이라 예측하고 multiprocessing을 이용해서 8 프로세스로 동작하게 했다. 결과는 만족. 2분 정도만에 모든 만화와 에피소드 목록을 가져올 수 있었다.

만화 및 에피소드를 가져온다. 8 프로세스로 동작해서 약 2분만에 모든 만화와 에피소드 정보를 가져올 수 있다.

또, 레진코믹스의 웹 사이트 구조가 변경될 경우 업데이트가 정상적으로 이루어지지 않을 것이다. 이 경우를 판단하기 위해 업데이트 중 파싱 에러가 발생할 경우 나에게 에러 정보를 gmail을 통해 보내도록 만들어 놓았다. 이젠 간단하게 cron에 매 시간 정각에 동작하도록 추가하면 끝.

구독정보 생성

만화 및 에피소드 정보를 모두 가져왔으니, 이젠 이를 Atom과 RSS로 만들어서 제공하자. Atom 1.0의 경우Flask는 Werkzeug를 통해 동작하므로, Werkzeug의 Atom Syndication 문서를 통해 구현하면 된다. RSS 2.0은 PyRSS2Gen을 통해 제공하기로 했다.

Firefox로 열어본 Atom 구독정보

메인페이지 제작

사실 위의 작업보단 웹에서 구독할 수 있는 만화들을 보여주는 메인페이지 제작이 더 큰일이었다. 왜냐하면 내가 웹을 잘 못하니까… 그래서 대충 대충 간단하게 끝냈다.

기존 레진코믹스 로고를 이용해 간단하게 만들어본 RSS 개(…)

우선 JQuery와 Bootstrap을 써서 간단하게 틀을 잡고, 제목대로 만화를 정렬해서 페이지당 50개씩 보여주기로 했다. 이 작업을 하면서 처음으로 Flask-SQLAlchemy에서 pagenate라는걸 사용할 수 있다는 것을 알았다. 여태까지 페이지 작업하는 코드를 일일히 짜줬는데, 이 녀석이면 간단하게 해결 할 수 있다.

검색같은 경우에는 처음에는 Whoosh 사용을 고려했지만, 한글 설정이 미묘했고, 굳이 많지 않은 제목과 작가 검색에 검색 엔진까지 붙여 사용할 필요성을 느끼지 못해 사용하지 않았다.

결론


결과적으로 사이트는 간단하게 완성할 수 있었다. 처음 목표로 했던 기능은 모두 구현했으므로, 특별히 이상이 생기지 않는 한 더 이상 건드릴 일은 없을 것이다.

본 프로젝트의 전체 소스코드는 https://git.update.sh/nesswit/lezhin-rss/tree/master 에서 확인할 수 있다. 만약 이 프로젝트를 클론해서 직접 실행해보고 싶다면, app/db 폴더를 생성하고, update.py 파일에서 mail 보내는 부분을 모두 지운 뒤, update.py를 실행시켜 db를 생성하고, debug.py 파일로 임시 웹서버를 실행해보면 된다.




Dictionary for AP named U+Net****

U+Net**** 의 비밀번호 규칙은 의외로 간단하다. Perl을 이용해서 간단히 U+Net**** 용 dictionary를 생성해보았다.

아래 코드를 uplusnet_dict.pl 라는 이름으로 저장한 후,

$ perl uplusnet_dict.pl > uplusnet.dict

와 같이 실행해주면 된다.

 

 

 




Sudoku Solver

Sudoku Solver는 스도쿠 게임의 해법을 찾아주는 어플리케이션입니다.
어려운 스도쿠 게임에서 한계에 봉착했을 때, 여러분들께 활로를 제공해 드립니다.
친구, 직장 동료와의 내기에서 승리하십시오!
– Google Play Store의 Sudoku Solver 설명문

위에서 말한것과 같이, Sudoku Solver는 스도쿠(스도쿠가 뭔지 모르신다면 여기 참조)의 해답을 찾아주는 어플리케이션입니다.

어느 날인가, 갑자기 페이스북에 스도쿠를 풀어주는 프로그램들을 만들어서 올리는걸 보고 ‘아, 나도 하나 만들어볼까?’ 해서 만들어진 어플리케이션입니다. 페이스북에 갑자기 스도쿠 프로그램들이 올라온 까닭이 KAIST 학생들의 Assignment 이었기 때문이라 들었지만 Command Line Interface로 만들면 스도쿠를 입력하기 힘들었기 때문에 GUI로 제작하리라 마음을 먹었습니다.

요새 계속 사용해서 손에 익은것이 JAVA였고, 그중에서도 Android Application을 계속 만들고 있었기 때문에 Android Application이 된 것 뿐이지 사실 큰 이유는 없었습니다. 애초에 스도쿠를 풀어주는 Object는 따로 구현이 되어있어서 터미널에서도 충분히 사용할 수 있습니다.(다만, 스도쿠를 입력받는 부분과 해결한 스도쿠를 출력하는 부분을 따로 만들어줘야 하겠지요.)

풀이를 완료한 후에 실행 시간을 보여주는건 단순히 필요했다기 보단 공간이 남길래(…) 넣었습니다. 만약 시간이 난다면 OCR 기능을 추가해서 카메라로 비추면 자동으로 스도쿠를 풀어주는 기능도 추가를 하고싶습니다.

다운로드는 아래 버튼으로 할 수 있습니다.

Get it on Google Play




HTML5로 만든 오픈소스 퍼즐 게임 엔진 PuzzleScript

  • 2013/10/09
  • Web

http://www.puzzlescript.net

PuzzleScript는 HTML5로 제작한 퍼즐 게임 엔진이라고 한다. 홈페이지에서 간단하게 퍼즐 게임을 제작하는 방법을 설명하고, 실제로 제작해 볼 수 있는 환경을 제공한다.

Heroes of SokobanHeroes of Sokoban 2, Dang I’m Huge 등의 게임이 PuzzleScript로 제작되었다.

이하 프로젝트 페이지에 게시되어 있는 설명

PuzzleScript was developed by me (with the assistance/feedback of many other people + tools), Stephen Lavelle (@increpare). Programming puzzle games isn’t all that hard in the grand scheme of things, but the barrier to entry is way higher than it needs to be.

It’s not a general purpose game making tool, it’s not even a general purpose puzzle game making tool. Or even a avatar-based turn-based puzzle game making tool – games likeDROD don’t work, or games with very nuanced behaviours, like Puzzles proved impossible. But it is a tool, and I think it might prove handy/enabling for a number of people, if one is accepting of its limitations.

I’ve worked on a lot of puzzle games, and decided on a particular group of those that could be handily modelled with a scripting language. I’ve seen someone do a similar pattern-matching system before (I can’t remember who, though), but it was a lot less flexible – the movement system in this engine is what gives it its real power.

Getting the engine out the door, so to speek, took several weeks of full-time, unreumerated work, and no doubt there’s plenty more to come. I am not a wealthy man, I’m an impoverished, self-employed independent game developer whose involvement with such projects amounts to happy indiscretions. If you appreciate this piece of software, please consider chipping some money my way, okay?

I’ve you’ve made something cool with the engine, please let me know ( analytic@gmail.com or @increpare ) – I’d love to highlight (and maybe even host as examples) wonderful things that people’ve made with this.




ProgressMultiPartEntity를 이용한 File upload progress 구현

안드로이드에서 서버로 파일을 업로드해야 할 때가 있다.
이때, MultipartEntity라는 것을 이용하여 FileBody를 추가하여 업로드 할 수 있다.

이를 이용하면 쉽게 파일을 업로드 할 수 있다.
그렇다면, 파일 업로드의 progress를 확인하려면 어떻게 해야 할까?

구글링을 해 본 결과, 여기에서 해답을 찾을 수 있었다.
따로 ProgressMultiPartEntity와 ProgressListener를 정의하여 사용하면 된다.




프로젝트 오일러 59번

컴퓨터상의 모든 문자들은 유일한 코드값에 대응되는데, 보통 ASCII 표준이 널리 쓰입니다. 예를 들면 대문자 A는 65, 별표(*)는 42, 소문자 k는 107라는 값을 가집니다.

현대적인 암호화 기법 중에, 텍스트 파일의 내용을 ASCII 코드로 바꾸고 각 바이트를 비밀키의 값으로 XOR 시키는 것이 있습니다. 이 방법의 장점은 암호화와 복호화에 동일한 키를 사용할 수 있다는 것입니다. 예를 들어 65 XOR 42 = 107 이고, 107 XOR 42 = 65 가 됩니다.

암호문을 절대 깰 수 없도록 하려면, 암호화할 문장의 길이와 같은 무작위의 비밀키를 만들면 됩니다. 암호문과 비밀키는 따로따로 보관해둬야 하고, 그 반쪽짜리 정보 두 개를 함께 확보하지 않는 한 해독은 불가능합니다.

하지만 이 방법은 대부분의 경우 실용적이지 못하므로, 원문보다 짧은 비밀키를 사용하게 됩니다. 이런 경우 비밀키는 전체 메시지에 대해서 반복적으로 돌아가며 적용됩니다. 이 때 키의 길이는 보안상 충분할 정도로 길어야 하지만 또한 쉽게 기억할 수 있을 정도로 짧아야 한다는 미묘한 균형이 요구됩니다.

이번 문제를 위해서 준비된 암호화 키는 단 3개의 영어 소문자이고, cipher1.txt 파일에 암호화된 ASCII 코드값이 들어있습니다. 원래의 메시지는 평범한 영어 문장임을 힌트로 삼아서 암호문을 해독하고, 원문에 포함된 모든 ASCII 코드 값의 총합을 구하세요.

일반적인 문장에서의 알파벳의 빈도 수를 추측하여 문제를 해결한다… 라고 생각해서 알파벳 ‘e’가 가장 많이 나올 것이라 추측하면 오산.
일반적인 문장에서는 ‘띄어쓰기’가 가장 많이 나온다.

참고로 복호화하여 나오는 문장은 다음과 같다.

(The Gospel of John, chapter 1) 1 In the beginning the Word already existed. He was with God, and he was God. 2 He was in the beginning with God. 3 He created everything there is. Nothing exists that he didn’t make. 4 Life itself was in him, and this life gives light to everyone. 5 The light shines through
the darkness, and the darkness can never extinguish it. 6 God sent John the Baptist 7 to tell everyone about the light so that everyone might believe because of his testimony. 8 John himself was not the light; he was only a witness to the light. 9 The one who is the true light, who gives light to everyone,
was going to come into the world. 10 But although the world was made through him, the world didn’t recognize him when he came. 11 Even in his own land and among his own people, he was not accepted. 12 But to all who believed him and accepted him, he gave the right to become children of God. 13 They are rebo
rn! This is not a physical birth resulting from human passion or plan, this rebirth comes from God.14 So the Word became human and lived here on earth among us. He was full of unfailing love and faithfulness. And we have seen his glory, the glory of the only Son of the Father.




프로젝트 오일러 58번

  • 2013/09/21
  • Perl

숫자를 1부터 시작해서 하나씩 늘려가며 아래와 같이 시계반대방향으로 감아가면 한 변의 크기가 7인 정사각형 소용돌이가 만들어집니다.

37 36 35 34 33 32 31
38 17 16 15 14 13 30
39 18   5   4   3 12 29
40 19   6   1   2 11 28
41 20   7   8   9 10 27
42 21 22 23 24 25 26
43 44 45 46 47 48 49

우하단 대각선쪽으로 홀수 제곱수(9, 25, 49)들이 늘어서 있는 것이 눈에 띕니다만, 더 흥미로운 사실은 양 대각선상에 놓인 13개의 숫자 중 8개가 소수라는 것입니다. 그 비율은 대략 8/13 ≈ 62% 정도가 됩니다.

이런 식으로 계속 소용돌이를 만들어갈 때, 양 대각선상의 소수 비율이 처음으로 10% 미만이 되는 것은 언제입니까? 정사각형 한 변의 크기로 답하세요.

2차원 배열을 이용하여 문제를 풀 수도 있겠지만, 모든 값들을 계속 저장하고 있을 필요가 없기 때문에 수열처럼 각 대각선상의 수를 차례대로 얻는 식을 구해 이용했다.




프로젝트 오일러 57번

  • 2013/09/21
  • Perl

제곱근 2는 다음과 같은 연분수의 형태로 나타낼 수 있습니다.

√ 2 = 1 + 1/(2 + 1/(2 + 1/(2 + … ))) = 1.414213…

위 식을 처음부터 한 단계씩 확장해 보면 아래와 같습니다.

1 + 1/2 = 3/2 = 1.5
1 + 1/(2 + 1/2) = 7/5 = 1.4
1 + 1/(2 + 1/(2 + 1/2)) = 17/12 = 1.41666…
1 + 1/(2 + 1/(2 + 1/(2 + 1/2))) = 41/29 = 1.41379…

그 다음은 99/70, 239/169, 577/408 로 확장이 되다가, 여덟번째인 1393/985 에 이르면 처음으로 분자의 자릿수가 분모의 자릿수를 넘어섭니다.

처음부터 1천번째 단계까지 확장하는 중에, 분자의 자릿수가 분모보다 많아지는 경우는 몇 번이나 됩니까?

처음에는 마법의 함수 eval로 어떻게든 해보려고 했지만, 왠지 찜찜해서 eval을 사용하지 않는 쪽으로 코드를 고쳤다.
분수 덧샘과 나눗셈을 하는 함수를 구현해서 풀었다.




프로젝트 오일러 56번

  • 2013/09/21
  • Perl

구골(googol)은 10100을 일컫는 말로, 1 뒤에 0이 백 개나 붙는 어마어마한 수입니다.
100100은 1 뒤에 0이 2백 개가 붙으니 상상을 초월할만큼 크다 하겠습니다.
하지만 이 숫자들이 얼마나 크건간에, 각 자릿수를 모두 합하면 둘 다 겨우 1밖에 되지 않습니다.

a, b < 100 인 자연수 ab 에 대해서, 자릿수의 합이 최대인 경우 그 값은 얼마입니까?

사실 구골하면 구글밖에 생각이 안나지만…
무튼 간단한 문제다. 포럼에서는 갑자기 코드 골프가 되어버려 참전하고 싶었지만 내 펄 스킬이 너무 낮아 감히 도전하지 못했다.




프로젝트 오일러 55번

  • 2013/09/21
  • Perl

47이란 숫자를 골라서 뒤집은 다음 다시 원래 수에 더하면, 47 + 74 = 121 과 같이 대칭수(palindrome)가 됩니다.
물론 모든 숫자가 이토록 쉽게 대칭수를 만들어내지는 않습니다. 예를 들어 349의 경우,

349 + 943 = 1292
1292 + 2921 = 4213
4213 + 3124 = 7337

위에서 보는 것처럼 3번의 반복과정을 거쳐야 대칭수가 됩니다.

196과 같은 몇몇 숫자들은 이와 같은 과정을 아무리 반복해도 대칭수가 되지 않을 것이라고 추측되는데, 이런 수를 라이크렐 수 (Lychrel number) 라고 부릅니다. 아직 증명되지는 않았지만, 문제 풀이를 위해서 일단 라이크렐 수가 존재한다고 가정을 하겠습니다.

또한 1만 이하의 숫자들은, 50번 미만의 반복으로 대칭수가 되든지 라이크렐 수이든지 둘 중 하나라고 합니다.
1만을 넘어서면 10677에 이르렀을 때 비로소 53번의 반복으로 4668731596684224866951378664 라는 28자리의 대칭수가 만들어집니다.

그러면 1만 이하에는 몇 개의 라이크렐 수가 존재합니까?

굳이 Math::BigInt를 사용하지 않아도 정답은 나오지만, 경고 투성이인 터미널을 보기 안타까워 BigInt를 썼는데…
이상하게도 a==b 으로 할 때와 a-b==0 으로 할 때가 달라서 한참 멘붕.