바로가기
실험실

클린리뷰

협찬글을 가려내고 내돈내산만 남기는 맛집 검색

화면


01
01

검색창 하나. 워드마크를 두 번 닦아 내고 나면 검색어 예시가 위에서 내려온다

확인 중
02확인 중

글을 하나씩 열어 보는 동안 글자를 빛이 훑고 지나간다

검색 결과
03검색 결과

협찬으로 확인된 글은 흐려지고 그 위에 표시가 뜬다

업체 확인
04업체 확인

눌러서 흐림을 풀면 어느 체험단 업체인지 나온다

협찬 숨기기
05협찬 숨기기

켜면 협찬글이 목록에서 아예 빠진다

더 보기
06더 보기

30건씩 이어 붙인다. 다 보면 몇 건을 확인했는지 알려 준다

웹과 폰
07웹과 폰

같은 결과가 폰에서는 한 열로 접히고 발췌문이 빠진다

기술 스택


02
TypeScriptNext.jsReactVercelVitestChrome

데이터와 API


03
네이버 검색·블로그

검색어로 블로그 글 목록을 받고, 글마다 본문을 열어 협찬 흔적을 찾는다

구상


04
검색 결과에서 협찬 글을 걸러내고 내돈내산 글만 남긴다
왜 협찬으로 봤는지 근거를 같이 보여준다
네이버 검색 결과를 걸러 자체 사이트에 목록으로 띄운다
협찬 배너 이미지를 지문으로 찾아낸다
이미지에만 박힌 표기를 OCR 로 읽는다

기획


05
제약

외부 LLM 도 클라우드 OCR 도 쓰지 않기로 했어요. 판정이 공짜로 끝나야 혼자 계속 굴릴 수 있으니까요

성공 기준

멀쩡한 글을 협찬으로 몰지 않는 것. 놓치는 건 감수하더라도 잘못 찍지는 않기로 했어요

넣지 않은 것

내돈내산 도장은 안 찍습니다. 협찬으로 확인된 것만 표시하고, 표시가 없다고 깨끗한 글이라고 말하지 않아요

인터뷰


06

어떤 서비스인가요?

네이버에 "성수 카페"를 치면 블로그 글 30건을 대신 열어 보고, 체험단 업체의 흔적이 남은 글을 흐리게 덮어 주는 검색 서비스예요. 어느 업체인지도 같이 보여 줍니다

어떤 계기로 만들게 됐나요?

네이버에 "건대 맛집", "건대 고기집" 이런 식으로 쳐 놓고 블로그를 쭉 훑는 게 제 검색 방식이었는데, 열에 여덟아홉이 협찬글이더라고요. 글 끝에 협찬 스티커가 붙어 있거나 소정의 원고료를 받았다고 적혀 있고요

협찬을 받으면 아무래도 좋게 쓸 수밖에 없잖아요. 싫은 소리를 못 하게 되니까요. 그래서 진짜 내돈내산 글만 골라 보고 싶었어요

주로 어떤 분이 쓰나요?

저처럼 지역에 업종을 붙여 검색해 놓고 블로그 목록을 빠르게 스캔해 내려가는 사람이요

유저 플로우


07
  1. 01
    검색창에 지역과 업종을 친다

    `성수 카페` 처럼요. 홈에는 검색창 하나뿐입니다.

  2. 02
    확인이 끝나기를 기다린다

    글 30건을 하나씩 열어 봅니다. 다 끝나야 목록이 한 번에 그려집니다.

    처음 찾는 검색어4~6초누가 이미 찾은 검색어1초 안
  3. 03
    목록을 훑는다

    협찬으로 확인된 글은 흐려져 있고, 깨끗한 글은 아무 표시 없이 평소처럼 보입니다.

  4. 04
    흐린 글이 궁금하면 누른다

    흐림이 풀리고 어느 업체인지 나옵니다. 다시 덮을 수도 있습니다.

    그래도 읽는다원문으로됐다다시 가리기
  5. 05
    마음에 드는 글을 연다

    줄 어디를 눌러도 네이버 원문으로 넘어갑니다.

