all files
This commit is contained in:
205
getviews.js
Normal file
205
getviews.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
import playwright from 'playwright-extra';
|
||||
const { chromium } = playwright;
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import AdmZip from 'adm-zip';
|
||||
import { parse } from 'csv-parse/sync';
|
||||
|
||||
// JSON 파일에서 세션 상태를 불러오기
|
||||
const loadSession = async (filePath) => {
|
||||
const jsonData = fs.readFileSync(filePath, 'utf-8');
|
||||
return JSON.parse(jsonData);
|
||||
};
|
||||
|
||||
// 디렉토리 없으면 생성
|
||||
function ensureDirExists(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
// 체크박스를 원하는 상태(true)로 강건하게 맞춤
|
||||
async function ensureChecked(dlg, page, label) {
|
||||
const cb = dlg.getByRole('checkbox', { name: label, exact: true });
|
||||
await cb.scrollIntoViewIfNeeded();
|
||||
let state = await cb.getAttribute('aria-checked');
|
||||
if (state !== 'true') {
|
||||
await cb.click({ force: true });
|
||||
// 클릭 후 짧게 재확인
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await page.waitForTimeout(100);
|
||||
state = await cb.getAttribute('aria-checked');
|
||||
if (state === 'true') break;
|
||||
}
|
||||
if (state !== 'true') throw new Error(`체크 실패: ${label}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 브라우저 생성
|
||||
async function createBrowser() {
|
||||
const optionsBrowser = {
|
||||
headless: false,
|
||||
args: [
|
||||
'--disable-blink-features=AutomationControlled',
|
||||
'--no-sandbox',
|
||||
'--disable-web-security',
|
||||
'--disable-infobars',
|
||||
'--disable-extensions',
|
||||
'--start-maximized',
|
||||
'--window-size=1280,720',
|
||||
],
|
||||
};
|
||||
return chromium.launch(optionsBrowser);
|
||||
}
|
||||
|
||||
// 컨텍스트 생성(다운로드 허용, 스토리지 상태 적용)
|
||||
async function createContext(browser, sessionState) {
|
||||
const optionsContext = {
|
||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
|
||||
locale: 'ko-KR',
|
||||
viewport: { width: 1280, height: 720 },
|
||||
deviceScaleFactor: 1,
|
||||
acceptDownloads: true,
|
||||
storageState: sessionState,
|
||||
extraHTTPHeaders: {
|
||||
'sec-ch-ua': '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
|
||||
'sec-ch-ua-arch': '"arm"',
|
||||
'sec-ch-ua-bitness': '"64"',
|
||||
'sec-ch-ua-form-factors': '"Desktop"',
|
||||
'sec-ch-ua-full-version': '"139.0.7258.154"',
|
||||
'sec-ch-ua-full-version-list': '"Not;A=Brand";v="99.0.0.0", "Google Chrome";v="139.0.7258.154", "Chromium";v="139.0.7258.154"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-model': '""',
|
||||
'sec-ch-ua-platform': '"macOS"',
|
||||
'sec-ch-ua-platform-version': '"15.6.1"',
|
||||
'sec-ch-ua-wow64': '?0',
|
||||
}
|
||||
};
|
||||
return browser.newContext(optionsContext);
|
||||
}
|
||||
|
||||
// 스튜디오 접속 후 분석 → 고급 모드 진입
|
||||
async function openAnalyticsAdvanced(page) {
|
||||
await page.goto('https://studio.youtube.com/');
|
||||
await page.locator('ytcp-navigation-drawer').getByRole('button', { name: '분석', exact: true }).click();
|
||||
await page.getByRole('link', { name: '고급 모드', exact: true }).click();
|
||||
}
|
||||
|
||||
// 기간: 지난 7일 선택 → 사용자설정 → 종료일 = 시작일 설정 → 적용
|
||||
async function configureDateRangeSingleDay(page) {
|
||||
// 기간 드롭다운 열기 → 지난 7일 선택
|
||||
await page.locator('yta-time-picker #picker-trigger ytcp-dropdown-trigger[role="button"]').click();
|
||||
await page.locator('tp-yt-paper-item[test-id="week"]').click();
|
||||
|
||||
// 다시 열기 → 사용자설정 선택
|
||||
await page.locator('yta-time-picker #picker-trigger ytcp-dropdown-trigger[role="button"]').click();
|
||||
await page.locator('tp-yt-paper-item[test-id="fixed"]').click();
|
||||
|
||||
// 날짜 기간 다이얼로그에서 종료일 값을 시작일에 복사
|
||||
const caldlg = page.locator('tp-yt-paper-dialog:has(ytcp-date-period-picker)');
|
||||
await caldlg.waitFor({ state: 'visible' });
|
||||
const endInput = caldlg.locator('#end-date input');
|
||||
await endInput.waitFor({ state: 'visible' });
|
||||
const endVal = await endInput.inputValue();
|
||||
await caldlg.locator('#start-date input').fill(endVal);
|
||||
await caldlg.locator('#apply-button[aria-disabled="false"] button').click();
|
||||
return { startDate: endVal, endDate: endVal };
|
||||
}
|
||||
|
||||
// 측정항목에서 Premium 섹션 지정 → 필요한 항목 체크 → 적용
|
||||
async function configureMetrics(page) {
|
||||
await page.locator('yta-explore-column-picker-dropdown[title="측정항목"] ytcp-dropdown-trigger').click();
|
||||
const dlg = page.getByRole('dialog', { name: '측정항목' });
|
||||
await page.locator('h2.picker-text', { hasText: 'Premium' }).click();
|
||||
await dlg.getByRole('button', { name: '전체 선택 해제' }).click();
|
||||
await ensureChecked(dlg, page, '조회수');
|
||||
await ensureChecked(dlg, page, '유효 조회수');
|
||||
await ensureChecked(dlg, page, '시청 시간(단위: 시간)');
|
||||
await ensureChecked(dlg, page, 'YouTube Premium 조회수');
|
||||
await dlg.getByRole('button', { name: '적용' }).click();
|
||||
}
|
||||
|
||||
// 현재 화면 내보내기(CSV) → 다운로드 대기 → ZIP 저장 및 압축 해제
|
||||
async function exportCsvAndExtract(page, downloadDir) {
|
||||
ensureDirExists(downloadDir);
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent('download'),
|
||||
(async () => {
|
||||
await page.locator('ytcp-icon-button#export-button, ytcp-icon-button[aria-label="현재 화면 내보내기"]').click();
|
||||
await page.locator('tp-yt-paper-item[test-id="CSV"]').click();
|
||||
})()
|
||||
]);
|
||||
const suggested = download.suggestedFilename();
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const zipPath = path.join(downloadDir, `${timestamp}-${suggested || 'export.zip'}`);
|
||||
await download.saveAs(zipPath);
|
||||
const unzipDir = path.join(downloadDir, `${timestamp}-unzipped`);
|
||||
ensureDirExists(unzipDir);
|
||||
const zip = new AdmZip(zipPath);
|
||||
zip.extractAllTo(unzipDir, true);
|
||||
return { zipPath, unzipDir };
|
||||
}
|
||||
|
||||
// 압축 해제된 폴더에서 원하는 CSV 파일 선택
|
||||
function chooseCsvFile(unzipDir, preferredPattern = /표 데이터\.csv$/) {
|
||||
const files = fs.readdirSync(unzipDir).filter(f => f.toLowerCase().endsWith('.csv'));
|
||||
if (files.length === 0) throw new Error('압축 해제 폴더에 CSV가 없습니다.');
|
||||
const picked = preferredPattern ? (files.find(f => preferredPattern.test(f)) || files[0]) : files[0];
|
||||
return path.join(unzipDir, picked);
|
||||
}
|
||||
|
||||
// CSV → JSON 배열 파싱
|
||||
function parseCsvToJson(csvPath) {
|
||||
const csvContent = fs.readFileSync(csvPath, 'utf-8');
|
||||
return parse(csvContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
bom: true,
|
||||
relax_column_count: true,
|
||||
trim: true,
|
||||
});
|
||||
}
|
||||
|
||||
// 메인 플로우
|
||||
async function main() {
|
||||
let browser;
|
||||
try {
|
||||
// 1) 브라우저/컨텍스트 준비
|
||||
browser = await createBrowser();
|
||||
const sessionData = await loadSession('everlogin.json');
|
||||
const context = await createContext(browser, sessionData);
|
||||
const page = await context.newPage();
|
||||
|
||||
// 2) 분석 고급 모드 진입 및 기간/측정항목 설정
|
||||
await openAnalyticsAdvanced(page);
|
||||
const { startDate, endDate } = await configureDateRangeSingleDay(page);
|
||||
await configureMetrics(page);
|
||||
|
||||
// 3) CSV 내보내기 → ZIP 저장/압축 해제 → CSV 선택
|
||||
const downloadDir = path.resolve(process.cwd(), 'downloads');
|
||||
const { unzipDir } = await exportCsvAndExtract(page, downloadDir);
|
||||
const csvPath = chooseCsvFile(unzipDir, /표 데이터\.csv$/);
|
||||
|
||||
// 4) CSV → JSON 파싱 후 출력
|
||||
const records = parseCsvToJson(csvPath);
|
||||
console.log(`설정된 기간: ${startDate} ~ ${endDate}`);
|
||||
const count = records.length;
|
||||
console.log(`레코드 개수: ${count}`);
|
||||
console.log(JSON.stringify(records, null, 2));
|
||||
|
||||
// (선택) 잠시 대기(디버깅용)
|
||||
const waitAfterMs = 5_000;
|
||||
await new Promise((r) => setTimeout(r, waitAfterMs));
|
||||
} finally {
|
||||
// 브라우저 종료 보장
|
||||
try { await browser?.close(); } catch {}
|
||||
}
|
||||
}
|
||||
|
||||
// 실행
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user