דלג לתוכן הראשי
scraping.
חזרה לכל המאמרים

Playwright מתקדם — טכניקות ל-scraping מקצועי

8 במאי 20268 דק׳ קריאה
איור מופשט של קווי נתונים וצמתים ברשת, המסמל את המורכבות של web scraping מתקדם עם Playwright

מעבר ל-`page.goto()`: למה ה-scraper הבסיסי שלך ייכשל

רוב המדריכים על Playwright נעצרים אחרי שמראים איך לפתוח דף, ללחוט על כפתור ולגרד טקסט. זה נחמד, אבל זה לא scraping. זה סקריפט אוטומציה פשוט. כשאתה מריץ scraper אמיתי, בסקייל, מול אתרים אמיתיים עם הגנות אמיתיות, הגישה הזו נשברת תוך שעות, אם לא דקות.

בניתי מערכות שגירדו מיליוני דפים ביום, ובניתי גם כאלה שקרסו אחרי 1000. ההבדל הוא לא בלולאה הראשית, אלא בכלים שאתה מפעיל מסביב. אנחנו נדבר על הכלים האלה. על הטכניקות שמאפשרות ל-scraper שלך לרוץ יציב, מהר, ובלי שיחסמו אותך אחרי חצי שעה. אם אתה כאן רק כדי להעתיק קוד ל-`page.locator()`, אתה במקום הלא נכון. אם אתה רוצה לבנות משהו שמחזיק מעמד, תמשיך לקרוא.

למה ה-Scraper שלך נשבר ב-3 לפנות בוקר? הכירו את ה-Trace Viewer

זה התרחיש: אתה מתעורר בבוקר, מסתכל על הדאשבורד, ורואה שה-scraper שלך הפסיק לעבוד ב-3:17 לפנות בוקר. הלוגים סתמיים: "TimeoutError: waiting for selector". מה קרה שם? אולי קפץ פופאפ? אולי ה-UI השתנה במפתיע? אולי נתקעת ב-CAPTCHA? אין לך דרך לדעת.

כאן נכנס ה-Playwright Trace Viewer. זה כלי שמקליט את כל מה שקרה בריצה — כל פעולה, כל קריאת רשת, כל שינוי ב-DOM, וצילומי מסך לפני ואחרי כל שלב. זה כמו קופסה שחורה לריצות שלך. במקום לנחש, אתה פשוט פותח את קובץ ה-trace ומקבל סרטון אינטראקטיבי של הכישלון.

ההפעלה שלו פשוטה עד גיחוך. כשאתה יוצר browser context חדש, אתה פשוט אומר לו להתחיל להקליט, ובבלוק ה-`finally` או ה-`except` אתה שומר את הקובץ אם משהו השתבש.

import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        context = await browser.new_context()
        # Start tracing
        await context.tracing.start(screenshots=True, snapshots=True, sources=True)
        page = await context.new_page()
        try:
            await page.goto("https://example.com/login")
            # ... Your scraping logic that might fail ...
            await page.locator("#non-existent-element").click(timeout=5000)
        except Exception as e:
            print(f"An error occurred: {e}")
            # Save the trace file on failure
            await context.tracing.stop(path = "trace_on_failure.zip")
        finally:
            await browser.close()

asyncio.run(main())

הקובץ `trace_on_failure.zip` שנוצר הוא כל מה שאתה צריך. פותחים אותו עם `playwright show-trace trace_on_failure.zip` בטרמינל ומקבלים תחקיר מלא. זה חוסך שעות של דיבאגינג. אל תריצו scraper רציני בלי זה.

לשלוט ברשת: Network Interception עם Route Handlers

דפדפן מודרני טוען המון זבל. פיקסלים של פייסבוק, סקריפטים של אנליטיקס, גופנים, תמונות, קבצי CSS. כל אלה מאטים את ה-scraper שלך, צורכים רוחב פס יקר (במיוחד אם אתה משתמש ב-residential proxies יקרים) ולא תורמים כלום למידע שאתה צריך.

עם `page.route()`, אתה יכול ליירט כל בקשת רשת שיוצאת מהדף ולהחליט מה לעשות איתה: לאפשר לה להמשיך, לחסום אותה, או אפילו לשנות אותה בזמן אמת. חסימת משאבים לא רלוונטיים יכולה להאיץ את טעינת הדפים ב-40-70% ולהפחית את צריכת הנתונים באופן דרמטי.

async def block_unnecessary_requests(page):
    await page.route("**/*", lambda route: route.abort() 
        if route.request.resource_type in ["image", "stylesheet", "font", "media"] 
        else route.continue_())

# ... inside your main function
page = await context.new_page()
await block_unnecessary_requests(page)
await page.goto("https://some-heavy-website.com")
# This will load much faster now

