JavaScirpt/Node.js

[Node.js] Starting Node.js with Crawling (insta crawling, last)

Tree_Park 2020. 12. 14. 22:02
728x90

crawling

서론

평소에 나는 인스타그램을 잘 하지 않지만, 인스타그램에 어떤 태그에 어떠한 태그들을 같이 

쓰는가에 대한 호기심이 있어왔다. 그래서 특정 태그를 인스타그램에서 검색하고,

검색한 태그로 게시물을 탐색하면서 같이 쓰인 태그들을 수집하고, 어떤 태그가 많이 쓰이고

같이 쓰였는지를 이번 기회에 알아보았다.

 

데이터 수집

태그 수집을 위해 라이브러리는 PhantomJS, SlimerJS, CasperJS를 사용하였다.

 

환경 설정
# nodejs 및 npm이 설치되어 있어야 한다.

npm install phantomjs

npm install slimerjs #export SLIMERJSLAUNCHER=[firefox bin path under 5.9v]

npm install casperjs

 

코드 및 크롤링 수행

크롤링 코드는 크게 세 부분으로 나누어진다. 

- [인스타그램 로그인]

- [태그 검색]

- [게시물 탐색[

 

위 세 부분에서 [인스타그램 로그인], [태그 검색]은 한 번씩만 수행되고 [게시물 탐색]은

내가 인자로 지정한 횟수만큼 계속해서 수행하게 된다.

 

코드

var casper = require('casper').create({
  //  verbose: true, 
  //  logLevel: 'debug',
    userAgent: 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36',
    pageSettings: {         // The WebPage instance used by Casper will);
      //loadPlugins: true
      "loadPlugins": true,         
      "webSecurityEnabled": false,
      "ignoreSslErrors": true,
      "resourceTimeout":10000,
        }
      },  
  );
var utils = require('utils');
var fs = require('fs');
// url and login info variables
var url = "http://instagram.com";
var id = "******"
var pwd = "*******";
var target = "[searching_tag]".toString('utf-8'); // 실제로 검색할 태그

casper.start();
casper.viewport(1024, 768);

// 로그인 INPUT창 탐색
casper.thenOpen(url);
casper.waitForSelector('input[name=username]', 
    function pass(){
        this.echo('found login view');
    },
    function fail(){
        this.echo("didn't find login view");
    },
    10000
); 

// sendKey for lgoin to insta
// 로그인 ID, PASSWORD 브라우저에 전달
casper.then(function(){
    casper.sendKeys('input[name=username]', id);
    casper.sendKeys('input[name=password]', pwd);
});

// click login button
// 로그인 수행
casper.then(function(){
    casper.click('button[type=submit]');
    this.then(function(){currentUrl=this.getCurrentUrl();})
});

// 로그인 과정
casper.then(function() {
    casper.waitForSelector('img[data-testid=user-avatar]', 
        function pass(){
            this.echo('found avatar');
            this.click('button[type=button]:nth-child(1)');
        },
        function fail(){
            this.echo("didn't find avatar");
        },
        10000
    ); 
});

// 검색버튼 클릭 및 검색 수행
casper.then(function() {
    casper.waitForSelector('input[autocapitalize=none]', 
        function pass(){
            this.echo('found search');
            this.sendKeys('input[autocapitalize=none]', target);
            // this.open(url + 'explore/tags/coffee');
      },
        function fail(){
            this.echo("didn't find search.");
        },
        10000
    );
});

// 검색된 요소중 가장 상위 요소 클릭
casper.then(function() {
    casper.waitForSelector(`a[href$='/tags/${target}/']`, 
        function pass(){
            this.echo(`found ${target}`);
            this.click(`a[href$='/${target}/']`);
            // this.open(url + 'explore/tags/coffee');
      },
        function fail(){
            this.echo("didn't find coffee.");
        },
        10000
    );
});

// 클릭한 요소 로드될 때까지 대기
casper.wait(6000, function(){this.echo("I've waited for a second")});
casper.then(function() {
    casper.waitForSelector("main[role=main]", 
        function pass(){
            this.echo('found main');
            this.scrollToBottom();
            // this.open(url + 'explore/tags/coffee');
      },
        function fail(){
            this.echo("didn't find main.");
        },
        10000
    );
})


// 게시물 탐색 수행
let tag = {};
casper.then(function(){
    casper.waitForSelector("a[tabindex='0']:first-child", 
        function pass(){
            this.echo('found tabs');
            this.click("a[tabindex='0']:first-child");
            for(let i=0; i<10000; i++) {
              this.echo(`read ${i+1}`)
              next(i+1);
            }
      },
        function fail(){
            this.echo("didn't find tabs.");
        },
        10000
    );
})

// 게시물 탐색 코드
function next(idx) {
  casper.waitForSelector('a._65Bje.coreSpriteRightPaginationArrow',
    function pass() {
      let jbRandom = Math.random();
      let randomTime = 1500 + Math.floor(jbRandom * 3000);
      let str = this.fetchText("a.xil3i");
      let tags = str.split('#');
      for(let i in tags) {
        tag[`${tags[i]}`] = tag[`${tags[i]}`] === undefined ? 0 : tag[`${tags[i]}`]+1;  
      }
      this.capture(`${target}${idx}.png`, {
        top:0, left:0, width:1024, height:768 
     });
      this.echo('read');
      fs.write(`${target}.json`, JSON.stringify(tag), 'w');
      casper.wait(randomTime, function(){this.echo(`I've waited for a ${idx} during ${randomTime} ms`)});      
      this.click('a._65Bje.coreSpriteRightPaginationArrow');
    },
    function fail() {

    }
  )
}

casper.then(function() {
  this.echo('done----------------------------------------------------------');
});

casper.run();

 

- 위의 로그인 코드를 보면, waitForSelector()라는 메소드명을 볼 수 있을 것이다.

  기본적으로 크롤링을 수행할 때 'CSS Selector'를 통해 페이지의 태그를 찾고, 그 태그에 클릭 이벤트

  나, 키 전달 이벤트를 수행하는 식으로 코드의 전체적인 흐름이 진행된다.

- 또한 각각의 메소드 수행은 동기적으로 이루어져야 하기에 capser 라이브러리에서 동기적 수행을

  제어하기 위한 then() 메소드를 사용한다.

- 기본적으로 이번 태그 수집에서는 10000번의 게시물 탐색을 통해 태그가 수집되고, 분석의

  리소스로 쓰일 것이다.

- 위 소스에서 중점있게 볼 부분을 아래에 다시 표시해놓았다.

tag[`${tags[i]}`] = tag[`${tags[i]}`] === undefined ? 0 : tag[`${tags[i]}`]+1;

 

- 기본적인 map 객체에, 태그가 존재하지 않으면 value를 0으로,

- 존재하면 기존의 value에 하나씩 더해주는 부분이다.

- 앞으로의 데이터 분석에 있어서 편리를 주기 위해 약간의 전처리 과정을 주었다.

- 또한, 위의 코드에서 난수를 생성하여 랜덤으로 탐색하는 부분은 '인스타그램 봇(혹은 탐지기)'

  에서 일정주기로 크롤링봇 탐색을 감지하는 것을 방지하기 위함이다.

- 탐색하는 부분에서 wait()의 인자를 높게 줄 수록 감지가 될 학률을 낮아지지만 결국은 

  만들어둔 아이디가 3개가 차단당하였다. 뜻깊은 경험이였고 앞으로 크롤링은 안할듯 싶다.

 

크롤링이 수행되는 과정