본문 바로가기

예약 발행한 블로그 글이 실제 공개됐는지 자동으로 확인하는 방법|Apps Script 활용

BinniH2 2026. 10. 15.
반응형

노트북의 발행 점검 목록과 커피잔이 놓인 책상에 예약 발행 공개 확인 문구가 표시된 모습

 

블로그 글을 예약 발행해 두면 정해진 시각에 자동으로 공개될 것으로 생각하기 쉽습니다. 그런데 실제로는 예약 시간을 잘못 입력했거나 발행 과정에 문제가 생겨 글이 보이지 않을 수도 있습니다. 글이 여러 개라면 매번 사이트에 들어가서 확인하기도 번거롭지요.

예약 발행 공개 확인은 Google Sheets에 예약시각과 글 주소를 기록해 두고, Google Apps Script가 외부에서 페이지에 접속해 결과를 시트에 남기도록 만들면 됩니다. 티스토리뿐 아니라 공개 URL을 확인할 수 있는 블로그에서 응용할 수 있습니다.

핵심 요약

Google Sheets에 예약시간·글 제목·공개 주소를 적고 Apps Script 시간 트리거로 페이지 응답과 제목을 비교하면 공개 상태를 자동 점검할 수 있습니다. 다만 HTTP 200만으로 공개를 확정하지 말고 제목 일치 여부를 함께 확인하며, 판단이 어려운 결과는 직접 살펴봐야 합니다.

예약 발행 글이 공개됐는지 확인할 때 필요한 정보

준비물은 구글 계정, Google Sheets 파일, 예약 발행 시간과 실제 공개될 글 URL입니다. 미리보기 주소나 글쓰기 관리 화면 주소는 일반 방문자가 접근하는 공개 주소와 다를 수 있으니 사용하지 마세요.

Google Sheets에 발행점검이라는 이름의 시트 탭을 만들고 첫 줄을 다음처럼 구성합니다.

열 항목 입력 방법
A 예약일시 날짜와 시간 형식으로 입력
B 글제목 공개 페이지 제목과 동일하게 입력
C 공개주소 https://로 시작하는 최종 글 URL
D 점검상태 스크립트가 자동 입력
E 점검시각 스크립트가 자동 입력
F 응답코드 스크립트가 자동 입력
G 상세 스크립트가 자동 입력

예를 들어 A2에 예약 발행 시각, B2에 정확한 제목, C2에 해당 글의 최종 주소를 입력하면 됩니다. 시트와 Apps Script 프로젝트의 시간대는 모두 Asia/Seoul로 맞춰 두는 것이 편리합니다.

Apps Script로 예약 발행 공개 확인 자동화하기

시트의 확장 프로그램 → Apps Script에서 프로젝트를 열고 기존 예시 코드를 아래 코드로 바꿉니다. 이 코드는 예약시각이 지난 행만 검사하고, 이미 '공개 확인'으로 처리한 행은 다시 검사하지 않습니다.

const SHEET_NAME = '발행점검';

function checkPublishedPosts() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName(SHEET_NAME);
  if (!sheet) throw new Error('발행점검 시트를 찾을 수 없습니다.');

  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return;

  const rows = sheet.getRange(2, 1, lastRow - 1, 7).getValues();
  const now = new Date();

  rows.forEach((row, index) => {
    const [scheduledAt, expectedTitle, url, oldStatus] = row;
    if (oldStatus === '공개 확인') return;
    if (!(scheduledAt instanceof Date) || isNaN(scheduledAt.getTime())) {
      writeResult(sheet, index + 2, '입력 확인', now, '', 'A열 날짜 형식 확인');
      return;
    }
    if (now < scheduledAt) return;
    if (!expectedTitle || !/^https?:\/\//i.test(String(url))) {
      writeResult(sheet, index + 2, '입력 확인', now, '', 'B열 제목 또는 C열 주소 확인');
      return;
    }

    let status = '추가 확인';
    let code = '';
    let detail = '';
    try {
      const response = UrlFetchApp.fetch(String(url).trim(), {
        method: 'get',
        followRedirects: true,
        muteHttpExceptions: true
      });
      code = response.getResponseCode();
      if (code === 200) {
        const page = response.getContentText('UTF-8');
        const match = page.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i);
        const actual = normalizeTitle(match ? match[1] : '');
        const expected = normalizeTitle(expectedTitle);
        if (actual && actual.includes(expected)) {
          status = '공개 확인';
          detail = '외부 요청 200 + HTML 제목 일치';
        } else {
          detail = '200 응답이지만 HTML 제목 불일치';
        }
      } else if (code === 404 || code === 410) {
        status = '응답 대기';
        detail = '페이지를 찾지 못함. 다음 점검에서 재시도';
      } else {
        status = '접속 오류';
        detail = 'HTTP ' + code + '. 다음 점검에서 재시도';
      }
    } catch (error) {
      status = '접속 오류';
      detail = String(error.message || error).slice(0, 120);
    }
    writeResult(sheet, index + 2, status, now, code, detail);
  });
}

function normalizeTitle(value) {
  return String(value)
    .replace(/&amp;/gi, '&').replace(/&quot;/gi, '"')
    .replace(/&#39;|&apos;/gi, "'")
    .replace(/&lt;/gi, '<').replace(/&gt;/gi, '>')
    .replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)))
    .replace(/\s+/g, '').trim();
}

