fix: 대표 이미지를 원본 기사 og:image 크롤링으로 변경
- Unsplash Source API 중단으로 기존 폴백 작동 안 함 - 원본 기사 URL에서 og:image / twitter:image 크롤링 (가장 확실) - 우선순위: RSS 이미지 → og:image 크롤링 → Pexels API - lxml 파서 사용 (이미 Docker에 설치됨) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -206,9 +206,38 @@ def build_json_ld(article: dict, blog_url: str = '') -> str:
|
|||||||
return f'<script type="application/ld+json">\n{json.dumps(schema, ensure_ascii=False, indent=2)}\n</script>'
|
return f'<script type="application/ld+json">\n{json.dumps(schema, ensure_ascii=False, indent=2)}\n</script>'
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_og_image(url: str) -> str:
|
||||||
|
"""원본 기사 URL에서 og:image 메타태그 크롤링"""
|
||||||
|
if not url or not url.startswith('http'):
|
||||||
|
return ''
|
||||||
|
try:
|
||||||
|
resp = requests.get(url, timeout=10, headers={
|
||||||
|
'User-Agent': 'Mozilla/5.0 (compatible; BlogBot/1.0)',
|
||||||
|
})
|
||||||
|
if resp.status_code != 200:
|
||||||
|
return ''
|
||||||
|
soup = BeautifulSoup(resp.text, 'lxml')
|
||||||
|
# og:image
|
||||||
|
og = soup.find('meta', property='og:image')
|
||||||
|
if og and og.get('content', '').startswith('http'):
|
||||||
|
return og['content']
|
||||||
|
# twitter:image
|
||||||
|
tw = soup.find('meta', attrs={'name': 'twitter:image'})
|
||||||
|
if tw and tw.get('content', '').startswith('http'):
|
||||||
|
return tw['content']
|
||||||
|
# 본문 첫 번째 큰 이미지
|
||||||
|
for img in soup.find_all('img', src=True):
|
||||||
|
src = img['src']
|
||||||
|
if src.startswith('http') and not any(x in src.lower() for x in ['logo', 'icon', 'avatar', 'banner', 'ad']):
|
||||||
|
return src
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"og:image 크롤링 실패 ({url}): {e}")
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def fetch_featured_image(article: dict) -> str:
|
def fetch_featured_image(article: dict) -> str:
|
||||||
"""대표 이미지: 원본 소스 이미지 → Pexels → Unsplash 순으로 시도"""
|
"""대표 이미지: RSS 이미지 → og:image 크롤링 → Pexels 순으로 시도"""
|
||||||
# 1) 원본 RSS 소스 이미지 (가장 우선)
|
# 1) RSS 수집 시 가져온 소스 이미지
|
||||||
source_image = article.get('source_image', '')
|
source_image = article.get('source_image', '')
|
||||||
if source_image and source_image.startswith('http'):
|
if source_image and source_image.startswith('http'):
|
||||||
try:
|
try:
|
||||||
@@ -216,18 +245,21 @@ def fetch_featured_image(article: dict) -> str:
|
|||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
return source_image
|
return source_image
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # 접근 불가면 다음 방법으로
|
pass
|
||||||
|
|
||||||
# 2) 검색 키워드: 태그 또는 코너 기반
|
# 2) 원본 기사 URL에서 og:image 크롤링
|
||||||
tags = article.get('tags', [])
|
source_url = article.get('source_url', '')
|
||||||
if isinstance(tags, str):
|
og_image = _fetch_og_image(source_url)
|
||||||
tags = [t.strip() for t in tags.split(',')]
|
if og_image:
|
||||||
corner = article.get('corner', '')
|
return og_image
|
||||||
query = tags[0] if tags else corner or 'technology'
|
|
||||||
|
|
||||||
# Pexels API (키가 있을 때)
|
# 3) Pexels API (키가 있을 때)
|
||||||
pexels_key = os.getenv('PEXELS_API_KEY', '')
|
pexels_key = os.getenv('PEXELS_API_KEY', '')
|
||||||
if pexels_key:
|
if pexels_key:
|
||||||
|
tags = article.get('tags', [])
|
||||||
|
if isinstance(tags, str):
|
||||||
|
tags = [t.strip() for t in tags.split(',')]
|
||||||
|
query = tags[0] if tags else article.get('corner', 'technology')
|
||||||
try:
|
try:
|
||||||
resp = requests.get(
|
resp = requests.get(
|
||||||
'https://api.pexels.com/v1/search',
|
'https://api.pexels.com/v1/search',
|
||||||
@@ -242,10 +274,7 @@ def fetch_featured_image(article: dict) -> str:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Pexels 이미지 검색 실패: {e}")
|
logger.warning(f"Pexels 이미지 검색 실패: {e}")
|
||||||
|
|
||||||
# Unsplash Source (API 키 불필요, 직접 URL)
|
return ''
|
||||||
import urllib.parse
|
|
||||||
encoded = urllib.parse.quote(query)
|
|
||||||
return f'https://source.unsplash.com/1200x630/?{encoded}'
|
|
||||||
|
|
||||||
|
|
||||||
def build_full_html(article: dict, body_html: str, toc_html: str) -> str:
|
def build_full_html(article: dict, body_html: str, toc_html: str) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user