이번엔 첫번쨰 트러블 슈팅을 작성해 볼까 하는데 사실 트러블슈팅이랄것도 아니라 이게 안되서 막혔는데 이렇게 하니까 됐어요
쨔잔 멋지죠? 이런느낌이라 트러블슈팅아닌 슈팅을 작성해볼까 합니다.
정리해 보자면,
첫 번째로 썸네일 URL이 NULL로 저장되는 문제. DC Inside 게시글을 수집할 때 thumbnail_url 필드가 NULL로 저장되는데, Reddit은 정상적으로 썸네일 URL이 저장되더라고요. dcapi.read.post()로 가져온 post_data의 images 필드가 비어있었어서 그랬습니다.
두 번째로는 selenium_title에는 thumbnail 필드가 존재한다는 점. dcapi.read.title_selenium으로 가져온 데이터에는 thumbnail 필드가 있는데, dcapi.read.post()로 가져온 상세 정보에는 썸네일이 없었습니다.
세 번째 문제는 403 Forbidden 에러. 썸네일 URL을 추출한 후 브라우저에서 직접 열면 403 에러가 발생했는데, Reddit 이미지는 정상적으로 표시되었습니다.
네 번째로는 이미지 다운로드 문제. 프록시 URL을 통해 이미지를 제공했는데, 브라우저에서 이미지가 표시되지 않고 다운로드되더라고요. 개발자 도구에서 확인해보니 content-type이 application/octet-stream으로 나왔습니다.
원인 분석
NULL 값이 나온 이유는 dcapi.read.post()가 반환하는 post_data의 images 필드가 비어있었고, content HTML에서 이미지를 추출하려 했지만 실패했어요. 그리고 selenium_title의 thumbnail 필드를 사용하지 않았던 것도 원인이었습니다.
403 에러가 발생한 이유는 DC Inside 서버가 Referer 헤더를 검증해서 직접 접근을 차단하기 때문이었어요. Reddit은 이런 제한이 없어서 바로 접근이 가능했습니다.
다운로드 문제는 DC Inside 서버가 Content-Type을 application/octet-stream으로 반환해서 브라우저가 이를 일반 파일로 인식하고 다운로드 처리했기 때문이에요. 실제 이미지 데이터는 정상이었지만 Content-Type이 잘못 설정되어 있었습니다.
해결 방법
1. selenium_title의 thumbnail 필드 우선 사용
dcapi.read.post()로 가져온 데이터에 썸네일이 없을 경우, selenium_title에서 가져온 thumbnail 필드를 우선 사용하도록 했지요.
if post_info.get('thumbnail'):
thumbnail_url = post_info.get('thumbnail')
self.logger.info(f"게시글 {post_num}: 썸네일 추출 성공 (selenium_title): {thumbnail_url[:150]}")
2. content HTML에서 이미지 추출 (백업)
selenium_title에도 없을 경우, post_data['content'] HTML에서 이미지 URL을 추출하도록 했어요.
if not thumbnail_url:
content = post_data.get('content', '')
if content:
import re
from html import unescape
# <img src="..." /> 패턴 찾기
img_pattern = r'<img[^>]+src\s*=\s*["\']?([^"\'>\s]+)["\']?[^>]*>'
matches = re.findall(img_pattern, content, re.IGNORECASE)
if matches:
decoded_matches = [unescape(url) for url in matches]
# 실제 게시글 이미지 필터링 (dcimg, dccdn 도메인만)
image_domains = ['dcimg', 'dccdn', 'viewimage']
filtered_matches = [
url for url in decoded_matches
if any(domain in url for domain in image_domains)
and 'nstatic' not in url
and 'logo' not in url.lower()
]
if filtered_matches:
thumbnail_url = filtered_matches[0]
3. 이미지 프록시 엔드포인트 구현
class ThumbnailProxyView(APIView):
"""
썸네일 이미지 프록시 뷰
DC Inside 등 Referer 헤더가 필요한 이미지를 프록시를 통해 제공합니다.
Reddit 이미지는 그대로 리다이렉트합니다.
"""
permission_classes = [AllowAny]
def get(self, request):
image_url = request.query_params.get('url')
# ... (상세 구현)
4. Serializer에서 자동 url 변환
BaseSocialMediaPostSerializer의 to_representation 메서드에서 DC Inside 이미지 URL을 프록시 URL로 자동 변환하고, Reddit 이미지는 원본 URL을 유지하도록 했어요.
def to_representation(self, instance):
representation = super().to_representation(instance)
thumbnail_url = representation.get('thumbnail_url')
if thumbnail_url and ('dcinside' in thumbnail_url.lower() or
'dcimg' in thumbnail_url.lower() or
'dccdn' in thumbnail_url.lower()):
# 프록시 URL로 변환
encoded_url = quote(thumbnail_url, safe='')
request = self.context.get('request')
if request:
base_url = request.build_absolute_uri('/')[:-1]
representation['thumbnail_url'] = f"{base_url}/api/collector/thumbnail-proxy/?url={encoded_url}"
return representation
5. Content-Type 자동 감지 (매직 넘버 사용)
서버가 반환한 Content-Type이 application/octet-stream인 경우, 실제 이미지 데이터의 매직 넘버로 타입을 감지하도록 했어요.
if len(image_data) >= 4:
# JPEG: FF D8 FF
if image_data[:3] == b'\xff\xd8\xff':
content_type = 'image/jpeg'
# PNG: 89 50 4E 47
elif image_data[:4] == b'\x89PNG':
content_type = 'image/png'
# GIF: 47 49 46 38 (GIF8)
elif image_data[:4] == b'GIF8':
content_type = 'image/gif'
# WebP: RIFF...WEBP
elif len(image_data) >= 12 and image_data[:4] == b'RIFF' and image_data[8:12] == b'WEBP':
content_type = 'image/webp'
해결 과정 요약
NULL 값 문제는 selenium_title의 thumbnail 필드를 우선 사용하고, 없으면 content HTML에서 이미지 URL을 추출하도록 해서 해결했어요. 그리고 DCInsidePostSerializer의 Meta.fields에 'thumbnail_url'을 추가했습니다.
403 에러는 이미지 프록시 엔드포인트를 구현하고, Referer 헤더를 포함해서 DC Inside 서버에서 이미지를 가져오도록 했어요. Serializer에서 자동으로 프록시 URL로 변환하도록 했습니다.
다운로드 문제는 매직 넘버로 실제 이미지 타입을 감지하고, Content-Type을 올바르게 설정했어요. image/jpeg, image/png 등으로 설정하고, Content-Disposition을 inline으로 설정했습니다.
포인트
DB에는 원본 URL을 저장하는데, 이건 DB/Admin에서 원본 URL이 보이는 게 정상이에요. API 응답에서만 프록시 URL로 변환하는 거고, 이건 Serializer의 to_representation에서 변환합니다.
매직 넘버로 Content-Type을 감지하는 건 서버 응답의 Content-Type이 잘못된 경우 실제 데이터로 판단하기 위해서예요. 그리고 Content-Disposition을 inline으로 설정해서 브라우저에서 직접 표시되도록 했습니다.
'AI_RSS_트래픽 프로젝트' 카테고리의 다른 글
| 모르는 상태로 하는 RSS&분석&RAG 프로젝트(15) 크로스 플랫폼 키워드 추출 및 빈도 분석 (0) | 2025.12.30 |
|---|---|
| 모르는 상태로 하는 RSS&분석&RAG 프로젝트(14) 데이터 분석(계획 및 공부) (0) | 2025.12.30 |
| 모르는 상태로 하는 RSS&분석&RAG 프로젝트(12) 데이터 수집(6) (1) | 2025.12.16 |
| 모르는 상태로 하는 RSS&분석&RAG 프로젝트(11) 데이터 수집(5) (0) | 2025.12.10 |
| 모르는 상태로 하는 RSS&분석&RAG 프로젝트(10) 데이터 수집(4) (0) | 2025.12.08 |