구조


08
보내는 것되돌아오는 것

개발 과정


09
  1. 01

    브라우저 확장으로 간단하게 시작했어요

    "건대 맛집"을 검색해 놓고 블로그를 훑다 보면 열에 여덟아홉이 협찬글이에요. 스크롤을 한참 내려 마지막 줄에서 "소정의 원고료를 받아 작성했습니다"를 보고 나서야 아 이것도 광고였구나 하게 되고요.

    사실 처음 떠올린 그림은 검색 사이트였습니다. 제 주소로 검색어를 넣으면 협찬을 걷어낸 목록이 나오는 거요. 그런데 따져 보니 걸리는 게 많아 보였어요. 네이버가 주는 목록을 제 힘으로 다시 만들어야 하고, 검색 한 번에 글이 서른 개씩 붙는 요청이 전부 서버 한 곳에서 나가고, 그러면 서버 비용도 듭니다. 혼자 취미로 굴릴 물건인데 무겁다고 봤어요.

    그래서 브라우저 확장으로 접었습니다. 목록은 네이버가 이미 그려 주고 있으니 그 위에 판정만 얹으면 되고, 요청도 각 사용자의 브라우저가 자기 몫만 자기 IP 로 쏩니다. 서버가 아예 없으니 비용이 0 이고요. 제일 적은 노력으로 "이게 쓸모가 있긴 한가"를 확인할 수 있는 모양이었어요.

    붙인 것
    TypeScript
  2. 02

    판정 로직부터 떼어 놨어요

    DOM 도 chrome.* 도 모르는 순수 함수로 core/ 를 만들었습니다. 브라우저를 안 띄우고도 픽스처 몇 개로 결과가 뒤집히는지 바로 볼 수 있어서였는데, 나중에 이 결정이 훨씬 크게 값을 했어요. 사이트를 만들 때 판정 쪽은 손댈 게 하나도 없었거든요.

    붙인 것
    Vitest
  3. 03

    판정은 브라우저 안에서 끝내기로 했어요

    처음엔 본문을 LLM 에 던지면 제일 정확하겠다 싶었는데, 검색 결과 한 페이지가 서른 개고 스크롤할 때마다 늘어나니까 호출 수가 감당이 안 되겠더라고요. 사용자가 자기 API 키를 넣게 하는 것도 이상하고요.

    그래서 사전 기반으로 갔습니다. 체험단 플랫폼 이름과 공정위 표기 문구를 JSON 으로 모으고, 감성·솔직함·구체성·광고성 사전을 따로 뒀어요. 여기에 긍정어 도배 지수를 얹어서, 단점이 한 줄도 없이 칭찬만 가득한 글도 잡아 보려고 했습니다.

    키워드 매칭은 회피가 쉬워서 매칭 전에 정규화를 넣었어요. zero-width 문자를 지우고, 분리된 자모를 털고, 공백을 다 없앤 압축본으로 비교합니다. 소 정 의 원 고 료 처럼 띄어 놓거나 글자 사이에 안 보이는 문자를 끼워 넣어도 걸리게요.

  4. 04

    목록에서 판정하려면 본문을 미리 읽어야 했어요

    검색 결과 카드에는 제목과 요약 두어 줄뿐이라 협찬 표기가 안 들어 있어요. 표기는 늘 글 맨 아래에 있고요. 결국 카드마다 본문을 미리 가져와야 했는데, 이게 컨텍스트 문제로 번졌습니다(→ 시행착오).

    가져오는 게 되고 나서는 크롤러처럼 안 보이게 만드는 데 신경을 썼어요. 뷰포트에 곧 들어올 카드만, 동시 3건까지, 요청 사이 최소 간격에 랜덤 지터를 얹고 결과는 캐시합니다. 이 값들은 전부 config.ts 한 곳에 모아 뒀어요.

  5. 05

    협찬 표기를 이미지로 박아 둔 글이 많더라고요

    텍스트 매칭이 돌기 시작하니까 곧바로 빈틈이 보였습니다. 표기를 이미지 배너로 넣으면 본문 텍스트에는 아무것도 안 남아요. 처음엔 배너 이미지의 지문(dHash)을 떠서 대조하려고 했는데, 대조할 DB 를 제가 직접 모아야 한다는 데서 막혔습니다(→ 시행착오).

    대신 배너 이미지의 출처 도메인을 봤어요. 체험단 업체는 자기 도메인에 배너를 호스팅하니까, reviewnote.cloud 가 이미지 주소에 들어 있으면 그것만으로 확정에 가깝습니다. 네이버 이미지 프록시로 감싸인 경우도 있어서 URL 을 디코드한 뒤 안쪽까지 훑게 했고요.

    나중에 실제 글 475건을 모아 재 봤는데, 텍스트로 걸린 게 24건인데 도메인으로 걸린 게 149건이었습니다. 사전을 키우는 것보다 업체 도메인을 채우는 게 훨씬 남는 장사였어요.

    같은 맥락에서 해시태그도 따로 긁습니다. #협찬 을 본문 밖 태그 영역에만 달아 두는 글이 있는데, 본문 컨테이너만 보면 통째로 놓쳐요.

  6. 06

    재 보니 점수가 필요 없었어요

    100점에서 감점·가점해 세 등급을 매기는 채점기를 공들여 만들어 뒀는데, 표본 475건으로 실측해 보니 점수만으로 걸린 글이 0건이었습니다(→ 시행착오). 걸린 160건은 전부 업체 도메인이나 명시 표기 같은 확정 신호였어요.

    그래서 밖으로 나가는 건 예/아니오 하나로 줄였습니다. 신뢰도 점수도, 협찬 의심 등급도 없앴어요. 100점 만점의 기준을 사용자가 알 길이 없으니 숫자가 판단에 도움이 안 되고, 노랑 등급은 애초에 비어 있었으니까요.

    대신 놓치는 쪽을 명시했습니다. 표본에서 외부 도메인이 아예 없는 글이 198건인데, 배너를 자기 블로그에 다시 올리면 출처가 지워져 원천적으로 못 잡아요. 그래서 화면 아래에 "표시가 없다고 협찬이 아닌 것은 아닙니다"를 붙였습니다.

  7. 07

    일반 사용자를 생각하니 결국 사이트여야 했어요

    확장앱은 제가 쓰기엔 편한데, 남에게 권할 물건이 아니었습니다. 스토어에 들어가서 설치하고 권한을 허용해야 비로소 시작이고, 무엇보다 맛집은 밖에서 찾는데 모바일 크롬은 확장을 아예 지원하지 않아요. 링크 하나 보내면 바로 열리는 물건이어야 했습니다.

    그래서 1번에서 접어 뒀던 검색 사이트를 다시 꺼냈는데, 막상 손대 보니 제약이라고 생각했던 게 대부분 착각이었어요. 목록을 제가 만들어야 한다고 여겼던 게 제일 컸는데, 네이버 검색 결과가 서버 HTML 에 30건씩 그대로 담겨 옵니다. API 키도 인증도 없이 주소 하나로 받아서 파싱하면 끝이었어요. 요청량 걱정도 판정 결과를 캐시하면 같은 검색어는 한 번만 읽으면 되니 생각만큼 크지 않았고요.

    남은 일도 적었습니다. 판정 엔진이 DOM 을 모르는 순수 함수라 그대로 옮겨 갔고, 오프스크린 문서가 하던 fetch → 파싱 → 판정 을 서버에서 하는 것도 같은 모양이었어요. 새로 짠 건 검색 결과 목록을 뽑는 파서 하나입니다.

    화면은 최대한 덜어냈습니다. 홈은 검색창 하나, 결과는 네이버 목록과 같은 모양에 협찬 표시만 얹었어요. 깨끗한 글에는 아무것도 안 붙입니다 — 표시는 할 말이 있을 때만 나오는 게 맞으니까요.

    붙인 것
    Next.jsReactVercel

