Anyhunt

Scrape API

从任意网页提取多种格式的内容

Scrape API

Scrape API 是提取网页内容的主要接口。支持多种输出格式,包括 Markdown、HTML、链接和截图。

接口端点

方法路径描述
POST/api/v1/scrape创建抓取任务
GET/api/v1/scrape/:id获取任务状态和结果
GET/api/v1/scrape获取抓取历史

创建抓取任务

POST /api/v1/scrape

请求参数

必填参数

参数类型描述
urlstring要抓取的 URL(必须是有效的 HTTP/HTTPS)

输出格式选项

参数类型默认值描述
formatsstring[]["markdown"]输出格式:markdownhtmlrawHtmllinksscreenshot
onlyMainContentbooleantrue仅提取主要内容(使用 Readability 算法)
includeTagsstring[]-要包含的 CSS 选择器
excludeTagsstring[]-要排除的 CSS 选择器(截图时也会隐藏这些元素)

页面配置

参数类型默认值描述
viewportobject-自定义视口 {width, height}
viewport.widthnumber1280视口宽度(100-3840)
viewport.heightnumber800视口高度(100-2160)
mobilebooleanfalse使用移动端视口和 User-Agent
devicestring-设备预设:desktoptabletmobile
darkModebooleanfalse启用深色模式
headersobject-自定义 HTTP 请求头

时间选项

参数类型默认值描述
waitFornumber | string-等待时间(毫秒),或要等待的 CSS 选择器
timeoutnumber30000页面超时时间(毫秒)

截图选项

仅当 formats 包含 screenshot 时生效:

参数类型默认值描述
screenshotOptions.fullPagebooleanfalse捕获完整页面高度
screenshotOptions.formatstring"png"图片格式:pngjpegwebp
screenshotOptions.qualitynumber80图片质量(1-100)
screenshotOptions.clipstring-元素截图的 CSS 选择器
screenshotOptions.responsestring"url"响应类型:urlbase64

页面交互

在抓取前执行交互操作:

参数类型描述
actionsAction[]要执行的操作数组

操作类型:

类型参数描述
waitmilliseconds等待指定时间
clickselector点击元素
typeselector, text在输入框中输入文本
presskey按下键盘按键
scrolldirection (up/down), amount滚动页面
screenshot-拍摄中间截图

请求示例

curl -X POST https://server.anyhunt.app/api/v1/scrape \
  -H "Authorization: Bearer ah_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/article",
    "formats": ["markdown", "screenshot"],
    "onlyMainContent": true,
    "viewport": {
      "width": 1920,
      "height": 1080
    },
    "screenshotOptions": {
      "fullPage": true,
      "format": "webp",
      "quality": 85
    }
  }'

响应

API 返回任务 ID。通过轮询 GET /api/v1/scrape/:id 获取结果。

{
  "id": "scrape_abc123",
  "status": "PENDING"
}

缓存命中响应:

如果相同的 URL 最近被抓取过,API 会立即返回缓存结果:

{
  "id": "scrape_abc123",
  "url": "https://example.com/article",
  "fromCache": true,
  "markdown": "# 文章标题\n\n文章内容...",
  "screenshot": {
    "url": "https://cdn.anyhunt.app/scraper/scrape_abc123.webp",
    "width": 1920,
    "height": 3500,
    "format": "webp",
    "fileSize": 245000,
    "expiresAt": "2024-02-15T10:30:00.000Z"
  },
  "metadata": {
    "title": "文章标题",
    "description": "文章描述"
  }
}

获取抓取任务

GET /api/v1/scrape/:id

获取特定抓取任务的状态和结果。

响应

{
  "id": "scrape_abc123",
  "url": "https://example.com/article",
  "status": "COMPLETED",
  "fromCache": false,
  "markdown": "# 文章标题\n\n内容...",
  "metadata": {
    "title": "文章标题",
    "description": "文章描述"
  },
  "screenshot": {
    "url": "https://cdn.anyhunt.app/scraper/scrape_abc123.webp",
    "width": 1920,
    "height": 3500,
    "format": "webp",
    "fileSize": 245000
  },
  "timings": {
    "queueWaitMs": 50,
    "fetchMs": 1200,
    "renderMs": 500,
    "transformMs": 100,
    "screenshotMs": 800,
    "totalMs": 2650
  }
}

