למה סקריפינג של e-commerce שונה מכל דבר אחר
בואו נשים את הדברים על השולחן. אם עשיתם scraping לבלוגים או לאתרי חדשות, אתם חושבים שאתם יודעים את העבודה. אתם לא. סקריפינג של e-commerce זה משחק אחר לגמרי. זה לא לגרד טקסט סטטי מעמוד HTML פשוט. זה להיכנס לזירת קרב דיגיטלית שבה כל פיסת מידע – מחיר, מלאי, תמונה – היא נכס עסקי שהאתר מגן עליו בקנאות.
ההבדל המהותי הוא שהמידע דינמי, מותאם אישית, ומוגש דרך שכבות של JavaScript ו-API פנימיים. אתר חדשות רוצה שתקראו את התוכן שלו. אתר e-commerce רוצה שתקנו, אבל הוא ממש לא רוצה שתאספו את כל המחירים של המתחרים שלו ב-50 אלף בקשות לדקה. לכן הם משקיעים הון באמצעי הגנה, ואנחנו, כמהנדסים, צריכים להיות יצירתיים יותר.
האויב הגדול ביותר: וריאציות מוצר
רוב ה-scrapers המתחילים נופלים כאן. הם מצליחים להוציא את שם המוצר והמחיר הראשי, חושבים שניצחו, וממשיכים הלאה. אבל מה עם הנעל הזאת מגיעה ב-8 צבעים ו-12 מידות? זה 108 וריאציות שונות (SKUs), ולכל אחת יכול להיות מחיר שונה, מלאי שונה, או מבצע אחר. התעלמות מזה הופכת את הדאטה שלכם לחסר ערך.
פיצוח ה-JSON הנסתר
המידע על הווריאציות כמעט אף פעם לא נמצא ישירות ב-HTML. למה? כי זה לא יעיל לטעון את כל המידע מראש. במקום זה, הוא חבוי באחד משני מקומות:
- אובייקט JSON בתוך תג
<script>: חפשו בתגובת ה-HTML הגולמית תגים שנראים כמו<script type="application/json">או משתני JavaScript גלובליים כמוwindow.productData. - קריאת API פנימית: פתחו את ה-Developer Tools (טאב Network), שנו צבע או מידה בדף, וראו איזו בקשת XHR/Fetch נשלחת. לרוב תמצאו קריאת API שמחזירה JSON נקי עם כל המידע שאתם צריכים.
ברגע שמצאתם את המקור, העבודה הופכת להיות פשוטה יותר. במקום לנתח HTML שביר, אתם עובדים עם JSON מובנה.
# This is a conceptual example, not for a specific site
import requests
import json
import re
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
}
response = requests.get('https://example-shop.com/product/cool-sneakers', headers=headers)
# Find the script tag containing the product data
match = re.search(r'<script id="__PRODUCT_DATA__" type="application/json">(.*?)</script>', response.text)
if match:
product_data_json = json.loads(match.group(1))
variants = product_data_json.get('variants', [])
for variant in variants:
print(f"Size: {variant['size']}, Color: {variant['color']}, Price: {variant['price']}, In Stock: {variant['inStock']}")
מלחמת המחירים: תמחור דינמי ו-A/B Testing
אוקיי, אז הצלחתם למצוא את כל המחירים לכל הווריאציות. עבודה יפה. הבעיה? המחיר שאתם רואים הוא לא בהכרח המחיר שמשתמש אחר רואה. אתרי e-commerce מתוחכמים משתמשים בכמה טקטיקות כדי למקסם רווחים, וה-scraper שלכם חייב להיות מודע להן:
- תמחור דינמי (Dynamic Pricing): המחיר משתנה על בסיס שעה ביום, ביקוש, מחירי מתחרים, או אפילו מזג האוויר. המחיר של מטריה יכול לעלות ב-15% ביום גשום.
- A/B Testing: האתר מציג לשתי קבוצות של משתמשים שני מחירים שונים כדי לבדוק איזה מהם ממיר טוב יותר. אם אתם לא מחליפים זהויות, תהיו תקועים לנצח בקבוצה A.
- תמחור גיאוגרפי (Geo-Pricing): המחיר משתנה לפי המיקום הפיזי של המשתמש. משתמש מתל אביב עשוי לראות מחיר שונה ממשתמש מניו יורק.
הדרך היחידה להילחם בזה היא לאסוף דאטה ממגוון רחב של כתובות IP ומיקומים. כאן שימוש ב-residential proxies הוא לא מותרות, הוא חובה. אתם צריכים לדמות משתמשים אמיתיים ממדינות, ערים וספקי אינטרנט שונים כדי לקבל תמונה מלאה ואמינה של המחירים.
איך מזהים שהמוצר אזל מהמלאי
זיהוי מלאי הוא אחד האתגרים הכי פחות זוהרים, אבל הכי קריטיים. הודעת "אזל מהמלאי" לא תמיד מופיעה בבירור. לפעמים כפתור "הוסף לסל" פשוט הופך ללא לחיץ (disabled). לפעמים הוא נעלם לגמרי. במקרים גרועים יותר, לחיצה עליו מובילה לעמוד שגיאה או פשוט לא עושה כלום.
באתרים מודרניים מבוססי JavaScript, אי אפשר לסמוך על ה-HTML הראשוני. צריך לרנדר את הדף במלואו. כלים כמו Playwright או Puppeteer הם חברים טובים שלכם כאן. אתם יכולים לטעון את הדף, להמתין שה-JavaScript ירוץ, ואז לבדוק את המאפיינים של כפתור ההוספה לסל.
from playwright.sync_api import sync_playwright
def check_stock_status(url):
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto(url)
# Wait for the button to be visible, but don't fail if it takes time
add_to_cart_button = page.locator('#add-to-cart-button')
add_to_cart_button.wait_for(timeout=5000)
is_disabled = add_to_cart_button.is_disabled()
browser.close()
if is_disabled:
return "Out of Stock"
else:
return "In Stock"
# Example usage
# print(check_stock_status('https://example-shop.com/product/cool-sneakers'))
הגישה הזו דורשת יותר משאבים מ-requests פשוט, אבל היא הדרך היחידה לקבל תשובה מדויקת ב-95% מהמקרים. למידע נוסף על איך לגרום ל-Playwright להיראות אנושי, כדאי לקרוא על שימוש ב-Playwright עם יכולות התחמקות.
גירוד תמונות: יותר מסובך ממה שזה נשמע
"מה הבעיה, פשוט מורידים את ה-URL מהתג <img>". זה עובד, עד שזה לא. באתרי e-commerce, התמונות הן סיפור בפני עצמו. לרוב תמצאו גלריה שלמה של תמונות לכל מוצר, ברזולוציות שונות, ולפעמים הן נטענות רק כשהמשתמש גולל אליהן (Lazy Loading). בנוסף, התמונות מוגשות דרך רשתות להפצת תוכן (CDNs) שיכולות להיות להן חוקים משלהן. לפעמים CDN יחסום אתכם אם תנסו להוריד תמונה בלי לשלוח את ה-header הנכון (למשל, Referer שמצביע על דף המוצר).
הפרוטוקול הנכון הוא: לאסוף את כל כתובות ה-URL של התמונות ברזולוציה הגבוהה ביותר (לא ה-thumbnails), ואז להוריד אותן בתהליך נפרד עם headers מתאימים, תוך כיבוד קצבי הורדה סבירים כדי לא להעמיס על השרתים.
איפה רוב הגישות נכשלות (וסיפור על כישלון אחד)
רוב ה-scrapers נכשלים כי הם מתייחסים ל-e-commerce כמו לכל אתר אחר. הם מריצים סקריפט מ-IP יחיד של שרת בענן, עושים 100 בקשות בשנייה, ומתפלאים כשהם נחסמים אחרי 10 דקות או, גרוע מזה, מקבלים דאטה שגוי במשך שבועות.
סיפור כישלון אישי: בתחילת דרכי בניתי scraper עבור לקוח שרצה לעקוב אחרי מחירי מתחרים. במשך חודש שלם, ה-scraper רץ 24/7 משרת AWS יחיד ואסף מיליוני נקודות דאטה. הכל נראה מושלם. הבעיה? האתר זיהה את ה-IP כמשתמש חדש וכל הזמן הגיש לו מחירים עם "10% הנחת הצטרפות". כל הדאטה שאספנו היה מוטה ב-10% ולא שווה כלום. למדנו בדרך הקשה שרוטציית IP וניהול sessions הם לא המלצה, הם הכרח מוחלט.
הכישלון נובע מחוסר הבנה של המערכת שמולך. אתה לא נלחם ב-HTML, אתה נלחם במערכת הגנה מורכבת שכוללת זיהוי בוטים, rate limiting, וטכניקות הטעיה. התעלמות מהן היא הבטחה לכישלון.
הערה על אחריות: מה אסור לאסוף
עם כוח גדול באה אחריות גדולה. המטרה שלנו היא לאסוף מידע מוצר ציבורי: מחיר, שם, תיאור, תמונות, מלאי. מה שאסור לנו לגעת בו זה מידע אישי. אל תאספו ביקורות עם שמות משתמשים. אל תנסו לשמור קופונים אישיים שנוצרו עבור חשבון ספציפי. אל תגרדו פרטים אישיים של מוכרים בשוק מקוון. זה קו אדום. שמירה על כללי משחק אתיים לא רק מגנה עליכם משפטית, היא גם הדרך הנכונה לעבוד. תתמקדו בדאטה האגרגטיבי, לא בפרטים של משתמשים בודדים.
שאלות נפוצות
הדרך היעילה ביותר היא לא לנסות לנתח את ה-HTML של כל אפשרות, אלא למצוא את מקור המידע המובנה. ברוב אתרי ה-e-commerce המודרניים, המידע על כל הווריאציות קיים כאובייקט JSON בתוך תג `<script>` במקור הדף, או שהוא נטען דינמית דרך קריאת API פנימית. השתמשו ב-Developer Tools (במיוחד בטאב Network) כדי לאתר את הקריאה הזו, לחקות אותה, ולקבל את כל נתוני הווריאציות בפורמט JSON נקי ונוח לעיבוד.
קבלת מחירים מדויקים דורשת הדמייה של משתמשים ממקומות שונים ובתנאים שונים. הפתרון הוא שימוש בשירותי פרוקסי, ובפרט ב-residential proxies. על ידי שליחת בקשות דרך כתובות IP של משתמשים אמיתיים ממדינות וערים שונות, אתם יכולים לראות את המחיר כפי שהוא מוצג ללקוח מקומי. מומלץ לבצע דגימה ממספר מיקומים רלוונטיים ובשעות שונות כדי לקבל תמונה מלאה של אסטרטגיית התמחור של האתר.
התשובה תלויה במורכבות האתר. אם מידע המוצר (מחיר, מלאי) זמין ב-HTML הראשוני או ב-JSON מוטמע, ספריית Requests (או HTTP client אחר) תהיה מהירה ויעילה פי 10. אבל, אם האתר טוען מידע קריטי באמצעות JavaScript לאחר טעינת הדף, אין ברירה אלא להשתמש בכלי שיודע להריץ דפדפן מלא כמו Playwright או Selenium. גישה היברידית היא לרוב הטובה ביותר: השתמשו ב-Requests לרוב הבקשות, ועברו ל-Playwright רק עבור הדפים הספציפיים שדורשים זאת.
הטעות הנפוצה ביותר היא שליחת כמות בקשות גדולה מדי בפרק זמן קצר מכתובת IP יחידה. מערכות הגנה מזהות בקלות דפוס כזה כפעילות של בוט. שליחת 50 בקשות בשנייה מ-IP של שרת AWS היא דגל אדום ענק. כדי להימנע מכך, חובה להשתמש ברוטציית פרוקסיז, להגביל את קצב הבקשות לקצב אנושי סביר (למשל, בקשה כל 2-3 שניות פר IP), ולגוון את ה-User-Agents וה-headers כדי לחקות תנועה של משתמשים אמיתיים.
קשה לדעת בוודאות של 100% בבקשה בודדת, אבל ניתן להגיע למסקנה ברמת ודאות גבוהה על ידי איסוף נתונים שיטתי. שלחו בקשות לאותו דף מוצר דרך מספר כתובות IP שונות (באמצעות פרוקסי) ובאמצעות sessions נקיים (ללא קוקיז). אם אתם מקבלים באופן עקבי מחירים שונים עבור אותה בקשה בדיוק, לדוגמה 49.99$ מ-IP אחד ו-54.99$ מאחר, סביר מאוד להניח שהאתר מריץ A/B test על תמחור. תיעוד התוצאות הללו הוא קריטי לניתוח תחרותי אמיתי.
