현우봇 v2.0: 지피터스 사례글 자동 수집부터 이미지 OCR까지, 그리고 코드 리팩토링의 여정

소개

지난 1차 개발기에서 URL 요약과 AI 검색 기능을 탑재한 현우봇을 소개했었죠. 그런데 막상 스터디원들과 사용하다 보니 아쉬운 점들이 하나둘 보이기 시작했습니다.

"이건 왜 요약이 안돼요?"
"현우봇이 왜 자고 있죠?"

특히 매주 스터디 전날, 일일이 지피터스 사이트에 들어가서 우리 스터디 사례글을 확인하는 것도 좋지만 사례글 링크를 한 번에 가져와 정리해 주면 좋겠다 싶었어요.

그래서 결심했습니다. "현우봇아, 너가 알아서 사례글을 정리해줘!"

진행 방법

1. 지피터스 사례글 자동 수집의 도전

처음엔 단순하게 생각했습니다. "그냥 지피터스 사이트 크롤링하면 되겠지?"

python
# 처음 시도한 순진한 접근
async def crawl_gpters(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    # ... 이렇게 하면 될 줄 알았죠 😅

하지만 현실은... 빈 페이지만 돌아왔습니다. 그래서 Selenium으로 크롤링에 성공은 했지만, 속도가 너무 느렸습니다.

단순히 "최근 7일"로 하면 스터디 시간에 따라 어긋날 수 있었습니다. 그래서 각 스터디의 요일과 시간을 고려한 로직을 구현했죠.

python
@classmethod
def calculate_study_date_range(cls, study_name: str) -> Tuple[date, date]:
    """스터디 일정 기반 날짜 범위 계산"""
    study_info = STUDY_INFO.get(study_name)
    today = cls.get_today()
    study_day = study_info["day_of_week"]
    
    # 오늘이 스터디날인지, 스터디 시간 전인지 확인
    # 지난 스터디 다음날부터 다음 스터디까지의 사례글 수집


아무튼 지난 일요일 저녁에 느린 스터디 사례글 정리봇을 공개했는데, 관계자 도움으로 지피터스 홈페이지에서 해당 정보만 빠르게 제공받을 수 있었고, 사례글을 정리해 주는 봇을 완성했습니다. 짜잔~

전화에 문자 메시지의 스크린 샷
전화에 문자 메시지의 스크린 샷

2. URL 요약이 안되던 사이트도 요약해랏!! (크롤링 고도화 : 3단계 폴백 시스템)

기존에 요약이 안 되던 사이트들(조선일보, Streamlit 앱 등)을 위해 3단계 폴백 시스템을 구현했습니다.

python
async def get_dynamic_content_async(url: str) -> str:
    # 1단계: Firecrawl API (가장 빠름)
    if not bypass_methods or "firecrawl" in bypass_methods:
        try:
            content = await ContentExtractor._get_content_with_firecrawl_async(url)
            if content: return content
        except: pass
    
    # 2단계: ScrapingBee API (안정적)
    if not bypass_methods or "scrapingbee" in bypass_methods:
        try:
            content = await ContentExtractor._get_content_with_scrapingbee(url)
            if content: return content
        except: pass
    
    # 3단계: Playwright (최후의 수단)
    if PLAYWRIGHT_AVAILABLE:
        try:
            content = await ContentExtractor._get_content_with_playwright(url)
            if content: return content
        except: pass

특히 조선일보는 광고와 팝업이 많아서 특별 처리가 필요했습니다:

python
# 리소스 로딩 차단 (이미지, 폰트, 비디오 등)
await page.route('**/*.{png,jpg,jpeg,gif,svg,pdf,woff,woff2,ttf,mp4,webm,ogg}', 
    lambda route: route.abort())

# 봇 감지 방지 스크립트
await page.add_init_script("""
    Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined
    });
""")

3. 이미지 OCR 기능 추가 - 진행중(90% 완성)

"이 이미지에 뭐라고 써있어?"라는 질문을 받을 때마다 답답했던 현우봇. 이제는 이미지도 이해합니다!

javascript
// 메신저봇R 코드에서 이미지 감지
if (imageDB && imageDB.getImageBase64 && imageDB.getImageBase64()) {
    var base64Image = imageDB.getImageBase64();
    ImageSessions.saveImage(sender, base64Image);
    replier.reply("이미지가 수신되었습니다. 1분 이내에 '//'로 시작하는 메시지를 보내면 이미지와 함께 처리됩니다.");
}

4. 코드 리팩토링: 모듈화의 여정

처음엔 main.py 하나에 3000줄이 넘는 코드가 있었습니다. 기능을 추가할 때마다 스크롤의 압박이... 😵

# Before (v1.2)
main.py (3000+ lines)

# After (v1.3)
main.py (800 lines)
├── youtube_service.py     # YouTube 관련 기능
├── scraping_service.py    # 웹 스크래핑 기능
├── gpters_service.py      # GPTers API 기능
├── config/
│   ├── newspaper_configs.py
│   └── dynamic_sites.py
└── utils/
    └── api_status_checker.py

환경변수도 .env 파일로 분리했습니다:

bash
GEMINI_API_KEY=your_api_key
PERPLEXITY_API_KEY=your_api_key
GPTERS_API_TOKEN=your_api_token
SCRAPINGBEE_API_KEY=your_api_key
FIRECRAWL_API_KEY=your_api_key

결과와 배운 점

성공적인 결과들

  1. 스터디글 자동 정리: 이제 카톡방에서 "스터디글"만 입력하면 자동으로 정리됩니다!

    # 16기 CTO 사례글 목록 (11.21. ~ 11.28. / 9개)
    
    ## 작성인원(9명): 이사라, WOORIM LEE, 김현우...
    
    1. 이사라 / 윈드서프 너 뭐 돼? (feat. 동그란토마토)
    https://www.gpters.org/...
  2. 향상된 크롤링 성공률: 조선일보 95%, Streamlit 앱 90%, GPTers 100% 성공!

  3. 이미지 이해 능력: 스크린샷, 차트, 텍스트 이미지 모두 분석 가능

시행착오와 교훈

  1. API 우선 접근: 크롤링보다 API가 있다면 무조건 API를 사용하자

  2. 폴백의 중요성: 한 가지 방법에만 의존하지 말고 여러 대안을 준비

  3. 모듈화는 필수: 기능이 늘어나면 모듈화로 유지보수 용이하게~

  4. 봇 감지 대응: User-Agent 랜덤화, 지연 시간, 브라우저 지문 위장 등

앞으로의 계획

  1. 8시만 되면 조간뉴스 자동으로 슉~: n8n과 연결

4
2개의 답글

👉 이 게시글도 읽어보세요