状态值:

状态描述
PENDING任务已排队
PROCESSING任务正在处理
COMPLETED任务成功完成
FAILED任务失败

获取抓取历史

GET /api/v1/scrape

获取最近的抓取任务列表。

查询参数

参数类型默认值描述
limitnumber20最大结果数(1-100)
offsetnumber0跳过的结果数,用于分页

响应

[
  {
    "id": "scrape_abc123",
    "url": "https://example.com",
    "status": "COMPLETED",
    "fromCache": false,
    "createdAt": "2024-01-15T10:30:00.000Z",
    "completedAt": "2024-01-15T10:30:02.650Z"
  }
]

代码示例

Node.js

// 启动抓取任务
const response = await fetch('https://server.anyhunt.app/api/v1/scrape', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ah_your_api_key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example.com',
    formats: ['markdown', 'links'],
    onlyMainContent: true,
  }),
});

const data = await response.json();

// 如果缓存命中,数据已经可用
if (data.fromCache) {
  console.log(data.markdown);
} else {
  // 轮询获取结果
  const result = await pollForResult(data.id);
  console.log(result.markdown);
}

async function pollForResult(id) {
  while (true) {
    const res = await fetch(`https://server.anyhunt.app/api/v1/scrape/${id}`, {
      headers: { 'Authorization': 'Bearer ah_your_api_key' },
    });
    const data = await res.json();
    if (data.status === 'COMPLETED') return data;
    if (data.status === 'FAILED') throw new Error(data.error?.message);
    await new Promise(r => setTimeout(r, 1000)); // 等待 1 秒
  }
}

Python

import requests
import time

# 启动抓取任务
response = requests.post(
    'https://server.anyhunt.app/api/v1/scrape',
    headers={
        'Authorization': 'Bearer ah_your_api_key',
        'Content-Type': 'application/json',
    },
    json={
        'url': 'https://example.com',
        'formats': ['markdown', 'links'],
        'onlyMainContent': True,
    },
)

data = response.json()

# 如果缓存命中,数据已经可用
if data.get('fromCache'):
    print(data['markdown'])
else:
    # 轮询获取结果
    while True:
        res = requests.get(
            f"https://server.anyhunt.app/api/v1/scrape/{data['id']}",
            headers={'Authorization': 'Bearer ah_your_api_key'}
        )
        result = res.json()
        if result['status'] == 'COMPLETED':
            print(result['markdown'])
            break
        elif result['status'] == 'FAILED':
            raise Exception(result.get('error', {}).get('message'))
        time.sleep(1)

使用页面交互

curl -X POST https://server.anyhunt.app/api/v1/scrape \
  -H "Authorization: Bearer ah_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "formats": ["markdown"],
    "actions": [
      {"type": "wait", "milliseconds": 1000},
      {"type": "click", "selector": "#load-more"},
      {"type": "scroll", "direction": "down"},
      {"type": "wait", "milliseconds": 500}
    ]
  }'

错误码

错误码状态码描述
INVALID_URL400URL 格式无效或被阻止
URL_NOT_ALLOWED400URL 被 SSRF 防护阻止
PAGE_TIMEOUT504页面加载超时
SELECTOR_NOT_FOUND400页面上未找到 CSS 选择器
BROWSER_ERROR500浏览器崩溃或错误
NETWORK_ERROR500网络请求失败
RATE_LIMITED429请求过于频繁
QUOTA_EXCEEDED429月度配额已用完

缓存

响应默认缓存 1 小时。缓存命中时,响应中会显示 fromCache: true,且不计入配额。

缓存键由 SHA256(url + options) 计算,因此相同的请求将返回缓存结果。