본문 바로가기

HTML5_JS_CSS

인스타그램 크롤러 만들기

얼마전에 전자액자를 하나 샀습니다. 더운 여름에 기분전환을 해볼까 싶어 구매했는데 날이 다 시원해지고서야 제 손에 들어왔네요. 처음에는 직접 찍은 사진도 올려보고, 괜찮은 그림도 올려봤지만 오래지 않아 귀차니즘이 찾아오더군요. 결국 매일 업데이트되는 인스타그램 피드에 있는 사진을 보여주고 싶어졌습니다. 사진의 품질도 나쁘지 않을 뿐더러 누군가가 업데이트까지 해주니 바로 이거다 싶었습니다.


인스타그램에서는 API를 제공하고 있어 API를 사용할까 찾아보니, 저에게 필요한 피드 정보를 얻을 수 있는 API는 작년에 중단됐더군요. 이런 날벼락이. 어렴풋이 작년에 인스타그램이 피드 API를 중단해서 인스타그램 클론이 나오기 힘들게 됐다는 기사가 기억났습니다. 이런건 찾아보기 전에 기억날 것이지. 그래서 결국 웹페이지를 크롤링하기로 했습니다.


# 크롤러 만들기


웹페이지를 크롤링하기 위해 PhantomJS, CasperJS, SpookyJS를 사용했습니다. 간단히 설명하면 PhantomJS는 webkit 기반의 headless browser, CasperJS는 PhantomJS를 사용하여 페이지를 이동할 때 편리하게 사용할 수 있는 기능을 제공합니다. 문제는 이 두 프로그램이 node와는 관계 없이 동작하는 프로그램이라는 점입니다. SpookyJS는 node와 관계 없이 동작하는 PhantomJS/CasperJS를 node에서 컨트롤 할 수 있게 해주는 프로그램입니다. 만약 node의 기능을 사용하지 않는다면 CasperJS만으로 충분하겠지만, 저는 크롤링한 데이터를 DB에 저장할 생각이었으므로 node가 꼭 필요했습니다.


처음에는 SpookyJS를 바로 사용해서 바로 프로그램을 작성했다가 SpookyJS가 동작하는 방식을 제대로 알지 못하는 바람에 이전에 사용해본 경험이 있던 CasperJS를 기반으로 프로그램을 만든 후, 이 프로그램을 SpookyJS를 이용해서 동작하도록 수정했습니다. 간단히 정리하면 casper.xxx()로 실행되는 함수를 spooky.xxx()정도로 변환하는 정도면 큰 수정 없이 코드를 재사용할 수 있습니다. 같은 파일 내에 있지만 일반적인 JavaScript 코드와는 context가 다른 어려움이 있는데, 이는 문서를 살펴보시는 편이 가장 나을 것입니다. 사실, 이 부분을 잘 이해하는 것이 SpookyJS를 잘 사용하는 지름길입니다. 저는 프로그램을 최대한 간단히 작성하고자 했기에 크게 문제가 되는 부분은 없었습니다.


프로그램을 작성하면서 처음에 어려움을 겪었던 부분은 인스타그램이 ReactJS로 되어 있어 1. 특정 엘리먼트가 생성될 때까지 실행을 지연시켜야 했던 점과 2. 사람에게 불친절한 selector의 사용으로 특정한 엘리먼트를 찾을 때 한 번 더 생각을 했던 점 그리고 3. 폼에 값을 입력하는 부분이었습니다. 1번의 경우 CasperJS에서 제공하는 waitFor 함수를 사용했고, 2번의 경우에는 id나 class selector를 사용하지 못하고 element selector와 *-child selector를 사용하여 해결했습니다. 3번의 경우에는 ReactJS의 특성상 input element에 바로 값을 설정해도 ReactJS내부의 변수에 값이 정상적으로 설정되지 않는 문제... 라고 생각했습니다만 제 오해였습니다. PhantomJS를 사용하면 문제가 발생하지만 CasperJS에서는 그런 것 없습니다. sendKeys와 click 함수가 모든 문제를 해결해줍니다.


인스타그램은 화면의 최하단까지 스크롤할 경우 자동으로 다음 페이지의 내용을 불러옵니다. CasperJS에서 제공하는 함수를 쓰면 화면 최하단까지 이동하는 것은 어렵지 않습니다. 문제는 그 후에 값을 다시 불러오는 부분이었습니다. 이 부분은 조건에 따라 다음 페이지의 내용을 불러올 때까지 기다렸다가 더 불러올 글이 있는지 확인하고 다시 다음 페이지를 불러야 하는 기능입니다. 그런데 이 부분을 만들면서 보니 CasperJS의 동작을 동적으로 추가하는 것이 다소 어렵게 느껴져 구현하지 않았습니다. 자세히 살펴보지 않았지만 추측하건데 처음 SpookyJS가 동작하면서 CasperJS와 연동되어 돌아가는 작업의 개수를 실행 전에 결정하는 것이 아닌가 합니다. 디버그 메시지를 보니 이런 의심이 들더군요. 위에서도 적은 것처럼 '최대한 간단히'가 목표 중에 하나였으므로 과감히 패스했습니다. 그리고 이 문제는 스크립트를 계획보다 짧은 간격으로 실행시키는 방식으로 해결했습니다.


나머지는 node에서 DB에 값을 넣는 부분으로 크게 어려운 문제는 없었습니다. 여기서도 property 이름을 잘못 적는 바람에 삽질은 면하지 못했습니다만.


# 자동으로 실행시키기


프로그램을 만든 후 주기적으로 프로그램을 실행시키는 방법으로 처음에는 crontab을 사용하는 것을 고려했습니다. 그런데 node의 exec 함수를 테스트해보니 간단하게 프로그램을 동작시킬 수 있겠더군요. 그래서 exec 함수와 setInterval 함수를 사용하여 일정 시간마다 크롤링을 하도록 만들었습니다.


exec를 사용하지 않고 함수를 호출하여 SpookyJS 코드를 실행시키는 방법을 잠시 생각하기도 했었는데 a. 이미 작성한 코드를 변경할 필요가 없고, b. a로 인해 테스트를 하지 않아도 되서 exec를 사용하는 방법으로 적용해버렸습니다. 일단 주기적으로 프로그램을 실행시키는데는 문제가 없어보입니다.


# 만들고 나니


프로그램은 주기적으로 잘 실행됩니다. 그런데 이제보니 인스타그램의 피드에 노출되는 사진이 시간 순서가 아니군요. 지금은 중복 데이터를 걸러내기 위해 마지막으로 저장한 사진보다 최신의 사진만 DB에 저장하고 있는데, 종종 과거의 사진이 최신 사진보다 위에 노출되는 경우가 있습니다. 이 경우에는 과거의 사진을 빠트리게 되는군요. 아무래도 중복된 사진을 걸러내는 방법을 바꿔야 할 것 같습니다. 오늘은 늦었으니 이건 다음 시간에.

반응형