# Instagram Graph API POC 섀계 λ¬Έμ„œ ## πŸ“‹ 1. μš”κ΅¬μ‚¬ν•­ μš”μ•½ ### 1.1 κΈ°λŠ₯적 μš”κ΅¬μ‚¬ν•­ | κΈ°λŠ₯ | μ„€λͺ… | μš°μ„ μˆœμœ„ | |------|------|----------| | 인증 | Access Token 관리, Long-lived Token κ΅ν™˜, Token 검증 | ν•„μˆ˜ | | 계정 정보 | λΉ„μ¦ˆλ‹ˆμŠ€ 계정 ID 쑰회, ν”„λ‘œν•„ 정보 쑰회 | ν•„μˆ˜ | | λ―Έλ””μ–΄ 관리 | λͺ©λ‘/상세 쑰회, 이미지/λΉ„λ””μ˜€ κ²Œμ‹œ (Container β†’ Publish) | ν•„μˆ˜ | | μΈμ‚¬μ΄νŠΈ | 계정/미디어별 μΈμ‚¬μ΄νŠΈ 쑰회 | ν•„μˆ˜ | | λŒ“κΈ€ 관리 | λŒ“κΈ€ 쑰회, λ‹΅κΈ€ μž‘μ„± | ν•„μˆ˜ | ### 1.2 λΉ„κΈ°λŠ₯적 μš”κ΅¬μ‚¬ν•­ - **비동기 처리**: λͺ¨λ“  API ν˜ΈμΆœμ€ async/await μ‚¬μš© - **Rate Limit μ€€μˆ˜**: μ‹œκ°„λ‹Ή 200 μš”μ²­ μ œν•œ, 429 μ—λŸ¬ μ‹œ μ§€μˆ˜ λ°±μ˜€ν”„ - **μ—λŸ¬ 처리**: 체계적인 μ˜ˆμ™Έ 계측 ꡬ쑰 - **λ‘œκΉ…**: μš”μ²­/응닡 좔적 κ°€λŠ₯ - **λ³΄μ•ˆ**: Credentials ν™˜κ²½λ³€μˆ˜ 관리, 민감정보 λ‘œκΉ… λ°©μ§€ --- ## πŸ“ 2. 섀계 κ°œμš” ### 2.1 μ•„ν‚€ν…μ²˜ λ‹€μ΄μ–΄κ·Έλž¨ ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ examples/ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ auth_example β”‚ β”‚media_example β”‚ β”‚insights_exampleβ”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ InstagramGraphClient β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ - _request() : 곡톡 HTTP μš”μ²­ (μž¬μ‹œλ„, λ‘œκΉ…) β”‚ β”‚ β”‚ β”‚ - debug_token() : 토큰 검증 β”‚ β”‚ β”‚ β”‚ - exchange_token() : Long-lived 토큰 κ΅ν™˜ β”‚ β”‚ β”‚ β”‚ - get_account() : 계정 정보 쑰회 β”‚ β”‚ β”‚ β”‚ - get_media_list() : λ―Έλ””μ–΄ λͺ©λ‘ β”‚ β”‚ β”‚ β”‚ - get_media() : λ―Έλ””μ–΄ 상세 β”‚ β”‚ β”‚ β”‚ - publish_image() : 이미지 κ²Œμ‹œ β”‚ β”‚ β”‚ β”‚ - publish_video() : λΉ„λ””μ˜€ κ²Œμ‹œ β”‚ β”‚ β”‚ β”‚ - get_account_insights() : 계정 μΈμ‚¬μ΄νŠΈ β”‚ β”‚ β”‚ β”‚ - get_media_insights() : λ―Έλ””μ–΄ μΈμ‚¬μ΄νŠΈ β”‚ β”‚ β”‚ β”‚ - get_comments() : λŒ“κΈ€ 쑰회 β”‚ β”‚ β”‚ β”‚ - reply_comment() : λŒ“κΈ€ λ‹΅κΈ€ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ models.py β”‚ β”‚ exceptions.py β”‚ β”‚ config.py β”‚ β”‚ (Pydantic v2) β”‚ β”‚ (μ»€μŠ€ν…€ μ˜ˆμ™Έ) β”‚ β”‚ (Settings) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### 2.2 λͺ¨λ“ˆ μ˜μ‘΄μ„± 관계 ``` config.py ◄─────────────────────────────────────┐ β”‚ β”‚ β–Ό β”‚ exceptions.py ◄─────────────────────┐ β”‚ β”‚ β”‚ β”‚ β–Ό β”‚ β”‚ models.py ◄──────────────┐ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β–Ό β”‚ β”‚ β”‚ client.py β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό examples/ (μ‚¬μš© 예제) ``` --- ## 🌐 3. API 섀계 (Instagram Graph API μ—”λ“œν¬μΈνŠΈ) ### 3.1 Base URL ``` https://graph.instagram.com (Instagram Platform API) https://graph.facebook.com/v21.0 (Facebook Graph API - 일뢀 κΈ°λŠ₯) ``` ### 3.2 인증 API #### 3.2.1 토큰 검증 (Debug Token) ``` GET /debug_token ?input_token={access_token} &access_token={app_id}|{app_secret} Response: { "data": { "app_id": "123456789", "type": "USER", "application": "App Name", "expires_at": 1234567890, "is_valid": true, "scopes": ["instagram_basic", "instagram_content_publish"], "user_id": "17841400000000000" } } ``` #### 3.2.2 Long-lived Token κ΅ν™˜ ``` GET /access_token ?grant_type=ig_exchange_token &client_secret={app_secret} &access_token={short_lived_token} Response: { "access_token": "IGQVJ...", "token_type": "bearer", "expires_in": 5184000 // 60일 (초) } ``` #### 3.2.3 토큰 κ°±μ‹  ``` GET /refresh_access_token ?grant_type=ig_refresh_token &access_token={long_lived_token} Response: { "access_token": "IGQVJ...", "token_type": "bearer", "expires_in": 5184000 } ``` ### 3.3 계정 API #### 3.3.1 계정 정보 쑰회 ``` GET /me ?fields=id,username,name,account_type,profile_picture_url,followers_count,follows_count,media_count &access_token={access_token} Response: { "id": "17841400000000000", "username": "example_user", "name": "Example User", "account_type": "BUSINESS", "profile_picture_url": "https://...", "followers_count": 1000, "follows_count": 500, "media_count": 100 } ``` ### 3.4 λ―Έλ””μ–΄ API #### 3.4.1 λ―Έλ””μ–΄ λͺ©λ‘ 쑰회 ``` GET /{ig-user-id}/media ?fields=id,media_type,media_url,thumbnail_url,caption,timestamp,permalink,like_count,comments_count &limit=25 &access_token={access_token} Response: { "data": [ { "id": "17880000000000000", "media_type": "IMAGE", "media_url": "https://...", "caption": "My photo", "timestamp": "2024-01-01T00:00:00+0000", "permalink": "https://www.instagram.com/p/...", "like_count": 100, "comments_count": 10 } ], "paging": { "cursors": { "before": "...", "after": "..." }, "next": "https://graph.instagram.com/..." } } ``` #### 3.4.2 λ―Έλ””μ–΄ 상세 쑰회 ``` GET /{ig-media-id} ?fields=id,media_type,media_url,thumbnail_url,caption,timestamp,permalink,like_count,comments_count,children{id,media_type,media_url} &access_token={access_token} ``` #### 3.4.3 이미지 κ²Œμ‹œ (2단계 ν”„λ‘œμ„ΈμŠ€) **Step 1: Container 생성** ``` POST /{ig-user-id}/media ?image_url={public_image_url} &caption={caption_text} &access_token={access_token} Response: { "id": "17889000000000000" // container_id } ``` **Step 2: Container μƒνƒœ 확인** ``` GET /{container-id} ?fields=status_code,status &access_token={access_token} Response: { "status_code": "FINISHED", // IN_PROGRESS, FINISHED, ERROR "id": "17889000000000000" } ``` **Step 3: κ²Œμ‹œ** ``` POST /{ig-user-id}/media_publish ?creation_id={container_id} &access_token={access_token} Response: { "id": "17880000000000001" // κ²Œμ‹œλœ media_id } ``` #### 3.4.4 λΉ„λ””μ˜€ κ²Œμ‹œ (3단계 ν”„λ‘œμ„ΈμŠ€) **Step 1: Container 생성** ``` POST /{ig-user-id}/media ?media_type=REELS &video_url={public_video_url} &caption={caption_text} &share_to_feed=true &access_token={access_token} Response: { "id": "17889000000000000" } ``` **Step 2 & 3**: 이미지와 동일 (μƒνƒœ 확인 β†’ κ²Œμ‹œ) ### 3.5 μΈμ‚¬μ΄νŠΈ API #### 3.5.1 계정 μΈμ‚¬μ΄νŠΈ ``` GET /{ig-user-id}/insights ?metric=impressions,reach,profile_views,accounts_engaged &period=day &metric_type=total_value &access_token={access_token} Response: { "data": [ { "name": "impressions", "period": "day", "values": [{"value": 1000}], "title": "Impressions", "description": "Total number of times..." } ] } ``` #### 3.5.2 λ―Έλ””μ–΄ μΈμ‚¬μ΄νŠΈ ``` GET /{ig-media-id}/insights ?metric=impressions,reach,engagement,saved &access_token={access_token} Response: { "data": [ { "name": "impressions", "period": "lifetime", "values": [{"value": 500}], "title": "Impressions" } ] } ``` ### 3.6 λŒ“κΈ€ API #### 3.6.1 λŒ“κΈ€ λͺ©λ‘ 쑰회 ``` GET /{ig-media-id}/comments ?fields=id,text,username,timestamp,like_count,replies{id,text,username,timestamp} &access_token={access_token} Response: { "data": [ { "id": "17890000000000000", "text": "Great photo!", "username": "commenter", "timestamp": "2024-01-01T12:00:00+0000", "like_count": 5, "replies": { "data": [...] } } ] } ``` #### 3.6.2 λŒ“κΈ€ λ‹΅κΈ€ μž‘μ„± ``` POST /{ig-comment-id}/replies ?message={reply_text} &access_token={access_token} Response: { "id": "17890000000000001" } ``` --- ## πŸ“¦ 4. 데이터 λͺ¨λΈ (Pydantic v2) ### 4.1 인증 λͺ¨λΈ ```python class TokenInfo(BaseModel): """토큰 정보""" access_token: str token_type: str = "bearer" expires_in: int # 초 λ‹¨μœ„ class TokenDebugData(BaseModel): """토큰 디버그 정보""" app_id: str type: str application: str expires_at: int # Unix timestamp is_valid: bool scopes: list[str] user_id: str class TokenDebugResponse(BaseModel): """토큰 디버그 응닡""" data: TokenDebugData ``` ### 4.2 계정 λͺ¨λΈ ```python class Account(BaseModel): """Instagram λΉ„μ¦ˆλ‹ˆμŠ€ 계정""" id: str username: str name: Optional[str] = None account_type: str # BUSINESS, CREATOR profile_picture_url: Optional[str] = None followers_count: int = 0 follows_count: int = 0 media_count: int = 0 biography: Optional[str] = None website: Optional[str] = None ``` ### 4.3 λ―Έλ””μ–΄ λͺ¨λΈ ```python class MediaType(str, Enum): """λ―Έλ””μ–΄ νƒ€μž…""" IMAGE = "IMAGE" VIDEO = "VIDEO" CAROUSEL_ALBUM = "CAROUSEL_ALBUM" REELS = "REELS" class Media(BaseModel): """λ―Έλ””μ–΄ 정보""" id: str media_type: MediaType media_url: Optional[str] = None thumbnail_url: Optional[str] = None caption: Optional[str] = None timestamp: datetime permalink: str like_count: int = 0 comments_count: int = 0 children: Optional[list["Media"]] = None # μΊλŸ¬μ…€μš© class MediaContainer(BaseModel): """λ―Έλ””μ–΄ μ»¨ν…Œμ΄λ„ˆ (κ²Œμ‹œ μ „ μƒνƒœ)""" id: str status_code: Optional[str] = None # IN_PROGRESS, FINISHED, ERROR status: Optional[str] = None class MediaList(BaseModel): """λ―Έλ””μ–΄ λͺ©λ‘ 응닡""" data: list[Media] paging: Optional[Paging] = None class Paging(BaseModel): """νŽ˜μ΄μ§• 정보""" cursors: Optional[dict[str, str]] = None next: Optional[str] = None previous: Optional[str] = None ``` ### 4.4 μΈμ‚¬μ΄νŠΈ λͺ¨λΈ ```python class InsightValue(BaseModel): """μΈμ‚¬μ΄νŠΈ κ°’""" value: int end_time: Optional[datetime] = None class Insight(BaseModel): """μΈμ‚¬μ΄νŠΈ 정보""" name: str period: str # day, week, days_28, lifetime values: list[InsightValue] title: str description: Optional[str] = None id: str class InsightResponse(BaseModel): """μΈμ‚¬μ΄νŠΈ 응닡""" data: list[Insight] ``` ### 4.5 λŒ“κΈ€ λͺ¨λΈ ```python class Comment(BaseModel): """λŒ“κΈ€ 정보""" id: str text: str username: str timestamp: datetime like_count: int = 0 replies: Optional["CommentList"] = None class CommentList(BaseModel): """λŒ“κΈ€ λͺ©λ‘ 응닡""" data: list[Comment] paging: Optional[Paging] = None ``` ### 4.6 μ—λŸ¬ λͺ¨λΈ ```python class APIError(BaseModel): """Instagram API μ—λŸ¬ 응닡""" message: str type: str code: int error_subcode: Optional[int] = None fbtrace_id: Optional[str] = None class ErrorResponse(BaseModel): """μ—λŸ¬ 응닡 래퍼""" error: APIError ``` --- ## 🚨 5. μ˜ˆμ™Έ 처리 μ „λž΅ ### 5.1 μ˜ˆμ™Έ 계측 ꡬ쑰 ```python class InstagramAPIError(Exception): """Instagram API κΈ°λ³Έ μ˜ˆμ™Έ""" def __init__(self, message: str, code: int = None, subcode: int = None): self.message = message self.code = code self.subcode = subcode super().__init__(self.message) class AuthenticationError(InstagramAPIError): """인증 κ΄€λ ¨ μ—λŸ¬ (토큰 만료, 무효 λ“±)""" # code: 190 (Invalid OAuth access token) pass class RateLimitError(InstagramAPIError): """Rate Limit 초과 (HTTP 429)""" def __init__(self, message: str, retry_after: int = None): super().__init__(message, code=4) self.retry_after = retry_after class PermissionError(InstagramAPIError): """κΆŒν•œ λΆ€μ‘± μ—λŸ¬""" # code: 10 (Permission denied) # code: 200 (Requires business account) pass class MediaPublishError(InstagramAPIError): """λ―Έλ””μ–΄ κ²Œμ‹œ μ‹€νŒ¨""" # 이미지 URL μ ‘κ·Ό λΆˆκ°€, 포맷 였λ₯˜ λ“± pass class InvalidRequestError(InstagramAPIError): """잘λͺ»λœ μš”μ²­ (νŒŒλΌλ―Έν„° 였λ₯˜ λ“±)""" # code: 100 (Invalid parameter) pass class ResourceNotFoundError(InstagramAPIError): """λ¦¬μ†ŒμŠ€λ₯Ό 찾을 수 μ—†μŒ""" # code: 803 (Object does not exist) pass ``` ### 5.2 μ—λŸ¬ μ½”λ“œ λ§€ν•‘ | API Error Code | Subcode | Exception Class | μ„€λͺ… | |----------------|---------|-----------------|------| | 4 | - | RateLimitError | Rate limit 초과 | | 10 | - | PermissionError | κΆŒν•œ λΆ€μ‘± | | 100 | - | InvalidRequestError | 잘λͺ»λœ νŒŒλΌλ―Έν„° | | 190 | 458 | AuthenticationError | μ•± κΆŒν•œ μ—†μŒ | | 190 | 463 | AuthenticationError | 토큰 만료 | | 190 | 467 | AuthenticationError | μœ νš¨ν•˜μ§€ μ•Šμ€ 토큰 | | 200 | - | PermissionError | λΉ„μ¦ˆλ‹ˆμŠ€ 계정 ν•„μš” | | 803 | - | ResourceNotFoundError | λ¦¬μ†ŒμŠ€ μ—†μŒ | ### 5.3 μž¬μ‹œλ„ μ „λž΅ ```python RETRY_CONFIG = { "max_retries": 3, "base_delay": 1.0, # 초 "max_delay": 60.0, # 초 "exponential_base": 2, "retryable_status_codes": [429, 500, 502, 503, 504], "retryable_error_codes": [4, 17, 341], # Rate limit κ΄€λ ¨ } ``` --- ## πŸ”§ 6. ν΄λΌμ΄μ–ΈνŠΈ μΈν„°νŽ˜μ΄μŠ€ ### 6.1 InstagramGraphClient 클래슀 ```python class InstagramGraphClient: """Instagram Graph API ν΄λΌμ΄μ–ΈνŠΈ""" def __init__( self, access_token: str, app_id: Optional[str] = None, app_secret: Optional[str] = None, api_version: str = "v21.0", timeout: float = 30.0, ): """ Args: access_token: Instagram μ•‘μ„ΈμŠ€ 토큰 app_id: Facebook μ•± ID (토큰 검증 μ‹œ ν•„μš”) app_secret: Facebook μ•± μ‹œν¬λ¦Ώ (토큰 κ΅ν™˜ μ‹œ ν•„μš”) api_version: Graph API 버전 timeout: HTTP μš”μ²­ νƒ€μž„μ•„μ›ƒ (초) """ pass async def __aenter__(self) -> "InstagramGraphClient": """비동기 μ»¨ν…μŠ€νŠΈ λ§€λ‹ˆμ € μ§„μž…""" pass async def __aexit__(self, *args) -> None: """비동기 μ»¨ν…μŠ€νŠΈ λ§€λ‹ˆμ € μ’…λ£Œ""" pass # ==================== 인증 ==================== async def debug_token(self) -> TokenDebugResponse: """ν˜„μž¬ 토큰 정보 쑰회 (μœ νš¨μ„± 검증)""" pass async def exchange_long_lived_token(self) -> TokenInfo: """단기 토큰을 μž₯κΈ° 토큰(60일)으둜 κ΅ν™˜""" pass async def refresh_token(self) -> TokenInfo: """μž₯κΈ° 토큰 κ°±μ‹ """ pass # ==================== 계정 ==================== async def get_account(self) -> Account: """ν˜„μž¬ 계정 정보 쑰회""" pass async def get_account_id(self) -> str: """ν˜„μž¬ 계정 ID만 쑰회""" pass # ==================== λ―Έλ””μ–΄ ==================== async def get_media_list( self, limit: int = 25, after: Optional[str] = None, ) -> MediaList: """λ―Έλ””μ–΄ λͺ©λ‘ 쑰회 (νŽ˜μ΄μ§€λ„€μ΄μ…˜ 지원)""" pass async def get_media(self, media_id: str) -> Media: """λ―Έλ””μ–΄ 상세 쑰회""" pass async def publish_image( self, image_url: str, caption: Optional[str] = None, ) -> Media: """이미지 κ²Œμ‹œ (Container 생성 β†’ μƒνƒœ 확인 β†’ κ²Œμ‹œ)""" pass async def publish_video( self, video_url: str, caption: Optional[str] = None, share_to_feed: bool = True, ) -> Media: """λΉ„λ””μ˜€/릴슀 κ²Œμ‹œ""" pass async def publish_carousel( self, media_urls: list[str], caption: Optional[str] = None, ) -> Media: """μΊλŸ¬μ…€(λ©€ν‹° 이미지) κ²Œμ‹œ""" pass # ==================== μΈμ‚¬μ΄νŠΈ ==================== async def get_account_insights( self, metrics: list[str], period: str = "day", ) -> InsightResponse: """계정 μΈμ‚¬μ΄νŠΈ 쑰회 Args: metrics: μ‘°νšŒν•  λ©”νŠΈλ¦­ λͺ©λ‘ - impressions, reach, profile_views, accounts_engaged λ“± period: κΈ°κ°„ (day, week, days_28) """ pass async def get_media_insights( self, media_id: str, metrics: Optional[list[str]] = None, ) -> InsightResponse: """λ―Έλ””μ–΄ μΈμ‚¬μ΄νŠΈ 쑰회 Args: media_id: λ―Έλ””μ–΄ ID metrics: μ‘°νšŒν•  λ©”νŠΈλ¦­ (κΈ°λ³Έ: impressions, reach, engagement, saved) """ pass # ==================== λŒ“κΈ€ ==================== async def get_comments( self, media_id: str, limit: int = 50, ) -> CommentList: """λ―Έλ””μ–΄μ˜ λŒ“κΈ€ λͺ©λ‘ 쑰회""" pass async def reply_comment( self, comment_id: str, message: str, ) -> Comment: """λŒ“κΈ€μ— λ‹΅κΈ€ μž‘μ„±""" pass # ==================== λ‚΄λΆ€ λ©”μ„œλ“œ ==================== async def _request( self, method: str, endpoint: str, params: Optional[dict] = None, data: Optional[dict] = None, ) -> dict: """ 곡톡 HTTP μš”μ²­ 처리 - Rate Limit μ‹œ μ§€μˆ˜ λ°±μ˜€ν”„ μž¬μ‹œλ„ - μ—λŸ¬ 응닡 β†’ μ»€μŠ€ν…€ μ˜ˆμ™Έ λ³€ν™˜ - μš”μ²­/응닡 λ‘œκΉ… """ pass async def _wait_for_container( self, container_id: str, timeout: float = 60.0, poll_interval: float = 2.0, ) -> MediaContainer: """μ»¨ν…Œμ΄λ„ˆ μƒνƒœκ°€ FINISHEDκ°€ 될 λ•ŒκΉŒμ§€ λŒ€κΈ°""" pass ``` --- ## πŸ“ 7. 파일 ꡬ쑰 ``` poc/instagram/ β”œβ”€β”€ __init__.py # νŒ¨ν‚€μ§€ μ΄ˆκΈ°ν™” 및 public API export β”œβ”€β”€ config.py # Settings (ν™˜κ²½λ³€μˆ˜ 관리) β”œβ”€β”€ exceptions.py # μ»€μŠ€ν…€ μ˜ˆμ™Έ 클래슀 β”œβ”€β”€ models.py # Pydantic v2 데이터 λͺ¨λΈ β”œβ”€β”€ client.py # InstagramGraphClient β”œβ”€β”€ examples/ β”‚ β”œβ”€β”€ __init__.py β”‚ β”œβ”€β”€ auth_example.py # 토큰 검증, κ΅ν™˜ 예제 β”‚ β”œβ”€β”€ account_example.py # 계정 정보 쑰회 예제 β”‚ β”œβ”€β”€ media_example.py # λ―Έλ””μ–΄ 쑰회/κ²Œμ‹œ 예제 β”‚ β”œβ”€β”€ insights_example.py # μΈμ‚¬μ΄νŠΈ 쑰회 예제 β”‚ └── comments_example.py # λŒ“κΈ€ 쑰회/λ‹΅κΈ€ 예제 β”œβ”€β”€ DESIGN.md # 섀계 λ¬Έμ„œ (λ³Έ λ¬Έμ„œ) └── README.md # μ‚¬μš© κ°€μ΄λ“œ ``` ### 7.1 각 파일의 μ—­ν•  | 파일 | μ—­ν•  | μ˜μ‘΄μ„± | |------|------|--------| | `config.py` | ν™˜κ²½λ³€μˆ˜ λ‘œλ“œ, API μ„€μ • | pydantic-settings | | `exceptions.py` | μ»€μŠ€ν…€ μ˜ˆμ™Έ μ •μ˜ | - | | `models.py` | API μš”μ²­/응닡 Pydantic λͺ¨λΈ | pydantic | | `client.py` | Instagram Graph API ν΄λΌμ΄μ–ΈνŠΈ | httpx, μœ„ λͺ¨λ“ˆλ“€ | | `examples/*.py` | μ‹€ν–‰ κ°€λŠ₯ν•œ 예제 μ½”λ“œ | client.py | --- ## πŸ“‹ 8. κ΅¬ν˜„ μˆœμ„œ 개발 μ—μ΄μ „νŠΈκ°€ 따라야 ν•  μˆœμ„œ: ### Phase 1: 기반 λͺ¨λ“ˆ (μ˜μ‘΄μ„± μ—†μŒ) 1. `config.py` - Settings 클래슀 2. `exceptions.py` - μ˜ˆμ™Έ 클래슀 계측 ### Phase 2: 데이터 λͺ¨λΈ 3. `models.py` - Pydantic λͺ¨λΈ (Token, Account, Media, Insight, Comment) ### Phase 3: ν΄λΌμ΄μ–ΈνŠΈ κ΅¬ν˜„ 4. `client.py` - InstagramGraphClient - 4.1: κΈ°λ³Έ ꡬ쑰 및 `_request()` λ©”μ„œλ“œ - 4.2: 인증 λ©”μ„œλ“œ (debug_token, exchange_token, refresh_token) - 4.3: 계정 λ©”μ„œλ“œ (get_account, get_account_id) - 4.4: λ―Έλ””μ–΄ λ©”μ„œλ“œ (get_media_list, get_media, publish_image, publish_video) - 4.5: μΈμ‚¬μ΄νŠΈ λ©”μ„œλ“œ (get_account_insights, get_media_insights) - 4.6: λŒ“κΈ€ λ©”μ„œλ“œ (get_comments, reply_comment) ### Phase 4: 예제 및 λ¬Έμ„œ 5. `examples/auth_example.py` 6. `examples/account_example.py` 7. `examples/media_example.py` 8. `examples/insights_example.py` 9. `examples/comments_example.py` 10. `README.md` --- ## βœ… 9. 섀계 κ²€μˆ˜ κ²°κ³Ό ### κ²€μˆ˜ 체크리슀트 - [x] **κΈ°μ‘΄ ν”„λ‘œμ νŠΈ νŒ¨ν„΄κ³Ό 일관성** - 3계측 ꡬ쑰, Pydantic v2, 비동기 νŒ¨ν„΄ 적용 - [x] **비동기 처리** - httpx.AsyncClient, async/await 전체 적용 - [x] **N+1 쿼리 문제** - ν•΄λ‹Ή μ—†μŒ (μ™ΈλΆ€ API 호좜) - [x] **νŠΈλžœμž­μ…˜ 경계** - ν•΄λ‹Ή μ—†μŒ (DB λ―Έμ‚¬μš©) - [x] **μ˜ˆμ™Έ 처리 μ „λž΅** - κ³„μΈ΅ν™”λœ μ˜ˆμ™Έ, μ—λŸ¬ μ½”λ“œ λ§€ν•‘, μž¬μ‹œλ„ 둜직 - [x] **ν™•μž₯μ„±** - μƒˆ μ—”λ“œν¬μΈνŠΈ μΆ”κ°€ 용이, λͺ¨λΈ ν™•μž₯ κ°€λŠ₯ - [x] **직관적 ꡬ쑰** - λͺ…ν™•ν•œ λͺ¨λ“ˆ 뢄리, μΌκ΄€λœ 넀이밍 - [x] **SOLID 원칙** - 단일 μ±…μž„, 개방-폐쇄 원칙 μ€€μˆ˜ ### μ°Έκ³  λ¬Έμ„œ - [Instagram Graph API 곡식 κ°€μ΄λ“œ](https://elfsight.com/blog/instagram-graph-api-complete-developer-guide-for-2025/) - [Instagram Platform API κ΅¬ν˜„ κ°€μ΄λ“œ](https://gist.github.com/PrenSJ2/0213e60e834e66b7e09f7f93999163fc) --- ## πŸ”„ λ‹€μŒ 단계 섀계가 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€. `/develop` λͺ…λ ΉμœΌλ‘œ 개발 μ—μ΄μ „νŠΈλ₯Ό ν˜ΈμΆœν•˜μ—¬ κ΅¬ν˜„μ„ μ§„ν–‰ν•©λ‹ˆλ‹€.