Files
kkgu889 9f27300fe6 https://soohyun.store/
메타 태그 body 적용 완료
2026-10-20 13:07:14 +09:00

118 lines
6.2 KiB
JavaScript

const http = require('http');
async function auditPage(url, pageName) {
try {
const res = await fetch(url);
if (!res.ok) {
console.log(`[${pageName}] HTTP Status: ${res.status}`);
return;
}
const html = await res.text();
console.log(`\n==============================================`);
console.log(`🔍 [정밀 SEO 감사 보고서] ${pageName}`);
console.log(`🌐 대상 URL: ${url}`);
console.log(`==============================================`);
// 1. Title
const titleMatch = html.match(/<title>([^<]+)<\/title>/i);
const title = titleMatch ? titleMatch[1].trim() : '❌ 없음';
console.log(`1. <title>`);
console.log(` - 텍스트: "${title}"`);
console.log(` - 글자수: ${title.length}자 (네이버/구글 검색 최적 권장 30~35자 내외)`);
console.log(` - 검색결과 말줄임(…) 방지 평가: ${title.length >= 25 && title.length <= 40 ? '✅ 최적 (말줄임 없음)' : '⚠️ 점검 권장'}`);
// 2. H1 태그
const h1Matches = [...html.matchAll(/<h1[^>]*>([\s\S]*?)<\/h1>/gi)];
console.log(`\n2. <h1> 태그`);
console.log(` - 발견된 H1 개수: ${h1Matches.length}개 (검색엔진 권장 단독 1개 원칙)`);
h1Matches.forEach((m, i) => {
const cleanH1 = m[1].replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
console.log(` - H1 #${i + 1}: "${cleanH1}"`);
});
console.log(` - 평가: ${h1Matches.length === 1 ? '✅ 단독 H1 규격 준수 (검색 가중치 집중)' : '⚠️ H1 중복 또는 누락'}`);
// 3. Meta Description
const descMatch = html.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']+)["']/i)
|| html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*name=["']description["']/i);
const desc = descMatch ? descMatch[1].trim() : '❌ 없음';
console.log(`\n3. Meta Description`);
console.log(` - 내용: "${desc}"`);
console.log(` - 글자수: ${desc.length}자 (권장 80~140자)`);
console.log(` - 검색 스니펫 CTR 평가: ${desc.length >= 50 && desc.length <= 160 ? '✅ 최적' : '⚠️ 점검 권장'}`);
// 4. Canonical URL
const canonMatch = html.match(/<link[^>]*rel=["']canonical["'][^>]*href=["']([^"']+)["']/i);
const canonical = canonMatch ? canonMatch[1] : '❌ 없음';
console.log(`\n4. Canonical URL (표준 URL)`);
console.log(` - 대상 URL: ${canonical}`);
console.log(` - 평가: ${canonical.startsWith('http') ? '✅ 절대경로 표준 준수 (중복 문서 페널티 방지)' : '❌ 상대경로 또는 누락'}`);
// 5. Naver Site Verification
const naverMatch = html.match(/<meta[^>]*name=["']naver-site-verification["'][^>]*content=["']([^"']+)["']/i);
console.log(`\n5. 네이버 서치어드바이저 소유확인 태그`);
console.log(` - 키 값: ${naverMatch ? naverMatch[1] : '❌ 없음'}`);
// 6. Open Graph & Twitter Cards
const ogTitle = (html.match(/<meta[^>]*property=["']og:title["'][^>]*content=["']([^"']+)["']/i) || [])[1];
const ogImage = (html.match(/<meta[^>]*property=["']og:image["'][^>]*content=["']([^"']+)["']/i) || [])[1];
const twitterCard = (html.match(/<meta[^>]*name=["']twitter:card["'][^>]*content=["']([^"']+)["']/i) || [])[1];
console.log(`\n6. 소셜 미디어 메타데이터 (OG & Twitter)`);
console.log(` - og:title: ${ogTitle || '❌'}`);
console.log(` - og:image: ${ogImage || '❌'}`);
console.log(` - twitter:card: ${twitterCard || '❌'}`);
// 7. JSON-LD 구조화 데이터
const ldJsonMatches = [...html.matchAll(/<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi)];
console.log(`\n7. JSON-LD Schema.org 구조화 데이터`);
console.log(` - 삽입된 스키마 개수: ${ldJsonMatches.length}개`);
ldJsonMatches.forEach((m, i) => {
try {
const parsed = JSON.parse(m[1]);
const type = parsed['@type'] ? (Array.isArray(parsed['@type']) ? parsed['@type'].join(', ') : parsed['@type']) : 'Unknown';
console.log(` - 스키마 #${i + 1} 타입: ${type} (✅ JSON 구문 유효)`);
} catch (e) {
console.log(` - 스키마 #${i + 1}: ❌ JSON 파싱 에러 (${e.message})`);
}
});
// 8. 이미지 태그 점검 (alt, width, height, 한글 파일명)
const imgMatches = [...html.matchAll(/<img([^>]+)>/gi)];
console.log(`\n8. 이미지 (<img>) SEO 최적화 점검`);
console.log(` - 총 이미지 태그 수: ${imgMatches.length}개`);
let missingAlt = 0;
let hasKoreanSrc = 0;
imgMatches.forEach(m => {
const attrs = m[1];
if (!attrs.includes('alt=') || attrs.includes('alt=""')) missingAlt++;
const srcMatch = attrs.match(/src=["']([^"']+)["']/);
if (srcMatch && /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(srcMatch[1])) hasKoreanSrc++;
});
console.log(` - alt 누락 이미지: ${missingAlt}개`);
console.log(` - 한글 파일명 src 이미지: ${hasKoreanSrc}개`);
console.log(` - 평가: ${missingAlt === 0 && hasKoreanSrc === 0 ? '✅ 100% 최적 (크롤러 인코딩 오류 0건)' : '⚠️ 일부 점검 필요'}`);
// 9. 로봇 수집 차단 방해 요소 점검 (noindex, nofollow)
const robotsMeta = (html.match(/<meta[^>]*name=["']robots["'][^>]*content=["']([^"']+)["']/i) || [])[1];
console.log(`\n9. 크롤러 방해 요소 점검 (Crawl Obstacles)`);
console.log(` - robots 메타태그: ${robotsMeta || '설정 없음 (기본 index, follow 허용)'}`);
if (robotsMeta && robotsMeta.includes('noindex')) {
console.log(` - 🚨 치명적: noindex로 인해 검색엔진 수집이 차단됩니다!`);
} else {
console.log(` - 수집 차단 여부: ✅ 정상 (검색엔진 차단 방해 요소 없음)`);
}
} catch (err) {
console.error(`Error auditing ${url}:`, err.message);
}
}
(async () => {
// 포트 3015(부산 서버) 점검
await auditPage('http://localhost:3015/', '부산 메인 홈페이지');
await auditPage('http://localhost:3015/%EC%9A%B0%EB%8F%99-%EC%A4%91%ED%95%99%EC%83%9D-%EC%88%98%ED%95%99', '부산 지역 롱테일 상세 (우동 중학생 수학)');
// 포트 3011(site1) 점검 시도
await auditPage('http://localhost:3011/', '서울 site1 메인 홈페이지');
})();