Spaces:
Sleeping
Sleeping
Claude
Claude Sonnet 5
Add rate limiting, tests, better extraction, and HF auto-deploy
ebab0e2 unverified Download app/scraper.py from Almaatla/web-scraper: direct link, hf CLI and curl.
- Browser
- Download file 2.58 kB
-
https://huggingface.co/spaces/Almaatla/web-scraper/resolve/main/app/scraper.py
- Command line
-
hf download hf://spaces/Almaatla/web-scraper/app/scraper.py
-
curl -L -o scraper.py https://huggingface.co/spaces/Almaatla/web-scraper/resolve/main/app/scraper.py
2.58 kB
| from typing import Callable, Optional | |
| from playwright.async_api import Browser, async_playwright | |
| from playwright.async_api import TimeoutError as PlaywrightTimeoutError | |
| from playwright_stealth import stealth_async | |
| from app.errors import ScrapeError | |
| class BrowserManager: | |
| """Owns the single, long-lived Playwright browser process for the app's | |
| lifetime, started/stopped via FastAPI's lifespan hook. Launching a fresh | |
| browser per request is prohibitively slow and memory-hungry; requests | |
| instead get an isolated `new_context()` from this shared browser.""" | |
| def __init__(self): | |
| self._playwright = None | |
| self._browser: Optional[Browser] = None | |
| async def start(self) -> None: | |
| self._playwright = await async_playwright().start() | |
| self._browser = await self._playwright.chromium.launch( | |
| headless=True, | |
| args=["--disable-blink-features=AutomationControlled"], | |
| ) | |
| async def stop(self) -> None: | |
| if self._browser is not None: | |
| await self._browser.close() | |
| if self._playwright is not None: | |
| await self._playwright.stop() | |
| def get_browser(self) -> Optional[Browser]: | |
| return self._browser | |
| class PlaywrightScraper: | |
| def __init__( | |
| self, | |
| browser_provider: Callable[[], Optional[Browser]], | |
| *, | |
| user_agent: str, | |
| timeout_ms: int, | |
| stealth: Callable = stealth_async, | |
| ): | |
| self._browser_provider = browser_provider | |
| self._user_agent = user_agent | |
| self._timeout_ms = timeout_ms | |
| self._stealth = stealth | |
| async def scrape(self, url: str) -> str: | |
| browser = self._browser_provider() | |
| if browser is None: | |
| raise ScrapeError(503, "Scraper is not ready yet, try again shortly.") | |
| # A fresh context per request keeps cookies/storage isolated between | |
| # callers while reusing the shared browser process. | |
| context = await browser.new_context( | |
| user_agent=self._user_agent, | |
| viewport={"width": 1920, "height": 1080}, | |
| locale="en-US", | |
| timezone_id="America/New_York", | |
| ) | |
| try: | |
| page = await context.new_page() | |
| await self._stealth(page) | |
| try: | |
| await page.goto(url, wait_until="domcontentloaded", timeout=self._timeout_ms) | |
| except PlaywrightTimeoutError: | |
| raise ScrapeError(504, "Timed out loading the page.") | |
| return await page.content() | |
| finally: | |
| await context.close() | |