> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adscrawl.net/llms.txt
> Use this file to discover all available pages before exploring further.

# 渲染 HTML、Markdown 或 Article JSON

> 通过住宅代理加载任意公开 URL，返回完整渲染的原始 HTML、可读 Markdown 或结构化文章 JSON。

使用 `POST /html` 在真实浏览器中加载任意公开 URL，并以应用所需的格式返回渲染后的内容。选择 `"html"` 获取完整 DOM，选择 `"markdown"` 获取干净可读的文章内容，或选择 `"json"` 获取包含标题、作者、摘要和正文结构的 Readability 结构化数据。所有请求均通过住宅代理并配合随机浏览器指纹进行路由。

<ParamField body="url" type="string" required>
  目标页面 URL。仅支持 80 和 443 端口。
</ParamField>

<ParamField body="contentMode" type="string">
  控制响应格式。可选值：`"html"`（默认）、`"markdown"`、`"json"`。`"markdown"` 和 `"json"` 均使用 Readability 提取可读文章内容。
</ParamField>

<ParamField body="selector" type="string">
  在提取前等待第一个匹配的 CSS 元素出现。当 `contentMode` 为 `"html"` 时，仅返回该元素的 HTML。当为 `"markdown"` 或 `"json"` 时，Readability 将针对该元素运行。如果未找到选择器，则返回 `422 CONTENT_SELECTOR_NOT_FOUND`。
</ParamField>

<ParamField body="waitUntil" type="string">
  在提取内容前等待的导航事件。默认值为 `"load"`。

  | Value | Behaviour |
  | - | - |
  | `"domcontentloaded"` | 等待 DOMContentLoaded。HTML 已解析，无需等待图片等次要资源。推荐用于 HTML 提取。 |
  | `"load"` | 等待页面和依赖资源（图片、样式表）加载完成后的 `window.load`。 |
  | `"networkidle"` | 等待至少 500 毫秒内没有任何网络连接。长轮询、分析脚本或懒加载资源可能导致超时。 |
</ParamField>

<ParamField body="timeoutMs" type="number">
  任务完成的最长等待时间，单位为毫秒。必须是正整数且不超过 `3,600,000`。超出范围的值将回退为服务器默认值。
</ParamField>

<ParamField body="viewport" type="object">
  渲染页面时使用的视口尺寸。

  <Expandable title="viewport 字段">
    <ParamField body="width" type="number">
      视口宽度，单位为像素。
    </ParamField>

    <ParamField body="height" type="number">
      视口高度，单位为像素。
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="locale" type="string">
  浏览器语言环境，例如 `"en-US"` 或 `"zh-CN"`。会影响 `navigator.language` 和 `Accept-Language` header。
</ParamField>

<ParamField body="timezoneId" type="string">
  IANA 时区标识符，例如 `"Asia/Shanghai"` 或 `"America/New_York"`。
</ParamField>

<ParamField body="geolocation" type="object">
  通过 Geolocation API 暴露给页面的地理位置坐标。

  <Expandable title="geolocation 字段">
    <ParamField body="latitude" type="number">
      纬度，以十进制度数表示。
    </ParamField>

    <ParamField body="longitude" type="number">
      经度，以十进制度数表示。
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="proxy" type="object">
  自定义代理配置。不能与 `countryCode` 同时使用。提供 `server` 或拆分形式（`protocol` + `host` + `port`）。不能将凭据嵌入 `server` 中。

  <Expandable title="proxy 字段">
    <ParamField body="server" type="string">
      完整的代理 URL，如 `http://host:port` 或 `socks5://host:port`。不能与 `host` 同时使用。
    </ParamField>

    <ParamField body="protocol" type="string">
      `"http"` 或 `"socks5"`。用于拆分形式。
    </ParamField>

    <ParamField body="host" type="string">
      代理主机。用于拆分形式。
    </ParamField>

    <ParamField body="port" type="number | string">
      端口，范围为 1 到 65535。
    </ParamField>

    <ParamField body="username" type="string">
      代理用户名。必须与 `password` 一起提供。
    </ParamField>

    <ParamField body="password" type="string">
      代理密码。必须与 `username` 一起提供。
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="countryCode" type="string">
  管理的住宅代理区域。不能与 `proxy` 同时使用。

  * `"GLOBAL"`：从 15 个热门区域中动态选择出口。
  * 两位字母国家代码（例如 `"US"`、`"DE"`）：优先使用该区域的受信任代理，并支持动态回退。
  * 省略：自动随机选择受信任代理。
</ParamField>

<ParamField body="userAgentMode" type="string">
  `"random"` 让服务器从其库中选择 User-Agent。未显式提供 `userAgent` 的请求已默认使用 `"random"`。
</ParamField>

<ParamField body="userAgentOs" type="string">
  当 `userAgentMode` 为 `"random"` 时使用的操作系统。可选值：`"windows"`（默认）或 `"macos"`。