function writeResult(sheet, row, status, checkedAt, code, detail) {
  sheet.getRange(row, 4, 1, 4)
    .setValues([[status, checkedAt, code, detail]]);
}

function installHourlyTrigger() {
  ScriptApp.getProjectTriggers()
    .filter(t => t.getHandlerFunction() === 'checkPublishedPosts')
    .forEach(t => ScriptApp.deleteTrigger(t));
  ScriptApp.newTrigger('checkPublishedPosts')
    .timeBased().everyHours(1).create();
}

코드는 Google의 UrlFetchApp.fetch()로 실제 공개 주소에 요청을 보냅니다. HTTP 응답 코드가 200이면서 HTML의 <title>에 예상 제목이 포함돼 있을 때 '공개 확인'으로 기록합니다. 404·410은 '응답 대기', 다른 오류는 '접속 오류'로 남겨 다음 실행에서 재점검합니다.

시간 트리거 설정과 작동 테스트 순서

  1. 시트 탭 이름과 A~G열 구성이 예제와 동일한지 확인합니다.
  2. Apps Script 편집기에서 checkPublishedPosts를 한 번 실행해 권한을 승인합니다.
  3. 과거에 공개된 실제 글 1건을 시험 행으로 넣어 D~G열 결과를 확인합니다.
  4. installHourlyTrigger를 한 번 실행해 시간 기반 트리거를 생성합니다.
  5. 이후에는 예약 시간이 지난 글을 매시간 자동 점검합니다.

시간 기반 트리거는 초 단위의 정확한 발행 확인 알람이 아닙니다. 예약시각 직후가 아니라 다음 트리거 실행 때 결과가 기록될 수 있으며, 인터넷 연결 상황이나 사이트 응답에 따라 결과가 달라질 수 있습니다.

점검상태를 읽는 방법과 주의할 점

  • 공개 확인: 외부 HTTP 200 응답과 페이지 제목 일치가 확인된 상태입니다.
  • 응답 대기: 아직 주소를 찾지 못해 재확인이 필요한 상태입니다.
  • 추가 확인: 200 응답이지만 제목이 달라 로그인 안내나 오류 페이지일 가능성이 있습니다.
  • 접속 오류: 403, 500 또는 네트워크 요청 실패처럼 별도 확인이 필요한 경우입니다.
  • 입력 확인: 시트의 날짜, 제목 또는 URL을 확인해야 합니다.

흔한 실수는 200 응답만 보고 글이 공개됐다고 판단하는 것입니다. 일부 오류 안내 페이지도 200을 보낼 수 있고, 블로그 스킨이나 제목 표기 방식 때문에 정상 공개 글이 '추가 확인'으로 나올 수도 있습니다. 게시물 비공개 여부를 운영자 화면에서 별도로 확인하는 것도 좋습니다.

또한 검색엔진에 노출됐는지는 이 검사로 알 수 없습니다. 공개 페이지 접속 확인과 네이버·구글 검색 반영은 서로 다른 문제입니다. 점검 결과가 계속 이상하다면 시크릿 창에서 해당 주소를 직접 열어 비교해 보세요.

자주 묻는 질문

Q1. 티스토리 예약 발행도 확인할 수 있나요?

네. 일반 방문자가 열 수 있는 최종 글 주소를 알고 있다면 같은 방식으로 검사할 수 있습니다. 단, 예약 전에 최종 URL을 확보하지 못했다면 발행 자동화 과정에서 URL을 시트에 기록하는 단계가 먼저 필요합니다.

Q2. '공개 확인'으로 표시되면 검색 결과에도 뜨나요?

아닙니다. 외부 접속으로 페이지가 열리는 것과 검색엔진의 수집·색인은 별개의 과정입니다.

Q3. 예약시간이 지났는데 '응답 대기'로 나옵니다. 발행 실패인가요?

즉시 실패로 단정할 수 없습니다. URL 오타, 일시적인 반영 지연, 접근 제한 등을 살펴보고 다음 점검 결과도 확인하세요.

Q4. 이미 공개된 글을 매시간 다시 조회하나요?

아닙니다. 예제에서는 D열이 '공개 확인'인 행을 건너뜁니다. 공개 후 삭제·비공개 전환까지 감시하려면 그 조건을 수정해야 합니다.

정리: 예약 발행 점검을 발행 이력에 연결하세요

블로그 자동화에서 중요한 것은 글을 예약했다는 기록보다 독자가 실제로 글을 열 수 있는지 확인하는 과정입니다. 글 제목·예약시각·공개 URL을 발행 이력과 함께 관리하면 놓친 글을 찾아내기가 훨씬 쉬워집니다.

처음에는 기존 공개 글 한 편으로 테스트하고, 블로그마다 다른 제목 표시 방식과 접근 제한을 확인한 뒤 예약 글에 적용해 보세요. 이 예제의 판단은 외부 HTTP 요청을 바탕으로 한 점검 결과이지, 플랫폼 내부 발행 상태를 직접 조회한 공식 증명은 아닙니다.

관련 자료

반응형

댓글