perf: reuse browser across chart scrapes

This commit is contained in:
Hermes Agent
2026-07-26 23:27:06 +00:00
parent dafc21b352
commit 1e50760f27
2 changed files with 67 additions and 22 deletions
+39 -22
View File
@@ -2,6 +2,7 @@
import logging
import traceback
from contextlib import contextmanager
log = logging.getLogger(__name__)
@@ -51,31 +52,42 @@ CHARTS = {
}
def scrape_chart(chart_path, timeout=25000):
"""Scrape a single chart from LookIntoBitcoin. Returns list of trace dicts or None."""
@contextmanager
def browser_page():
"""Open one headless browser page for a batch of chart requests."""
from playwright.sync_api import sync_playwright
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
try:
yield browser.new_page()
finally:
browser.close()
def scrape_chart(chart_path, timeout=25000, page=None):
"""Scrape one chart, optionally reusing a caller-owned browser page."""
if page is None:
with browser_page() as owned_page:
return scrape_chart(chart_path, timeout=timeout, page=owned_page)
store = {"data": None}
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
def handle_response(response):
if "_dash-update-component" in response.url:
try:
store["data"] = response.json()
except Exception:
pass
def handle_response(response):
if "_dash-update-component" in response.url:
try:
store["data"] = response.json()
except Exception:
pass
page.on("response", handle_response)
try:
page.goto(f"{BASE_URL}{chart_path}", timeout=timeout)
page.wait_for_timeout(6000)
except Exception as e:
log.warning("Navigation error for %s: %s", chart_path, e)
finally:
browser.close()
page.on("response", handle_response)
try:
page.goto(f"{BASE_URL}{chart_path}", timeout=timeout)
page.wait_for_timeout(6000)
except Exception as exc:
log.warning("Navigation error for %s: %s", chart_path, exc)
finally:
page.remove_listener("response", handle_response)
if store["data"]:
try:
@@ -172,13 +184,18 @@ def _get_recent_values(trace, n=30):
def scrape_all():
"""Scrape all charts and return parsed metric values."""
"""Scrape all charts while reusing one browser process and page."""
with browser_page() as page:
return _scrape_all_with_page(page)
def _scrape_all_with_page(page):
results = {}
for metric_key, chart_info in CHARTS.items():
log.info("Scraping %s ...", metric_key)
try:
traces = scrape_chart(chart_info["path"])
traces = scrape_chart(chart_info["path"], page=page)
if not traces:
log.warning("No data for %s", metric_key)
results[metric_key] = {"value": None, "error": "No data returned"}
+28
View File
@@ -1,3 +1,5 @@
from contextlib import contextmanager
from scrapers import lookintobitcoin
from scoring import engine
@@ -26,3 +28,29 @@ def test_vdd_derived_return_is_labeled_as_momentum_not_raw_multiple():
assert vdd["name"] == "VDD 30-Period Momentum"
assert vdd["transform"] == "30_period_return"
def test_scrape_all_reuses_one_browser_page(monkeypatch):
page = object()
seen_pages = []
@contextmanager
def fake_browser_page():
yield page
def fake_scrape_chart(_path, timeout=25000, page=None):
seen_pages.append(page)
return [{"name": "metric", "y": [1.0]}]
monkeypatch.setattr(lookintobitcoin, "CHARTS", {
"first": {"path": "/first", "traces": ["metric"]},
"second": {"path": "/second", "traces": ["metric"]},
})
monkeypatch.setattr(lookintobitcoin, "browser_page", fake_browser_page)
monkeypatch.setattr(lookintobitcoin, "scrape_chart", fake_scrape_chart)
result = lookintobitcoin.scrape_all()
assert seen_pages == [page, page]
assert result["first"]["value"] == 1.0
assert result["second"]["value"] == 1.0