</ParamField>

<ParamField body="userAgent" type="string">
  显式指定 User-Agent 字符串。覆盖随机选择。
</ParamField>

<ParamField body="fingerprint" type="object">
  浏览器指纹设置。省略时，所有信号默认随机，同时保持操作系统、GPU、CPU、内存、字体和设备信号的一致性。

  <Expandable title="fingerprint 字段">
    <ParamField body="webRtc" type="string">
      `"forward"` 使用代理出口地址。也可使用 `"real"` 或 `"disabled"`。
    </ParamField>

    <ParamField body="webGl" type="string">
      `"random"` 或 `"real"`。控制 WebGL 厂商和渲染器元数据。
    </ParamField>

    <ParamField body="webGpu" type="string">
      `"random"`、`"real"` 或 `"disabled"`。随机模式遵循 WebGL GPU 设置。
    </ParamField>

    <ParamField body="webGlImage" type="string">
      `"random"` 或 `"real"`。控制 WebGL 图像噪声。
    </ParamField>

    <ParamField body="canvas" type="string">
      `"random"` 或 `"real"`。控制 canvas 噪声。
    </ParamField>

    <ParamField body="audioContext" type="string">
      `"random"` 或 `"real"`。控制音频指纹噪声。
    </ParamField>

    <ParamField body="clientRects" type="string">
      `"random"` 或 `"real"`。控制布局测量噪声。
    </ParamField>

    <ParamField body="speechVoices" type="string">
      `"random"` 或 `"real"`。返回与操作系统匹配的语音列表。
    </ParamField>

    <ParamField body="fonts" type="string">
      `"random"` 或 `"real"`。返回与操作系统匹配的字体列表。
    </ParamField>

    <ParamField body="hardware" type="string">
      `"random"` 或 `"real"`。生成一致的 CPU 线程数和内存配对。
    </ParamField>

    <ParamField body="doNotTrack" type="string">
      `"random"`、`"enabled"` 或 `"disabled"`。
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="cookies" type="array">
  在导航前注入浏览器上下文的 Cookie 列表。每个 Cookie 对象需要 `name`、`value` 和 `domain`。

  <Expandable title="cookie 字段">
    <ParamField body="name" type="string" required>
      Cookie 名称。
    </ParamField>

    <ParamField body="value" type="string" required>
      Cookie 值。
    </ParamField>

    <ParamField body="domain" type="string" required>
      目标域名，如 `.example.com`。
    </ParamField>

    <ParamField body="path" type="string">
      Cookie 路径。默认值为 `/`。
    </ParamField>

    <ParamField body="secure" type="boolean">
      是否仅通过 HTTPS 发送 Cookie。
    </ParamField>

    <ParamField body="httpOnly" type="boolean">
      是否禁止客户端 JavaScript 访问 Cookie。
    </ParamField>

    <ParamField body="sameSite" type="string">
      SameSite 属性。
    </ParamField>

    <ParamField body="session" type="boolean">
      设置为 `true` 表示会话 Cookie。
    </ParamField>

    <ParamField body="expirationDate" type="number">
      Unix 过期时间戳，单位为秒。也接受 `expires` 或 `expiry`。
    </ParamField>
  </Expandable>
</ParamField>

## 响应

<ResponseField name="title" type="string">
  从页面提取的文章标题。
</ResponseField>

<ResponseField name="byline" type="string">
  作者名称或署名（如果存在）。
</ResponseField>

<ResponseField name="excerpt" type="string">
  文章的简短摘要或描述。
</ResponseField>

<ResponseField name="siteName" type="string">
  网站名称（如果可用）。
</ResponseField>

<ResponseField name="lang" type="string">
  文章的语言代码，例如 `"en"`。
</ResponseField>

<ResponseField name="dir" type="string">
  文本方向，例如 `"ltr"` 或 `"rtl"`。如果未检测到则为 `null`。
</ResponseField>

<ResponseField name="content" type="string">
  文章正文的清理后 HTML。
</ResponseField>

<ResponseField name="textContent" type="string">
  文章正文的纯文本版本，空白字符已规范化。
</ResponseField>

<ResponseField name="length" type="number">
  `textContent` 的字符数。
</ResponseField>

<ResponseField name="publishedTime" type="string">
  ISO 8601 发布日期时间戳，如果未找到则为 `null`。
</ResponseField>

