|
10 | 10 | logger = get_logger("web-loader") |
11 | 11 |
|
12 | 12 |
|
| 13 | +def _warn_on_error_status(response: Any, url: str) -> None: |
| 14 | + """Log a warning when a navigation returned an HTTP error status. |
| 15 | +
|
| 16 | + Playwright's ``page.goto()`` returns the main-frame ``Response``, but the |
| 17 | + scrapers only keep ``page.content()``. Without this check an error page |
| 18 | + (404, 403, 500, a captcha wall, a login redirect) is indistinguishable |
| 19 | + from the intended document once it reaches the LLM, which then produces a |
| 20 | + confidently wrong answer with no signal that anything went wrong. |
| 21 | +
|
| 22 | + This mirrors the behaviour of the ``use_soup=True`` path in ``FetchNode``: |
| 23 | + it warns rather than raising, so scraping error pages on purpose keeps |
| 24 | + working. |
| 25 | +
|
| 26 | + Args: |
| 27 | + response: The ``Response`` returned by ``page.goto()``; may be ``None`` |
| 28 | + (for example on a same-document navigation) or lack a usable status. |
| 29 | + url: The URL that was requested, used in the warning message. |
| 30 | + """ |
| 31 | + status = getattr(response, "status", None) |
| 32 | + if isinstance(status, int) and status >= 400: |
| 33 | + logger.warning( |
| 34 | + f"Received HTTP {status} for {url}; the scraped content is likely " |
| 35 | + "an error page, not the intended document." |
| 36 | + ) |
| 37 | + |
| 38 | + |
13 | 39 | class ChromiumLoader: |
14 | 40 | """Scrapes HTML pages from URLs using a (headless) instance of the |
15 | 41 | Chromium web driver with proxy protection. |
@@ -251,7 +277,8 @@ async def ascrape_playwright_scroll( |
251 | 277 | context = await browser.new_context() |
252 | 278 | await Malenia.apply_stealth(context) |
253 | 279 | page = await context.new_page() |
254 | | - await page.goto(url, wait_until="domcontentloaded") |
| 280 | + response = await page.goto(url, wait_until="domcontentloaded") |
| 281 | + _warn_on_error_status(response, url) |
255 | 282 | await page.wait_for_load_state(self.load_state) |
256 | 283 |
|
257 | 284 | previous_height = None |
@@ -364,7 +391,8 @@ async def ascrape_playwright(self, url: str, browser_name: str = "chromium") -> |
364 | 391 | ) |
365 | 392 | await Malenia.apply_stealth(context) |
366 | 393 | page = await context.new_page() |
367 | | - await page.goto(url, wait_until="domcontentloaded") |
| 394 | + response = await page.goto(url, wait_until="domcontentloaded") |
| 395 | + _warn_on_error_status(response, url) |
368 | 396 | await page.wait_for_load_state(self.load_state) |
369 | 397 | results = await page.content() |
370 | 398 | logger.info("Content scraped") |
@@ -421,7 +449,8 @@ async def ascrape_with_js_support( |
421 | 449 | storage_state=self.storage_state |
422 | 450 | ) |
423 | 451 | page = await context.new_page() |
424 | | - await page.goto(url, wait_until="networkidle") |
| 452 | + response = await page.goto(url, wait_until="networkidle") |
| 453 | + _warn_on_error_status(response, url) |
425 | 454 | results = await page.content() |
426 | 455 | logger.info("Content scraped after JavaScript rendering") |
427 | 456 | return results |
|
0 commit comments