אבל זה רק קצה הקרחון. אפשר להשתמש ב-interception כדי לטפל בבקשות API. נניח שאתר טוען את המידע שלו דרך קריאת `fetch` ל-API פנימי. במקום לנתח HTML, אתה יכול ליירט את התגובה של ה-API ולקחת את ה-JSON הנקי ישירות משם. זה מהיר יותר, יציב יותר, ופחות שביר מ-selectors של CSS.

בידוד זה שם המשחק: Browser Contexts להרצה מקבילית

הדרך הנאיבית להריץ כמה משימות במקביל היא לפתוח כמה `chromium.launch()` נפרדים. זו טעות. כל instance כזה הוא תהליך כבד שצורך המון זיכרון ו-CPU. הדרך הנכונה היא להשתמש ב-Browser Contexts.

חשוב על `Browser` כעל התקנת כרום שלך, ועל `BrowserContext` כעל פרופיל משתמש נפרד (או חלון אינקוגניטו). כל context הוא מבודד לחלוטין מהאחרים — יש לו קוקיז, localStorage, ו-session storage משלו. זה מאפשר לך להריץ עשרות סשנים במקביל תחת תהליך דפדפן יחיד, עם ביצועים טובים בהרבה.

זה קריטי כשאתה צריך לגרד מידע שדורש התחברות עם מספר חשבונות במקביל, או כשאתה רוצה שכל משימה תתחיל מסביבה "נקייה" לחלוטין בלי שרידים של משימות קודמות. ניהול נכון של contexts הוא הבסיס לכל ארכיטקטורת scraping בסקייל.

async def run_task_in_context(browser, url, task_id):
    context = await browser.new_context()
    page = await context.new_page()
    print(f"Task {task_id}: starting")
    await page.goto(url)
    title = await page.title()
    print(f"Task {task_id}: got title '{title}'")
    await context.close() # Crucial to release resources

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        tasks = [
            run_task_in_context(browser, "https://www.google.com", 1),
            run_task_in_context(browser, "https://www.bing.com", 2),
            run_task_in_context(browser, "https://www.duckduckgo.com", 3)
        ]
        await asyncio.gather(*tasks)
        await browser.close()

שימוש נכון ב-contexts מאפשר לך להגיע למאות ואלפי בקשות בדקה ממכונה בודדת, תוך שמירה על בידוד מלא בין המשימות.

אבל רגע — מה קורה כשהזיכרון דולף?

גם עם contexts, תהליכי דפדפן הם חיות מורכבות. אחרי ריצה של שעות ואלפי דפים, דליפות זיכרון קטנות יכולות להצטבר. ראיתי את זה קורה יותר מדי פעמים: scraper שעובד נהדר במשך שעתיים, ואז מתחיל להאט, ובסוף קורס כשהשרת נחנק בלי RAM. זה כישלון שקט ומתסכל.

הפתרון הוא לא לנסות למצוא את הדליפה המדויקת בכרום (בהצלחה עם זה), אלא לנקוט בגישה הגנתית. תכנן את ה-worker שלך כך שיעשה ריסטרט לעצמו באופן יזום. למשל, אחרי כל 100 משימות, או כל 30 דקות, תסגור את ה-`browser` instance לחלוטין ותפתח אחד חדש. זה אולי נשמע "מלוכלך", אבל בפרקטיקה זו הדרך הכי יציבה להבטיח שהתהליכים שלך נשארים רזים ובריאים לאורך זמן. ה-overhead של פתיחת דפדפן חדש כל כמה מאות משימות הוא זניח לעומת העלות של קריסה באמצע ריצה ארוכה.

מעבר ל-DOM: הפעלת קוד JS עם `evaluate()`

לפעמים, המידע שאתה מחפש לא נמצא ב-HTML. הוא מחושב דינמית על ידי JavaScript בצד הלקוח, או מאוחסן במשתנה גלובלי כמו `window.INITIAL_STATE`. ניסיון לחקות את הלוגיקה הזו או לחכות שהיא תופיע ב-DOM יכול להיות סיוט.

במקום זה, אפשר פשוט לבקש מהדפדפן להריץ קוד JavaScript בשבילנו ולקבל את התוצאה. הפונקציה `page.evaluate()` עושה בדיוק את זה. היא מקבלת פונקציית JS, מריצה אותה בקונטקסט של הדף, ומחזירה את התוצאה בחזרה לקוד הפייתון שלך. זה פותח דלת לעולם שלם של אפשרויות.

# Assuming the page has a JS variable: window.appData = { user: 'John', score: 120 };
user_score = await page.evaluate("() => window.appData.score")
print(f"The user's score is: {user_score}") # Output: The user's score is: 120

# You can also pass arguments to the function
user_agent = await page.evaluate("() => navigator.userAgent")
print(f"Browser user agent: {user_agent}")

