Projects

검색 빈도 분석 프로젝트

Jero++ 2021. 4. 17. 01:46

* 모든 소스코드는 Github에 업로드하였습니다 *

github.com/Jun0zo/trend_grapher

 

Jun0zo/trend_grapher

show trend graph :sunglasses:. Contribute to Jun0zo/trend_grapher development by creating an account on GitHub.

github.com

 

1. 내 "키워드"는 얼마나 많이 검색되었을까?  

 

그림 1. 검색어 '마스크'

 검색량의 추이를 알아보는 것으로 현재 사람들의 관심사를 알 수 있는 등 여러가지 인사이트를 얻을 수 있습니다. 그림 1은 "마스크"를 검색했을 때의 그래프 입니다. 코로나 초기에 많은 사람들이 마스크를 구매했기 때문에 검색량이 높지만 점차 마스크 보급이 원활해 짐에 따라 구매에 대한 관심도가 하락하여 검색량에 영향을 끼쳤다 라고 판단이 됩니다.  Navaer API에서는 어떠한 키워드에 대해 금월 검색횟수와 지난 12개월 내의 검색량 백분위를 제공하기 때문에 이 두 정보를 활용하여 그림 1과 같이 사용자가 키워드를 입력하면 해당 키워드의 구체적인 검색량과 추이를 그려 반환하는 프로그램을 만들어 보겠습니다.

 

 

2. 구동방식

 

그림 2. 구동 방식

 

Node.js로 구현되었고 네이버 API를 사용했습니다.

nodejs를 실행시키고 http://localhost:3333/keytool?kw=마스크 를 사용하면

위와 같은 결과를 얻게 됩니다. (그림 2참고)

 

 

3. 구현

3.1 Request Parsing

 

구현은 아래와 같이 했습니다.

 

app.get('/keytool', async(req, res) => {
    keyword = req.query.kw;
    var date = Addon.getDate(); // [before, today, today(날짜가 1)];
    var dir = Addon.getDir(keyword, date, target="image_file");
    var exists = fs.existsSync(dir);
    if(!exists){  // if file is not exist
        var trend_pc = await Addon.getTrendp(keyword, date, device='pc')
        var trend_mobile = await Addon.getTrendp(keyword, date, device='mobile');
        var trend_all = await Addon.getTrendp(keyword, date, device='total');
        var trends = [trend_pc, trend_mobile, trend_all]
        Addon.setChart(trends);
        Addon.veg2png(keyword, date).then(function(){
            fs.readFile(dir, function(err, data){
                if(err) {
                    res.status(404).end();
                    console.log(err);
                }
                else{
                    res.set('Content-Type', 'image/png');
                    res.send(data);
                }
            });
        });
    }else{  //file is exist 
        console.log("graph already exist!!");
        fs.readFile(dir, function(err, data){
            if(err) {
                res.status(404).end();
                console.log(err);
            }
            else{
                res.set('Content-Type', 'image/png');
                console.log(data);
                res.send(data);
            }
        });
    }
});

server.js 파일

 

 Nodejs 로 구현하였고 /keyword 로 GET request를 받으면 URL query 중 kw 의 value값을 사용자가 입력한 키워드로 인식하고 이를 분석한 후 해당 키워드에 대한 검색빈도를 그래프로 나타내 이미지로 전송합니다. 만약 "코로나"라는 키워드의 검색빈도를 그래프로 보고 싶다면 다음과 같은 URL에 접속합니다.

 

localhost:3333/keytool?kw=코로나

 

 키워드를 분석하기 위해서는 네이버 API를 사용해야하기 때문에 같은 키워드를 여러번 분석하면 효율이 떨어질 뿐더러 API 사용량을 빠르게 소모할 수 있습니다. 따라서 키워드 분석이 완료가 되면 해당 키워드의 이름으로 폴더를 생성합니다. 이렇게 되면 만약 "코로나"키워드가 검색이 된 상태에서 한번 더 검색이 되면 이전에 만들어 진 이미지 파일을 그대로 보여주기만 하면 됩니다. 따라서 파일이 없을 때에만 키워드를 분석하여 사용자에게 보여줍니다. 키워드를 분석할 때에는 Addon.js 파일에 존재하는 getTrendp함수를 호출합니다. getTrendp함수의 동작은 3.2에 기술하였습니다. 

 

3.2 Keyword 분석

 

async getTrendp(keyword, date, device){
        var click_ratio_json;
        if(device==='total')
            click_ratio_json = await getClickRatios(keyword, date);
        else if(device==='pc')
            click_ratio_json = await getClickRatios(keyword, date, 'pc');
        else if(device==='mobile')
            click_ratio_json = await getClickRatios(keyword, date, 'mo');

        var info = await getClickCnt(keyword, date);
        var data = info.data.keywordList[0];
        click_info = [data.monthlyPcQcCnt, data.monthlyMobileQcCnt];
        click_info[0] = click_info[0]=='< 10'?0:click_info[0];
        click_info[1] = click_info[1]=='< 10'?0:click_info[1];

        if(device==='total')   
            click_total = click_info[0] + click_info[1];
        else if(device=='pc')
            click_total = click_info[0];
        else if(device=='mobile')
            click_total = click_info[1];

        var click_per_ratio = click_total / click_ratio_json.results[0].data[11]['ratio'];
        var click_trend = [];
        for(var i=0; i<12; i++)
            click_trend.push(Math.ceil(click_ratio_json.results[0].data[i]['ratio'] * click_per_ratio));
        
        return click_trend;
    }

