| """ |
| Meta Description Generator |
| AI-powered meta description generation for SEO |
| """ |
|
|
| import os |
| import re |
| from typing import Dict, List |
|
|
| def generate_meta_description(page: Dict, api_keys: Dict = None) -> str: |
| """ |
| Generate SEO-optimized meta description for a page |
| Uses AI if available, falls back to smart extraction |
| """ |
| title = page.get('title', '') |
| text = page.get('text', '') |
| headings = page.get('headings', []) |
| url = page.get('url', '') |
| |
| |
| ai_description = _generate_with_ai(title, text, headings, api_keys) |
| if ai_description: |
| return ai_description |
| |
| |
| return _generate_smart_description(title, text, headings) |
|
|
|
|
| def _generate_with_ai(title: str, text: str, headings: List, api_keys: Dict = None) -> str: |
| """Generate meta description using AI (Groq or OpenAI)""" |
| try: |
| |
| groq_key = (api_keys or {}).get('groq') or os.getenv('GROQ_API_KEY') |
| if groq_key: |
| return _generate_with_groq(title, text, headings, groq_key) |
| |
| |
| openai_key = (api_keys or {}).get('openai') or os.getenv('OPENAI_API_KEY') |
| if openai_key: |
| return _generate_with_openai(title, text, headings, openai_key) |
| |
| return None |
| |
| except Exception as e: |
| print(f"AI meta generation failed: {e}") |
| return None |
|
|
|
|
| def _generate_with_groq(title: str, text: str, headings: List, api_key: str) -> str: |
| """Generate using Groq API""" |
| try: |
| import requests |
| |
| |
| text_sample = text[:1000] if text else '' |
| headings_text = ' '.join([h.get('text', '') if isinstance(h, dict) else str(h) for h in headings[:5]]) |
| |
| prompt = f"""اكتب وصف meta description مثالي لصفحة ويب بالمواصفات التالية: |
| - العنوان: {title} |
| - المحتوى: {text_sample} |
| - العناوين: {headings_text} |
| |
| المتطلبات: |
| - الطول: 150-160 حرف بالضبط |
| - يجب أن يكون جذاب ومقنع |
| - يحتوي على كلمات مفتاحية |
| - يشجع على النقر |
| - بدون علامات تنصيص |
| |
| اكتب الوصف فقط بدون أي نص إضافي:""" |
|
|
| response = requests.post( |
| 'https://api.groq.com/openai/v1/chat/completions', |
| headers={ |
| 'Authorization': f'Bearer {api_key}', |
| 'Content-Type': 'application/json' |
| }, |
| json={ |
| 'model': 'llama-3.3-70b-versatile', |
| 'messages': [{'role': 'user', 'content': prompt}], |
| 'temperature': 0.7, |
| 'max_tokens': 100 |
| }, |
| timeout=15 |
| ) |
| |
| if response.status_code == 200: |
| data = response.json() |
| description = data['choices'][0]['message']['content'].strip() |
| |
| description = description.replace('"', '').replace("'", '').strip() |
| |
| if len(description) > 160: |
| description = description[:157] + '...' |
| return description |
| |
| return None |
| |
| except Exception as e: |
| print(f"Groq meta generation error: {e}") |
| return None |
|
|
|
|
| def _generate_with_openai(title: str, text: str, headings: List, api_key: str) -> str: |
| """Generate using OpenAI API""" |
| try: |
| import requests |
| |
| text_sample = text[:1000] if text else '' |
| headings_text = ' '.join([h.get('text', '') if isinstance(h, dict) else str(h) for h in headings[:5]]) |
| |
| prompt = f"""Write a perfect meta description for a webpage with these details: |
| - Title: {title} |
| - Content: {text_sample} |
| - Headings: {headings_text} |
| |
| Requirements: |
| - Exactly 150-160 characters |
| - Compelling and click-worthy |
| - Contains keywords |
| - No quotation marks |
| |
| Write only the description:""" |
|
|
| response = requests.post( |
| 'https://api.openai.com/v1/chat/completions', |
| headers={ |
| 'Authorization': f'Bearer {api_key}', |
| 'Content-Type': 'application/json' |
| }, |
| json={ |
| 'model': 'gpt-3.5-turbo', |
| 'messages': [{'role': 'user', 'content': prompt}], |
| 'temperature': 0.7, |
| 'max_tokens': 100 |
| }, |
| timeout=15 |
| ) |
| |
| if response.status_code == 200: |
| data = response.json() |
| description = data['choices'][0]['message']['content'].strip() |
| description = description.replace('"', '').replace("'", '').strip() |
| if len(description) > 160: |
| description = description[:157] + '...' |
| return description |
| |
| return None |
| |
| except Exception as e: |
| print(f"OpenAI meta generation error: {e}") |
| return None |
|
|
|
|
| def _generate_smart_description(title: str, text: str, headings: List) -> str: |
| """Generate meta description using smart extraction (no AI)""" |
| |
| text = re.sub(r'\s+', ' ', text).strip() |
| |
| |
| sentences = re.split(r'[.!?]+', text) |
| |
| description = '' |
| for sentence in sentences: |
| sentence = sentence.strip() |
| if len(sentence) > 50 and len(sentence) < 200: |
| description = sentence |
| break |
| |
| |
| if not description and headings: |
| first_heading = headings[0] |
| heading_text = first_heading.get('text', '') if isinstance(first_heading, dict) else str(first_heading) |
| description = f"{title} - {heading_text}" |
| |
| |
| if not description: |
| description = f"{title} - اكتشف خدماتنا ومنتجاتنا المتميزة" |
| |
| |
| if len(description) > 160: |
| description = description[:157] + '...' |
| elif len(description) < 120: |
| description += ' - تواصل معنا الآن للمزيد من المعلومات' |
| |
| return description |
|
|
|
|
| def generate_meta_descriptions_for_audit(audit: Dict, api_keys: Dict = None) -> List[Dict]: |
| """Generate meta descriptions for all pages in audit""" |
| pages = audit.get('pages', []) |
| results = [] |
| |
| for page in pages: |
| url = page.get('url', '') |
| title = page.get('title', '') |
| |
| |
| html = page.get('html', '') |
| existing_meta = re.search(r'<meta\s+name=["\']description["\']\s+content=["\']([^"\']+)["\']', html, re.IGNORECASE) |
| |
| has_meta = bool(existing_meta) |
| existing_description = existing_meta.group(1) if existing_meta else None |
| |
| |
| new_description = generate_meta_description(page, api_keys) |
| |
| |
| issues = [] |
| if has_meta: |
| if len(existing_description) < 120: |
| issues.append('قصير جداً (أقل من 120 حرف)') |
| elif len(existing_description) > 160: |
| issues.append('طويل جداً (أكثر من 160 حرف)') |
| |
| if not any(keyword in existing_description.lower() for keyword in title.lower().split()[:3]): |
| issues.append('لا يحتوي على كلمات مفتاحية من العنوان') |
| |
| results.append({ |
| 'url': url, |
| 'title': title, |
| 'has_meta': has_meta, |
| 'existing': existing_description, |
| 'suggested': new_description, |
| 'issues': issues, |
| 'status': 'جيد ✓' if has_meta and not issues else 'يحتاج تحسين ⚠️' if has_meta else 'مفقود ❌' |
| }) |
| |
| return results |
|
|
|
|
| def get_meta_description_recommendations(audit: Dict) -> Dict: |
| """Get summary and recommendations for meta descriptions""" |
| pages = audit.get('pages', []) |
| |
| total_pages = len(pages) |
| pages_with_meta = 0 |
| pages_with_good_meta = 0 |
| pages_missing_meta = 0 |
| |
| for page in pages: |
| html = page.get('html', '') |
| meta_match = re.search(r'<meta\s+name=["\']description["\']\s+content=["\']([^"\']+)["\']', html, re.IGNORECASE) |
| |
| if meta_match: |
| pages_with_meta += 1 |
| description = meta_match.group(1) |
| if 120 <= len(description) <= 160: |
| pages_with_good_meta += 1 |
| else: |
| pages_missing_meta += 1 |
| |
| score = int((pages_with_good_meta / total_pages * 100)) if total_pages > 0 else 0 |
| |
| recommendations = [] |
| if pages_missing_meta > 0: |
| recommendations.append(f'أضف meta description لـ {pages_missing_meta} صفحة') |
| |
| if pages_with_meta - pages_with_good_meta > 0: |
| recommendations.append(f'حسّن meta description لـ {pages_with_meta - pages_with_good_meta} صفحة') |
| |
| return { |
| 'score': score, |
| 'total_pages': total_pages, |
| 'pages_with_meta': pages_with_meta, |
| 'pages_with_good_meta': pages_with_good_meta, |
| 'pages_missing_meta': pages_missing_meta, |
| 'status': 'ممتاز ✅' if score >= 90 else 'جيد ✓' if score >= 70 else 'يحتاج تحسين ⚠️', |
| 'recommendations': recommendations |
| } |
|
|