השימוש ב-`evaluate` הוא טכניקה חזקה במיוחד נגד אתרים מבוססי React/Vue/Angular, שם חלק גדול מהסטייט של האפליקציה חי באובייקטים של JavaScript ולא ישירות ב-DOM. זה גם חלק חשוב בארסנל של טכניקות התחמקות מזיהוי בוטים, כי זה מאפשר לך לאסוף מידע שהדפדפן "יודע" על עצמו.

לסיכום: להרכיב את הפאזל

Playwright הוא הרבה יותר מסתם כלי לאוטומציה. הוא פלטפורמה שלמה לבניית מערכות איסוף מידע אמינות. הטכניקות שסקרנו כאן — Trace Viewer לדיבאג, יירוט רשת לאופטימיזציה, contexts לבידוד, ו-`evaluate` לגישה ישירה ל-JS — הן אבני הבניין של scraper מקצועי.

השלב הבא הוא לשלב את הכלים האלה במערכת גדולה יותר, עם תור עבודות, ניהול פרוקסי, וטיפול ב-retries. כלים כמו Scrapy או Celery יכולים לנהל את החלק של התזמון והמשימות, כש-Playwright משמש כמנוע הביצוע החזק שלהם. המטרה היא לא רק לגרום לסקריפט לעבוד פעם אחת על המחשב שלך, אלא לבנות מכונה משומנת שיודעת להתמודד עם כישלונות, חסימות ושינויים באתר המטרה, ולהמשיך לרוץ 24/7.

שאלות נפוצות

הבחירה תלויה בעיקר באתר המטרה. אם האתר הוא static HTML פשוט, שימוש ב-Requests ו-BeautifulSoup יהיה מהיר ויעיל בהרבה. אבל, אם האתר דורש אינטראקציה, טוען תוכן דינמית עם JavaScript (כמו רוב האתרים המודרניים), או מוגן במערכות כמו Cloudflare, אז Playwright הוא הכרחי. הוא מריץ דפדפן אמיתי, מה שמאפשר לו להתמודד עם כל לוגיקה בצד הלקוח ולבצע פעולות כמו משתמש אנושי, מה שהופך אותו לחזק לאין שיעור במקרים מורכבים.

Playwright כשלעצמו לא פותר CAPTCHA, אבל הוא מספק את התשתית לעשות זאת. ניתן לשלב אותו עם שירותי פתרון CAPTCHA של צד שלישי דרך API. בנוגע לזיהוי בוטים, שימוש בגרסאות stealth של Playwright (כמו `playwright-stealth`) עוזר להסוות את העובדה שהדפדפן מופעל אוטומטית. טכניקות מתקדמות כמו שינוי User-Agent, שימוש ב-residential proxies, וחימום קוקיז בתוך Browser Contexts מבודדים הן קריטיות כדי להיראות כמו תנועה אנושית לגיטימית.

ההבדל הוא בעיקר במשאבים וביצועים. פתיחת `Browser` instance חדש מפעילה תהליך דפדפן נפרד וכבד, שצורך כמות משמעותית של זיכרון ו-CPU. לעומת זאת, `BrowserContext` הוא מבנה לוגי קל משקל שחי בתוך תהליך `Browser` קיים. זה מאפשר לך ליצור עשרות סביבות גלישה מבודדות (עם קוקיז ו-storage נפרדים) תוך צריכת משאבים נמוכה משמעותית. להרצה מקבילית בסקייל, שימוש ב-contexts הוא חובה.

ל-web scraping, הגרסה האסינכרונית (AsyncIO) היא כמעט תמיד הבחירה הנכונה. טבעו של scraping כולל המון המתנה – לדף להיטען, לרכיב להופיע, לתשובת רשת. AsyncIO מאפשר לתוכנית שלך לבצע משימות אחרות בזמן ההמתנה הזה, במקום סתם לחסום את התהליך. זה מאפשר יעילות גבוהה בהרבה, במיוחד כשמריצים מספר דפדפנים או contexts במקביל. הגרסה הסינכרונית נוחה יותר לסקריפטים פשוטים או לבדיקות, אבל לא מתאימה לסקייל.

הטעות הגדולה ביותר היא הזנחת ניהול משאבים וכישלונות. מתחילים נוטים לכתוב סקריפט שעובד על המחשב שלהם פעם אחת, ומניחים שהוא ירוץ לנצח על שרת. בפרודקשן, חובה לסגור contexts ודפדפנים כשלא צריכים אותם (`context.close()`), לטפל בחריגות כמו Timeouts, להגדיר מנגנון retries עם backoff, ולנטר את צריכת הזיכרון. ללא הנדסת אמינות כזו, ה-scraper יקרוס באופן בלתי נמנע, בדרך כלל בזמן הכי גרוע.

אהבתם את הכתבה? הצטרפו לניוזלטר ה-AI.

סיכום שבועי של כל מה שחדש ב-AI, פרומפטים מעשיים וביקורות כלים — ישר למייל שלכם.

הירשמו עכשיו

עוד לקריאה