getTrendp 함수

 

 getTrendp함수는 keyword를 인자로 받으면 현재 날짜로 부터 1개월 간격으로 12개의 이전 날짜의 검색빈도수를 List형태로 반환합니다. 만약 현재 5월이면 다음과 같은 List가 반환됩니다.

 

[ 5월 검색량, 4월 검색량, 3월 검색량, 2월 검색량, 1월 검색량, 12월 검색량, 11월 검색량, 10월 검색량, 9월 검색량, 8월 검색량, 7월 검색량, 6월 검색량]

 

device라는 인자값으로 pc, mobile 혹은 pc와 mobile를 합친 검색량을 결정할 수 있습니다. g

 

getTrendp는 궁극적으로 getClickRatios와 getClickCnt를 통해 검색 추이를 계산합니다. getClickRatios는 금월의 검색량을 100으로 가정하고 지난 12개월의 검색량을 백분위로 나타냅니다. 이를 통해 추이는 볼 수 있지만 구체적인 검색 횟수는 알 수 없습니다. 따라서 getClickCnt를 통해 금월의 구체적인 검색 횟수를 계산하고 이를 통해 지난 12개월의 모든 검색량을 백분위에서 실제 검색횟수로 변환합니다. 해당 로직은 아래와 같다. click_trend라는 List에 지난 12개월의 검색량을 계산하여 push 합니다.

 

var click_per_ratio = click_total / click_ratio_json.results[0].data[11]['ratio'];
var click_trend = [];
for(var i=0; i<12; i++)
	click_trend.push(Math.ceil(click_ratio_json.results[0].data[i]['ratio'] * click_per_ratio));
        

getTrendp 함수 일부

 

getClickRatios와 getClickCnt 는 아래와 같이 Naver API에 request보내기 때문에 부가 설명 없이 넘어가겠습니다.

 

async getClickRatios(keyword, date, device='', gender=''){
        console.log('device : ', device)
        var request_body = {
            "startDate": date[0],
            "endDate": date[1],
            "timeUnit": "month",
            "keywordGroups": [
                {
                "groupName": "KW",
                "keywords":[ keyword ]
                }
            ],
            "device":device,
            "gender":gender
        }

        client_id = 'd_vuzn1yAG_j0aeFSCW0';
        client_secret = '3LNguW4s7I';

        var option = {
            method: 'POST',
            uri: 'https://openapi.naver.com/v1/datalab/search',
            body: JSON.stringify(request_body),
            headers: {
                'X-Naver-Client-Id': client_id,
                'X-Naver-Client-Secret': client_secret,
                'Content-Type': 'application/json'
            }
        }
        var msg = await request(option);
        var json = JSON.parse(msg);
        return json;
    },


    async getClickCnt(keyword, date){
        client_id = 'd_vuzn1yAG_j0aeFSCW0';
        client_secret = '3LNguW4s7I';

        const path      = '/keywordstool'
        const timestamp = new Date().getTime()
        const msg       = `${timestamp}.GET.${path}`
        const signature = crypto.createHmac('SHA256', account.secretKey).update(msg).digest('base64')
        const option = {
            method: 'get',
            url   : `https://api.naver.com${path}`,
            params: {
                'format'      : 'json',
                'hintKeywords': keyword
            },
            headers: {
                'X-Timestamp': timestamp,
                'X-Customer' : account.customerId,
                'X-API-KEY'  : account.accessLicense,
                'X-Signature': signature
            }
        }

        return await axios(option);
    },

getClickRatios함수와 getClickCnt함수 

 

3.3 Image로 변환하여 반환

 

마지막으로 setChart함수와 veg2png함수를 통해 List에 있는 검색량 정보를 그래프로 변환하여 저장한 후 사용자에게 반환합니다. 

 

 

setChart(trends){
  spec.data[0].values = [];
  for(var i=0; i<12; i++){ // if dic (pc and mobile and all)
  	for(var j=0; j<trends.length; j++){
  		var graph_data = {"x": i, "y": trends[j][i], "c":j};
  		spec.data[0].values.push(graph_data);
  	}
  }
  fs.writeFileSync('./default_chart.spec.json',JSON.stringify(spec));
}  
    

async veg2png(keyword, date){
  return new Promise(function(resolve, reject){
    var view = new vega.View(vega.parse(spec), {renderer:'none'}).initialize();
    view.toCanvas()
    .then(function (canvas) {
      console.log('Writing PNG to file...')
      const directory_file_dir =  getDir(keyword, date, target='directory_file');
      const image_file_dir = getDir(keyword, date, target='image_file');
      mkdirp(directory_file_dir, err => {
      fs.writeFileSync(image_file_dir, canvas.toBuffer());
          resolve();
      })
    })
    .catch(function (err) {
      console.log("Error writing PNG to file:");
      console.error(err);
    });
  })
}

setChart함수와 veg2png함수

 

setChart함수는 List에 존재하는 검색량 정보를 x, y값으로 변환하면서 그래프를 만들기 위한 정보를 json 형태로 저장합니다. veg2png는 vega.js를 이용하여 저장된 json을 실제 그래프로 변환하고 이미지 파일로 만들어 저장합니다. 이 때 이미지 파일의 이름은 현재 날짜이고 이 파일은 키워드이름으로 만들어진 폴더에 저장됩니다.

 

만약 2021년 5월에 누군가 "코로나"라는 키워드로 검색을 하면 다음과 같이 폴더와 파일이 생성됩니다.