Update app.py
Browse files
app.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import streamlit as st
|
|
|
2 |
from streamlit_option_menu import option_menu
|
3 |
import requests
|
4 |
import pandas as pd
|
@@ -16,506 +17,320 @@ import dns.resolver
|
|
16 |
from urllib.parse import urlparse
|
17 |
import json
|
18 |
import numpy as np
|
|
|
|
|
|
|
19 |
from PIL import Image
|
20 |
import io
|
21 |
import time
|
22 |
-
import matplotlib.pyplot as plt
|
23 |
-
import seaborn as sns
|
24 |
-
from datetime import timedelta
|
25 |
-
import tldextract
|
26 |
-
from concurrent.futures import ThreadPoolExecutor
|
27 |
-
import re
|
28 |
-
from collections import Counter
|
29 |
-
from wordcloud import WordCloud
|
30 |
-
import advertools as adv
|
31 |
|
32 |
-
#
|
33 |
-
st.set_page_config(
|
34 |
-
layout="wide",
|
35 |
-
page_title="محلل المواقع المتقدم | Website Analyzer Pro",
|
36 |
-
page_icon="🔍",
|
37 |
-
initial_sidebar_state="expanded"
|
38 |
-
)
|
39 |
|
40 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
st.markdown("""
|
42 |
<style>
|
43 |
-
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&display=swap');
|
44 |
-
|
45 |
-
* {
|
46 |
-
font-family: 'Tajawal', sans-serif;
|
47 |
-
}
|
48 |
-
|
49 |
.main {
|
50 |
-
background:
|
51 |
-
padding: 20px;
|
52 |
-
}
|
53 |
-
|
54 |
-
.metric-card {
|
55 |
-
background: white;
|
56 |
-
border-radius: 15px;
|
57 |
-
padding: 20px;
|
58 |
-
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
59 |
-
transition: all 0.3s ease;
|
60 |
-
margin-bottom: 20px;
|
61 |
}
|
62 |
-
|
63 |
-
.metric-card:hover {
|
64 |
-
transform: translateY(-5px);
|
65 |
-
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
66 |
-
}
|
67 |
-
|
68 |
-
.metric-value {
|
69 |
-
font-size: 2em;
|
70 |
-
font-weight: bold;
|
71 |
-
color: #2196F3;
|
72 |
-
}
|
73 |
-
|
74 |
-
.metric-label {
|
75 |
-
color: #666;
|
76 |
-
font-size: 1.1em;
|
77 |
-
}
|
78 |
-
|
79 |
.stButton>button {
|
80 |
-
background: linear-gradient(45deg, #2196F3, #21CBF3);
|
81 |
color: white;
|
82 |
-
|
83 |
-
|
|
|
84 |
border: none;
|
85 |
-
box-shadow: 0 4px 15px rgba(33,150,243,0.3);
|
86 |
-
transition: all 0.3s ease;
|
87 |
-
font-size: 1.1em;
|
88 |
-
font-weight: 500;
|
89 |
-
width: 100%;
|
90 |
}
|
91 |
-
|
92 |
.stButton>button:hover {
|
93 |
-
|
94 |
-
|
95 |
-
}
|
96 |
-
|
97 |
-
h1, h2, h3 {
|
98 |
-
color: #1E3D59;
|
99 |
-
font-weight: 700;
|
100 |
-
}
|
101 |
-
|
102 |
-
.stTextInput>div>div>input {
|
103 |
-
border-radius: 10px;
|
104 |
-
border: 2px solid #E0E0E0;
|
105 |
-
padding: 12px;
|
106 |
-
font-size: 1.1em;
|
107 |
-
transition: all 0.3s ease;
|
108 |
-
}
|
109 |
-
|
110 |
-
.stTextInput>div>div>input:focus {
|
111 |
-
border-color: #2196F3;
|
112 |
-
box-shadow: 0 0 0 2px rgba(33,150,243,0.2);
|
113 |
}
|
114 |
-
|
115 |
-
.streamlit-expanderHeader {
|
116 |
background-color: white;
|
117 |
border-radius: 10px;
|
118 |
-
padding: 10px;
|
119 |
-
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
120 |
-
}
|
121 |
-
|
122 |
-
.stProgress > div > div > div {
|
123 |
-
background-color: #2196F3;
|
124 |
-
}
|
125 |
-
|
126 |
-
.tab-content {
|
127 |
padding: 20px;
|
128 |
-
|
129 |
-
border-radius: 15px;
|
130 |
-
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
131 |
-
}
|
132 |
-
|
133 |
-
.insight-card {
|
134 |
-
background: #f8f9fa;
|
135 |
-
border-right: 4px solid #2196F3;
|
136 |
-
padding: 15px;
|
137 |
-
margin: 10px 0;
|
138 |
-
border-radius: 8px;
|
139 |
-
}
|
140 |
-
|
141 |
-
.chart-container {
|
142 |
-
background: white;
|
143 |
-
padding: 20px;
|
144 |
-
border-radius: 15px;
|
145 |
-
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
146 |
-
margin: 20px 0;
|
147 |
}
|
148 |
</style>
|
149 |
""", unsafe_allow_html=True)
|
150 |
|
151 |
-
class
|
152 |
def __init__(self):
|
153 |
self.headers = {
|
154 |
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
155 |
}
|
156 |
-
|
157 |
-
|
158 |
-
def load_history(self):
|
159 |
-
try:
|
160 |
-
return pd.read_csv('analysis_history.csv')
|
161 |
-
except:
|
162 |
-
return pd.DataFrame(columns=['url', 'timestamp', 'performance_score', 'seo_score', 'security_score'])
|
163 |
-
|
164 |
-
def save_history(self, data):
|
165 |
-
self.history = self.history.append(data, ignore_index=True)
|
166 |
-
self.history.to_csv('analysis_history.csv', index=False)
|
167 |
-
|
168 |
async def analyze_performance(self, url):
|
169 |
try:
|
170 |
start_time = time.time()
|
171 |
async with httpx.AsyncClient() as client:
|
172 |
response = await client.get(url)
|
173 |
load_time = time.time() - start_time
|
174 |
-
page_size = len(response.content) / 1024
|
175 |
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
css_files = soup.find_all('link', {'rel': 'stylesheet'})
|
181 |
-
|
182 |
-
performance_metrics = {
|
183 |
-
"زمن التحميل": round(load_time, 2),
|
184 |
-
"حجم الصفحة": round(page_size, 2),
|
185 |
-
"حالة الاستجابة": response.status_code,
|
186 |
-
"عدد الصور": len(images),
|
187 |
-
"عدد ملفات JavaScript": len(scripts),
|
188 |
-
"عدد ملفات CSS": len(css_files),
|
189 |
-
"تقييم الأداء": self._calculate_performance_score(load_time, page_size, len(images), len(scripts)),
|
190 |
-
"توصيات التحسين": self._get_performance_recommendations(load_time, page_size, len(images), len(scripts))
|
191 |
}
|
192 |
-
|
193 |
-
# إضافة تحليل الموارد
|
194 |
-
resources_analysis = await self._analyze_resources(url)
|
195 |
-
performance_metrics.update(resources_analysis)
|
196 |
-
|
197 |
-
return performance_metrics
|
198 |
except Exception as e:
|
199 |
-
return {"error":
|
200 |
|
201 |
-
async def
|
202 |
try:
|
203 |
async with httpx.AsyncClient() as client:
|
204 |
response = await client.get(url)
|
205 |
soup = BeautifulSoup(response.text, 'html.parser')
|
206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
# تحليل الصور
|
208 |
images = soup.find_all('img')
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
|
218 |
return {
|
219 |
-
"
|
220 |
-
"
|
221 |
-
"
|
222 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
}
|
224 |
}
|
225 |
except Exception as e:
|
226 |
-
return {"error":
|
227 |
|
228 |
-
def
|
229 |
-
|
230 |
-
|
231 |
-
# تأثير زمن التحميل
|
232 |
-
if load_time > 2:
|
233 |
-
score -= min(30, (load_time - 2) * 10)
|
234 |
|
235 |
-
|
236 |
-
|
237 |
-
|
|
|
|
|
238 |
|
239 |
-
|
240 |
-
|
241 |
-
|
|
|
|
|
|
|
242 |
|
243 |
-
|
244 |
-
|
245 |
-
score -= min(15, (script_count - 5) * 2)
|
246 |
|
247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
|
249 |
-
def
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
"الحل": "تحسين سرعة الخادم وتفعيل التخزين المؤقت",
|
256 |
-
"الأولوية": "عالية"
|
257 |
-
})
|
258 |
-
|
259 |
-
if page_size > 1000:
|
260 |
-
recommendations.append({
|
261 |
-
"المشكلة": "حجم الصفحة كبير",
|
262 |
-
"الحل": "ضغط الملفات وتحسين الكود",
|
263 |
-
"الأولوية": "متوسطة"
|
264 |
-
})
|
265 |
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
"الحل": "تحسين حجم الصور واستخدام التحميل الكسول",
|
270 |
-
"الأولوية": "متوسطة"
|
271 |
-
})
|
272 |
|
273 |
-
|
274 |
-
|
275 |
-
"المشكلة": "عدد كبير من ملفات JavaScript",
|
276 |
-
"الحل": "دمج وضغط ملفات JavaScript",
|
277 |
-
"الأولوية": "عالية"
|
278 |
-
})
|
279 |
|
280 |
-
|
281 |
-
|
282 |
-
async def analyze_seo(self, url):
|
283 |
-
try:
|
284 |
-
async with httpx.AsyncClient() as client:
|
285 |
-
response = await client.get(url)
|
286 |
-
soup = BeautifulSoup(response.text, 'html.parser')
|
287 |
-
|
288 |
-
# تحليل المحتوى
|
289 |
-
content_analysis = self._analyze_content(soup)
|
290 |
-
|
291 |
-
# تحليل الروابط
|
292 |
-
links_analysis = self._analyze_links(soup)
|
293 |
-
|
294 |
-
# تحليل الكلمات المفتاحية
|
295 |
-
keywords_analysis = self._extract_keywords(soup)
|
296 |
-
|
297 |
-
seo_analysis = {
|
298 |
-
"تحليل العنوان": self._analyze_title(soup),
|
299 |
-
"تحليل الوصف": self._analyze_description(soup),
|
300 |
-
"تحليل الكلمات المفتاحية": keywords_analysis,
|
301 |
-
"تحليل العناوين": self._analyze_headings(soup),
|
302 |
-
"تحليل الروابط": links_analysis,
|
303 |
-
"تحليل المحتوى": content_analysis,
|
304 |
-
"تقييم SEO": self._calculate_seo_score(soup),
|
305 |
-
"توصيات تحسين SEO": self._get_seo_recommendations(soup)
|
306 |
-
}
|
307 |
-
|
308 |
-
return seo_analysis
|
309 |
except Exception as e:
|
310 |
-
return
|
311 |
-
|
312 |
-
def _analyze_content(self, soup):
|
313 |
-
# استخراج النص
|
314 |
-
text_content = ' '.join([p.text for p in soup.find_all('p')])
|
315 |
-
|
316 |
-
# تحليل طول المحتوى
|
317 |
-
word_count = len(text_content.split())
|
318 |
-
|
319 |
-
# تحليل قراءة المحتوى
|
320 |
-
readability_score = self._calculate_readability(text_content)
|
321 |
-
|
322 |
-
# تحليل كثافة الكلمات المفتاحية
|
323 |
-
keyword_density = self._calculate_keyword_density(text_content)
|
324 |
-
|
325 |
-
return {
|
326 |
-
"عدد الكلمات": word_count,
|
327 |
-
"مستوى القراءة": readability_score,
|
328 |
-
"كثافة الكلمات المفتاحية": keyword_density,
|
329 |
-
"التقييم": "ممتاز" if word_count > 300 and readability_score > 60 else "يحتاج تحسين"
|
330 |
-
}
|
331 |
-
|
332 |
-
def _calculate_readability(self, text):
|
333 |
-
# حساب مؤشر بسيط لسهولة القراءة
|
334 |
-
sentences = len(re.split(r'[.!?]+', text))
|
335 |
-
words = len(text.split())
|
336 |
-
if sentences == 0:
|
337 |
-
return 0
|
338 |
-
return min(100, round((words / sentences) * 10))
|
339 |
-
|
340 |
-
def _calculate_keyword_density(self, text):
|
341 |
-
words = text.lower().split()
|
342 |
-
word_freq = Counter(words)
|
343 |
-
total_words = len(words)
|
344 |
-
|
345 |
-
if total_words == 0:
|
346 |
-
return {}
|
347 |
-
|
348 |
-
return {word: round((count / total_words) * 100, 2)
|
349 |
-
for word, count in word_freq.most_common(5)}
|
350 |
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
|
356 |
-
|
357 |
-
"
|
358 |
-
"
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
headers = response.headers
|
406 |
-
security_headers = {
|
407 |
-
'Strict-Transport-Security': 'HSTS',
|
408 |
-
'Content-Security-Policy': 'CSP',
|
409 |
-
'X-Frame-Options': 'X-Frame',
|
410 |
-
'X-Content-Type-Options': 'X-Content-Type',
|
411 |
-
'X-XSS-Protection': 'XSS Protection'
|
412 |
-
}
|
413 |
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
"موجود": header in headers,
|
418 |
-
"القيمة": headers.get(header, "غير موجود")
|
419 |
-
}
|
420 |
-
return results
|
421 |
-
except:
|
422 |
-
return {"error": "فشل فحص headers الأمان"}
|
423 |
-
|
424 |
-
def _check_security_risks(self, url):
|
425 |
-
risks = []
|
426 |
-
|
427 |
-
# فحص بروتوكول HTTP
|
428 |
-
if not url.startswith('https'):
|
429 |
-
risks.append({
|
430 |
-
"المستوى": "عالي",
|
431 |
-
"النوع": "بروتوكول غير آمن",
|
432 |
-
"الوصف": "الموقع يستخدم HTTP بدلاً من HTTPS"
|
433 |
-
})
|
434 |
-
|
435 |
-
# فحص تحديث شهادة SSL
|
436 |
-
ssl_info = self._check_ssl(url)
|
437 |
-
if ssl_info.get("الحالة") == "غير آمن ❌":
|
438 |
-
risks.append({
|
439 |
-
"المستوى": "عالي",
|
440 |
-
"النوع": "شهادة SSL",
|
441 |
-
"الوصف": "شهادة SSL غير صالحة أو منتهية"
|
442 |
-
})
|
443 |
-
|
444 |
-
# فحص headers الأمان
|
445 |
-
headers = self._check_security_headers(url)
|
446 |
-
if isinstance(headers, dict) and not headers.get("HSTS", {}).get("موجود"):
|
447 |
-
risks.append({
|
448 |
-
"المستوى": "متوسط",
|
449 |
-
"النوع": "HSTS غير مفعل",
|
450 |
-
"الوصف": "عدم وجود حماية النقل الآمن الصارم"
|
451 |
-
})
|
452 |
-
|
453 |
-
return {
|
454 |
-
"المخاطر المكتشفة": risks,
|
455 |
-
"عدد المخاطر": len(risks),
|
456 |
-
"مستوى الخطورة": "عالي" if any(r["المستوى"] == "عالي" for r in risks) else "متوسط" if risks else "منخفض"
|
457 |
-
}
|
458 |
-
|
459 |
-
def _calculate_security_score(self, url):
|
460 |
-
score = 100
|
461 |
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
score -= 25
|
470 |
-
|
471 |
-
# فحص Headers
|
472 |
-
headers = self._check_security_headers(url)
|
473 |
-
if isinstance(headers, dict):
|
474 |
-
for header_info in headers.values():
|
475 |
-
if not header_info.get("موجود"):
|
476 |
-
score -= 5
|
477 |
-
|
478 |
-
# فحص مخاطر الأمان
|
479 |
-
risks = self._check_security_risks(url)
|
480 |
-
score -= (risks.get("عدد المخاطر", 0) * 10)
|
481 |
-
|
482 |
-
return max(0, score)
|
483 |
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
# فحص HTTPS
|
488 |
-
if not url.startswith('https'):
|
489 |
-
recommendations.append({
|
490 |
-
"المشكلة": "عدم استخدام HTTPS",
|
491 |
-
"الحل": "قم بتفعيل HTTPS وتثبيت شهادة SSL",
|
492 |
-
"الأولوية": "عالية"
|
493 |
-
})
|
494 |
-
|
495 |
-
# فحص SSL
|
496 |
-
ssl_info = self._check_ssl(url)
|
497 |
-
if ssl_info.get("الحالة") == "غير آمن ❌":
|
498 |
-
recommendations.append({
|
499 |
-
"المشكلة": "شهادة SSL غير صالحة",
|
500 |
-
"الحل": "قم بتجديد أو تثبيت شهادة SSL جديدة",
|
501 |
-
"الأولوية": "عالية"
|
502 |
-
})
|
503 |
-
|
504 |
-
# فحص Headers
|
505 |
-
headers = self._check_security_headers(url)
|
506 |
-
if isinstance(headers, dict):
|
507 |
-
for name, info in headers.items():
|
508 |
-
if not info.get("موجود"):
|
509 |
-
recommendations.append({
|
510 |
-
"المشكلة": f"عدم وجود {name}",
|
511 |
-
"الحل": f"قم بإضافة header الأمان {name}",
|
512 |
-
"الأولوية": "متوسطة"
|
513 |
-
})
|
514 |
-
|
515 |
-
return recommendations if recommendations else [
|
516 |
-
{
|
517 |
-
"المشكلة": "لا توجد مشاكل أمنية واضحة",
|
518 |
-
"الحل": "استمر في مراقبة وتحديث إعدادات الأمان",
|
519 |
-
"الأولوية": "منخفضة"
|
520 |
-
}
|
521 |
-
]
|
|
|
1 |
import streamlit as st
|
2 |
+
from streamlit_lottie import st_lottie
|
3 |
from streamlit_option_menu import option_menu
|
4 |
import requests
|
5 |
import pandas as pd
|
|
|
17 |
from urllib.parse import urlparse
|
18 |
import json
|
19 |
import numpy as np
|
20 |
+
from selenium import webdriver
|
21 |
+
from selenium.webdriver.chrome.options import Options
|
22 |
+
from webdriver_manager.chrome import ChromeDriverManager
|
23 |
from PIL import Image
|
24 |
import io
|
25 |
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
+
# تخصيص المظهر
|
28 |
+
st.set_page_config(layout="wide", page_title="محلل المواقع المتقدم")
|
|
|
|
|
|
|
|
|
|
|
29 |
|
30 |
+
# تحميل الأنيميشن
|
31 |
+
def load_lottieurl(url):
|
32 |
+
try:
|
33 |
+
r = requests.get(url)
|
34 |
+
r.raise_for_status()
|
35 |
+
return r.json()
|
36 |
+
except Exception as e:
|
37 |
+
# Fallback to a basic loading animation JSON
|
38 |
+
return {
|
39 |
+
"v": "5.5.7",
|
40 |
+
"fr": 29.9700012207031,
|
41 |
+
"ip": 0,
|
42 |
+
"op": 180.00000733155,
|
43 |
+
"w": 500,
|
44 |
+
"h": 500,
|
45 |
+
"nm": "Loading",
|
46 |
+
"ddd": 0,
|
47 |
+
"assets": [],
|
48 |
+
"layers": [
|
49 |
+
{
|
50 |
+
"ddd": 0,
|
51 |
+
"ind": 1,
|
52 |
+
"ty": 4,
|
53 |
+
"nm": "Loading Circle",
|
54 |
+
"sr": 1,
|
55 |
+
"ks": {
|
56 |
+
"o": {"a": 0, "k": 100, "ix": 11},
|
57 |
+
"r": {"a": 1, "k": [{"i": {"x": [0.833], "y": [0.833]}, "o": {"x": [0.167], "y": [0.167]}, "t": 0, "s": [0], "e": [360]}, {"t": 180.00000733155}], "ix": 10}
|
58 |
+
}
|
59 |
+
}
|
60 |
+
]
|
61 |
+
}
|
62 |
+
|
63 |
+
# تحميل الأنيميشن الافتراضي
|
64 |
+
lottie_analyzing = load_lottieurl("https://assets5.lottiefiles.com/packages/lf20_qpwbqki6.json")
|
65 |
+
|
66 |
+
# تصميم CSS مخصص
|
67 |
st.markdown("""
|
68 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
.main {
|
70 |
+
background-color: #f0f2f6;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
.stButton>button {
|
|
|
73 |
color: white;
|
74 |
+
background-color: #ff4b4b;
|
75 |
+
border-radius: 10px;
|
76 |
+
padding: 15px 25px;
|
77 |
border: none;
|
|
|
|
|
|
|
|
|
|
|
78 |
}
|
|
|
79 |
.stButton>button:hover {
|
80 |
+
background-color: #ff6b6b;
|
81 |
+
border: none;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
}
|
83 |
+
.metric-card {
|
|
|
84 |
background-color: white;
|
85 |
border-radius: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
padding: 20px;
|
87 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
}
|
89 |
</style>
|
90 |
""", unsafe_allow_html=True)
|
91 |
|
92 |
+
class WebsiteAnalyzer:
|
93 |
def __init__(self):
|
94 |
self.headers = {
|
95 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
96 |
}
|
97 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
async def analyze_performance(self, url):
|
99 |
try:
|
100 |
start_time = time.time()
|
101 |
async with httpx.AsyncClient() as client:
|
102 |
response = await client.get(url)
|
103 |
load_time = time.time() - start_time
|
104 |
+
page_size = len(response.content) / 1024 # KB
|
105 |
|
106 |
+
return {
|
107 |
+
"load_time": round(load_time, 2),
|
108 |
+
"page_size": round(page_size, 2),
|
109 |
+
"status_code": response.status_code
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
except Exception as e:
|
112 |
+
return {"error": str(e)}
|
113 |
|
114 |
+
async def analyze_seo(self, url):
|
115 |
try:
|
116 |
async with httpx.AsyncClient() as client:
|
117 |
response = await client.get(url)
|
118 |
soup = BeautifulSoup(response.text, 'html.parser')
|
119 |
|
120 |
+
# تحليل العناوين مع تقييم
|
121 |
+
headings = {
|
122 |
+
'h1': len(soup.find_all('h1')),
|
123 |
+
'h2': len(soup.find_all('h2')),
|
124 |
+
'h3': len(soup.find_all('h3'))
|
125 |
+
}
|
126 |
+
|
127 |
+
# تقييم العناوين
|
128 |
+
heading_score = 0
|
129 |
+
if headings['h1'] > 0:
|
130 |
+
heading_score += 20 # تأكد من وجود H1
|
131 |
+
heading_score += min(headings['h2'], 3) * 5 # حتى 3 عناوين H2
|
132 |
+
heading_score += min(headings['h3'], 3) * 3 # حتى 3 عناوين H3
|
133 |
+
|
134 |
+
# تحليل الروابط
|
135 |
+
links = soup.find_all('a')
|
136 |
+
internal_links = len([link for link in links if urlparse(link.get('href', '')).netloc == urlparse(url).netloc])
|
137 |
+
external_links = len(links) - internal_links
|
138 |
+
|
139 |
# تحليل الصور
|
140 |
images = soup.find_all('img')
|
141 |
+
images_without_alt = len([img for img in images if not img.get('alt')])
|
142 |
+
images_alt_score = max(0, 100 - (images_without_alt * 5)) # خصم 5 نقاط لكل صورة بدون Alt
|
143 |
+
|
144 |
+
# تحليل العنوان والوصف
|
145 |
+
title = soup.title.string if soup.title else "لا يوجد عنوان"
|
146 |
+
meta_description = soup.find("meta", {"name": "description"}) or soup.find("meta", {"property": "og:description"})
|
147 |
+
description_text = meta_description.get("content") if meta_description else "لا يوجد وصف"
|
148 |
+
|
149 |
+
# حساب نقاط العنوان والوصف
|
150 |
+
title_score = 0
|
151 |
+
if title and len(title) > 10 and len(title) <= 60:
|
152 |
+
title_score = 20
|
153 |
+
|
154 |
+
description_score = 0
|
155 |
+
if description_text != "لا يوجد وصف" and 70 <= len(description_text) <= 160:
|
156 |
+
description_score = 20
|
157 |
+
|
158 |
+
# حساب النقاط الإجمالية
|
159 |
+
total_seo_score = heading_score + images_alt_score + title_score + description_score
|
160 |
|
161 |
return {
|
162 |
+
"title": {
|
163 |
+
"text": title,
|
164 |
+
"score": title_score,
|
165 |
+
"recommendation": "يجب أن يكون العنوان بين 10-60 حرفًا" if title_score == 0 else "العنوان مثالي"
|
166 |
+
},
|
167 |
+
"meta_description": {
|
168 |
+
"text": description_text,
|
169 |
+
"score": description_score,
|
170 |
+
"recommendation": "يجب أن يكون الوصف بين 70-160 حرفًا" if description_score == 0 else "الوصف مثالي"
|
171 |
+
},
|
172 |
+
"headings": {
|
173 |
+
"details": headings,
|
174 |
+
"score": heading_score,
|
175 |
+
"recommendation": "تأكد من وجود عنوان H1 واستخدم العناوين الفرعية بشكل هرمي"
|
176 |
+
},
|
177 |
+
"internal_links": {
|
178 |
+
"count": internal_links,
|
179 |
+
"score": min(internal_links, 20),
|
180 |
+
"recommendation": "الروابط الداخلية جيدة للتنقل وتحسين SEO"
|
181 |
+
},
|
182 |
+
"external_links": {
|
183 |
+
"count": external_links,
|
184 |
+
"recommendation": "استخدم الروابط الخارجية بحكمة للمصداقية"
|
185 |
+
},
|
186 |
+
"images": {
|
187 |
+
"total": len(images),
|
188 |
+
"without_alt": images_without_alt,
|
189 |
+
"alt_score": images_alt_score,
|
190 |
+
"recommendation": f"أضف نص بديل لـ {images_without_alt} صورة"
|
191 |
+
},
|
192 |
+
"total_seo_score": {
|
193 |
+
"score": total_seo_score,
|
194 |
+
"max_score": 100,
|
195 |
+
"grade": (
|
196 |
+
"ممتاز" if total_seo_score >= 80 else
|
197 |
+
"جيد" if total_seo_score >= 60 else
|
198 |
+
"متوسط" if total_seo_score >= 40 else
|
199 |
+
"ضعيف"
|
200 |
+
)
|
201 |
}
|
202 |
}
|
203 |
except Exception as e:
|
204 |
+
return {"error": str(e)}
|
205 |
|
206 |
+
def analyze_security(self, url):
|
207 |
+
try:
|
208 |
+
domain = urlparse(url).netloc
|
|
|
|
|
|
|
209 |
|
210 |
+
# فحص SSL
|
211 |
+
ctx = ssl.create_default_context()
|
212 |
+
with ctx.wrap_socket(socket.socket(), server_hostname=domain) as s:
|
213 |
+
s.connect((domain, 443))
|
214 |
+
cert = s.getpeercert()
|
215 |
|
216 |
+
# فحص DNS
|
217 |
+
dns_records = {
|
218 |
+
'A': [str(r) for r in dns.resolver.resolve(domain, 'A')],
|
219 |
+
'MX': [str(r) for r in dns.resolver.resolve(domain, 'MX')],
|
220 |
+
'TXT': [str(r) for r in dns.resolver.resolve(domain, 'TXT')]
|
221 |
+
}
|
222 |
|
223 |
+
# معلومات WHOIS
|
224 |
+
domain_info = whois.whois(domain)
|
|
|
225 |
|
226 |
+
return {
|
227 |
+
"ssl_valid": True,
|
228 |
+
"ssl_expiry": cert['notAfter'],
|
229 |
+
"dns_records": dns_records,
|
230 |
+
"domain_info": {
|
231 |
+
"registrar": domain_info.registrar,
|
232 |
+
"creation_date": domain_info.creation_date,
|
233 |
+
"expiration_date": domain_info.expiration_date
|
234 |
+
}
|
235 |
+
}
|
236 |
+
except Exception as e:
|
237 |
+
return {"error": str(e)}
|
238 |
|
239 |
+
async def take_screenshot(self, url):
|
240 |
+
try:
|
241 |
+
chrome_options = Options()
|
242 |
+
chrome_options.add_argument('--headless')
|
243 |
+
chrome_options.add_argument('--no-sandbox')
|
244 |
+
chrome_options.add_argument('--disable-dev-shm-usage')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
|
246 |
+
driver = webdriver.Chrome(ChromeDriverManager().install(), options=chrome_options)
|
247 |
+
driver.get(url)
|
248 |
+
driver.set_window_size(1920, 1080)
|
|
|
|
|
|
|
249 |
|
250 |
+
screenshot = driver.get_screenshot_as_png()
|
251 |
+
driver.quit()
|
|
|
|
|
|
|
|
|
252 |
|
253 |
+
return Image.open(io.BytesIO(screenshot))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
254 |
except Exception as e:
|
255 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
|
257 |
+
def main():
|
258 |
+
st.title("🔍 محلل المواقع المتقدم")
|
259 |
+
|
260 |
+
# إضافة قائمة جانبية
|
261 |
+
with st.sidebar:
|
262 |
+
selected = option_menu(
|
263 |
+
menu_title="القائمة الرئيسية",
|
264 |
+
options=["تحليل جديد", "التقارير السابقة", "الإعدادات"],
|
265 |
+
icons=["search", "file-text", "gear"],
|
266 |
+
menu_icon="cast",
|
267 |
+
default_index=0,
|
268 |
+
)
|
269 |
+
|
270 |
+
if selected == "تحليل جديد":
|
271 |
+
col1, col2 = st.columns([2, 1])
|
272 |
|
273 |
+
with col1:
|
274 |
+
url = st.text_input("أدخل رابط الموقع", "https://example.com")
|
275 |
+
if st.button("بدء التحليل"):
|
276 |
+
with st.spinner("جاري التحليل..."):
|
277 |
+
st_lottie(lottie_analyzing, height=200)
|
278 |
+
|
279 |
+
analyzer = WebsiteAnalyzer()
|
280 |
+
|
281 |
+
# تحليل متزامن
|
282 |
+
loop = asyncio.new_event_loop()
|
283 |
+
asyncio.set_event_loop(loop)
|
284 |
+
performance_data = loop.run_until_complete(analyzer.analyze_performance(url))
|
285 |
+
seo_data = loop.run_until_complete(analyzer.analyze_seo(url))
|
286 |
+
security_data = analyzer.analyze_security(url)
|
287 |
+
screenshot = loop.run_until_complete(analyzer.take_screenshot(url))
|
288 |
+
|
289 |
+
# عرض النتائج
|
290 |
+
st.success("تم اكتمال التحليل!")
|
291 |
+
|
292 |
+
# عرض البطاقات الإحصائية
|
293 |
+
cols = st.columns(3)
|
294 |
+
with cols[0]:
|
295 |
+
st.metric("زمن التحميل", f"{performance_data.get('load_time', 'N/A')}s")
|
296 |
+
with cols[1]:
|
297 |
+
st.metric("حجم الصفحة", f"{performance_data.get('page_size', 'N/A')} KB")
|
298 |
+
with cols[2]:
|
299 |
+
st.metric("الروابط الداخلية", seo_data.get('internal_links', 'N/A')['count'])
|
300 |
+
|
301 |
+
# عرض تحليل SEO
|
302 |
+
with st.expander("تحليل SEO", expanded=True):
|
303 |
+
st.json(seo_data)
|
304 |
+
|
305 |
+
# رسم بياني للعناوين
|
306 |
+
if 'headings' in seo_data and 'details' in seo_data['headings']:
|
307 |
+
fig = px.bar(
|
308 |
+
x=list(seo_data['headings']['details'].keys()),
|
309 |
+
y=list(seo_data['headings']['details'].values()),
|
310 |
+
title="توزيع العناوين",
|
311 |
+
labels={'x': 'نوع العنوان', 'y': 'العدد'}
|
312 |
+
)
|
313 |
+
st.plotly_chart(fig)
|
314 |
+
|
315 |
+
# عرض تحليل الأمان
|
316 |
+
with st.expander("تحليل الأمان", expanded=True):
|
317 |
+
st.json(security_data)
|
318 |
+
|
319 |
+
# عرض لقطة الشاشة
|
320 |
+
if screenshot:
|
321 |
+
st.image(screenshot, caption="لقطة شاشة للموقع", use_column_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
322 |
|
323 |
+
with col2:
|
324 |
+
st.subheader("آخر التحليلات")
|
325 |
+
# هنا يمكن إضافة قائمة بآخر المواقع التي تم تحليلها
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
326 |
|
327 |
+
elif selected == "التقارير السابقة":
|
328 |
+
st.subheader("التقارير السابقة")
|
329 |
+
# هنا يمكن إضافة عرض للتقارير السابقة
|
330 |
|
331 |
+
elif selected == "الإعدادات":
|
332 |
+
st.subheader("إعدادات التحليل")
|
333 |
+
# هنا يمكن إضافة إعدادات التحليل
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
334 |
|
335 |
+
if __name__ == "__main__":
|
336 |
+
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|