أتمتة تتبع أسعار المنتجات بـ Playwright و Discord
لو بتستنى منتج ينزل سعره على Amazon أو Noon، بتدخل تشيك يدوي كل يوم وبتنسى أو بتلاقي السعر طلع تاني. السكربت اللي هنا بيراقب لك السعر كل ساعة، ويبعتلك Discord notification لمّا ينزل تحت الحد اللي حددته، بدون اشتراك شهري ولا أدوات خارجية.
المشكلة باختصار
تتبع الأسعار يدوي مش مستدام. سواء بتراقب لاب توب، شاشة، أو حتى تذكرة طيران، الـ price drops غالبًا بتحصل في وقت غير متوقع وبتختفي خلال ساعات. أدوات زي Keepa أو CamelCamelCamel ممتازة لـ Amazon US، لكن مش بتغطي Noon ولا Amazon.eg ولا متاجر إقليمية تانية. والاشتراكات الجاهزة بتاخد منك من 5 لـ 15 دولار شهريًا لكل منصة.
الحل اللي هنبنيه هنا بيشتغل على أي متجر فيه HTML واضح للسعر، بيتكلّف صفر دولار، وبيشتغل على VPS بـ 5 دولار شهريًا لـ 50 منتج بسهولة.
المكونات اللي محتاجها
- Playwright: browser automation framework بيفتح المتجر وياخد السعر زي ما المستخدم العادي يعمل، فبيتجاوز الـ anti-bot الأساسي اللي بيقع فيه requests/BeautifulSoup.
- Python 3.10+: اللغة اللي هنشغل بيها السكربت.
- Discord Webhook: مجاني تمامًا، بيبعت إشعار مباشر لأي قناة في السيرفر بتاعك. تقدر تعمله من Server Settings → Integrations → Webhooks.
- cron job: لتشغيل السكربت كل ساعة أو حسب التردد اللي عايزه.
مثال بسيط للقارئ المبتدئ
تخيّل إن عندك صديق بيدخل صفحة المنتج كل ساعة، يبص على السعر، ولو لقاه نزل بيبعتلك واتساب. اللي هنعمله هنا بالظبط نفس الفكرة — بس الصديق ده برنامج بيشتغل لوحده، Discord بدل واتساب، و cron هو اللي بيقوله "روح شوف دلوقتي". دي اللي اسمها أتمتة دورية — أي مهمة بتتكرر بتتحوّل لسكربت + جدولة زمنية.
السكربت الكامل
السكربت بياخد قائمة منتجات من ملف JSON، يفتح كل منتج في Playwright، يستخرج السعر بالـ CSS selector اللي حددته، يقارنه بالحد المسجل، ولو السعر نزل بيبعت Discord notification بكل التفاصيل.
import asyncio
import json
import os
import httpx
from playwright.async_api import async_playwright
DISCORD_WEBHOOK = os.environ["DISCORD_WEBHOOK_URL"]
async def get_price(page, product):
await page.goto(product["url"], wait_until="domcontentloaded", timeout=30000)
await page.wait_for_selector(product["selector"], timeout=15000)
text = await page.inner_text(product["selector"])
digits = "".join(c for c in text if c.isdigit() or c == ".")
return float(digits)
async def notify(product, current_price):
message = {
"content": (
f"السعر نزل! **{product['name']}**\n"
f"السعر الحالي: {current_price} EGP\n"
f"الحد المستهدف: {product['target_price']} EGP\n"
f"{product['url']}"
)
}
async with httpx.AsyncClient(timeout=10) as client:
await client.post(DISCORD_WEBHOOK, json=message)
async def main():
with open("products.json") as f:
products = json.load(f)
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
)
)
page = await context.new_page()
for product in products:
try:
price = await get_price(page, product)
if price <= product["target_price"]:
await notify(product, price)
print(f"[ALERT] {product['name']}: {price}")
else:
print(f"[OK] {product['name']}: {price} > {product['target_price']}")
except Exception as e:
print(f"[ERROR] {product['name']}: {e}")
await browser.close()
if __name__ == "__main__":
asyncio.run(main())