시행착오


10
증상감성 사전, 긍정어 도배 지수, 솔직함·구체성 가점까지 얹어 세 등급을 매기게 만들어 뒀는데, 실제로 쓰다 보니 `협찬 의심`(노랑) 등급을 본 기억이 없었습니다.
시도처음엔 임계값이 잘못 잡힌 줄 알고 문턱을 낮춰 볼까 했어요. 그런데 그러면 멀쩡한 글까지 노랑이 될 것 같아서, 먼저 검색어 16개로 실제 글 475건을 모아 재 봤습니다.
결론점수만으로 걸린 글이 **0건**이었습니다. 걸린 160건은 전부 업체 도메인·명시 표기 같은 확정 신호였어요. 산수를 따져 보니 당연했습니다 — 도배 지수는 최대 8점쯤 깎고 약한 단서는 건당 10점인데, 100점에서 40점 아래로 내리려면 60점이 필요합니다. 확정 신호(70점) 없이는 문턱을 넘을 수가 없었어요. 그래서 밖으로 나가는 판정을 예/아니오 하나로 줄였습니다. 점수와 등급은 내부 진단값으로만 남겼고요.
배너 도메인까지 확인된 글인데 `협찬` 이 아니라 `협찬 의심` 으로 표시되는 게 눈에 띄었습니다.
블로그 글을 열면 콘텐츠 스크립트는 분명히 돌았는데 추출한 본문 길이가 0 이었습니다. 셀렉터를 바꿔도, 로딩을 더 기다려도 똑같았어요.
검색 결과 페이지에서 각 글의 본문을 미리 받아오려는데 어느 컨텍스트에서 해도 실패했습니다.
걸러지는 비율이 열에 셋 정도라 체감(여덟아홉)과 차이가 컸습니다. 놓친 글을 뒤져 보려고 네이버 외부 도메인을 빈도순으로 뽑았더니 1위가 `d3i7y4ugnppb9p.cloudfront.net` 이었어요. CloudFront 주소라 어느 업체인지 알 수가 없었습니다.
GitHub 를 연결해 배포하니 실패했습니다. 로그를 보면 컴파일도 정적 페이지 생성도 다 성공하고 맨 마지막에서만 죽었어요.

