인스타그램 크롤러 만들기

2016. 9. 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 casperjs, headless browser, instagram, node, phantomjs, react, reactjs, spookyjs, 인스타그램

  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 이 페이지를 참고해보시면 어떨까 합니다. 제 코드도 대략 이런 수준으로 작성되어 있습니다.

Wework Seoul - 강남역

2016. 8. 4. 00:57


지난 8월 1일 강남역에 문을 연 wework에 잠깐 다녀왔습니다. 방문 후의 감상은 '작지 않은 비용이지만 비용이 아깝게 느껴지지 않는, 소수가 일하기에 매우 훌륭한 공간'이라고 정리할 수 있습니다.


대부분의 분들이 처음 맞닥뜨리게 될 가격은 꽤 비싼 편입니다. 보증금이 없다는 장점은 있지만 매월 지불해야 할 비용으로 직접 사무실을 얻는다면 두 배 정도의 인원을 수용할만한 공간을 얻을 수 있지 않을까 합니다. 하지만 wework의 장점은 비용이 아니라 공간과 그 공간을 채우고 있는 요소에 있습니다. 깔끔하게 정리된 다양한 공간이나 오며가며 만날 수 있는 사람들이 wework의 장점입니다. 또한 업무 이외의 사무실 유지에 필요한 다양한 일들에서 최대한 해방되어 업무에만 집중할 수 있다는 점이 마음에 들었습니다. 물론 여기에는 멀쩡한 정신을 오랜 시간동안 유지하는데 도움을 주는 맛있으면서도 무료인 커피, 요즘같이 더운 여름 날에 더 가치가 높아지는 제빙기, 한국의 사무실에서는 쉽게 발견하기 힘든 (진짜 맛있는!!) 크래프트 맥주도 포함됩니다. 물론 해외에서도 일을 하는 분이라면 해외의 wework 사무실을 이용할 수 있다는 점도 더해야겠습니다.


wework 내부에서 사용하는 머그컵에 적혀 있던 always Do What You Love 라는 문장이 피부로 느껴지는 공간이었습니다.

nundefined ETC wework

YouTube 모바일 웹페이지에서는 동영상이 자동으로 재생된다?

2016. 7. 9. 13:08

모바일 사파리나 크롬 모바일과 같은 모바일 브라우저에서는 동영상이 자동으로 재생되지 않습니다. autoplay 속성을 사용하거나 JavaScript 함수를 이용해서 자동으로 재생시킬 수 없습니다. 이것은 데이터 네트워크에서 사용자가 의도하지 않게 고용량의 동영상을 시청할 때 발생할 수 있는 비용을 막고자 함입니다. (참고) 결과적으로 클릭과 같은 사용자의 행동이 있어야만 동영상이 재생됩니다.


그런데 우연히 유튜브 모바일 웹에서는 자동으로 동영상이 재생된다는 이야기를 들었습니다. 저 또한 모바일 웹에서 자동으로 동영상을 재생시키려다 포기한 적이 있어 그 방법이 궁금해서 잠시 살펴봤는데, 결론은 자동으로 재생되는 것 처럼 보인다는 것입니다.


자동으로 재생되는 것처럼 보이는 이유는 유튜브 모바일 웹이 Single Page Application (SPA) 이기 때문입니다. 첫페이지에서 동영상을 클릭하면 마치 페이지가 이동하는 것처럼 보이지만 실제로는 페이지의 모습이 다른 형태로 변경되는 것을 발견했습니다. 페이지 이동이 아니니 사용자가 동영상 목록에서 특정 동영상을 클릭했을 때 페이지의 모습을 바꾸고 동영상을 바로 재생시킬 수가 있는 것입니다. 시청하던 동영상의 URL을 복사하여 새로운 탭에서 붙여 넣고 유튜브 사이트로 접근하면 동영상이 자동으로 재생하지 않는 것을 알 수 있습니다. 만약 자동재생이 가능했다면 동영상의 URL로 직접 접근하더라도 자동으로 동영상이 재생되어야 할 것입니다.


개발자 도구의 element나 network, timeline 탭을 열고 페이지를 사용해보시면 금방 확인할 수 있습니다.


nundefined ETC autoplay, Chrome, mobile, mobile safari, YouTube, 동영상, 동영상 자동재생, 모바일, 모바일 사파리, 유튜브, 자동재생, 크롬

  1. Blog Icon
    de

    자동재생 되는데요??유튜브 캐시 재거하고 사파리에서도 띄어보고 먼가 다른 방법으로 구현한거같아요

  2. Blog Icon
    de

    아모바일에서 맞네여 흐음 난감하네요 모바일에서 채팅 푸쉬 알람 소리로 구현해야하는데 ㅜ

Nginx를 reverse proxy로 사용할 때 POST 요청에서 500 에러 발생

2016. 7. 5. 00:56

오늘 마주친 문제는 Nginx에서 reverse proxy를 설정해 application server를 연결했을 때, POST 요청을 보내면 500 에러가 발생하는 현상입니다. 요청에 따라 정상적으로 실행되기도 합니다. Nginx를 사용해 많은 개발 작업을 해봤던 분들이라면 한 번쯤 겪었을 듯 한 문제가 아닐까 하는 생각이 듭니다.


문제의 원인은 client_body_buffer_size 보다 큰 값이 post의 body로 전달됐기 때문입니다. 기본 값이 32bit에서는 8K, 64bit에서는 16K인데, 문제를 발생시켰던 요청의 body는 24K가 넘었습니다. 요청 중에 body의 크기가 7K 정도였던 것은 정상적으로 실행된 이유도 함께 알 수 있습니다.


