Raw Source
myso / 네이버 블로그 통계 지표 다운로드 플러스

// ==UserScript==
// @namespace    https://tampermonkey.myso.kr/
// @name         네이버 블로그 통계 지표 다운로드 플러스
// @description  네이버 블로그 통계의 지표 다운로드 기능을 개선하여 줍니다.
// @copyright    2021, myso (https://tampermonkey.myso.kr)
// @license      Apache-2.0
// @version      1.0.13
// @updateURL    https://github.com/myso-kr/kr.myso.tampermonkey/raw/master/service/com.naver.blog-analytics.msexcel.exporter.user.js
// @downloadURL  https://github.com/myso-kr/kr.myso.tampermonkey/raw/master/service/com.naver.blog-analytics.msexcel.exporter.user.js
// @author       Won Choi
// @connect      naver.com
// @match        *://admin.blog.naver.com/*/stat/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @require      https://cdn.jsdelivr.net/npm/kr.myso.tampermonkey@1.0.24/assets/vendor/gm-app.js
// @require      https://cdn.jsdelivr.net/npm/kr.myso.tampermonkey@1.0.24/assets/vendor/gm-add-style.js
// @require      https://cdn.jsdelivr.net/npm/kr.myso.tampermonkey@1.0.24/assets/vendor/gm-add-script.js
// @require      https://cdn.jsdelivr.net/npm/kr.myso.tampermonkey@1.0.24/assets/vendor/gm-xmlhttp-request-async.js
// @require      https://cdn.jsdelivr.net/npm/kr.myso.tampermonkey@1.0.24/assets/donation.js
// @require      https://cdn.jsdelivr.net/npm/kr.myso.tampermonkey@1.0.24/assets/lib/naver-blog.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.33/moment-timezone.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.7.2/bluebird.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/toastify-js/1.11.0/toastify.min.js
// ==/UserScript==