남은 것


11
  • 협찬 배너를 자기 블로그에 다시 올린 글은 못 잡아요.표본 475건 중 외부 도메인이 아예 없는 글이 198건인데, 그중 얼마가 협찬인지 알 방법이 지금은 없습니다.
  • 같은 배너를 이미지로만 걸어 둔 글은 배너 그림 자체를 대조해야 잡히는데, 대조할 배너를 아직 못 모았어요.슈퍼멤버스 배너처럼 규격이 일정한 것부터 모으면 위 198건 중 재사용된 배너는 걸립니다.
  • 식당이 직접 협찬하면서 표기를 이미지로만 넣은 경우는 글자를 읽어내는 수밖에 없어요.텍스트로 적힌 것은 이미 잡습니다.
  • 아는 체험단 업체가 아직 적어요.셋을 추가해 걸러지는 비율이 29%에서 43%로 올랐는데, 표본 475건에서는 더 안 나왔습니다. 표본을 늘려 같은 절차를 반복하면 롱테일이 나옵니다.
  • 가게 단위로 묶는 게 다음 목표예요."이 집은 블로그 후기 94건 중 내돈내산 14건" 같은 숫자는 글 하나를 걸러 주는 것과 급이 다른 정보인데, 가게 목록을 어디서 얻을지와 표본을 어떻게 정직하게 표기할지를 먼저 풀어야 합니다.