Observability
系统化梳理日志、指标与追踪的协同机制及成本控制策略。
通过 Python SDK 使用 Scrapfly API 爬取网站,支持结构化数据提取与批量下载。
openclaw skills install @scrapfly/scrapfly-crawler命令、参数、文件名以原文为准
通过 Python SDK 使用 Scrapfly 爬虫 API,可调度并管理全站爬取任务,支持自动链接发现、深度控制和结构化数据提取。
pip install scrapfly-sdkAPI 密钥必须通过环境变量 SCRAPFLY_API_KEY 提供,或直接传递给客户端。
from scrapfly import ScrapflyClient, CrawlerConfig, Crawl用于 Webhook 的额外导入:
from scrapfly import (
webhook_from_payload,
CrawlStartedWebhook,
CrawlUrlDiscoveredWebhook,
CrawlUrlFailedWebhook,
CrawlCompletedWebhook,
)| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
url | str | 必填 | 爬取的起始 URL |
page_limit | int | None | 最多爬取页面数量 |
max_depth | int | None | 从起始 URL 开始的最大链接遍历深度 |
max_duration | int | None | 爬取最大持续时间(秒) |
exclude_paths | list[str] | None | 要跳过的 URL 路径模式(支持 *,最多 100 个)。与 include_only_paths 互斥 |
include_only_paths | list[str] | None | 仅爬取匹配这些路径模式的 URL(支持 *,最多 100 个)。与 exclude_paths 互斥 |
ignore_base_path_restriction | bool | False | 允许爬取同一域名下的任意路径,不限于种子 URL 的基础路径 |
follow_external_links | bool | False | 是否跟随外部域名链接 |
allowed_external_domains | list[str] | None | 当 follow_external_links=True 时,允许的外部域名白名单(支持 *,最多 250 个) |
headers | dict | None | 自定义 HTTP 请求头(JSON 对象格式) |
delay | int | None | 请求之间的延迟时间(毫秒,范围 0-15000) |
user_agent | str | None | 自定义 User-Agent 字符串(当 asp=True 时会被忽略) |
max_concurrency | int | None | 最大并发爬取请求数 |
rendering_delay | int | None | 页面加载后等待多少毫秒再进行内容提取(0-25000)。设为 0 则禁用浏览器渲染 |
use_sitemaps | bool | False | 若存在 sitemap.xml,则从中发现 URL |
respect_robots_txt | bool | False | 遵守 robots.txt 规则和 Disallow 指令 |
ignore_no_follow | bool | False | 忽略链接上的 rel="nofollow" 属性 |
cache | bool | False | 启用已爬取页面的缓存层 |
cache_ttl | int | None | 缓存存活时间(秒,范围 0-604800) |
cache_clear | bool | False | 强制刷新缓存页面 |
content_formats | list[str] | None | 要提取的内容格式:html、clean_html、markdown、text |
extraction_rules | dict | None | 自定义结构化数据提取规则 |
asp | bool | False | 启用反爬保护功能,使用浏览器渲染 |
proxy_pool | str | None | 使用的代理池(如 public_residential_pool) |
country | str | None | 代理所在国家(ISO 国家代码) |
webhook_name | str | None | 仪表板中配置的 webhook 名称 |
webhook_events | list[str] | None | 要订阅的事件类型(如 crawler_started、crawler_url_visited、crawler_finished) |
max_api_credit | int | None | 此次爬取最多消耗的 API 积分 |
"crawler_started" - 爬虫任务开始执行"crawler_url_visited" - 成功爬取了一个 URL"crawler_url_skipped" - URL 被跳过"crawler_url_discovered" - 发现了新的 URL"crawler_url_failed" - 某个 URL 爬取失败"crawler_stopped" - 因错误或限制而停止爬取"crawler_cancelled" - 爬取被手动取消"crawler_finished" - 爬取任务成功完成import os
from scrapfly import ScrapflyClient, CrawlerConfig, Crawl
client = ScrapflyClient(key=os.environ["SCRAPFLY_API_KEY"])
# 创建、启动并等待爬取完成(链式调用)
# 2. 创建并运行爬虫
crawl = Crawl(
client,
CrawlerConfig(url='https://web-scraping.dev/products', page_limit=5)
).crawl().wait()
# 3. 获取结果
pages = crawl.warc().get_pages()
# 4. 处理结果
print(f"已爬取 {len(pages)} 个页面:")
for page in pages:
print(f" • {page['url']} ({page['status_code']})")
# 5. 访问特定 URL 内容
html = crawl.read('https://web-scraping.dev/products')
if html:
print(f"\n主页大小为 {len(html):,} 字节")import os
import time
from scrapfly import ScrapflyClient, CrawlerConfig
client = ScrapflyClient(key=os.environ["SCRAPFLY_API_KEY"])
config = CrawlerConfig(
url='https://web-scraping.dev',
page_limit=10,
max_depth=2,
content_formats=['html', 'markdown']
)
# 1. 启动爬虫
start_response = client.start_crawl(config)
print(f"爬虫已启动:{start_response.uuid}")
# 2. 轮询状态
while True:
status = client.get_crawl_status(start_response.uuid)
print(f"状态:{status.status} - {status.progress_pct:.1f}%")
print(f"已爬取:{status.urls_crawled}/{status.urls_discovered} 个页面")
if status.is_complete:
break
elif status.is_failed:
print("爬取失败!")
break
time.sleep(5)
# 3. 下载并处理结果
if status.is_complete:
artifact = client.get_crawl_artifact(start_response.uuid)
pages = artifact.get_pages()
print(f"已下载 {len(pages)} 个页面")
for page in pages:
print(f" {page['url']}: {page['status_code']} ({len(page['content'])} 字节)")
# 保存到文件
artifact.save('crawl_results.warc.gz')# Scrapfly 爬虫技能 (Scrapfly Crawler)
## 基础爬取config = CrawlerConfig(
url='https://web-scraping.dev/',
page_limit=50,
max_depth=5,
include_only_paths=['*/product/*'],
content_formats=['markdown', 'text'],
)
crawl = Crawl(client, config).crawl().wait()
## 绕过反爬机制的爬取config = CrawlerConfig(
url='https://web-scraping.dev',
page_limit=50,
asp=True,
content_formats=['html'],
)
crawl = Crawl(client, config).crawl().wait()
## 使用提取规则的爬取config = CrawlerConfig(
url='https://web-scraping.dev/products',
page_limit=100,
max_depth=3,
include_only_paths=['/products/*'],
extraction_rules={
"/products/*": {
"type": "model",
"value": "product",
},
},
)
crawl = Crawl(client, config).crawl().wait()
## 读取特定 URL 的内容content = crawl.read('https://web-scraping.dev/product/1')
if content:
print(f"URL: {content.url}")
print(f"状态码: {content.status_code}")
print(f"耗时: {content.duration}s")
print(content.content)
content = crawl.read('https://web-scraping.dev/product/1', format='markdown')
if content:
print(content.content)
## 遍历匹配模式的 URLfor content in crawl.read_iter(pattern="*/products?page=*", format="markdown"):
print(f"{content.url}: {len(content.content)} 字符")
## 批量内容获取urls = ['https://web-scraping.dev/product/1', 'https://web-scraping.dev/product/2']
contents = crawl.read_batch(urls, formats=['markdown'])
for url, formats_dict in contents.items():
markdown = formats_dict.get('markdown', '')
print(f"{url}: {len(markdown)} 字符")
## 下载 WARC/HAR 归档文件artifact = crawl.warc()
artifact.save('crawl_results.warc.gz')
artifact = crawl.har()
artifact.save('crawl_results.har')
for record in artifact.iter_responses():
print(f"{record.url}: {record.status_code}")
## 从爬取的文档构建 LLM.txt 文件import os
from scrapfly import ScrapflyClient, CrawlerConfig, Crawl
client = ScrapflyClient(key=os.environ["SCRAPFLY_API_KEY"])
config = CrawlerConfig(
url='https://web-scraping.dev/',
include_only_paths=['/docs', '/docs/*'], # 仅允许文档资源
page_limit=50,
max_depth=5,
follow_external_links=False,
content_formats=['markdown'],
)
crawl = Crawl(client, config).crawl().wait(poll_interval=5, verbose=True)
warc_artifact = crawl.warc()
all_urls = [
record.url for record in warc_artifact.iter_responses()
if record.status_code == 200
]
all_contents = {}
for i in range(0, len(all_urls), 100):
batch_urls = all_urls[i:i + 100]
batch_contents = crawl.read_batch(batch_urls, formats=['markdown'])
all_contents.update(batch_contents)
lines = ["# 文档\n"]
for url, formats_dict in all_contents.items():
markdown = formats_dict.get('markdown', '').strip()
if markdown:
lines.append(f"---\n\n### {url}\n\n{markdown}\n")
with open("llms-full.txt", "w", encoding="utf-8") as f:
f.write("\n".join(lines))
## 爬取统计信息stats = crawl.stats()
print(f"发现的 URL 数量: {stats['urls_discovered']}")
print(f"已爬取的 URL 数量: {stats['urls_crawled']}")
print(f"进度: {stats['progress_pct']:.1f}%")
## 主要 SDK 类
| 类 | 描述 |
|-------|-------------|
| `ScrapflyClient` | 主客户端,负责认证和 API 调用 |
| `CrawlerConfig` | 爬取任务配置 |
| `Crawl` | 高级爬取生命周期管理器(启动、等待、读取) |
| `CrawlerStartResponse` | `client.start_crawl()` 的返回结果 |
| `CrawlerStatusResponse` | `client.get_crawl_status()` 的返回结果,包含 `is_complete`、`is_failed`、`progress_pct` |
| `CrawlerArtifactResponse` | `client.get_crawl_artifact()` 的返回结果,包含 `get_pages()`、`iter_responses()`、`save()` 方法 |
| `CrawlContent` | 单个 URL 的内容,包含 `url`、`content`、`status_code`、`duration` |
## 重要注意事项
- 使用 `page_limit` 控制大型网站的费用支出
- 使用 `include_only_paths` 和 `exclude_paths` 限定爬取范围,聚焦于相关部分
- `exclude_paths` 与 `include_only_paths` 互斥,不可同时使用
- 爬取任务为异步执行,简单场景使用 `Crawl.crawl().wait()`,需要更精细控制时可轮询 `client.get_crawl_status()`
- WARC/HAR 归档文件可通过 `crawl.warc()` / `crawl.har()` 获取,用于离线处理
- `read_batch()` 支持单次请求最多 100 个 URL,实现高效批量内容获取
- 生产环境中应验证 Webhook 签名以确保安全
- 使用 `follow_external_links` 时需谨慎,避免意外爬取整个互联网已收录 3 个 Skill