--- id: "822cd59e-7511-4ab0-b366-8b83ae6dae52" name: "extract_text()" micro_summary: "Read me if you need to get text from a page. Antifragile extraction: handles still-loading pages, React SPAs, and empty bodies. The go-to helper for \"let me read the page.\"" status: "resolved" see_also: [] scope: "Callable helper: extract_text(selector=None) — antifragile text extraction from any page. Handles React/Next.js hydration, pages still loading, SPAs with deferred rendering. Falls back through multiple extraction strategies. Never returns empty string." warnings: "Cannot reach into iframes — extract_text() runs in the parent page context and silently skips iframe content. If a page has iframes (check with `js('document.querySelectorAll(\"iframe\").length')`), use parent-page JS to reach in: `contentWindow.document.body.innerHTML`." --- ## `extract_text(selector=None)` Call this when you need to read text from any page. Handles loading state, React hydration, and page shocks automatically. ### Why This Exists `document.body.innerHTML` on React/Next.js sites includes component definitions (`_jsx` calls), compiled MDX, and scripts — often 5-10x larger than readable text. Example: the glossary page had 976KB of innerHTML but only 17KB of actual text. `extract_text()` bypasses all of that. ### Signature ```python text = extract_text() # Full page text = extract_text(".prose") # Specific element ``` ### Fallback Chain | Step | Strategy | Why | |------|----------|-----| | 0 | `wait_for_load()` + `wait(1)` | React SPA hydration | | 1 | `innerText` on body/selector | Filters scripts, styles, hidden | | 2 | `cloneNode()` → remove scripts → `textContent` | Handles pages where innerText is empty | | 3 | `innerText` on `documentElement` | Pages with no `` | | 4 | `innerHTML` on target (last resort) | Keeps structure if all else fails | | Fail | `"[extract_text: no visible text found]"` | Never returns empty | ### Usage Patterns **Read the page inline (no screenshot needed):** ```python text = extract_text() print(text[:500]) # preview top without context bloat ``` **Search (Python's rtk like token-efficient grep):** ```python import re text = extract_text() pattern = re.compile(r"subagent", re.IGNORECASE) matches = [(i, line[:120]) for i, line in enumerate(text.split("\n")) if pattern.search(line)] print(f"{len(matches)} lines:" if matches else "no matches") for ln, line in matches[:20]: print(f" {ln}: {line}") if len(matches) > 20: print(f" [+{len(matches) - 20} more]") ``` **Search with surrounding context (like rg -C):** ```python import re text = extract_text() lines = text.split("\n") for m in re.finditer(r"(?i)subagent", text): line_no = text[:m.start()].count("\n") ctx_start = max(0, line_no - 1) ctx_end = min(len(lines), line_no + 2) for i in range(ctx_start, ctx_end): prefix = "→ " if i == line_no else " " print(f"{prefix}{i}: {lines[i][:120]}") print("---") ``` **Preview top and bottom to understand page shape:** ```python text = extract_text() print(text[:400]) # what's at the top? print("...") print(text[-400:]) # what's at the bottom? ``` **Save to file for later:** ```python from pathlib import Path Path("/tmp/page.md").write_text(text) ``` **Target a specific section:** ```python text = extract_text("#subagent") # just the subagent definition print(text[:200]) ```