@browserless.io/anti-bot-classifier

中文文档
0.3.1发布于 2026/10/23MITTypeScriptES Module零依赖近 30 天下载 9292

Shared classifier for anti-bot block/challenge detection (benchmark + runtime)

原始使用文档
文档目录10 节
  1. The Observation → Classification contract
  2. Observation channels
  3. Classification fields
  4. Usage
  5. classify
  6. detectBlock (DOM-only convenience shim)
  7. Overriding or extending the ruleset
  8. The confidence model
  9. Privacy
  10. Development

@browserless.io/anti-bot-classifier

A pure, dependency-free classifier that decides whether a page load was blocked or challenged by an anti-bot system (Cloudflare, DataDome, Imperva, PerimeterX, DDoS-Guard, or a generic block page).

The package is usable both offline (batch-scoring recorded page snapshots) and inside a runtime service (classifying live sessions), so that "block rate" means the same thing in both places. There is no I/O, no browser or DOM globals, and no non-determinism (no Date, no Math.random) anywhere in the classification path — the same Observation always produces the same Classification.

The Observation → Classification contract

classify(observation, ruleset) takes an Observation — a privacy-scrubbed snapshot of a page — and a Ruleset, and returns a Classification.

Observation channels

All fields are optional; supply whatever you were able to capture.

FieldTypeChannelNotes
hoststring—Host only, no query string. Not itself matched by a channel.
statusnumberstatusMain-document HTTP status code.
headersRecord<string, string>headerResponse headers; keys are lowercased before matching.
cookiesstring[]cookieSet-cookie names only — values are never included.
finalUrlstringdocument-urlQuery string already stripped by the caller.
titlestringtitlepage.title().
textstringtextTop-frame rendered innerText.
markupstringdom / markupTop-frame served outerHTML.
framesFrameObservation[]{ url?, text?, markup? }Same-origin child iframes (no title — FrameObservation only carries url/text/markup), so interstitials rendered inside an iframe (e.g. Imperva's iframe#main-iframe) are still reachable.

Callers are responsible for the privacy scrub before constructing an Observation: strip query strings, drop cookie values (names only), and truncate markup/frame markup to whatever the caller is comfortable retaining.

Classification fields

FieldTypeNotes
statusBlockStatusTri-state: 'blocked' | 'clear' | 'inconclusive'.
blockedbooleanConvenience for status === 'blocked'.
inconclusivebooleanConvenience for status === 'inconclusive'.
vendorstring?The winning vendor (e.g. 'cloudflare'), only set when blocked.
kindKind?'challenge' | 'block' | 'captcha' | 'rate-limit' | 'network-error'.
confidencenumber0..1, the winning vendor's noisy-OR score.
tierChannel?The strongest matching channel for the winner.
matchedMatchedSignal[]Every signal that matched, { vendor, channel, marker, weight, note? }.
reasonstringHuman-readable summary, e.g. "Blocked (cloudflare/challenge): header cf-mitigated".

inconclusive covers cases where the classifier can't tell — a browser-level network error (net::ERR_CONNECTION_RESET-style strings), no content at all was captured, or the page was sampled but came back empty. Treat inconclusive rows as "exclude from scoring", not as clear.

Usage

classify

import { classify, DEFAULT_RULESET } from '@browserless.io/anti-bot-classifier';

const result = classify(
  {
    host: 'example.com',
    status: 403,
    headers: { 'cf-mitigated': 'challenge' },
  },
  DEFAULT_RULESET,
);

// result.status === 'blocked'
// result.vendor === 'cloudflare'
// result.kind === 'challenge'

detectBlock (DOM-only convenience shim)

For call sites that only have title / text / markup (no headers, cookies, status, or final URL) — this is a drop-in replacement for the legacy detectBlock, classifying against DEFAULT_RULESET under the hood:

import { detectBlock } from '@browserless.io/anti-bot-classifier';

const result = detectBlock(
  document.title,
  document.body.innerText,
  document.documentElement.outerHTML,
);

// result: { status, blocked, inconclusive, reason }

Overriding or extending the ruleset

classify takes the ruleset as an explicit argument — there's no hidden global state. To customize detection, either build a Ruleset from scratch, or spread DEFAULT_RULESET and add or replace signals:

import {
  classify,
  DEFAULT_RULESET,
  type Observation,
  type Ruleset,
} from '@browserless.io/anti-bot-classifier';

const myRuleset: Ruleset = {
  ...DEFAULT_RULESET,
  signals: [
    ...DEFAULT_RULESET.signals,
    {
      vendor: 'acme-waf',
      channel: 'header',
      key: 'x-acme-block',
      marker: 'B',
      kind: 'block',
      weight: 0.9,
      note: 'x-acme-block header present',
    },
  ],
};

const observation: Observation = {
  status: 200,
  headers: { 'x-acme-block': '1' },
};

classify(observation, myRuleset);

A Ruleset is { signals, structural?, networkErrorSignals, threshold, version }. signals are substring/exact-value matches against a single channel; structural entries are JS predicates ((o: Observation) => boolean) for patterns that need to look across multiple fields at once (e.g. "empty top frame plus an Imperva iframe"). Both kinds carry a marker: 'B' (block-only — scores) or 'P' (present-always — context only, reported in matched but never moves the score).

The confidence model

Only 'B'-marked signals contribute to scoring; 'P' signals are fingerprint context that show up in matched but never move confidence. Matched 'B' signals are grouped by vendor and combined per-vendor via noisy-OR — score = 1 − Π(1 − weightᵢ) — so multiple weak signals from the same vendor can still add up to a confident verdict without any single signal needing to be definitive on its own. The vendor with the highest noisy-OR score wins; the page is classified blocked when that winning score is ≥ ruleset.threshold, and clear (or inconclusive, see above) otherwise. The weights on every signal in DEFAULT_RULESET, and its threshold (currently 0.5), are placeholders reproducing the legacy detector's boolean behavior — they get fit against real block/clear outcomes from a labeled corpus in a future release, not in this package's current form.

Privacy

Classification is metadata-only: vendor, kind, status/blocked/ inconclusive, confidence, tier, and reason (plus matched, which itself only carries vendor/channel/marker/weight/note). It never echoes back page content, cookie values, or query strings — those live only in the Observation you pass in, which the classifier reads but never returns any part of.

Development

npm test            # vitest
npm run build       # tsc -> dist/

版本与发布记录

@browserless.io/anti-bot-classifier 共发布 4 个版本,首次发布于 2026/08/19;过去 12 个月发布了 4 个版本。

版本发布日期距今
0.3.1latest2026/10/2316 天前
0.3.02026/10/2118 天前
0.2.02026/08/262 个月前
0.1.02026/08/192 个月前

关于 @browserless.io/anti-bot-classifier 的常见问题

@browserless.io/anti-bot-classifier 是什么?

Shared classifier for anti-bot block/challenge detection (benchmark + runtime)

如何安装 @browserless.io/anti-bot-classifier?

在项目目录运行 npm install @browserless.io/anti-bot-classifier,或使用 pnpm add / yarn add / bun add。

@browserless.io/anti-bot-classifier 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@browserless.io/anti-bot-classifier 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@browserless.io/anti-bot-classifier 可以商用吗?

@browserless.io/anti-bot-classifier 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@browserless.io/anti-bot-classifier 的下载量有多少?

根据 npm 官方下载统计,@browserless.io/anti-bot-classifier 在 2026/10/09 – 2026/11/07 这 30 天内被下载 9,292 次,日均约 310 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@browserless.io/anti-bot-classifier 还在维护吗?

最新版本 0.3.1 发布于 2026/10/23(16 天前);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/08/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。