هذا الالتزام موجود في:
2025-10-29 13:35:42 +00:00
الأصل b0ac92668d
التزام c144d20f7c

319
main.py
عرض الملف

@@ -1,46 +1,50 @@
import os import os
from fastapi import FastAPI, HTTPException import gzip
import json
from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from pydantic import BaseModel from pydantic import BaseModel
from dataforseo_sdk import DataForSeoClient from typing import List, Dict, Optional, Any
from dataforseo_sdk.config import DFSConfig
import asyncio
from typing import List, Dict, Optional
import pandas as pd import pandas as pd
from dotenv import load_dotenv from dotenv import load_dotenv
from client import RestClient
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Load environment variables # Load environment variables
load_dotenv() load_dotenv()
# Initialize FastAPI app # Initialize FastAPI app
app = FastAPI(title="Domain Ranking Keywords API", version="1.0.0") app = FastAPI(
title="DataForSEO API Service",
version="1.0.0",
description="Comprehensive DataForSEO API integration including Domain Keywords and AI Mode SERP - بسم الله الرحمن الرحيم"
)
# DataForSEO configuration # Request models
class DataForSEOConfig:
def __init__(self):
self.login = os.getenv('DATAFORSEO_API_LOGIN')
self.password = os.getenv('DATAFORSEO_API_PASSWORD')
if not self.login or not self.password:
raise ValueError("Please set DATAFORSEO_API_LOGIN and DATAFORSEO_API_PASSWORD environment variables")
self.config = DFSConfig(login=self.login, password=self.password)
# Request model
class DomainRequest(BaseModel): class DomainRequest(BaseModel):
domain: str domain: str
country_code: Optional[str] = "us" country_code: Optional[str] = "us"
language_code: Optional[str] = "en" language_code: Optional[str] = "en"
limit: Optional[int] = 1000 limit: Optional[int] = 100
# Response model class SERPRequest(BaseModel):
keyword: str
location_code: int
language_code: Optional[str] = "en"
# Response models
class KeywordRanking(BaseModel): class KeywordRanking(BaseModel):
keyword: str keyword: str
position: float position: float
url: str url: str
search_volume: Optional[int] search_volume: Optional[int] = 0
cpc: Optional[float] cpc: Optional[float] = 0.0
competition: Optional[float] competition: Optional[float] = 0.0
country_code: Optional[str] = "us"
class DomainResponse(BaseModel): class DomainResponse(BaseModel):
domain: str domain: str
@@ -48,19 +52,178 @@ class DomainResponse(BaseModel):
keywords: List[KeywordRanking] keywords: List[KeywordRanking]
message: str message: str
class SERPItem(BaseModel):
type: str
title: str
url: str
description: Optional[str] = None
position: Optional[int] = None
rank_group: Optional[int] = None
class SERPResponse(BaseModel):
keyword: str
location_code: int
total_results: int
items: List[SERPItem]
search_metadata: Dict[str, Any]
# Initialize DataForSEO client # Initialize DataForSEO client
def get_dfs_client(): def get_dfs_client():
config = DataForSEOConfig() username = os.getenv('DATAFORSEO_API_LOGIN')
return DataForSeoClient(config.config) password = os.getenv('DATAFORSEO_API_PASSWORD')
if not username or not password:
raise ValueError("Please set DATAFORSEO_API_LOGIN and DATAFORSEO_API_PASSWORD environment variables")
return RestClient(username, password)
@app.get("/") @app.get("/")
async def root(): async def root():
return {"message": "Domain Ranking Keywords API - بسم الله الرحمن الرحيم"} return {
"message": "DataForSEO API Service - بسم الله الرحمن الرحيم",
"endpoints": {
"health": "/health",
"domain_keywords": "/domain-keywords",
"serp_search": "/api/search",
"export_csv": "/export-keywords-csv",
"download_csv": "/download-csv/{filename}"
}
}
@app.get("/health") @app.get("/health")
async def health_check(): async def health_check():
return {"status": "healthy", "message": "API is running"} try:
client = get_dfs_client()
# Test API connection with a simple request
test_response = client.get("/v3/applications/user")
return {
"status": "healthy",
"message": "API is running and DataForSEO connection is working",
"dataforseo_status": "connected"
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"DataForSEO connection failed: {str(e)}")
# AI Mode SERP Search Endpoint
@app.get("/api/search")
async def search_ai_mode(
keyword: str = Query(..., description="Search keyword"),
location_code: int = Query(..., description="Location code (integer)"),
language_code: str = Query("en", description="Language code")
):
"""
AI Mode SERP Search - Get comprehensive search results for a keyword
"""
# Validate required parameters
if not keyword:
raise HTTPException(status_code=400, detail="keyword parameter is required")
if not location_code:
raise HTTPException(status_code=400, detail="location_code parameter is required")
try:
client = get_dfs_client()
# Prepare request data
post_data = dict()
post_data[len(post_data)] = dict(
language_code=language_code,
location_code=location_code,
keyword=keyword
)
# Make API call
logger.info(f"Making AI Mode API call for keyword: {keyword}, location_code: {location_code}")
response = client.post("/v3/serp/google/ai_mode/live/advanced", post_data)
logger.info(f"Raw API response status: {response.get('status_code')}")
# Process response
if response.get("status_code") == 20000:
# Extract and simplify the response
simplified_response = extract_simplified_data(response)
return simplified_response
else:
logger.error(f"API error: {response.get('status_code')} - {response.get('status_message')}")
raise HTTPException(
status_code=500,
detail=f"API error. Code: {response.get('status_code')} Message: {response.get('status_message')}"
)
except Exception as e:
logger.error(f"Error in AI Mode search: {str(e)}")
raise HTTPException(status_code=500, detail=f"Search error: {str(e)}")
def extract_simplified_data(response: Dict) -> Dict:
"""
Extract and simplify the AI Mode SERP response data
"""
try:
simplified_items = []
if "tasks" in response and response["tasks"]:
for task in response["tasks"]:
if "result" in task and task["result"]:
for result in task["result"]:
# Extract basic search info
search_info = {
"keyword": result.get("keyword", ""),
"location_code": result.get("location_code"),
"language_code": result.get("language_code"),
"check_url": result.get("check_url"),
"datetime": result.get("datetime")
}
# Extract items
if "items" in result:
for item in result["items"]:
simplified_item = {
"type": item.get("type", "unknown"),
"title": item.get("title", ""),
"url": item.get("url", ""),
"description": item.get("description"),
"position": item.get("position"),
"rank_group": item.get("rank_group")
}
# Handle different item types
if item.get("type") == "organic":
simplified_item.update({
"domain": item.get("domain"),
"breadcrumb": item.get("breadcrumb"),
"website_name": item.get("website_name")
})
elif item.get("type") == "paid":
simplified_item.update({
"domain": item.get("domain"),
"price": item.get("price"),
"currency": item.get("currency")
})
elif item.get("type") == "featured_snippet":
simplified_item.update({
"domain": item.get("domain"),
"website_name": item.get("website_name")
})
simplified_items.append(simplified_item)
return {
"search_metadata": search_info,
"total_results": len(simplified_items),
"items": simplified_items,
"message": "تم جلب نتائج البحث بنجاح بفضل الله"
}
except Exception as e:
logger.error(f"Error simplifying response data: {str(e)}")
return {
"search_metadata": {},
"total_results": 0,
"items": [],
"message": "حدث خطأ في معالجة البيانات"
}
# Existing Domain Keywords Endpoints
@app.post("/domain-keywords", response_model=DomainResponse) @app.post("/domain-keywords", response_model=DomainResponse)
async def get_domain_keywords(request: DomainRequest): async def get_domain_keywords(request: DomainRequest):
""" """
@@ -69,7 +232,7 @@ async def get_domain_keywords(request: DomainRequest):
try: try:
client = get_dfs_client() client = get_dfs_client()
# Get domain rankings using DataForSEO # Get domain rankings using DataForSEO Domain Analytics API
rankings = await get_domain_rankings( rankings = await get_domain_rankings(
client, client,
request.domain, request.domain,
@@ -89,44 +252,55 @@ async def get_domain_keywords(request: DomainRequest):
raise HTTPException(status_code=500, detail=f"Error fetching data: {str(e)}") raise HTTPException(status_code=500, detail=f"Error fetching data: {str(e)}")
async def get_domain_rankings(client, domain: str, country_code: str = "us", async def get_domain_rankings(client, domain: str, country_code: str = "us",
language_code: str = "en", limit: int = 1000) -> List[KeywordRanking]: language_code: str = "en", limit: int = 100) -> List[KeywordRanking]:
""" """
Fetch domain ranking keywords from DataForSEO Fetch domain ranking keywords from DataForSEO using Domain Analytics API
""" """
try: try:
# Using DataForSEO's domain analytics service # Prepare the POST data for Domain Analytics API
domain_analytics = client.domain_analytics() post_data = [
{
# Get keywords for the domain "target": domain,
response = domain_analytics.get_domain_metrics_by_categories( "limit": limit,
target=domain, "country_code": country_code.upper(),
country_code=country_code, "language_code": language_code.lower(),
language_code=language_code, "filters": [
limit=limit ["country_code", "=", country_code.upper()],
) "and",
["language_code", "=", language_code.lower()]
]
}
]
# Make API request to Domain Analytics endpoint
response = client.post("/v3/dataforseo_labs/domain_metrics_by_categories", post_data)
rankings = [] rankings = []
if response and hasattr(response, 'tasks'): # Process the response
for task in response.tasks: if response and 'tasks' in response:
if hasattr(task, 'result') and task.result: for task in response['tasks']:
for item in task.result: if 'result' in task and task['result']:
for item in task['result']:
# Extract keyword data from the response
keyword_data = item.get('metrics', {})
rankings.append(KeywordRanking( rankings.append(KeywordRanking(
keyword=item.keyword if hasattr(item, 'keyword') else "N/A", keyword=item.get('key', 'N/A'),
position=float(item.pos) if hasattr(item, 'pos') else 0.0, position=float(keyword_data.get('pos', 0)),
url=item.url if hasattr(item, 'url') else "", url=item.get('domain', ''),
search_volume=int(item.search_volume) if hasattr(item, 'search_volume') else 0, search_volume=keyword_data.get('search_volume', 0),
cpc=float(item.cpc) if hasattr(item, 'cpc') else 0.0, cpc=float(keyword_data.get('cpc', 0)),
competition=float(item.competition) if hasattr(item, 'competition') else 0.0 competition=float(keyword_data.get('competition', 0)),
country_code=country_code
)) ))
return rankings return rankings
except Exception as e: except Exception as e:
print(f"Error in get_domain_rankings: {str(e)}") logger.error(f"Error in get_domain_rankings: {str(e)}")
return [] return []
# Export to CSV endpoint
@app.post("/export-keywords-csv") @app.post("/export-keywords-csv")
async def export_keywords_csv(request: DomainRequest): async def export_keywords_csv(request: DomainRequest):
""" """
@@ -143,28 +317,30 @@ async def export_keywords_csv(request: DomainRequest):
request.limit request.limit
) )
if not rankings:
raise HTTPException(status_code=404, detail="No keywords found for this domain")
# Convert to DataFrame # Convert to DataFrame
df = pd.DataFrame([keyword.dict() for keyword in rankings]) df = pd.DataFrame([keyword.dict() for keyword in rankings])
# Save to CSV in exports directory # Save to CSV in exports directory
exports_dir = "/app/exports" exports_dir = "/app/exports"
os.makedirs(exports_dir, exist_ok=True) os.makedirs(exports_dir, exist_ok=True)
filename = f"{request.domain}_keywords.csv" filename = f"{request.domain}_{request.country_code}_keywords.csv"
filepath = os.path.join(exports_dir, filename) filepath = os.path.join(exports_dir, filename)
df.to_csv(filepath, index=False, encoding='utf-8') df.to_csv(filepath, index=False, encoding='utf-8')
return { return {
"domain": request.domain, "domain": request.domain,
"filename": filename, "filename": filename,
"filepath": filepath,
"total_keywords": len(rankings), "total_keywords": len(rankings),
"message": f"تم تصدير البيانات إلى {filename} بنجاح" "download_url": f"/download-csv/{filename}",
"message": f"تم تصدير {len(rankings)} كلمة مفتاحية إلى {filename} بنجاح"
} }
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Export error: {str(e)}") raise HTTPException(status_code=500, detail=f"Export error: {str(e)}")
# Download CSV file endpoint
@app.get("/download-csv/{filename}") @app.get("/download-csv/{filename}")
async def download_csv(filename: str): async def download_csv(filename: str):
""" """
@@ -175,10 +351,37 @@ async def download_csv(filename: str):
return FileResponse( return FileResponse(
path=filepath, path=filepath,
filename=filename, filename=filename,
media_type='text/csv' media_type='text/csv',
headers={'Content-Disposition': f'attachment; filename={filename}'}
) )
else: else:
raise HTTPException(status_code=404, detail="File not found") raise HTTPException(status_code=404, detail="File not found. Please generate the CSV first using /export-keywords-csv")
@app.get("/keywords-by-country/{domain}")
async def get_keywords_by_country(domain: str, country_code: str = "us", limit: int = 50):
"""
Quick endpoint to get keywords for a domain by country
"""
try:
client = get_dfs_client()
rankings = await get_domain_rankings(
client,
domain,
country_code,
"en", # Default language
limit
)
return {
"domain": domain,
"country_code": country_code,
"total_keywords": len(rankings),
"keywords": rankings[:10] if rankings else [] # Return first 10 for quick view
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error: {str(e)}")
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn