
๋งํฌ ์ ์ฅ ์๋น์ค๋ฅผ ๋ง๋ค๋ค ๋ณด๋ฉด ๋ฐ๋์ ๋ง์ฃผ์น๋ ์ด์๊ฐ ํ๋ ์์ต๋๋ค.
๋ฐ๋ก Open Graph(OG) ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ๋น ๋ฅด๊ณ ์ ํํ๊ฒ ๋ถ๋ฌ์ฌ ๊ฒ์ธ๊ฐ์
๋๋ค.
๋งํฌ๋ฅผ ์ ์ฅํ์ ๋, ์ธ๋ค์ผยท์ ๋ชฉยท์ค๋ช
์ด ์๋์ผ๋ก ๋จ๋ ๊ทธ ๊ฒฝํ์ ๋ง๋ค๊ธฐ ์ํด
์ฐ๋ฆฌ๋ ์ด๋ค ๋ฐฉ์์ผ๋ก OG ๋ฐ์ดํฐ๋ฅผ ํฌ๋กค๋งํ๊ณ , ์ด๋ค ๊ตฌ์กฐ๋ฅผ ์ค๊ณํ์๊น์?
์ค๋์ ๊ทธ ๊ฐ๋ฐ๊ธฐ ๊ณผ์ ์ ๊ณต์ ํฉ๋๋ค.
์ฒ์์๋ open-graph-scraper ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๊น ํ์ง๋ง, ์ค์ ๋ก ์ ์ฉํด๋ณด๋ ๋ ๊ฐ์ง ์ด์ ๋ก ํฌ๊ธฐํ์ต๋๋ค.
์ฒซ์งธ, ์๋์
๋๋ค. ํ๊ท 2~3์ด ์ ๋๋ก ์๋ต ์๋๊ฐ ๋๋ ค ์ฌ์ฉ์ ๊ฒฝํ์ ์ํฅ์ ์คฌ์ต๋๋ค.
์ง์ ๊ตฌํํ FastAPI ๊ธฐ๋ฐ ์ ์ ํฌ๋กค๋ฌ๋ 0.2 ~ 0.4์ด๋ก ์ฝ 10๋ฐฐ ๋นจ๋์ต๋๋ค.
๋์งธ, ๊ฐ์ ธ์ค์ง ๋ชปํ๋ ์ฌ์ดํธ๊ฐ ๋๋ฌด ๋ง์์ต๋๋ค.
JavaScript๋ก OG ํ๊ทธ๋ฅผ ๋ ๋๋งํ๊ฑฐ๋, SPA ๊ตฌ์กฐ๋ก ๋ ํ์ด์ง๋ ์ ๋ณด๋ฅผ ์ ๋๋ก ๋ถ๋ฌ์ค์ง ๋ชปํ์ฃ .
์ด๋ฌํ ๋ฌธ์ ๋ก ์ธํด ์๋์ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋ชจ๋ ๋ง์กฑ์ํค๊ธฐ ์ํด ์ง์ ํฌ๋กค๋ฌ๋ฅผ ์ค๊ณํ๊ฒ ๋์์ต๋๋ค.
์ด๊ธฐ์๋ FastAPI ๊ธฐ๋ฐ์ ์ ์ ํฌ๋กค๋ฌ๋ฅผ ์ง์ ๋ง๋ค์์ต๋๋ค.
์ง์ ๊ตฌํํ์ ๋ ๊ฒฐ๊ณผ๋?
import requests
from bs4 import BeautifulSoup
def fetch_og_static(url: str) -> dict:
headers = {
"User-Agent": "Mozilla/5.0"
}
try:
response = requests.get(url, headers=headers, timeout=5)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
def get_meta(property_name):
tag = soup.find("meta", property=property_name)
return tag["content"] if tag and tag.get("content") else ""
og_data = {
"title": get_meta("og:title"),
"description": get_meta("og:description"),
"image": get_meta("og:image"),
}
return og_data
except Exception as e:
print(f"[ERROR] Failed to fetch OG data: {e}")
return {}
ํ์ง๋ง ํฐ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
JavaScript๋ก ๋ ๋๋ง๋๋ OG ํ๊ทธ๋ SPA ๊ตฌ์กฐ์ ํ์ด์ง์์๋ meta ์ ๋ณด๊ฐ ์์ ํฌํจ๋์ง ์๊ฑฐ๋,
์ด๊ธฐ ์์ฒญ ์์ ์๋ ์กด์ฌํ์ง ์์ ์ ์ ํ์ฑ์ผ๋ก๋ OG ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์์ต๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ๋ ์๋ฌด๋ฆฌ ๋น ๋ฅด๊ฒ ์ฒ๋ฆฌํด๋ ๊ฐ์ ธ์ฌ ์ ์๋ ์ ๋ณด์์ต๋๋ค.
์ ์ ํฌ๋กค๋ง๋ง์ผ๋ก๋ ํ๊ณ๊ฐ ์๋ค๋ ๊ฑธ ๋น ๋ฅด๊ฒ ์ฒด๊ฐํ๊ณ ,
์ฐ๋ฆฌ๋ ๋์ ํฌ๋กค๋ง์ ๋ณ๋ ฌ ๊ตฌ์กฐ๋ก ์ค๊ณํ์ต๋๋ค.
์ ์ ํฌ๋กค๋ง์ด ์คํจํ๋ฉด, ๋์ ํฌ๋กค๋ง์ผ๋ก ์๋ ์ ํ
๋์ ํฌ๋กค๋ง์ Playwright๋ฅผ ์ฌ์ฉํด ๊ตฌํํ์ต๋๋ค.
์ค์ ๋ธ๋ผ์ฐ์ ๋ฅผ ๋์ ํ์ด์ง๋ฅผ ๋ ๋๋งํ ๋ค, ๋ ๋๋ HTML์์ OG ๋ฐ์ดํฐ๋ฅผ ํ์ฑํฉ๋๋ค.
์ฌ์ฉํ ์คํ
from playwright.sync_api import sync_playwright
def fetch_og_with_playwright(url: str) -> dict:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
try:
page.goto(url, wait_until="domcontentloaded", timeout=8000)
og_data = {
"title": page.locator("meta[property='og:title']").get_attribute("content") or "",
"description": page.locator("meta[property='og:description']").get_attribute("content") or "",
"image": page.locator("meta[property='og:image']").get_attribute("content") or "",
}
return og_data
except Exception as e:
print(f"[ERROR] Failed to fetch OG data: {e}")
return {}
finally:
browser.close()
์ ์ /๋์ ํฌ๋กค๋ง ๊ตฌ์กฐ๋ฅผ ๊ฐ์ท์ง๋ง, ์ฌ์ ํ ํ ๊ฐ์ง ๋ฌธ์ ๊ฐ ๋จ์ ์์์ต๋๋ค.
๋ฐ๋ก โ๋ณด์์ด ์ฒ ์ ํ ์ฌ์ดํธโ์์๋ ํฌ๋กค๋ง ์์ฒด๊ฐ ์ฐจ๋จ๋๋ค๋ ์ ์
๋๋ค.
๋ํ์ ์ผ๋ก๋ ๋ค์๊ณผ ๊ฐ์ ์ฌ์ดํธ๋ค์ ๋๋ค:
์ด๋ฐ ์ฌ์ดํธ๋ ๋จ์ํ User-Agent ์กฐ์๋ง์ผ๋ก๋ ํตํ์ง ์์์ต๋๋ค.
๊ทธ๋์ ๋์ ํ ๊ฒ์ด ๋ฐ๋ก Smartproxy์ ๋๋ค.
Smartproxy๋ ์ ํฌ๊ฐ ํ
์คํธํ ์ฌ๋ฌ ํ๋ก์ ์๋น์ค ์ค์์
๊ฐ์ฅ ์์ ์ ์ด๊ณ ๋น ๋ฅธ ์๋, ๊ทธ๋ฆฌ๊ณ ์๊ธ ๊ตฌ์กฐ์ ์ ์ฐ์ฑ์ ๊ฐ์ถ ์๋น์ค์์ต๋๋ค.
์ด๋ฐ ํน์ฑ ๋๋ถ์ ํน์ ์ฌ์ดํธ๋ ํ๋ก์ ํฌ๋กค๋ง์ผ๋ก๋ง ์ ๊ทผํ๋๋ก ๊ตฌ์กฐ๋ฅผ ๋ถ๊ธฐํ์ต๋๋ค.
Smartproxy๋ HTTP ํ๋ก์ ๋ฐฉ์์ผ๋ก ์ ๊ทผํ๋ฏ๋ก, requests์์ ์์ฝ๊ฒ ์ฐ๋ํ ์ ์์ต๋๋ค.
import requests
# ์ค์ ์๋ต body๋ฅผ BeautifulSoup ๋ฑ์ผ๋ก ํ์ฑํ์ฌ OG ๋ฐ์ดํฐ ์ถ์ถ ๊ฐ๋ฅ
# ์: soup = BeautifulSoup(response.text, 'html.parser')
def fetch_with_proxy(url: str) -> dict:
proxies = {
"http": "http://username:password@gate.smartproxy.com:7000",
"https": "http://username:password@gate.smartproxy.com:7000"
}
headers = {
"User-Agent": "Mozilla/5.0"
}
try:
response = requests.get(url, headers=headers, proxies=proxies, timeout=10)
response.raise_for_status()
# ์ฌ๊ธฐ์๋ ๊ฐ๋จํ ์ํ ์ฝ๋๋ง ๋ฐํ (์ค์ ๊ตฌํ์์๋ BeautifulSoup ๋ฑ์ผ๋ก ํ์ฑ)
return {
"status": response.status_code,
"preview": response.text[:200] # ์ผ๋ถ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ฉ
}
except Exception as e:
print(f"[ERROR] Proxy fetch failed: {e}")
return {}
์ด ๋ชจ๋ ๊ณผ์ ์ ๊ฑฐ์ณ,
๋งํฌ ์ ์ฅ ์ ์ธ๋ค์ผ๊ณผ ์ ๋ชฉ์ด ๋น ๋ฅด๊ฒ ์ ๋จ๋ ๊ตฌ์กฐ๋ฅผ ์์ฑํ ์ ์์์ต๋๋ค.
์ฐ๋ฆฌ๋ ์ง๊ธ ๋งํฌ ๋๋ผํผ๋ผ๋ ์๋น์ค๋ฅผ ๋ง๋ค๊ณ ์์ต๋๋ค.
์ฌ๋ฌ๋ถ์ด ์ ์ฅํ ๋งํฌ๋ฅผ ๋ ์ ์ ๋ฆฌํ๊ณ , ๋ค์ ๊บผ๋ด๋ณผ ์ ์๊ฒ ๋๋ ์๋น์ค์
๋๋ค.
๐ ๐ ๋งํฌ ๋๋ผํผ ๋ฒ ํ ์ฒดํํ๋ฌ ๊ฐ๊ธฐ
์ง๊ธ ์ง์ ์ ์ฅํด๋ณด์๊ณ , ์ธ๋ค์ผ์ด ์ด๋ป๊ฒ ์ ๋์ค๋์ง ํ์ธํด๋ณด์ธ์!
์ฌ์ ํ ์ ํฌ๋ ๋ง์กฑ์ค๋ฌ์ด ํผํฌ๋จผ์ค๋ฅผ ์ป์ง ๋ชปํ์ต๋๋ค.
๋ค์ ๊ธ์์๋ โOpen Graph ํฌ๋กค๋ง ์๋๋ฅผ ์ด๋ป๊ฒ ๊ฐ์ ํ๋๊ฐโ์ ๋ํ ์ด์ผ๊ธฐ๋ฅผ ๋๋ ๋ณด๋ ค ํฉ๋๋ค.
์ฝ์ด์ฃผ์
์ ๊ฐ์ฌํฉ๋๋ค ๐
๊ถ๊ธํ ์ ์ด๋ ์๊ฒฌ์ ๋๊ธ๋ก ํธํ๊ฒ ๋จ๊ฒจ์ฃผ์ธ์!
ํ๋ฅญํ ๊ธ๊ณผ ์ข์ ํ๋ก๋ํธ ๊ฐ์ฌํฉ๋๋ค ! ๋๋ฌด ์จ๋ณด๊ณ ์ถ์๋ฐ ์นด์นด์ค๋ก ํ์๊ฐ์ ์ ์๋ํ๋ฉด 500์๋ฌ์ ํจ๊ป ํ์๊ฐ์ ์ด ์ด๋ค์ง์ง ์๋ค์ ใ ใ