switchback 0.6.2__tar.gz → 0.6.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {switchback-0.6.2 → switchback-0.6.3}/CHANGELOG.md +9 -0
- {switchback-0.6.2 → switchback-0.6.3}/PKG-INFO +1 -1
- {switchback-0.6.2 → switchback-0.6.3}/pyproject.toml +1 -1
- {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/_browser.py +8 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_4.py +1 -1
- {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_5.py +1 -1
- {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/PKG-INFO +1 -1
- {switchback-0.6.2 → switchback-0.6.3}/.env.example +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/CONTRIBUTING.md +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/LICENSE +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/MANIFEST.in +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/NOTICE +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/README.md +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/SECURITY.md +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/clients/node_bridge.md +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/clients/python_client.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/config/botwall_skip_urls.txt +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/config/extraction.example.json +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/setup.cfg +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/__init__.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/__main__.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/api.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/concurrency.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/content_cache.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/doctor.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/egress.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/extract.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/flags.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/highlights.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/mcp_server.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/normalize.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/orchestrator.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/policy/__init__.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/policy/botwall.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/policy/gates.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/py.typed +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/reporting.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/search.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/server.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/session_cache.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/session_trace.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/state.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/__init__.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_1.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_2.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_3.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_6.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/tiers/tier_7.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback/tracing.py +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/SOURCES.txt +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/dependency_links.txt +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/entry_points.txt +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/requires.txt +0 -0
- {switchback-0.6.2 → switchback-0.6.3}/switchback.egg-info/top_level.txt +0 -0
|
@@ -4,6 +4,15 @@ All notable changes to this project are documented here. Format loosely follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); this project uses semantic-ish
|
|
5
5
|
versioning while pre-1.0.
|
|
6
6
|
|
|
7
|
+
## [0.6.3] - 2026-10-14
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **"Page not found" pages returned as articles by the browser tiers.** tier_4
|
|
11
|
+
and tier_5 ignored the HTTP status of the page they loaded, so a site answering
|
|
12
|
+
404 (seen on bhaskarenglish.in, which serves its 404 page to some bot traffic)
|
|
13
|
+
had its "404 - Page not found" page returned as a success. A 404 or 410 now
|
|
14
|
+
fails the tier, as it already did in tier_2, and the cascade moves on.
|
|
15
|
+
|
|
7
16
|
## [0.6.2] - 2026-10-14
|
|
8
17
|
|
|
9
18
|
### Fixed
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "switchback"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.3"
|
|
8
8
|
description = "One cost-ordered scrape cascade (HTTP → stealth browser → paid), shared by every tool."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -34,6 +34,14 @@ def looks_blocked(html: str, url: str) -> bool:
|
|
|
34
34
|
return _looks_like_botwall(html_to_markdown(html, base_url=url))
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def raise_if_missing(response) -> None:
|
|
38
|
+
"""Fail the tier on a 404/410 document, as tier_2 does; a browser otherwise
|
|
39
|
+
renders the site's "Page not found" page and returns it as the article."""
|
|
40
|
+
status = getattr(response, "status", None)
|
|
41
|
+
if status in (404, 410):
|
|
42
|
+
raise RuntimeError(f"HTTP Error {status}: page not found")
|
|
43
|
+
|
|
44
|
+
|
|
37
45
|
def response_bytes(responses) -> int:
|
|
38
46
|
"""Total wire bytes a render pulled across every resource — the residential-cost
|
|
39
47
|
basis. Reads only the Content-Length header: it's non-blocking. (We deliberately
|
|
@@ -80,7 +80,7 @@ def fetch(url: str, timeout_ms: int = int(_TIMEOUT_S * 1000)) -> str:
|
|
|
80
80
|
page = ctx.new_page()
|
|
81
81
|
responses: list = []
|
|
82
82
|
page.on("response", lambda resp: responses.append(resp))
|
|
83
|
-
page.goto(url, timeout=timeout_ms, wait_until="domcontentloaded")
|
|
83
|
+
_browser.raise_if_missing(page.goto(url, timeout=timeout_ms, wait_until="domcontentloaded"))
|
|
84
84
|
html = page.content()
|
|
85
85
|
if len(html) < 5000:
|
|
86
86
|
try:
|
|
@@ -86,7 +86,7 @@ def fetch(url: str) -> str:
|
|
|
86
86
|
auth = session_cache.browser_cookies(url)
|
|
87
87
|
if auth:
|
|
88
88
|
page.context.add_cookies(auth)
|
|
89
|
-
page.goto(url, wait_until="networkidle", timeout=_TIMEOUT_MS)
|
|
89
|
+
_browser.raise_if_missing(page.goto(url, wait_until="networkidle", timeout=_TIMEOUT_MS))
|
|
90
90
|
html = page.content()
|
|
91
91
|
# JS bot-manager sensor interstitial → settle + reload once.
|
|
92
92
|
if _browser.looks_blocked(html, url):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|