| """ |
| Mobile-Friendly Checker |
| Analyzes website mobile responsiveness and provides recommendations |
| """ |
|
|
| import requests |
| import re |
| from bs4 import BeautifulSoup |
| from typing import Dict, List |
|
|
| def check_mobile_friendly(url: str) -> Dict: |
| """ |
| Check if a website is mobile-friendly |
| Returns score and recommendations |
| """ |
| try: |
| headers = { |
| 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15' |
| } |
| response = requests.get(url, headers=headers, timeout=10) |
| html = response.text |
| soup = BeautifulSoup(html, 'html.parser') |
| |
| score = 100 |
| issues = [] |
| recommendations = [] |
| |
| |
| viewport = soup.find('meta', attrs={'name': 'viewport'}) |
| if not viewport: |
| score -= 30 |
| issues.append({ |
| 'type': 'missing_viewport', |
| 'severity': 'critical', |
| 'message': 'لا يوجد viewport meta tag - الموقع غير متجاوب' |
| }) |
| recommendations.append('أضف <meta name="viewport" content="width=device-width, initial-scale=1">') |
| else: |
| content = viewport.get('content', '') |
| if 'width=device-width' not in content: |
| score -= 15 |
| issues.append({ |
| 'type': 'incorrect_viewport', |
| 'severity': 'high', |
| 'message': 'viewport غير صحيح' |
| }) |
| |
| |
| has_media_queries = False |
| for style in soup.find_all('style'): |
| if '@media' in style.string if style.string else '': |
| has_media_queries = True |
| break |
| |
| for link in soup.find_all('link', rel='stylesheet'): |
| href = link.get('href', '') |
| if href: |
| try: |
| css_response = requests.get(href if href.startswith('http') else url + href, timeout=5) |
| if '@media' in css_response.text: |
| has_media_queries = True |
| break |
| except: |
| pass |
| |
| if not has_media_queries: |
| score -= 20 |
| issues.append({ |
| 'type': 'no_media_queries', |
| 'severity': 'high', |
| 'message': 'لا توجد media queries - التصميم غير متجاوب' |
| }) |
| recommendations.append('استخدم CSS media queries للتصميم المتجاوب') |
| |
| |
| small_fonts = soup.find_all(string=re.compile(r'font-size:\s*[0-9]px')) |
| if len(small_fonts) > 5: |
| score -= 10 |
| issues.append({ |
| 'type': 'small_fonts', |
| 'severity': 'medium', |
| 'message': 'خطوط صغيرة جداً على الموبايل' |
| }) |
| recommendations.append('استخدم أحجام خطوط لا تقل عن 16px للنصوص الأساسية') |
| |
| |
| fixed_width = soup.find_all(style=re.compile(r'width:\s*[0-9]{4,}px')) |
| if fixed_width: |
| score -= 15 |
| issues.append({ |
| 'type': 'fixed_width', |
| 'severity': 'high', |
| 'message': f'عناصر بعرض ثابت ({len(fixed_width)} عنصر)' |
| }) |
| recommendations.append('استخدم النسب المئوية بدلاً من البكسل الثابت') |
| |
| |
| buttons = soup.find_all(['button', 'a']) |
| small_buttons = 0 |
| for btn in buttons: |
| style = btn.get('style', '') |
| if 'width' in style or 'height' in style: |
| |
| if re.search(r'(width|height):\s*[1-3][0-9]px', style): |
| small_buttons += 1 |
| |
| if small_buttons > 3: |
| score -= 10 |
| issues.append({ |
| 'type': 'small_touch_targets', |
| 'severity': 'medium', |
| 'message': f'أزرار صغيرة جداً ({small_buttons} زر)' |
| }) |
| recommendations.append('اجعل الأزرار والروابط بحجم 44x44px على الأقل') |
| |
| |
| if 'overflow-x' in html and 'scroll' in html: |
| score -= 10 |
| issues.append({ |
| 'type': 'horizontal_scroll', |
| 'severity': 'medium', |
| 'message': 'قد يحتوي على تمرير أفقي' |
| }) |
| |
| status = 'ممتاز ✅' if score >= 90 else 'جيد ✓' if score >= 70 else 'يحتاج تحسين ⚠️' if score >= 50 else 'ضعيف ❌' |
| |
| return { |
| 'ok': True, |
| 'score': max(0, score), |
| 'status': status, |
| 'issues': issues, |
| 'recommendations': recommendations, |
| 'details': { |
| 'has_viewport': bool(viewport), |
| 'has_media_queries': has_media_queries, |
| 'small_fonts_count': len(small_fonts), |
| 'fixed_width_count': len(fixed_width), |
| 'small_buttons_count': small_buttons |
| } |
| } |
| |
| except Exception as e: |
| return { |
| 'ok': False, |
| 'error': str(e), |
| 'score': 0, |
| 'status': 'فشل الفحص', |
| 'issues': [], |
| 'recommendations': [] |
| } |
|
|
|
|
| def get_mobile_score_from_pagespeed(url: str) -> Dict: |
| """ |
| Get mobile score from Google PageSpeed Insights API |
| """ |
| try: |
| import os |
| api_key = os.getenv('GOOGLE_PAGESPEED_KEY') |
| if not api_key: |
| return check_mobile_friendly(url) |
| |
| api_url = f'https://www.googleapis.com/pagespeedonline/v5/runPagespeed' |
| params = { |
| 'url': url, |
| 'key': api_key, |
| 'strategy': 'mobile', |
| 'category': 'performance' |
| } |
| |
| response = requests.get(api_url, params=params, timeout=30) |
| data = response.json() |
| |
| if 'lighthouseResult' not in data: |
| return check_mobile_friendly(url) |
| |
| lighthouse = data['lighthouseResult'] |
| categories = lighthouse.get('categories', {}) |
| performance = categories.get('performance', {}).get('score', 0) * 100 |
| |
| audits = lighthouse.get('audits', {}) |
| viewport_audit = audits.get('viewport', {}) |
| font_size_audit = audits.get('font-size', {}) |
| tap_targets_audit = audits.get('tap-targets', {}) |
| |
| issues = [] |
| recommendations = [] |
| score = int(performance) |
| |
| if viewport_audit.get('score', 1) < 1: |
| issues.append({ |
| 'type': 'viewport', |
| 'severity': 'critical', |
| 'message': viewport_audit.get('title', 'مشكلة في viewport') |
| }) |
| recommendations.append(viewport_audit.get('description', '')) |
| |
| if font_size_audit.get('score', 1) < 1: |
| issues.append({ |
| 'type': 'font_size', |
| 'severity': 'medium', |
| 'message': font_size_audit.get('title', 'خطوط صغيرة') |
| }) |
| recommendations.append(font_size_audit.get('description', '')) |
| |
| if tap_targets_audit.get('score', 1) < 1: |
| issues.append({ |
| 'type': 'tap_targets', |
| 'severity': 'medium', |
| 'message': tap_targets_audit.get('title', 'أزرار صغيرة') |
| }) |
| recommendations.append(tap_targets_audit.get('description', '')) |
| |
| status = 'ممتاز ✅' if score >= 90 else 'جيد ✓' if score >= 70 else 'يحتاج تحسين ⚠️' if score >= 50 else 'ضعيف ❌' |
| |
| return { |
| 'ok': True, |
| 'score': score, |
| 'status': status, |
| 'issues': issues, |
| 'recommendations': [r for r in recommendations if r], |
| 'source': 'pagespeed' |
| } |
| |
| except Exception as e: |
| return check_mobile_friendly(url) |
|
|