dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
dsh-scrapling
A web-scraping extension for dsh: Scrapling as agent tools — JavaScript rendering, CSS/XPath extraction with self-healing selectors, sessions with login state, a background crawler and page Q&A. You supply the interpreter and the browser engine; everything runs in one resident Python process.
dsh ships web_fetch and web_search. The built-in web_fetch is deliberately honest and minimal: a plain HTTP(S) GET with no JavaScript execution and an honest User-Agent. That is a good default — and it means a single-page application yields only its loading shell, and there is no way to ask for "just the product table". Measured on a page whose content exists only after JavaScript runs:
Pipeline · Text obtained raw HTML to text (no JS) · SPA Shell / LOADING_PLACEHOLDER Fetcher (curl_cffi, still no JS) · SPA Shell / LOADING_PLACEHOLDER browser render (patchright) · full rendered DOM, Markdown table, structured rows
This package adds what the built-ins structurally cannot do:
adaptive mode relocates an element after the site's markup changes; a selector that returns None after a class rename returns the relocated element with adaptive on.Everything runs against one long-lived Python process. That is not an optimisation: Scrapling's session classes keep cookies, the browser engines keep a page pool, and adaptive keeps a SQLite fingerprint database — a process per call would throw all of that away. Tools are registered conditionally: the plugin probes what the environment can actually do at startup and registers only what will work, so the model never burns a turn on a tool that was guaranteed to fail. Every tool renders its result row through the official tool-call view slot. scrapling_answer is the only tool that spends another model call — see The tools.
dsh plugin --profile web add @jayyuen66/dsh-scraplingRemove with dsh plugin --profile web remove @jayyuen66/dsh-scrapling. The package is on the public npm registry, so installation needs no credentials and no scoped-registry line.
require() of an ESM entry work at all, which is why engines.node is declared.engines.dsh (^0.2.1-rc.3), and it decides alone; a host on a later line is refused and needs the explicit risk grant dsh plugin --profile allow-version @ --dsh-version --accept-risk._env/.venv, or reachable from PATH, or named by pythonBin. In an installed package the _env/.venv leg does not exist, so the interpreter is the python3 on your PATH; whichever one is chosen is logged at load time, and a missing Scrapling reports an error carrying the install line to copy.pip install "scrapling[rag]>=0.5,<0.6"
python -m patchright install chromiumrag is fetchers plus markdownify, and the default extractionType is markdown — with plain fetchers the first fetch raises ModuleNotFoundError at the Markdown conversion.Selector, Spider, SessionManager, and the converter's noise-stripping helpers). The upper bound is pinned below 0.6; outside the range the guards still run, but the anti-injection cleaning and the crawl path may not behave as documented. In 0.5 the browser backend is patchright only (the old standalone stealth fetch is gone; its anti-detection arguments live on scrapling_render), capture_xhr became session-level recording (hence scrapling_session_network), and extra_flags became additive — this package sends none, and WebRTC leak protection is carried by the blockWebrtc switch.@deepseek-ai/* under dependencies, by contract** (test/publish-manifest.test.ts enforces it). Consumer profiles install with nodeLinker: hoisted and autoInstallPeers: false, so a host package there becomes a real second copy whose module-level state splits from the running host's — every native tool call then throws while build and tests stay green. The host packages the artifact imports for value are required peers and stay externalized; the gate imports the built artifact and asserts that no host package body is inlined.prepack rebuilds it, so what ships is always what the current source produces.If your profile pins @jayyuen66/dsh-plugin-shared with a pnpm overrides entry, that pin must be at least the version this package declares. A lower pin installs a copy without the names the sources use, and the host then reports N entries did not activate together with does not provide an export named ... at boot - the package installs fine and never loads, and no per-repository CI run can see it.
dsh --profile web --dump-config shows this package's layer line.# == and # == , patched by .google_search option; discovery needs a deployment-level searchEndpoint (see Settings). Without one the search provider stays unavailable and the built-in web_search keeps its behaviour./api surface from this package.dsh.bundle.patch points at cordis.patch.yml, whose single registration line is - id: scrapling with the bare package name — the host scans dsh.client on bare package-name entries only.plugins.bundle.config slot, keyed by the bundle package name — the host dispatches entryKey: pkg.name, so the card renders on this package's detail page) and one result row per tool on tool.call.toolview, drawn with the official tool-call view props.scrapling; precedence is settings-card value > registration-line config: > schema default. Setting a field back to its default saves a clear-override, not a write of the same value, so a later default change still reaches you.web_fetch guidance.scrapling_crawl registers its own job kind, so job_list labels its jobs as crawls rather than shell jobs.Thirteen tools, each behind one settings switch, all exercised by the test suite:
Tool · Backend · Needs a browser · Concurrency scrapling_fetch · Fetcher (curl_cffi) · no · concurrent; write verbs are serialised once allowWriteMethods is on scrapling_extract · Selector + CSS/XPath + adaptive · no · concurrent scrapling_render · StealthySession (patchright) · yes · serialised; the anti-detection switches (solveCloudflare / hideCanvas / blockWebrtc) live here scrapling_capture_xhr · one throwaway session, page-level recording · yes · serialised scrapling_session_open / scrapling_session_fetch / scrapling_session_network / scrapling_session_state_save / scrapling_session_state_load / scrapling_session_list / scrapling_session_close · Scrapling session classes · browser sessions only · serialised scrapling_crawl · Scrapling Spider framework, runs as a job · no · serialised scrapling_answer · any fetch path plus ctx.llm · with render: true · serialised
*MaxOutputChars, 8 MB per protocol frame, 16 queued frames. A ceiling answers with a specific code (SESSION_LIMIT / CRAWL_BUSY / BAD_FRAME) the model can back off from.job_list, read it with job_output, stop it with job_kill; the tool needs a jobs registry that can attach a controller, and a profile without one simply does not register it (single-page fetching is unaffected). The crawl has a wall-clock ceiling (crawlMaxRuntimeSeconds) and never leaves the start domain. Politeness defaults follow Scrapling's spider defaults — robots.txt is not obeyed, per-domain delay is 0, up to 4 requests run in parallel — and is a deployment decision, not a per-call one (see Settings).answerProvider / answerModel to route page-reading elsewhere. Page text is untrusted input — the prompt frames it explicitly as data, but that defence is prompt-level: it lowers the risk, it does not remove it.scrapling_render is much slower and heavier than scrapling_fetch, so start static. Two criteria:
One thing that is not a criterion but worth knowing: axis ticks inside SVG cannot be fetched at all — the Markdown conversion emits no ` / nodes, so chart numbers are out of reach whichever tool you use. For actual values, go through scrapling_capture_xhr` to locate the JSON endpoint.
The plugin registers itself as a fetch provider and a search provider on ctx.web, so the built-in web_fetch / web_search tools actually run on Scrapling instead of dsh's plain-HTTP implementation. The model still calls web_fetch / web_search — tool names cannot be swapped (same-name registration throws), the provider seam is the supported channel. Both providers are registered by default (provideWebFetch / provideWebSearch), but that alone displaces nothing:
provideWebFetch / provideWebSearch at their default (true);fetchProvider / searchProvider to "scrapling" in the deployment config.A configured id wins over auto-selection, which is why step 2 is required — and with two usable providers and no id named, ctx.web answers WEB_PROVIDER_AMBIGUOUS and the tool becomes unavailable outright. Two consequences of pointing that line at this plugin:
WEB_PROVIDER_CONFIGURED_MISSING — go back and remove fetchProvider / searchProvider too.fetchMaxOutputChars is cut to a head/tail window and flagged truncated, not passed through with a flag. Failures keep the sidecar code prefix (URL_BLOCKED, SIDECAR_TIMEOUT, SIDECAR_UNAVAILABLE), so a policy refusal is distinguishable from a crash or a timeout.Search is split in two, because Scrapling is not a search engine: discovery goes to a deployment-level searchEndpoint, expected to be SearXNG-compatible JSON (GET ?q=&format=json returning {"results": [{"url", "title", "content"}]} — any backend producing that shape works); reading is this plugin's — the first searchRenderTopN results are actually fetched and, with a browser, rendered, so one web_search call returns both which pages matter and what they say. Every returned URL goes through the same gate as a fetch; a page that fails to read is skipped alone, because search results routinely contain dead links.
One consequence to know: dsh-web's fetch body is a closed union of html | text with no markdown kind, and adding one is a cross-package change a plugin cannot make. The fetch provider therefore reports Scrapling's Markdown as text, which the built-in renderer displays normally. Use scrapling_fetch directly when you need html or a CSS selector.
Settings live on the scrapling card in the plugin detail page, grouped by purpose, each field with a one-line "what changes if you edit this". The card's language follows the host locale. Values marked below live on the card; the deployment table after them is only settable through the config: block of cordis.patch.yml.
Field · Type · Default · Purpose pythonBin · string · "" · Interpreter name or absolute path. Empty = the repository _env/.venv when executable, otherwise python3 on PATH failFastOnMissingPython · boolean · true · Refuses to load when the sidecar process cannot start (wrong path, protocol mismatch, handshake timeout). An interpreter that starts but lacks Scrapling always degrades to "tools unavailable" with the path named in the log, whatever this switch says fetchEnabled / extractEnabled / renderEnabled / captureXhrEnabled / networkEnabled / sessionEnabled / sessionStateEnabled / crawlEnabled / answerEnabled · boolean · true · One kill switch per tool or tool pair. sessionStateEnabled gates the state save / load pair, which also needs a deployment-side sessionStateDir extractionType · enum · markdown · markdown / html / text; all three run the same anti-injection cleaning, and html additionally strips event-handler attributes, so it is not byte-for-byte raw HTML mainContentOnly · boolean · true · Extraction scope *MaxOutputChars · natural · fetch / extract / render 200000, xhr 120000 · Per-tool output caps. The sidecar clips by characters, the host then trims to a byte budget crawlMaxItems · natural · 1000 · Ceiling over the model-supplied maxPages. An out-of-range maxPages is passed through so the sidecar reports the parameter error rather than this plugin silently clamping it stripInlineImages · boolean · true · Drops inline data: URI images from the body, keeping only alt text — pure token noise for the model. Code fences are left untouched; pages without inline images come back byte-identical. Stripping happens before the cap. Turn off only when the raw base64 is the data (colours, fingerprints) headless / networkIdle · boolean · true / false · Browser mode and whether a page must reach network idle before it is read blockAds · boolean · true · Stacks on top of the SSRF guard rather than competing with it: the guard is registered last (so the engine runs it first) and yields with route.fallback() pierceShadow · boolean · false · Merges open shadow roots into the body — the switch for pages that hide their text in shadow DOM. The model can also override it per call on the browser tools waitSelectorState · enum · attached · The state a waitSelector element must reach (attached / detached / hidden / visible) sidecarHandshakeTimeoutMs / fetchTimeoutMs / sidecarGraceMs · natural · 30000 / 120000 / 5000 · Handshake budget, per-call cooperative timeout (declared on the tools' timeoutMs), and how long the client waits for the process to exit before forcing it down. scrapling_crawl passes no timeout — a crawl is measured in minutes dataDir · string · "" · Sidecar working directory (the adaptive fingerprint database lands here); empty = ~/.dsh-scrapling. If it cannot be created, failFastOnMissingPython decides whether the plugin refuses to load or degrades to no tools provideWebFetch / provideWebSearch · boolean · true · Whether the ctx.web providers are registered at all — see Replacing the built-in fetch and search answerProvider / answerModel / answerMaxTokens · string / natural · "" / "" / 2000 · The model scrapling_answer routes to and the per-reply token cap. Provider and model left empty mean "follow the model the current session is already using" allowedHosts · string · "" · Extra host names that skip the public-address check (comma-separated). Pointing it at an intranet address means giving up SSRF protection for those targets on purpose syntheticDnsRanges · string · "" · Manual fallback for synthetic DNS: a comma-separated CIDR list of your proxy's fake-IP pool, for resolvers that mix synthetic and real answers. Invalid entries are dropped — a broken fallback degrades to "stay strict", not to a crash | proxyUrl | string | "" | Where fe