| Code | Meaning |
| - | - |
| `200` | 成功。根据 contentMode 返回 text/html、text/markdown 或 application/json。 |
| `400` | JSON、URL、contentMode、Cookie、代理、区域或 User-Agent 参数无效。请求体超过 1 MiB。 |
| `401` | `x-api-key` 缺失或无效。 |
| `402` | 余额不足。返回 `INSUFFICIENT_CREDITS`、`balance` 和 `requiredCredits`。 |
| `422` | 未找到选择器（`CONTENT_SELECTOR_NOT_FOUND`）或缺少可读内容（`READABILITY_CONTENT_NOT_FOUND`）。 |
| `429` | 任务请求频率受限。 |
| `502` | 代理不可达或目标 HTTP 失败。 |
| `503` | 队列、Worker、管理代理或 User-Agent 资源不可用。 |
| `504` | 任务、导航或代理连接超时。 |
| `500` | 未分类的任务执行失败。 |

<Note>
  一次请求验证后会消耗 1 积分，在任务入队之前扣除。请求体限制为 1 MiB。
</Note>

## 更多示例

<CodeGroup>
  ```bash cURL theme={null}
  curl -sS -X POST "https://api.adscrawl.net/html" \
    -H "content-type: application/json" \
    -H "x-api-key: YOUR_API_KEY" \
    -d '{
      "url": "https://example.com",
      "contentMode": "html",
      "selector": "main",
      "waitUntil": "domcontentloaded",
      "countryCode": "US"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.adscrawl.net/html", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-api-key": "YOUR_API_KEY",
    },
    body: JSON.stringify({
      url: "https://example.com",
      contentMode: "html",
      selector: "main",
      waitUntil: "domcontentloaded",
      countryCode: "US",
    }),
  });

  const html = await response.text();
  console.log(html);
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "https://api.adscrawl.net/html",
      headers={
          "content-type": "application/json",
          "x-api-key": "YOUR_API_KEY",
      },
      json={
          "url": "https://example.com",
          "contentMode": "html",
          "selector": "main",
          "waitUntil": "domcontentloaded",
          "countryCode": "US",
      },
  )

  response.raise_for_status()
  print(response.text)
  ```
</CodeGroup>

<RequestExample>
  ```bash cURL theme={null}
  curl -sS -X POST "https://api.adscrawl.net/html" \
    -H "content-type: application/json" \
    -H "x-api-key: YOUR_API_KEY" \
    -d '{
      "url": "https://example.com/article",
      "contentMode": "json",
      "waitUntil": "domcontentloaded",
      "viewport": { "width": 1280, "height": 720 },
      "locale": "en-US",
      "countryCode": "GLOBAL",
      "userAgentMode": "random",
      "userAgentOs": "windows"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.adscrawl.net/html", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-api-key": "YOUR_API_KEY",
    },
    body: JSON.stringify({
      url: "https://example.com/article",
      contentMode: "json",
      waitUntil: "domcontentloaded",
      viewport: { width: 1280, height: 720 },
      locale: "en-US",
      countryCode: "GLOBAL",
      userAgentMode: "random",
      userAgentOs: "windows",
    }),
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(`${response.status} ${err.code}: ${err.error}`);
  }

  const article = await response.json();
  console.log(article.title, article.byline);
  ```

  ```python Python theme={null}
  import httpx, json

  response = httpx.post(
      "https://api.adscrawl.net/html",
      headers={
          "content-type": "application/json",
          "x-api-key": "YOUR_API_KEY",
      },
      json={
          "url": "https://example.com/article",
          "contentMode": "json",
          "waitUntil": "domcontentloaded",
          "viewport": {"width": 1280, "height": 720},
          "locale": "en-US",
          "countryCode": "GLOBAL",
          "userAgentMode": "random",
          "userAgentOs": "windows",
      },
  )

  response.raise_for_status()
  article = response.json()
  print(article["title"], article["byline"])
  ```
</RequestExample>

<ResponseExample>
  ```json 200 JSON (contentMode: json) theme={null}
  {
    "title": "Example Article",
    "byline": "OpenAI",
    "excerpt": "A concise article summary.",
    "siteName": "Example",
    "lang": "en",
    "dir": null,
    "content": "<div><p>Readable body...</p></div>",
    "textContent": "Readable body...",
    "length": 2487,
    "publishedTime": null
  }
  ```

  ```markdown 200 Markdown (contentMode: markdown) theme={null}
  # Example Article

  Readable body...

  - key point one
  - key point two
  ```

  ```json 402 Insufficient credits theme={null}
  {
    "error": "Insufficient credits",
    "code": "INSUFFICIENT_CREDITS",
    "balance": 0,
    "requiredCredits": 1
  }
  ```

  ```json 422 Readability not found theme={null}
  {
    "error": "Readable article content was not found",
    "code": "READABILITY_CONTENT_NOT_FOUND"
  }
  ```

  ```json 400 Bad country code theme={null}
  {
    "error": "countryCode is not supported",
    "code": "INVALID_COUNTRY_CODE"
  }
  ```

  ```json 503 Proxy unavailable theme={null}
  {
    "error": "Dynamic country/region routing is unavailable",
    "code": "DYNAMIC_PROXY_NOT_CONFIGURED"
  }
  ```
</ResponseExample>
