Scavenge
Enables deterministic web evidence collection by inspecting a field (price or availability) on a web page and returning structured observations from multiple channels (raw DOM, structured data, embedded state, rendered DOM, network JSON) with exact provenance, without interpreting correctness.
README
Scavenge — deterministic web evidence for humans and coding agents
Scavenge inspects one field on one web page and reports every place that field appears, what each representation says, and exactly where each value came from.
It collects field observations with exact provenance. It does not determine which observation is semantically correct. That judgement needs to know what the page means, and the engine is deliberately incapable of it — see Why it stops there.
What it does
$ scavenge inspect https://example.com/product/123 --field price
FIELD: price
RAW_DOM 99.00 USD [raw_dom:0]
raw: '$99.00'
source: div.product-price
STRUCTURED_DATA 99.00 USD [structured_data:0]
raw: '99.00'
source: script[0]/offers/price
EMBEDDED_STATE not observed
RENDERED_DOM 79.00 USD [rendered_dom:0]
raw: '$79.00'
source: div.product-price
NETWORK_JSON 79.00 USD [network_json:0]
raw: '79.00'
source: GET https://example.com/api/products/123 /price
ACQUISITION
HTTP 200 48213 bytes 0.31s
RENDER OK
JSON responses observed: 12
The raw HTML says 99.00, the rendered page says 79.00, and an API call explains why. You can see that in two seconds; the engine does not assert it.
Why it exists
A page states the same fact in several places — visible HTML, JSON-LD, a hydration blob, the rendered DOM, an XHR response — and they disagree more often than you would like. Working out which ones carry your field is a DevTools job, done by hand, once per target, forever. An agent can do it too, but not reproducibly and not with exact provenance.
Why it stops there
The original goal was more ambitious: decide deterministically whether values found in different representations referred to the same real-world thing, and report where they agreed or disagreed.
Real storefronts broke that assumption. Values that looked comparable belonged to
recommendation carousels, carts, financing tables, store locators, product variants, and
second Product blocks on the same page. A validation run across 12 storefronts found that
every disagreement the engine reported was of that kind — the values and provenance were
right, the comparison was wrong.
Fixing it properly turns into entity resolution. So Scavenge stops one step earlier:
collect the evidence deterministically and let a human or an agent interpret it. The
full account, including the results that killed the earlier design, is in
docs/research/.
Supported fields
price and availability. Two, not "arbitrary".
Supported channels
RAW_DOM · STRUCTURED_DATA · EMBEDDED_STATE · RENDERED_DOM · NETWORK_JSON
They are not equally validated. In a 23-page validation across 12 storefronts,
STRUCTURED_DATA produced values in 28 of 46 runs and EMBEDDED_STATE in only 6.
MCP
One tool, inspect_web_field(url, field), returning structured evidence — not prose.
{
"mcpServers": {
"scavenge": { "command": "python", "args": ["-m", "scavenge.mcp"] }
}
}
The server reasons about nothing, generates no scraping code, and calls no model.
Structured output
{
"schema_version": 3,
"target": "https://example.com/product/123",
"field": "price",
"observations": [
{
"id": "raw_dom:0",
"channel": "RAW_DOM",
"normalized_value": {"kind": "money", "amount": "99.00", "currency": "USD"},
"raw": "$99.00",
"provenance": {"selector": "div.product-price"},
"subject": {"scope": "PAGE", "key": "", "reason": ""},
"status": "OK",
"note": ""
}
],
"acquisition": {
"http_status": 200, "http_bytes": 48213, "http_challenge": "",
"render_status": "OK", "json_responses": 12
},
"warnings": []
}
Architecture
MCP ─┐
├─→ evidence engine ─→ HTTP · raw DOM · structured data
CLI ─┘ embedded state · rendered DOM · network JSON
One engine. Both interfaces call it; a test asserts the CLI's JSON is the engine's JSON.
Install
pip install git+https://github.com/aarohim24/Scavenge.git
python -m playwright install chromium # required for rendered DOM and network channels
scavenge inspect https://example.com/p/1 --field price
Not on PyPI yet — deliberately, until the API has survived a round of external feedback.
--no-render skips the browser and reports the HTTP-only channels.
Security boundaries
http/https only. file://, localhost, loopback, link-local and private ranges are
refused before any request — which matters more under MCP, where an agent supplies URLs
without a human seeing them. Bodies capped at 512 KB, JSON responses capped at 40, bounded
waits, bounded robots fetch. robots.txt is honoured, and a timeout or unreachable
robots.txt is never treated as permission. No captured request is replayed, no
authenticated request reissued, nothing from a page executed.
What it explicitly does not do
- Decide which observation is correct. v0.1 publishes no comparison at all.
- Entity resolution. It cannot reliably tell the page's product from a second Product block, an upsell tile, a cart total, or a store-locator row.
- Crawl, schedule, or follow links.
- Defeat anti-bot systems. It detects an obvious challenge and says so; it never bypasses one.
- Call an LLM, in any mode.
Known limitations
- No comparison in v0.1. An earlier version published
EQUAL/DIFFERENTrelations. Real-world validation found that everyDIFFERENTit produced on live storefronts compared two different entities, so the feature was removed rather than patched. The full result is indocs/research/OSS-FINAL-CORRECTNESS.md. - Currency is often
null.$names a dozen currencies; without declared evidence the amount is kept and the currency refused. Unknown is common; wrong should be absent. - Minor units are not modelled — a Shopify
2950and a displayed29.50are two different observations. - Determinism is of report generation, not of the web. Same bytes in, same report out. The same URL will not always give the same bytes.
- A blocked render is detected only from named signals; a thin unnamed shell is not.
- Robots-respecting access excludes many large retailers entirely.
License
Apache-2.0.
Status
Experimental v0.1. Published for technical feedback, not for production. The API may change. It has been validated on 23 pages across 12 storefronts, plus a deterministic fixture suite; that is a small sample and it is not a benchmark.
The research record — including the results that killed earlier versions of this idea —
is in docs/research/.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。