컬처스텝

외부 서비스 없이 접속 통계 붙이기 — 1×1 픽셀 + 가드형 로그 파일

· 2026-07-30 (목) 12:39:55 · 1540 · 1

구글 애널리틱스를 붙이기 곤란한 사이트가 있습니다. 관공서·의료·법무처럼 방문 데이터를 외부로 내보내면 안 되는 곳, 또는 태그 하나 넣는 것도 승인이 필요한 곳입니다.

이런 경우 PHP 파일 두 개로 자체 통계를 만들 수 있습니다. 실제 운영 중인 구조를 그대로 공유합니다.

1. 기록기 — /p.php

모든 페이지가 이미지 한 장을 요청하게 하고, 그 요청을 기록합니다.

<?php
$dir = __DIR__.'/data/cs_inbox';
if (!is_dir($dir)) @mkdir($dir, 0755, true);
$log = $dir.'/visits.csv.php';

/* 3MB 넘으면 월 단위로 분리 */
if (file_exists($log) && @filesize($log) > 3145728) {
  @rename($log, $dir.'/visits_'.date('Ym_His').'.csv.php');
}

$row = array(
  date('Y-m-d H:i:s'),
  substr(md5(($_SERVER['REMOTE_ADDR'] ?? '').'내가정한소금값'), 0, 10), // IP 해시
  substr(preg_replace('/[",\r\n]/', ' ', $_GET['p'] ?? ''), 0, 80),    // 경로
  substr(preg_replace('/[",\r\n]/', ' ', $_GET['r'] ?? ''), 0, 180),   // 유입경로
  substr(preg_replace('/[",\r\n]/', ' ', $_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 140)
);
if (!file_exists($log)) @file_put_contents($log, "<?php http_response_code(404); exit; ?>\n");
@file_put_contents($log, '"'.implode('","', $row)."\"\n", FILE_APPEND|LOCK_EX);

header('Content-Type: image/gif');
header('Cache-Control: no-store');
echo base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');

2. 호출 — tail.php 또는 스킨 하단

<script>try{new Image().src="/p.php?p="+encodeURIComponent(location.pathname)
+"&r="+encodeURIComponent(document.referrer.slice(0,150));}catch(e){}</script>

짚어둘 점 세 가지

① 로그 파일 확장자를 .php 로 두고 첫 줄에 차단 코드를 넣습니다.
.csv 로 두면 주소를 아는 사람이 방문자 로그를 통째로 내려받을 수 있습니다. .php 로 두고 첫 줄에 <?php http_response_code(404); exit; ?> 를 넣으면, 브라우저로 직접 열려도 404가 나가고 뒤의 CSV 본문은 출력되지 않습니다. 읽을 때는 첫 줄만 건너뛰면 됩니다.

$fh = fopen($log, 'r');
$first = true;
while (($line = fgets($fh)) !== false) {
  if ($first) { $first = false; if (strpos($line, '<?php') === 0) continue; }
  $c = str_getcsv(rtrim($line, "\r\n"));
  // ...
}
fclose($fh);

② IP는 원본을 저장하지 말고 소금값을 섞어 해시로 저장합니다.
같은 방문자 구분은 되면서 원본 IP는 남지 않습니다. 개인정보 부담이 크게 줄어듭니다.

③ 봇을 걸러야 숫자가 쓸 만해집니다.
User-Agent에 아래 패턴이 있으면 집계에서 빼세요. 안 그러면 방문자 절반이 크롤러입니다.

bot|crawler|spider|slurp|yeti|facebookexternalhit|externalhit|scrap|preview
|curl|wget|python|headless|dataprovider|ahrefs|semrush|petal|bytespider
|gptbot|ccbot|perplexity|applebot|archive\.org|okhttp|go-http

참고로 yeti가 네이버 검색로봇, dataprovider·ahrefs·semrush는 SEO 스캐너입니다. UA가 아예 비어 있는 요청도 봇으로 처리하는 편이 낫습니다.

자기 접속을 빼는 것이 제일 중요합니다

이걸 안 하면 관리자 본인 접속이 통계의 대부분을 차지합니다. 저는 이걸 놓쳤다가 "오늘 방문자 24명"이 알고 보니 전부 제 테스트 접속이었던 적이 있습니다. 심지어 SNS 링크 미리보기를 확인하려고 돌린 OG 디버거까지 "페이스북 유입"으로 잡혀 있었습니다.

p.php 맨 위에 이 정도만 넣으면 됩니다.

$vh = substr(md5(($_SERVER['REMOTE_ADDR'] ?? '').'내가정한소금값'), 0, 10);

$skip = !empty($_COOKIE['no_track']);          // 작업용 브라우저 제외
if (!$skip) {                                   // 등록된 방문자 해시 제외
  $exf = $dir.'/exclude.php';
  if (file_exists($exf)) {
    $ex = @file_get_contents($exf);
    if ($ex !== false && strpos($ex, $vh) !== false) $skip = true;
  }
}
if ($skip) {
  header('Content-Type: image/gif');
  echo base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
  exit;
}

쿠키 방식은 브라우저를 바꾸면 풀리고, 해시 목록 방식은 과거 기록까지 소급해서 뺄 수 있습니다. 둘 다 넣어두면 편합니다.

집계 화면

CSV를 읽어 날짜별·페이지별·유입경로별로 세면 됩니다. 리퍼러 호스트만 보면 네이버·구글·다음 검색과 SNS를 구분할 수 있고, query·q 파라미터가 살아 있으면 검색어도 일부 잡힙니다(대부분은 안 넘어옵니다).

차트도 외부 라이브러리 없이 인라인 SVG 막대만으로 충분히 읽을 만합니다. 세션은 같은 방문자 해시의 요청 간격이 30분을 넘으면 새 세션으로 끊으면 됩니다.

집계 화면 쪽도 정리해서 필요하신 분 있으면 올리겠습니다.

2명이 반응했습니다
|

댓글 1개

감사합니다.
댓글을 작성하시려면 로그인이 필요합니다.

그누보드5 팁자료실

2,788건
+
제목 글쓴이 날짜 조회
22-06-20 조회 8,239
09-11 조회 144
09-04 조회 782
08-09 조회 1,636
08-05 조회 2,828
07-30 조회 1,638
07-30 조회 1,541
07-15 조회 2,819
07-01 조회 3,000
06-30 조회 2,997
06-22 조회 3,728
06-17 조회 2,872
06-14 조회 1,614
05-29 조회 1,710
05-16 조회 1,653
05-04 조회 3,154
04-29 조회 3,342
04-28 조회 3,157
04-27 조회 3,124
04-25 조회 2,153
04-24 조회 3,884
04-23 조회 3,279
04-14 조회 2,298
04-08 조회 3,134
04-01 조회 4,840
03-31 조회 3,856
03-16 조회 4,001
03-14 조회 5,174
03-13 조회 4,197
03-10 조회 3,955
03-09 조회 4,081