client_body_buffer_size를 50K로 변경한 후 다시 시도하니 정상적으로 요청을 처리할 수 있었습니다.


이 방법 외에도 테스트해보지는 않았지만 client_body_temp_path 를 설정해도 문제를 해결할 수 있을 것으로 보입니다. (테스트는 해보지 않았습니다.) 이 값을 설정하면 요청의 body를 임시로 해당 위치에 저장하게 됩니다.


참고: http://shim0mura.hatenadiary.jp/entry/20120701/1341162337


nundefined ETC client_body_buffer_size, client_body_temp_path, nginx, nginx proxy post body size, reverse proxy

Web Performance Tooling 요약

2016. 6. 15. 00:41

Web Performance Tooling의 요약입니다. 이 세션에서는 두 명이 동시에 연단에 등록하여 마치 만담을 하듯이 연기하며 성능 개선에 대한 이야기를 풀어갑니다. 종종 이런 형식을 빌려 세션을 진행하는 경우가 있는데 연기를 잘 하지는 못하지만 평범한 형식이 아니라서 개인적으로는 좋아하는 형태입니다.


- 성능은 일을 줄이는 기법이고 일을 효과적으로 진행할 수 있는 방법이다.

- 성능 개선을 진행하는 기본적인 방법

    - 사용자: 사용자가 만족하게 만드는 것이 성능 개선의 목적

    - 측정: 프로파일러를 사용. 살펴봐야 할 시간의 종류를 이해하는 것이 중요.

    - 비용의 해석: 일어나고 있는 일을 이해하는 것

    - 병목 지점의 파악: 문제가 있는 지점을 파악

    - 개선 시도

    - 재측정: 변화가 일으킨 결과를 확인


케이스 스터디


- android.com/auto

    - 3초 동안 스피너가 돈 후에 이미지가 화면에 노출되는 문제

    - 첫 추측: 이미지가 2MB가 넘을 것이며 압축을 하지 않았을 것이다.

        - 이미지는 1MB를 다운로드

        로드가 완료된 후 2초동안 네트워크로 다운로드 하는 것이 없다.

    - 코드의 해석

        - 이미지는 화면 크기에 따라 선택적으로 받고 있음.

        - cmd + shift + p 를 사용하여 devtools의 기능을 검색할 수 있는 기능을 켠 후 search를 입력하여 검색 기능을 사용

        - setInterval의 간격을 1초로 설정하여 이미지가 로드되었는지를 확인하는 코드를 발견

        - 이미지 로드 후 화면에 표시할 때까지 1초가 걸리는 코드를 발견

        - 이 두 가지 요소가 2초 동안 아무 동작을 하지 않는 이유를 설명해준다.

    - 스크롤 기능과 관련된 성능 확인

        - 어떤 인터랙션을 했는지 표시해줌

        - interactions > input events 에 표시된 붉은 줄은 사용자가 터치한 후 실제 이벤트 핸들러가 메인 스레드에서 실행될 때까지 걸린 시간을 의미


- modern-web.org

    - 로딩에 10초가 걸림

    - bottom-up

        - 메인 스레드에서 실행되는 모든 비용을 요약해서 보여줌

        - youtube 이미지와 addthis 기능이 도메인 기준으로 시간을 가장 많이 소비하는 도메인임

    - addthis의 기능을 뺐을 때를 간단히 확인해보기 위해 request blocking을 사용 (실험실 기능)

        - network 패널에서 특정한 요청을 선택한 후 오른쪽 버튼을 클릭하여 block request domain 선택

        - 개선 후 7초 이내에 로딩이 완료됨

    - 모바일은 랩탑보다 더 느릴 수 있다. 이를 위해 CPU throttling 기능이 실험실에 포함되어 있음.

    - Youtube는 iframe으로 로드된다. 유저의 관점에서 생각해보면 첫 비디오를 처음에 보고, 뒤에 나오는 비디오는 페이지 로드할 때 보지 않을 것이다. 따라서 인스턴스를 throttling해서 생성하거나  Intersection Observer 등을 사용하여 lazy loading과 같은 방법을 쓰면 될듯. 작업을 지연시키면 된다.

    - setTimeout을 이용하여 1.5초마다 youtube 플레이어를 생성한다.

    - 개선 후에는 4초 정도에서 로딩이 완료됨


- ESLint (node application의 성능 개선)

    - 파일이 수백개 정도인데 기대보다 느리다.

    - chrome devtools를 지원하는 node에서 옵션을 걸어 프로그램을 실행시키면 devtools을 사용할 수 있다.

    - Profiles에서 cpu profiles을 선택하고 실행시키면 프로그램을 실행한다.

        - 12초가 걸린다.

        - Glob.Sync 부분이 매우 크다.

        - 해당 bar를 클릭하면 source 패널로 이동하는데 소스 코드 좌측에 함수 실행 시간이 표시된다. 함수 단위 및 라인 레벨로 표시된다.

        - 원인: 26000개의 파일을 먼저 읽은 후 eslint ignore의 값으로 필터링 한 후 lint 작업을 한다. 최종적으로 lint 대상이 되는 파일은 30개.

        - 해결 방법: eslint ignore을 먼저 읽게 하고 filter object를 glob.sync에 전달하여 30개의 파일만 읽도록 함.

        - 4초 정도에 실행이 완료.

    - 이런 작업은 pull request로 프로그램에 반영하면 좋겠다.

        - https://github.com/eslint/eslint/pull/6215



이 동영상의 주요 내용은 데모를 이용하여 설명하므로 꼭 동영상을 직접 확인하시기 바랍니다. 



nundefined ETC cpu throttling, devtools, google io, google io 2016, intersection observer, lazy loading, Performance, request blocking, 성능, 성능개선, 퍼포먼스