// ==OpenUserJS==
// @author myso
// ==/OpenUserJS==
GM_App(async function main() {
    GM_donation('.l__container');
    GM_addStyle("@import url('https://cdnjs.cloudflare.com/ajax/libs/toastify-js/1.11.0/toastify.min.css')");
    moment.tz.setDefault("Asia/Seoul");
    async function download() {
        const edate = moment().day(-6).toDate();
        const range = _.range(15).map(o=>moment(edate).subtract(o, 'weeks').toISOString(true));
        const range_date = _.range(7 * 15).map(o=>moment(edate).subtract(o - 6, 'days').toISOString(true));
        const sdate = _.minBy(range, o=>moment(o).toDate());
        const xlsx = XLSX.utils.book_new();
        const xlsx_name = `블로그통계분석_${user.nickname}_${user.userId}_${moment(sdate).format('YYYY-MM-DD')}~${moment(edate).format('YYYY-MM-DD')}.xlsx`
        // 방문분석
        {
            Toastify({ text: `방문 통계 데이터 가져오는 중...`, }).showToast();
            const data_view = await NB_blogStat['방문분석'](user.userId, edate, 'WEEK');
            const xlsx_sheet_name = `방문`;
            const xlsx_sheet_data = [];
            {
                xlsx_sheet_data.push(['■ 방문통계 요약']);
                const cv_sum = _.sumBy(data_view['조회수']['cv'], 'total').toFixed(0);
                const cv_avg = _.meanBy(data_view['조회수']['cv'], 'total').toFixed(0);
                const uv_sum = _.sumBy(data_view['순방문자수']['uv'], 'total').toFixed(0);
                const uv_avg = _.meanBy(data_view['순방문자수']['uv'], 'total').toFixed(0);
                const rv_sum = _.sumBy(data_view['재방문율']['revisit'], 'total').toFixed(0);
                const rv_avg = _.meanBy(data_view['재방문율']['revisit'], 'total').toFixed(0);
                const rvp_avg = _.meanBy(data_view['재방문율']['revisit'], 'total_p').toFixed(2);
                const visit_sum = _.sumBy(data_view['방문횟수']['visit'], 'visit').toFixed(0);
                const visit_avg = _.meanBy(data_view['방문횟수']['visit'], 'visit').toFixed(0);
                const av_avg = _.meanBy(data_view['평균방문횟수']['averageVisit'], 'total').toFixed(2);
                const ad_avg = _.meanBy(data_view['평균사용시간']['averageDuration'], 'total').toFixed(2);
                xlsx_sheet_data.push(['', '조회수', '순방문자수', '재방문수', '재방문율 (%)', '방문횟수', '평균방문횟수', '평균사용시간 (초)']);
                xlsx_sheet_data.push(['평균', cv_avg, uv_avg, rv_avg, rvp_avg, visit_avg, av_avg, ad_avg]);
                xlsx_sheet_data.push(['합계', cv_sum, uv_sum, rv_sum, '-', visit_sum, '-', '-']);
            }
            xlsx_sheet_data.push([]);
            // 표
            {
                xlsx_sheet_data.push(['■ 방문통계 주차별 통계']);
                xlsx_sheet_data.push(['날짜', '조회수', '순방문자수', '재방문수', '재방문율 (%)', '방문횟수', '평균방문횟수', '평균사용시간 (초)']);
                xlsx_sheet_data.push(...Array.from(range).map((item) => {
                    const data = [];
                    const date = moment(item).format('YYYY-MM-DD');
                    data.push(date);
                    data.push(_.get(_.find(data_view['조회수']['cv'], { date }), 'total', 0).toFixed(0));
                    data.push(_.get(_.find(data_view['순방문자수']['uv'], { date }), 'total', 0).toFixed(0));
                    data.push(_.get(_.find(data_view['재방문율']['revisit'], { date }), 'total', 0).toFixed(0));
                    data.push(_.get(_.find(data_view['재방문율']['revisit'], { date }), 'total_p', 0).toFixed(2));
                    data.push(_.get(_.find(data_view['방문횟수']['visit'], { date }), 'visit', 0).toFixed(0));
                    data.push(_.get(_.find(data_view['평균방문횟수']['averageVisit'], { date }), 'total', 0).toFixed(2));
                    data.push(_.get(_.find(data_view['평균사용시간']['averageDuration'], { date }), 'total', 0).toFixed(2));
                    return data;
                }));
            }
            const xlsx_sheet =  XLSX.utils.aoa_to_sheet(xlsx_sheet_data);
            XLSX.utils.book_append_sheet(xlsx, xlsx_sheet, xlsx_sheet_name);
        }
        // 사용자분석
        {
            Toastify({ text: `이웃 통계 데이터 가져오는 중...`, }).showToast();
            const data_user = await NB_blogStat['사용자분석'](user.userId, edate, 'WEEK');
            const xlsx_sheet_name = `이웃`;
            const xlsx_sheet_data = [];
            {
                xlsx_sheet_data.push(['■ 이웃 증감 현황']);
                xlsx_sheet_data.push(['날짜', '서로이웃', '이웃추가', '이웃삭제']);
                xlsx_sheet_data.push(...Array.from(range).map((item) => {
                    const data = [];
                    const date = moment(item).format('YYYY-MM-DD');
                    data.push(date);
                    data.push(_.get(_.find(data_user['이웃증감수']['relationDelta'], { date }), 'friend', '-'));
                    data.push(_.get(_.find(data_user['이웃증감수']['relationDelta'], { date }), 'add', '-'));
                    data.push(_.get(_.find(data_user['이웃증감수']['relationDelta'], { date }), 'delete', '-'));
                    return data;
                }));
            }
            xlsx_sheet_data.push([]);
            {
                xlsx_sheet_data.push(['■ 이웃 방문 현황']);
                xlsx_sheet_data.push(['날짜', '조회수:전체', '순방문자수:전체', '조회수:서로이웃', '순방문자수:서로이웃', '조회수:이웃', '순방문자수:이웃', '조회수:기타', '순방문자수:기타']);
                xlsx_sheet_data.push(...Array.from(range).map((item) => {
                    const data = [];
                    const date = moment(item).format('YYYY-MM-DD');
                    data.push(date);
                    data.push(_.get(_.find(data_user['이웃방문현황']['조회수']['relationVisit'], { date }), 'total', '-'));
                    data.push(_.get(_.find(data_user['이웃방문현황']['순방문자수']['relationVisit'], { date }), 'total', '-'));
                    data.push(_.get(_.find(data_user['이웃방문현황']['조회수']['relationVisit'], { date }), 'friend', '-'));
                    data.push(_.get(_.find(data_user['이웃방문현황']['순방문자수']['relationVisit'], { date }), 'friend', '-'));
                    data.push(_.get(_.find(data_user['이웃방문현황']['조회수']['relationVisit'], { date }), 'follow', '-'));
                    data.push(_.get(_.find(data_user['이웃방문현황']['순방문자수']['relationVisit'], { date }), 'follow', '-'));
                    data.push(_.get(_.find(data_user['이웃방문현황']['조회수']['relationVisit'], { date }), 'etc', '-'));
                    data.push(_.get(_.find(data_user['이웃방문현황']['순방문자수']['relationVisit'], { date }), 'etc', '-'));
                    return data;
                }));
            }
            const xlsx_sheet =  XLSX.utils.aoa_to_sheet(xlsx_sheet_data);
            XLSX.utils.book_append_sheet(xlsx, xlsx_sheet, xlsx_sheet_name);
        }
        // 유입분석
        {
            function get_search_query(url) {
                try {
                    const uri = new URL(url);
                    if(uri.searchParams.has('topReferer')) { return get_search_query(uri.searchParams.get('topReferer')) };
                    if(uri.searchParams.has('proxyReferer')) { return get_search_query(uri.searchParams.get('proxyReferer')) };
                    return uri.searchParams.get('query') || uri.searchParams.get('q');
                } catch(e) {}
            }
            Toastify({ text: `유입 통계 데이터 가져오는 중...`, }).showToast();
            const xlsx_sheet_name = `유입`;
            const xlsx_sheet_data = [];
            {
                xlsx_sheet_data.push(['날짜', '도메인', '키워드', '경로', '유입수', '유입률 (%)']);
                const data_init = await Promise.map(range_date, async (date) => await NB_blogStat['사용자분석']['유입분석']['전체'](user.userId, date, 'DATE'));
                const data_view = await Promise.map(data_init, async (item) => {
                    const data = await Promise.map(item['refererTotal'], async (item) => {
                        const resp = await NB_blogStat['사용자분석']['유입분석']['전체']['상세'](user.userId, item.date, 'DATE', { searchEngine: item.referrerSearchEngine, refererDomain: item.referrerDomain }).catch(e=>null);
                        const data = (resp && resp['refererDetail']) || [];
                        return data.map((o)=>Object.assign({}, item, o)).flat();
                    });
                    return data.flat();
                });
                data_view.flat().map((item)=>{
                    if(!item.searchQuery) item.searchQuery = get_search_query(item.referrerUrl);
                    xlsx_sheet_data.push([item.date, item.referrerDomain, item.searchQuery, item.referrerUrl, item.cv, item.cv_p])
                });
            }
            const xlsx_sheet =  XLSX.utils.aoa_to_sheet(xlsx_sheet_data);
            XLSX.utils.book_append_sheet(xlsx, xlsx_sheet, xlsx_sheet_name);
        }
        // 순위분석
        {
            Toastify({ text: `게시물 통계 데이터 가져오는 중...`, }).showToast();
            const xlsx_sheet_name = `게시물`;
            const xlsx_sheet_data = [];
            const data_rank = await Promise.map(range, async (date) => { const resp = await NB_blogStat['순위']['조회수']['게시물'](user.userId, date, 'WEEK'); return resp.rankCv; });
            const data_rank_articles_kv = _.mapValues(_.groupBy(data_rank.filter(v=>!!v).flat(), 'uri'), (items) => {
                const stats = Array.from(range).map((item) => {
                    const date = moment(item).format('YYYY-MM-DD');
                    const defs = _.assign({}, items[0], { cv: 0, rank: 0, date });
                    const data = _.find(items, { date });
                    return data || defs;
                });
                stats.cv_avg = _.meanBy(stats, 'cv');
                stats.cv_sum = _.sumBy(stats, 'cv');
                stats.rank_avg = _.meanBy(stats, 'rank');
                return stats;
            });
            const data_rank_articles = _.orderBy(_.map(data_rank_articles_kv, (items, uri)=>(items.uri = uri, items.title = _.get(items, '[0].title', '-'), items)), ['cv_sum', 'rank_avg'], ['desc', 'asc']);
            data_rank_articles.cv_avg = _.meanBy(data_rank_articles, 'cv_avg').toFixed(2);
            data_rank_articles.cv_sum = _.sumBy(data_rank_articles, 'cv_sum').toFixed(0);

            xlsx_sheet_data.push(['날짜', '주소', '제목', '순위', '전체 누적 조회수', '주간 평균 조회수', '주간 누적 조회수']);
            data_rank_articles.map((item) => {
                item.map((data) => xlsx_sheet_data.push([data.date, item.uri, item.title, data.rank, item.cv_sum, item.cv_avg, data.cv]));
            });
            const xlsx_sheet =  XLSX.utils.aoa_to_sheet(xlsx_sheet_data);
            XLSX.utils.book_append_sheet(xlsx, xlsx_sheet, xlsx_sheet_name);
        };
        //XLSX 저장
        const xlsx_opts = { bookType:'xlsx', bookSST:false, type:'array' };
        const xlsx_blob = XLSX.write(xlsx, xlsx_opts);
        saveAs(new Blob([xlsx_blob],{type:"application/octet-stream"}), xlsx_name);
        Toastify({ text: `저장 완료`, }).showToast();
    }
    const user = await NB_blogInfo('', 'BlogUserInfo'); if(!user) return;
    const wrap = document.querySelector('#nav.lnb__local-menu'); if(!wrap) return;
    const cont = wrap.querySelector('.lnb__download') || document.createElement('div'); cont.classList.add('lnb__depth1', 'lnb__download'); wrap.append(cont);
    const link = cont.querySelector('.lnb__title') || document.createElement('a'); link.classList.add('lnb__title'); link.textContent = '지표 다운로드 플러스'; cont.append(link);
    cont.addEventListener('click', (event)=>{
        event.preventDefault();
        event.stopPropagation();
        download();
    });
});