هشرحلك بالتفصيل إزاي تبني نظام CDN + Archive للصور

← العودة
Blog Post

عنوان المحادثة: هشرحلك بالتفصيل إزاي تبني نظام CDN + Archive للصور والملفات، وإزاي ده ...

التاريخ: 13.06.2026

التصنيف: 💻 البرمجة وتطوير البرمجيات

إجمالي الرسائل: 4 | ياسر: 4 | M: 0

Yasser
هشرحلك بالتفصيل إزاي تبني نظام CDN + Archive للصور والملفات، وإزاي ده يخلي السكرابر يجمع بس الأسعار والتوفر من صفحات الفئات بدون فتح صفحات المنتجات.---🎯 المفهوم الأساسي: CDN + Archive + Cache1. CDN (Content Delivery Network)اللي هو إيه: شبكة سيرفرات موزعة جغرافياً بتخدم الملفات الثابتة (صور، CSS، JS) من أقرب نقطة للمستخدم.اللي مش هو: مش بس "سيرفر صور" — CDN بيقدم:- Edge caching → الصورة بتتخدم من أقرب سيرفر للمستخدم (مثلاً مصر → Cairo edge)- Global distribution → نفس الصورة متاحة في أمريكا، أوروبا، آسيا بسرعة- DDoS protection + SSL + HTTP/2 → ببلاش تقريباًاللي محتاجه:- Origin server = سيرفرك الأساسي اللي بيحمل الملفات الأصلية- CDN provider = Cloudflare (free), BunnyCDN, AWS CloudFront, KeyCDN- CNAME DNS = cdn.yourdomain.com → يشير لـ CDN---2. Archive System (أرشيف الملفات)اللي هو إيه: نظام بيحفظ كل ملف بشكل دائم مع metadata بتعرفه (URL المصدر، تاريخ التحميل، hash، إلخ).اللي مش هو: مش بس "مجلد ملفات" — Archive بيقدم:- Deduplication → نفس الصورة من موقعين مختلفين = ملف واحد (بناءً على content hash)- Versioning → لو الصورة اتغيرت على المصدر، تحتفظ بالنسختين- Permanent URLs → https://cdn.yourdomain.com/archive/abc123.jpg ثابت للأبد- Metadata indexing → تقدر تسأل: "جيب كل صور منتجات موقع X من شهر يناير"اللي محتاجه:- Storage backend = S3-compatible (MinIO self-hosted, AWS S3, Wasabi, Backblaze B2)- Database for metadata = PostgreSQL أو MongoDB- Hashing service = SHA-256 أو perceptual hash (phash) للصور- API layer = FastAPI/Flask بيستقبل URL ويرجع archived URL---3. Cache System (كاش ذكي)اللي هو إيه: نظام بيحفظ بيانات المنتجات الكاملة (اسم، وصف، صور، مواصفات) ويعتبرها "ثابتة" لحد ما يتغير شيء معين.اللي مش هو: مش بس "Redis cache" — Cache system بيقدم:- Product fingerprint = hash لكل بيانات المنتج (اسم + وصف + صور + مواصفات)- Partial invalidation = لما تيجي تعمل re-scrape، تفحص بس الأسعار والتوفر- Category-page scraping = تجمع كل المنتجات من صفحة واحدة (list view) بدون فتح صفحات individualاللي محتاجه:- Cache store = Redis أو PostgreSQL JSONB- Fingerprint generator = SHA-256 أو xxHash للبيانات- Diff engine = يقارن البيانات الجديدة بالقديمة ويحدد إيه اللي اتغير- Scrape orchestrator = يقرر: "ده product موجود → افحص بس السعر" أو "ده جديد → افحص كل حاجة"---🏗️ Architecture كاملة للنظام```┌─────────────────────────────────────────────────────────────┐│ SCRAPER ENGINE ││ ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐ ││ │ Spider (new) │ │ Spider (old) │ │ Category Scraper │ ││ │ Full scrape │ │ Price-only │ │ List page scrape │ ││ └──────┬───────┘ └──────┬───────┘ └──────────┬──────────┘ ││ │ │ │ ││ └─────────────────┴──────────────────────┘ ││ │ ││ ┌──────▼──────┐ ││ │ ORCHESTRATOR │ ← يقرر: full vs partial ││ │ (FastAPI) │ ││ └──────┬──────┘ │└───────────────────────────┼───────────────────────────────────┘ │ ┌───────────────────┼───────────────────┐ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │ CACHE │ │ ARCHIVE│ │ CDN │ │ (Redis) │ │ (MinIO) │ │(Cloud- │ │ │ │ │ │ flare) │ │ product │ │ image │ │ │ │ hash │ │ files │ │ edge │ │ price │ │ metadata│ │ cache │ │ status │ │ (PG) │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │
Yasser
now) last_accessed = Column(DateTime, default=datetime.utcnow) access_count = Column(Integer, default=0) # Metadata JSON meta_title = Column(String(500), nullable=True) meta_alt = Column(String(500), nullable=True) product_sku = Column(String(100), nullable=True, index=True) product_composite_sku = Column(String(200), nullable=True, index=True)pythonarchive_service/api.py (FastAPI)@app.post("/archive")async def archive_url(request: ArchiveRequest): """ بيستقبل URL ويرجع archived URL لو الملف موجود قبل كده (نفس hash) → يرجع الموجود لو مش موجود → يحمله ويخزنه ويرجع URL جديد """ # 1. Check if URL already archived existing = db.query(ArchivedFile).filter_by(original_url=request.url).first() if existing: existing.access_count += 1 existing.last_accessed = datetime.utcnow() db.commit() return {"url": existing.public_url, "cached": True} # 2. Download and hash content = await download_file(request.url) content_hash = hashlib.sha256(content).hexdigest() # 3. Deduplication check duplicate = db.query(ArchivedFile).filter_by(content_hash=content_hash).first() if duplicate: # Same file, different URL → create alias alias = ArchivedFile( original_url=request.url, archived_path=duplicate.archived_path, public_url=duplicate.public_url, content_hash=content_hash, # ... ) db.add(alias) db.commit() return {"url": duplicate.public_url, "deduplicated": True} # 4. Store new file path = f"{request.source_domain}/{content_hash[:2]}/{content_hash[2:4]}/{content_hash}.{ext}" s3.upload_fileobj(BytesIO(content), BUCKET, path) public_url = f"https://cdn.yourdomain.com/{path}" # 5. Save metadata archived = ArchivedFile( original_url=request.url, archived_path=path, public_url=public_url, content_hash=content_hash, # ... ) db.add(archived) db.commit() return {"url": public_url, "cached": False}---### B. Cache Service (كاش المنتجات)pythoncache_service/models.pyclass ProductCache(Base): __tablename__ = "product_cache" id = Column(Integer, primary_key=True) composite_sku = Column(String(200), nullable=False, index=True) source_site = Column(String(100), nullable=False, index=True) # Full product data (JSONB) full_data = Column(JSONB, nullable=True) data_fingerprint = Column(String(64), nullable=True, index=True) # hash of full_data # Fields that change frequently current_price = Column(Float, nullable=True) current_stock_status = Column(String(50), nullable=True) current_stock_quantity = Column(Integer, nullable=True) # Last checks last_full_scrape = Column(DateTime, nullable=True) last_price_check = Column(DateTime, nullable=True) last_seen = Column(DateTime, default=datetime.utcnow) # product still exists? # Scrape strategy scrape_strategy = Column(String(20), default="full") # full | price_only | category_listpythoncache_service/strategy.pydef determine_scrape_strategy(product_cache: ProductCache, spider_config: dict) -> str: """ يقرر: هنعمل full scrape ولا price check بس؟ """ now = datetime.utcnow() # Product never seen before → full scrape if not product_cache or not product_cache.last_full_scrape: return "full" # Last full scrape was recent (e.g., 7 days) → price only days_since_full = (now - product_cache.last_full_scrape).days (1/2)
Yasser
return "full" # Default: price only return "price_only"---### C. Category/List Page Scraper (السكرابر الجديد)ده الجزء المهم — بدل ما تفتح صفحة كل منتج، تجمع من صفحة الفئة:pythonspiders/category_list_scraper.pyclass CategoryListSpider(scrapy.Spider): """ بيجمع بيانات من صفحة قائمة المنتجات (list view) مش صفحة المنتج الفردي (detail view) """ def parse_category_page(self, response): """ صفحة الفئة بتجيبلك: - اسم المنتج - السعر - حالة التوفر - لينك الصورة المصغرة - SKU (لو موجود) - لينك صفحة المنتج (لو محتاج تفتحه) """ products = response.css('.product-item') # أو أي selector مناسب for product in products: # Extract from list view item = { 'name': product.css('.product-name::text').get(), 'price': product.css('.product-price::text').get(), 'stock_status': product.css('.stock-badge::text').get(), 'thumbnail_url': product.css('.product-img::attr(src)').get(), 'product_url': product.css('a::attr(href)').get(), 'sku': product.css('.sku::text').get(), } # Check cache: do we need full scrape? composite_sku = f"{self.name}_{item['sku']}" cached = get_from_cache(composite_sku) if cached and cached.scrape_strategy == "price_only": # We have this product archived → just update price yield { 'composite_sku': composite_sku, 'price': item['price'], 'stock_status': item['stock_status'], 'scrape_type': 'price_only', 'source': 'category_list' } else: # New product or stale → need full scrape yield response.follow(item['product_url'], callback=self.parse_product_detail)---### D. Spider Configuration (إعدادات السكرابر)pythonconfig/spiders.pySPIDER_CONFIG = { "madar": { "full_scrape_interval_days": 7, # كل 7 أيام نعمل full scrape "price_check_interval_hours": 6, # كل 6 ساعات نفحص السعر بس "category_list_enabled": True, # نستخدم صفحات الفئات "category_list_url": "https://madaronline.com/products?page={page}", "category_list_max_pages": 50, "detail_page_required_fields": ["description", "images", "specifications"], }, "salla_sites": { "full_scrape_interval_days": 14, # Salla أقل تغيير "price_check_interval_hours": 12, "category_list_enabled": True, # Salla API أو صفحة المنتجات "category_list_url": "https://{site}.salla.sa/products", }}---## 🔄 Flow كامل للـ Re-Scrape1. Scheduler يقول: "اعمل re-scrape لـ madar" ↓2. Spider يفتح صفحة الفئة: madaronline.com/products?page=1 ↓3. بيلاقي 24 منتج على الصفحة ↓4. لكل منتج: a. يستخرج: name, price, stock, thumbnail_url, sku b. يبني composite_sku = "madar_SKU123" c. يسأل الـ Cache: "هل المنتج ده موجود؟" ↓ ┌─ موجود + fresh (last_full_scrape │ → يرجع بيانات بس: price + stock_status │ │ → ميفتحش صفحة المنتج │ └─────────────────────────────────────────────┘ ┌─ مش موجود OR stale (>7 days) ──────────────┐ │ → يفتح صفحة المنتج (detail page) │ │ → يجمع كل البيانات + الصور │ │ → يبعت الصور للـ Archive Service │ │ → يخزن البيانات في Cache │ └─────────────────────────────────────────────┘ ↓5. يكرر لكل صفحات الفئات6. يعمل bulk update للأسعار في PostgreSQL7. يعمل bulk update للـ Cache```---🛠️ Tech Stack المقترح (1/2)
Yasser
| CDN | Cloudflare (free tier) | DDoS protection، edge cache، SSL مجاني || Cache DB | Redis + PostgreSQL JSONB | Redis للـ hot cache، PG للـ persistent cache || Archive API | FastAPI + SQLAlchemy | سريع، async، easy to maintain || Image Processing | Pillow + imagehash | perceptual hashing للـ deduplication || Queue | Redis + Celery | لـ background archiving || Monitoring | Prometheus + Grafana | track cache hit rate، archive size |---📊 Metrics هتتراقب**Cache Hit Rate**• الهدف: % المنتجات اللي مش محتاجة full scrape• التوقع: > 85%**Archive Deduplication Rate**• الهدف: % الملفات المكررة اللي اتعمل لها dedup• التوقع: > 40%**Category List Coverage**• الهدف: % المنتجات اللي اتجمعت من صفحات الفئات• التوقع: > 90%**Detail Page Requests Saved**• الهدف: عدد صفحات المنتجات اللي مفتتحتش• التوقع: يقل 80%**CDN Cache Hit**• الهدف: % الطلبات اللي اتخدمت من CDN• التوقع: > 95%---⚠️ Challenges هتواجهك1. Salla sites = مفيش صفحات فئات standard → محتاج تستخدم Salla API أو تكريب list view2. Dynamic pricing = بعض المواقع بتعرض السعر بعد JS render → Playwright مطلوب3. Stock status hidden = التوفر مش بيظهر في list view دايماً → compromise: افحص التوفر كل 12 ساعة4. Image changes = لو الصورة اتغيرت على المصدر، الـ perceptual hash هيقلك5. Cache invalidation = لو وصف المنتج اتغير، لازم تعرف امتى تعمل re-scrape---🎯 الخطة العملية (Roadmap)Phase 1: Archive Service (أسبوع)- MinIO + FastAPI + PostgreSQL metadata- API endpoint: /archive + /check- Deduplication by SHA-256Phase 2: Cache Layer (3-4 أيام)- Redis + PostgreSQL JSONB- Fingerprint generator- Strategy enginePhase 3: Category List Scraper (أسبوع)- New spider type: ListPageSpider- Extract from list view: price, stock, name, thumbnail- Decision engine: full vs price-onlyPhase 4: CDN Integration (2-3 أيام)- Cloudflare setup- CNAME: cdn.yourdomain.com- Origin pull from MinIOPhase 5: Migration (أسبوع)- Archive all existing images- Populate cache with current products- Switch spiders to new strategy---عايزني أبدأ implement أي جزء من ده؟
المحادثة الكاملة - 13.06.2026
ياسر
هشرحلك بالتفصيل إزاي تبني نظام CDN + Archive للصور والملفات، وإزاي ده يخلي السكرابر يجمع بس الأسعار والتوفر من صفحات الفئات بدون فتح صفحات المنتجات.---🎯 المفهوم الأساسي: CDN + Archive + Cache1. CDN (Content Delivery Network)اللي هو إيه: شبكة سيرفرات موزعة جغرافياً بتخدم الملفات الثابتة (صور، CSS، JS) من أقرب نقطة للمستخدم.اللي مش هو: مش بس "سيرفر صور" — CDN بيقدم:- Edge caching → الصورة بتتخدم من أقرب سيرفر للمستخدم (مثلاً مصر → Cairo edge)- Global distribution → نفس الصورة متاحة في أمريكا، أوروبا، آسيا بسرعة- DDoS protection + SSL + HTTP/2 → ببلاش تقريباًاللي محتاجه:- Origin server = سيرفرك الأساسي اللي بيحمل الملفات الأصلية- CDN provider = Cloudflare (free), BunnyCDN, AWS CloudFront, KeyCDN- CNAME DNS = cdn.yourdomain.com → يشير لـ CDN---2. Archive System (أرشيف الملفات)اللي هو إيه: نظام بيحفظ كل ملف بشكل دائم مع metadata بتعرفه (URL المصدر، تاريخ التحميل، hash، إلخ).اللي مش هو: مش بس "مجلد ملفات" — Archive بيقدم:- Deduplication → نفس الصورة من موقعين مختلفين = ملف واحد (بناءً على content hash)- Versioning → لو الصورة اتغيرت على المصدر، تحتفظ بالنسختين- Permanent URLs → https://cdn.yourdomain.com/archive/abc123.jpg ثابت للأبد- Metadata indexing → تقدر تسأل: "جيب كل صور منتجات موقع X من شهر يناير"اللي محتاجه:- Storage backend = S3-compatible (MinIO self-hosted, AWS S3, Wasabi, Backblaze B2)- Database for metadata = PostgreSQL أو MongoDB- Hashing service = SHA-256 أو perceptual hash (phash) للصور- API layer = FastAPI/Flask بيستقبل URL ويرجع archived URL---3. Cache System (كاش ذكي)اللي هو إيه: نظام بيحفظ بيانات المنتجات الكاملة (اسم، وصف، صور، مواصفات) ويعتبرها "ثابتة" لحد ما يتغير شيء معين.اللي مش هو: مش بس "Redis cache" — Cache system بيقدم:- Product fingerprint = hash لكل بيانات المنتج (اسم + وصف + صور + مواصفات)- Partial invalidation = لما تيجي تعمل re-scrape، تفحص بس الأسعار والتوفر- Category-page scraping = تجمع كل المنتجات من صفحة واحدة (list view) بدون فتح صفحات individualاللي محتاجه:- Cache store = Redis أو PostgreSQL JSONB- Fingerprint generator = SHA-256 أو xxHash للبيانات- Diff engine = يقارن البيانات الجديدة بالقديمة ويحدد إيه اللي اتغير- Scrape orchestrator = يقرر: "ده product موجود → افحص بس السعر" أو "ده جديد → افحص كل حاجة"---🏗️ Architecture كاملة للنظام```┌─────────────────────────────────────────────────────────────┐│ SCRAPER ENGINE ││ ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐ ││ │ Spider (new) │ │ Spider (old) │ │ Category Scraper │ ││ │ Full scrape │ │ Price-only │ │ List page scrape │ ││ └──────┬───────┘ └──────┬───────┘ └──────────┬──────────┘ ││ │ │ │ ││ └─────────────────┴──────────────────────┘ ││ │ ││ ┌──────▼──────┐ ││ │ ORCHESTRATOR │ ← يقرر: full vs partial ││ │ (FastAPI) │ ││ └──────┬──────┘ │└───────────────────────────┼───────────────────────────────────┘ │ ┌───────────────────┼───────────────────┐ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │ CACHE │ │ ARCHIVE│ │ CDN │ │ (Redis) │ │ (MinIO) │ │(Cloud- │ │ │ │ │ │ flare) │ │ product │ │ image │ │ │ │ hash │ │ files │ │ edge │ │ price │ │ metadata│ │ cache │ │ status │ │ (PG) │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │
13.06.2026 01:28
ياسر
now) last_accessed = Column(DateTime, default=datetime.utcnow) access_count = Column(Integer, default=0) # Metadata JSON meta_title = Column(String(500), nullable=True) meta_alt = Column(String(500), nullable=True) product_sku = Column(String(100), nullable=True, index=True) product_composite_sku = Column(String(200), nullable=True, index=True)pythonarchive_service/api.py (FastAPI)@app.post("/archive")async def archive_url(request: ArchiveRequest): """ بيستقبل URL ويرجع archived URL لو الملف موجود قبل كده (نفس hash) → يرجع الموجود لو مش موجود → يحمله ويخزنه ويرجع URL جديد """ # 1. Check if URL already archived existing = db.query(ArchivedFile).filter_by(original_url=request.url).first() if existing: existing.access_count += 1 existing.last_accessed = datetime.utcnow() db.commit() return {"url": existing.public_url, "cached": True} # 2. Download and hash content = await download_file(request.url) content_hash = hashlib.sha256(content).hexdigest() # 3. Deduplication check duplicate = db.query(ArchivedFile).filter_by(content_hash=content_hash).first() if duplicate: # Same file, different URL → create alias alias = ArchivedFile( original_url=request.url, archived_path=duplicate.archived_path, public_url=duplicate.public_url, content_hash=content_hash, # ... ) db.add(alias) db.commit() return {"url": duplicate.public_url, "deduplicated": True} # 4. Store new file path = f"{request.source_domain}/{content_hash[:2]}/{content_hash[2:4]}/{content_hash}.{ext}" s3.upload_fileobj(BytesIO(content), BUCKET, path) public_url = f"https://cdn.yourdomain.com/{path}" # 5. Save metadata archived = ArchivedFile( original_url=request.url, archived_path=path, public_url=public_url, content_hash=content_hash, # ... ) db.add(archived) db.commit() return {"url": public_url, "cached": False}---### B. Cache Service (كاش المنتجات)pythoncache_service/models.pyclass ProductCache(Base): __tablename__ = "product_cache" id = Column(Integer, primary_key=True) composite_sku = Column(String(200), nullable=False, index=True) source_site = Column(String(100), nullable=False, index=True) # Full product data (JSONB) full_data = Column(JSONB, nullable=True) data_fingerprint = Column(String(64), nullable=True, index=True) # hash of full_data # Fields that change frequently current_price = Column(Float, nullable=True) current_stock_status = Column(String(50), nullable=True) current_stock_quantity = Column(Integer, nullable=True) # Last checks last_full_scrape = Column(DateTime, nullable=True) last_price_check = Column(DateTime, nullable=True) last_seen = Column(DateTime, default=datetime.utcnow) # product still exists? # Scrape strategy scrape_strategy = Column(String(20), default="full") # full | price_only | category_listpythoncache_service/strategy.pydef determine_scrape_strategy(product_cache: ProductCache, spider_config: dict) -> str: """ يقرر: هنعمل full scrape ولا price check بس؟ """ now = datetime.utcnow() # Product never seen before → full scrape if not product_cache or not product_cache.last_full_scrape: return "full" # Last full scrape was recent (e.g., 7 days) → price only days_since_full = (now - product_cache.last_full_scrape).days (1/2)
13.06.2026 01:28
ياسر
return "full" # Default: price only return "price_only"---### C. Category/List Page Scraper (السكرابر الجديد)ده الجزء المهم — بدل ما تفتح صفحة كل منتج، تجمع من صفحة الفئة:pythonspiders/category_list_scraper.pyclass CategoryListSpider(scrapy.Spider): """ بيجمع بيانات من صفحة قائمة المنتجات (list view) مش صفحة المنتج الفردي (detail view) """ def parse_category_page(self, response): """ صفحة الفئة بتجيبلك: - اسم المنتج - السعر - حالة التوفر - لينك الصورة المصغرة - SKU (لو موجود) - لينك صفحة المنتج (لو محتاج تفتحه) """ products = response.css('.product-item') # أو أي selector مناسب for product in products: # Extract from list view item = { 'name': product.css('.product-name::text').get(), 'price': product.css('.product-price::text').get(), 'stock_status': product.css('.stock-badge::text').get(), 'thumbnail_url': product.css('.product-img::attr(src)').get(), 'product_url': product.css('a::attr(href)').get(), 'sku': product.css('.sku::text').get(), } # Check cache: do we need full scrape? composite_sku = f"{self.name}_{item['sku']}" cached = get_from_cache(composite_sku) if cached and cached.scrape_strategy == "price_only": # We have this product archived → just update price yield { 'composite_sku': composite_sku, 'price': item['price'], 'stock_status': item['stock_status'], 'scrape_type': 'price_only', 'source': 'category_list' } else: # New product or stale → need full scrape yield response.follow(item['product_url'], callback=self.parse_product_detail)---### D. Spider Configuration (إعدادات السكرابر)pythonconfig/spiders.pySPIDER_CONFIG = { "madar": { "full_scrape_interval_days": 7, # كل 7 أيام نعمل full scrape "price_check_interval_hours": 6, # كل 6 ساعات نفحص السعر بس "category_list_enabled": True, # نستخدم صفحات الفئات "category_list_url": "https://madaronline.com/products?page={page}", "category_list_max_pages": 50, "detail_page_required_fields": ["description", "images", "specifications"], }, "salla_sites": { "full_scrape_interval_days": 14, # Salla أقل تغيير "price_check_interval_hours": 12, "category_list_enabled": True, # Salla API أو صفحة المنتجات "category_list_url": "https://{site}.salla.sa/products", }}---## 🔄 Flow كامل للـ Re-Scrape1. Scheduler يقول: "اعمل re-scrape لـ madar" ↓2. Spider يفتح صفحة الفئة: madaronline.com/products?page=1 ↓3. بيلاقي 24 منتج على الصفحة ↓4. لكل منتج: a. يستخرج: name, price, stock, thumbnail_url, sku b. يبني composite_sku = "madar_SKU123" c. يسأل الـ Cache: "هل المنتج ده موجود؟" ↓ ┌─ موجود + fresh (last_full_scrape │ → يرجع بيانات بس: price + stock_status │ │ → ميفتحش صفحة المنتج │ └─────────────────────────────────────────────┘ ┌─ مش موجود OR stale (>7 days) ──────────────┐ │ → يفتح صفحة المنتج (detail page) │ │ → يجمع كل البيانات + الصور │ │ → يبعت الصور للـ Archive Service │ │ → يخزن البيانات في Cache │ └─────────────────────────────────────────────┘ ↓5. يكرر لكل صفحات الفئات6. يعمل bulk update للأسعار في PostgreSQL7. يعمل bulk update للـ Cache```---🛠️ Tech Stack المقترح (1/2)
13.06.2026 01:28
ياسر
| CDN | Cloudflare (free tier) | DDoS protection، edge cache، SSL مجاني || Cache DB | Redis + PostgreSQL JSONB | Redis للـ hot cache، PG للـ persistent cache || Archive API | FastAPI + SQLAlchemy | سريع، async، easy to maintain || Image Processing | Pillow + imagehash | perceptual hashing للـ deduplication || Queue | Redis + Celery | لـ background archiving || Monitoring | Prometheus + Grafana | track cache hit rate، archive size |---📊 Metrics هتتراقب**Cache Hit Rate**• الهدف: % المنتجات اللي مش محتاجة full scrape• التوقع: > 85%**Archive Deduplication Rate**• الهدف: % الملفات المكررة اللي اتعمل لها dedup• التوقع: > 40%**Category List Coverage**• الهدف: % المنتجات اللي اتجمعت من صفحات الفئات• التوقع: > 90%**Detail Page Requests Saved**• الهدف: عدد صفحات المنتجات اللي مفتتحتش• التوقع: يقل 80%**CDN Cache Hit**• الهدف: % الطلبات اللي اتخدمت من CDN• التوقع: > 95%---⚠️ Challenges هتواجهك1. Salla sites = مفيش صفحات فئات standard → محتاج تستخدم Salla API أو تكريب list view2. Dynamic pricing = بعض المواقع بتعرض السعر بعد JS render → Playwright مطلوب3. Stock status hidden = التوفر مش بيظهر في list view دايماً → compromise: افحص التوفر كل 12 ساعة4. Image changes = لو الصورة اتغيرت على المصدر، الـ perceptual hash هيقلك5. Cache invalidation = لو وصف المنتج اتغير، لازم تعرف امتى تعمل re-scrape---🎯 الخطة العملية (Roadmap)Phase 1: Archive Service (أسبوع)- MinIO + FastAPI + PostgreSQL metadata- API endpoint: /archive + /check- Deduplication by SHA-256Phase 2: Cache Layer (3-4 أيام)- Redis + PostgreSQL JSONB- Fingerprint generator- Strategy enginePhase 3: Category List Scraper (أسبوع)- New spider type: ListPageSpider- Extract from list view: price, stock, name, thumbnail- Decision engine: full vs price-onlyPhase 4: CDN Integration (2-3 أيام)- Cloudflare setup- CNAME: cdn.yourdomain.com- Origin pull from MinIOPhase 5: Migration (أسبوع)- Archive all existing images- Populate cache with current products- Switch spiders to new strategy---عايزني أبدأ implement أي جزء من ده؟
13.06.2026 01:28
← العودة إلى الرئيسية