인스타그램 크롤러 만들기

2016.09.10 01:32

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


인스타그램에서는 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에 저장하고 있는데, 종종 과거의 사진이 최신 사진보다 위에 노출되는 경우가 있습니다. 이 경우에는 과거의 사진을 빠트리게 되는군요. 아무래도 중복된 사진을 걸러내는 방법을 바꿔야 할 것 같습니다. 오늘은 늦었으니 이건 다음 시간에.

nundefined HTML5_JS_CSS , , , , , , , ,

  1. Blog Icon

    비밀댓글입니다

  2. Blog Icon

    비밀댓글입니다

  3. Blog Icon
    dlqm1681

    안녕하세요, 최근 Casperjs로 크롤링을 처음으로 공부하고 있는데, 인스타그램에서 로그인조차 성공하지 못해서 글 올립니다.ㅠ F12를 눌러서 나오는 클래스명들로 클릭하려고 해도 반응조차 없어서 혹시 어떤 방법을 사용하셨는지 문의드려도 될까요?

  4. 제가 사용한 방법은 다음과 같습니다.

    1. document.querySelectorAll('input').length > 1 을 사용하여 실제 input element가 렌더링 됐는지 확인
    2. sendKeys를 사용하여 id, password 입력
    3. click('form button')으로 버튼 클릭

    그리고 인스타그램의 로그인 과정 중에 http status code가 302가 내려오는 것으로 기억하는데요, 이 경우 casperjs에서 제대로 처리를 못합니다. 그래서 정상적으로 로그인됐는지 여부는 document.title의 값을 확인했습니다.

    도움이 되면 좋겠네요~

  5. Blog Icon
    dlqm1681

    정말 감사합니닷^^ 덕분에 로그인하고 캡쳐 성공했습니다.
    좋은 정보 포스팅해주셔서 감사하고 댓글로 친절하게 알려주셔서 감사합니다.:D

  6. Blog Icon
    zel

    안녕하세요. 인스타그램 클롤러를 만드려고 하는데 쉽지않어서 검색하다가 댓 남겨봅니다..

    우선 api를 사용하고 계신지 궁금합니다. 계속 심사에서 실패하고있어서...

    두번째로 PhantomJS, CasperJS, SpookyJS에 대해서 잘모르는데요 자바로도 가능할지 궁금합니다.
    javafx의 webview로 instagram 로그인 하고 스크롤링 할 수 있도록 javafx 함수공부중입니다..

    세번째로 댓글을보니 로그인 후 크롤링이 가능한것 같은데 이게 api를 사용하는건지도 잘모르겠어서요..

    그리고 혹시 샘플소스를 얻을 수 있는지도 궁금합니다..

  7. 1. 인스타그램의 API는 피드 api를 제공해주지 않고 있어 직접 인스타그램 홈페이지를 headless browser를 사용하여 크롤링 후 수집하고 있습니다. (웹브라우저로 보는 것을 자동화했다고 생각하시면 됩니다.) 그리고 개인적인 용도로만 사용하고 있어 별도의 심사를 받지는 않았습니다.

    2. Java는 잘 모르겠습니다만 headless browser로 검색해보시면 도움을 얻으실 수 있지 않을까 합니다.

    3. 로그인 및 크롤링 모두 api를 사용하지 않고 headless browser를 이용하여 작업했으므로 api와는 무관합니다.

    4. 샘플코드는 http://stackoverflow.com/questions/32330264/how-to-login-in-instagram-using-casperjs 를 참고하시면 도움이 될 것 같습니다. 제가 이 페이지의 코드를 많이 참고했습니다.

  8. Blog Icon
    zel

    아 친절한 답변감사합니다.

    알려주신 링크 참조해서 공부해보겠습니다 감사합니다.

  9. Blog Icon
    poeki

    안녕하세요.

    혹시 넥사크로나 마이플랫폼, 엑스플랫폼 같은 플랫폼을 이용한 화면에서도 이용가능할까요??

    이런 플랫폼을 쓰는 화면에서는 input태그 대신 edit, button같은 패키지 고유의 태그를 사용해서.. document.querySelectorAll('input') 이런 형식으로 쓰면 안먹히는것 같더라구요. 단순히 input을 edit로 바꿔도 소용이 없고..

    혹시 도움받을 수 있을까 해서 여쭤봅니다.

    감사합니다.

  10. 안녕하세요.

    말씀해주신 플랫폼을 사용한 적이 없어서 저도 잘 모르겠습니다. 잠깐 엑스플랫폼을 검색해보니 ActiveX를 기반으로 하는 솔루션으로 보이는데요, ActiveX 기반으로 동작한다면 위 방법은 사용할 수 없습니다.

    위 방법은 순수한 HTML으로 구성된 페이지에서만 동작하도록 되어 있습니다.

  11. Blog Icon

    비밀댓글입니다

  12. Blog Icon

    비밀댓글입니다

  13. Blog Icon

    비밀댓글입니다

  14. Blog Icon

    비밀댓글입니다

  15. 어떤 데이터가 필요하신지는 모르겠지만, 인스타그램 api를 사용하여 데이터를 확보하는 방법을 먼저 고려해보시면 어떨까 합니다. https://www.instagram.com/developer/

    "python 인스타그램 크롤링", "python 크롤링" 등의 키워드로 검색해보시면 관련 정보를 많이 찾으실 수 있을 것 같습니다. python용 인스타그램 프로그램도 있는 것 같으니 이를 참고해보셔도 좋을 것 같습니다.

    http://www.yes24.com/24/Goods/33469160?Acode=101 이런 책도 있으니 아직 안보셨으면 이 책도 참고해보시면 어떨까요.

  16. Blog Icon

    비밀댓글입니다

  17. 안녕하세요.

    DB를 하신적이 없다고 하시니 어디서부터 말씀드려야 할지 모르겠습니다. Stackoverflow를 많이 검색해보시길 추천합니다.

    죄송하지만 제 소스코드는 제공해드리지 않습니다. https://stackoverflow.com/questions/31128011/casperjs-and-inserting-data-into-a-database-when-spidering 이 페이지를 참고해보시면 어떨까 합니다. 제 코드도 대략 이런 수준으로 작성되어 있습니다.