last_edit / server /mobile_checker.py
Moharek
Deploy Moharek GEO Platform
a74b879
"""
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 = []
# Check viewport meta tag
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 غير صحيح'
})
# Check for responsive CSS
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 للتصميم المتجاوب')
# Check font sizes
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 للنصوص الأساسية')
# Check for fixed width elements
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('استخدم النسب المئوية بدلاً من البكسل الثابت')
# Check touch targets
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:
# Check if too small (less than 44px)
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 على الأقل')
# Check for horizontal scrolling
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)