X에서 AI 에이전트 게시물 검증: 83개 가짜 성공에서 94% 실제 성공으로
X에 자동 댓글을 다는 AI 에이전트가 83개의 성공 게시를 보고했지만, 실제로 나타난 건 16개뿐이었다. 문제는 불완전한 검증 때문이었다: '답글' 버튼을 클릭한다고 해서 콘텐츠가 플랫폼에 반드시 표시되는 건 아니다. X의 스팸 필터가 조용히 게시물을 삭제해 버리는데, 아무런 알림도 주지 않는다.
브라우저를 통한 게시 과정
에이전트는 Puppeteer CDP를 사용해 실제 브라우저를 제어한다. 스레드를 열고 텍스트를 입력한 뒤 '답글' 버튼을 클릭한다. 초기 버전 함수는 DOM 이벤트만 확인했다:
const button = await page.$('[data-testid="tweetButton"]');
if (button) {
const disabled = await button.evaluate(el =>
el.getAttribute('aria-disabled') === 'true'
);
if (!disabled) {
await button.click();
return true; // <-- 문제: 조기 성공 판단
}
}
return false;
true를 반환하면 DOM 수준에서 성공으로 간주됐지만, 플랫폼 확인은 없었다. 데이터베이스에 기록이 쌓이고 대시보드는 초록빛으로 빛났다.
X는 거부를 알리지 않는다: 입력 필드가 지워지고 URL은 그대로다. 속도 제한이나 콘텐츠 정책이 조용히 작동해 브라우저는 성공한 줄로만 안다.
게시 ID 문제
추적을 위해 클릭 후 page.url()에서 ID를 추출했다:
const tweetId = page.url().split('/status/')[1];
X는 새 게시물로 리다이렉트하지 않는다—URL은 부모 스레드의 것을 유지한다. 83개 기록 모두 같은 부모 tweet_id를 받아 문제는 '유효한' 데이터 뒤에 숨겨졌다.
문제는 X 분석 도구로 수동 확인 시 드러났다: 보고된 83개 vs 실제 답글 16개.
근본 원인: DOM과 플랫폼 간 격차
DOM: button.click() → 성공 ✓
브라우저: 이벤트 발생 → 성공 ✓
X 플랫폼: 콘텐츠 삭제 → ??? (침묵)
데이터베이스: "게시됨"으로 기록 → 성공 ✓
대시보드: 83개 참여 → 훌륭 ✓
현실: 실제 게시 16개
모니터링은 브라우저에서 멈췄다. 플랫폼 필터는 블랙박스다.
해결책: 페이지 새로고침을 통한 게시 검증
새 verifyReplyPosted 함수는 렌더링 대기 후 페이지를 새로고침하고 DOM에서 텍스트를 스캔한다:
export async function verifyReplyPosted(
page: Page,
replyText: string,
parentTweetId: string,
): Promise<{ verified: boolean; replyTweetId: string | null }> {
await new Promise(r => setTimeout(r, 3000));
await page.reload({ waitUntil: "domcontentloaded" });
await new Promise(r => setTimeout(r, 5000));
await page.evaluate("window.scrollBy(0, 1000)");
await new Promise(r => setTimeout(r, 2000));
const result = await page.evaluate(`
(function() {
var snippet = '${safe}'; // 처음 30자
var articles = document.querySelectorAll(
'article[data-testid="tweet"]'
);
for (var i = 0; i < articles.length; i++) {
var textEl = articles[i].querySelector(
'[data-testid="tweetText"]'
);
if (!textEl) continue;
var text = (textEl.innerText || '').slice(0, 60);
if (text.indexOf(snippet.slice(0, 30)) !== -1) {
var links = articles[i].querySelectorAll(
'a[href*="/status/"]'
);
return { verified: true, replyTweetId: realId };
}
}
return { verified: false, replyTweetId: null };
})()
`);
return result;
}
- 새로고침으로 캐시 없는 신선한 스레드 확보.
article[data-testid="tweet"]와[data-testid="tweetText"]로 검색.- ID는 기사 내
href링크에서 추출. verified: true인 경우만 데이터베이스에 로그.
이 방법이 어디서나 통하는 이유
X에 국한된 문제가 아니다. 소셜 미디어, 폼, 결제, 배포 등 플랫폼들이 콘텐츠를 조용히 거부한다.
주요 검증 원칙:
- 실제 사용자 흉내: 새로고침 + 스크롤로 지연 로딩 유발.
- 대상 DOM에서 마커(텍스트 조각) 찾기.
- 실제 요소에서 ID 추출, URL이나 이벤트 아님.
- 증거 없으면 로그 생략.
- 실패 즉시 로그.
핵심 요약
- DOM 이벤트 ≠ 플랫폼 성공: 클릭이 게시를 보장하지 않음.
- 조용한 삭제: 스팸 필터는 우회 방지를 위해 알림 없이 작동.
- DOM 스캔 검증: 새로고침 + 텍스트/ID 검색.
- 개선된 지표: 94% 검증 성공률, 실패 즉시 확인.
- 광범위 적용: 모든 자동 게시/제출에 사용자 같은 검증 필요.
구현 후 에이전트는 실제 게시만 추적하고, 대시보드는 진실을 보여준다. 이 방법은 유사 필터를 둔 다른 플랫폼에도 확장 가능하다.
— Editorial Team
아직 댓글이 없습니다.