Войти

Как устроен SDK

SDK для .NET - это тонкая обёртка над тем же HTTP API, который описан в API Reference. Каждый параметр Crawling API, который вы добавили бы как query string в чистом HTTP-запросе, доступен как опция Dictionary<string, object> - имена, значения по умолчанию и поведение совпадают один к одному.

Одна особенность, о которой стоит знать заранее: .NET SDK предоставляет состояние ответа на самом экземпляре API, а не в возвращаемом объекте. Вызовы вроде api.Get(url) возвращают void; результат вы читаете через api.StatusCode, api.Body и так далее. Это отличается от SDK для Python / Node / Ruby / PHP (которые возвращают объект ответа). Исключение - Storage API: его методы возвращают объект ответа, который вы читаете напрямую.

Что вы получаете, используя его вместо прямой работы с HttpClient:

  • URL-кодирование, валидация параметров и парсинг ответов работают из коробки.
  • Пара sync + async на каждый метод - выбирайте тот, что подходит для вашего места вызова.
  • Один клиентский класс на каждый Crawlbase API, все с одинаковой формой конструктора и вызова.
  • Разумные значения по умолчанию (таймаут 90 секунд, автоматический парсинг JSON для ответов format=json).

Исходный код на github.com/crawlbase/crawlbase-net.

Установка

Актуальная версия в NuGet. Поддерживает .NET 6+; протестировано вплоть до .NET 9.

# .NET CLI
dotnet add package CrawlbaseAPI

# Package Manager Console
Install-Package CrawlbaseAPI

# Or in csproj:
# <PackageReference Include="CrawlbaseAPI" Version="1.1.0" />

Аутентификация

Все Crawlbase APIs аутентифицируются по одной и той же модели токенов. На одном аккаунте существуют два типа токенов:

  • Normal Token (TCP) - для статичного HTML, JSON-эндпоинтов, всего, что не требует браузера. Быстрее и дешевле.
  • JavaScript Token - для SPA, лениво подгружаемых лент, всего, что прячет контент за клиентским рендерингом. Обязателен для использования page_wait, ajax_wait, scroll и css_click_selector.

В продакшене используйте переменные окружения или конфигурацию вашего DI-контейнера. Шаблон:

// Pick the right token at instantiation; the SDK doesn't switch
// tokens per-call, so keep two clients if you alternate.
var api = new Crawlbase.API(Environment.GetEnvironmentVariable("CRAWLBASE_TOKEN"));
var js = new Crawlbase.API(Environment.GetEnvironmentVariable("CRAWLBASE_JS_TOKEN"));

await api.GetAsync("https://github.com/anthropic");

var opts = new Dictionary<string, object> { ["page_wait"] = 2000 };
await js.GetAsync("https://feed.example.com", opts);

Полная модель токенов и их расположение в дашборде — на странице Аутентификация.

Быстрый старт

Три строки от namespace до полученного ответа. Обратите внимание, что состояние ответа живёт на экземпляре api:

var api = new Crawlbase.API("YOUR_TOKEN");
await api.GetAsync("https://github.com/anthropic");

if (api.StatusCode == 200) {
 Console.WriteLine(api.Body);
}

Ветвитесь по api.StatusCode (HTTP-статус запроса SDK к Crawlbase) и api.CrawlbaseStatus (вердикт Crawlbase - см. Ошибки ниже) при принятии решения о повторе. Передайте new Dictionary<string,object> { ["format"] = "json" }, чтобы получить JSON-обёртку вместо сырого содержимого страницы.

Все APIs в одном пакете

У каждого продукта Crawlbase есть соответствующий клиентский класс. Один и тот же конструктор (одна строка с токеном), одинаковая форма Get / GetAsync / Post / PostAsync.

string token = "YOUR_TOKEN";

var crawl = new Crawlbase.API(token); // Crawling API
var scraper = new Crawlbase.ScraperAPI(token); // parsed JSON for supported sites
var leads = new Crawlbase.LeadsAPI(token); // domain-scoped email extraction (legacy)
var shots = new Crawlbase.ScreenshotsAPI(token); // body is base64-encoded image
var storage = new Crawlbase.StorageAPI(token); // Cloud Storage CRUD

// Push high-volume async jobs to the Enterprise Crawler via the Crawling API:
// api.Get(url, options) where options carries `callback=true` + `crawler=YourCrawler`.
// See /docs/crawler for the queue-management workflow.

Типовые сценарии

JavaScript-рендеринг

Для SPA, лениво подгружаемых лент и страниц, где исходный HTML пуст, создавайте экземпляр с JavaScript token и передавайте любую комбинацию page_wait, ajax_wait, scroll и css_click_selector. Порядок мышления: фиксированное ожидание, затем network-idle, затем скролл для lazy-load, затем клик для любого закрывающего UI-элемента.

var api = new Crawlbase.API("YOUR_JS_TOKEN");

await api.GetAsync("https://spa.example.com", new Dictionary<string, object> {
 ["page_wait"] = 2000,
 ["ajax_wait"] = true,
 ["scroll"] = true,
});

Использование встроенного скрейпера

Полностью пропустите парсер на поддерживаемых сайтах. Передайте ["scraper"] = "NAME" — и body становится JSON-строкой со структурированными полями, описанными на странице соответствующего скрейпера.

using System.Text.Json;

var api = new Crawlbase.ScraperAPI("YOUR_TOKEN");
await api.GetAsync(
 "https://www.amazon.com/dp/1098145356",
 new Dictionary<string, object> { ["scraper"] = "amazon-product-details" }
);

var data = JsonSerializer.Deserialize<JsonElement>(api.Body);
Console.WriteLine($"{data.GetProperty("name")} - {data.GetProperty("price")}");

Гео-маршрутизация

Передайте ["country"] = "ISO", чтобы маршрутизировать crawl через выходные узлы этой страны. Используйте всякий раз, когда целевой сайт отдаёт локализованный контент в зависимости от IP.

var api = new Crawlbase.API("YOUR_TOKEN");

// Hit the German Amazon catalog from a German residential IP
await api.GetAsync(
 "https://www.amazon.com/dp/1098145356",
 new Dictionary<string, object> { ["country"] = "DE" }
);

Повторы с backoff

Рекомендуемая форма повторов: экспоненциальный backoff с лимитом 3–5 попыток, повторять только на временных ошибках (5xx или пустой body), не повторять на 4xx.

public async Task<bool> CrawlAsync(Crawlbase.API api, string url, int attempts = 5) {
 var rand = new Random();
 for (int i = 0; i < attempts; i++) {
 try {
 await api.GetAsync(url);
 } catch (Exception) {
 // SDK throws on transport failures - fall through to retry
 }
 if (api.StatusCode == 200 && api.CrawlbaseStatus == 200) {
 return true;
 }
 if (api.StatusCode is >= 400 and < 500) {
 throw new InvalidOperationException($"client error {api.StatusCode}: {url}");
 }
 // Exponential backoff with jitter
 var ms = (int) (rand.NextDouble() * Math.Pow(2, i) * 1000);
 await Task.Delay(ms);
 }
 return false;
}

Асинхронные crawls и вебхуки

Режим fire-and-forget. Передайте ["async"] = true вместе с URL в ["callback"]; вызов сразу возвращает управление, а Crawlbase отправит результат POST-ом на ваш вебхук, когда страница будет готова. Полезно для пакетных задач и медленных целей.

var api = new Crawlbase.API("YOUR_TOKEN");

await api.GetAsync("https://example.com", new Dictionary<string, object> {
 ["async"] = true,
 ["callback"] = "https://your-app.com/webhook",
});

// api.Body is a JSON envelope { rid: ... } - use that to correlate
// the eventual webhook delivery.
//
// Your ASP.NET / Minimal API endpoint receives a POST with:
// { rid, url, original_status, pc_status, body }

Для очень больших объёмов (миллионы URL-ов) используйте Enterprise Crawler, который стоит перед тем же асинхронным пайплайном.

Sticky-сессии

Некоторым сценариям нужен один и тот же резидентный IP в нескольких вызовах. Передайте cookies_session со стабильным идентификатором — и Crawlbase будет переиспользовать тот же выходной узел около 30 минут.

var api = new Crawlbase.API("YOUR_JS_TOKEN");

var session = $"checkout-{userId}";
var opts = new Dictionary<string, object> { ["cookies_session"] = session };

await api.GetAsync("https://shop.example.com/cart", opts);
await api.GetAsync("https://shop.example.com/checkout", opts);
await api.GetAsync("https://shop.example.com/confirm", opts);

CRUD для Cloud Storage

Storage API - исключение из паттерна «ответ на экземпляре api»: его методы возвращают объект ответа, который вы читаете напрямую. Полезно при чтении результатов, сохранённых предыдущим вызовом Crawling API (store=true).

var storage = new Crawlbase.StorageAPI("YOUR_TOKEN");

// Fetch by URL
var response = storage.GetByUrl("https://www.apple.com");
Console.WriteLine(response.OriginalStatus);
Console.WriteLine(response.CrawlbaseStatus);
Console.WriteLine(response.URL);
Console.WriteLine(response.RID);
Console.WriteLine(response.StoredAt);

// Or fetch by RID, delete, bulk-fetch, list RIDs, total count
var item = storage.GetByRID(rid);
bool deleted = storage.Delete(rid);
var items = storage.Bulk(new List<string> { rid1, rid2 });
var rids = storage.RIDs(100); // optional limit
var total = storage.TotalCount();

Ошибки и повторы

Платформа выдаёт два статус-кода в каждом ответе: собственный api.StatusCode SDK (HTTP-статус запроса к самой Crawlbase) и api.CrawlbaseStatus (вердикт Crawlbase по целевому ресурсу - полный список см. в таблице ошибок Crawling API). Всегда ветвитесь по api.CrawlbaseStatus при принятии решения о повторе - целевой ресурс может вернуть 200 с пустым body, и тогда StatusCode будет 200, а CrawlbaseStatus - 520.

try {
 await api.GetAsync(url);
} catch (Exception ex) {
 log.LogError(ex, "transport error");
 return;
}

int pc = api.CrawlbaseStatus;

switch (pc) {
 case 200:
 UseBody(api.Body);
 break;
 case 520 or 525:
 // 520 = empty body, 525 = anti-bot couldn't be solved.
 // Switch to JS token and retry.
 await RetryWithJsTokenAsync(url);
 break;
 case 521 or 522 or 523:
 // Target unreachable or timed out. Retry with backoff.
 ScheduleRetry(url);
 break;
 default:
 log.LogError("crawl failed url={Url} crawlbase_status={CrawlbaseStatus}", url, pc);
 break;
}

Все повторы к платформе бесплатны - только успешные ответы (CrawlbaseStatus: 200) учитываются в вашей квоте.

Производительность и лучшие практики

  • Переиспользуйте один клиент на токен. Зарегистрируйте его как singleton в вашем DI-контейнере - каждый экземпляр открывает свой собственный HttpClient. Не создавайте по экземпляру на запрос.
  • Используйте самый дешёвый токен, который работает. Не используйте JavaScript token по умолчанию «на всякий случай» - запросы с Normal token быстрее и расходуют меньше параллелизма.
  • Предпочитайте ajax_wait вместо page_wait. Фиксированные задержки сжигают конкурентность на каждом запросе, даже на быстрых.
  • Учитывайте общее состояние на экземпляре API. Поскольку Crawling/Scraper/Leads/Screenshots API записывают состояние ответа в объект api (а не возвращают значение), не разделяйте один экземпляр между параллельными Task - второй GetAsync() с await перезапишет состояние ответа первой задачи прямо во время чтения. Создайте пул из одного экземпляра на воркер или используйте методы StorageAPI с возвращаемым объектом, которые безопасно чередовать.
  • Для пакетных задач: async + вебхук или передача в Enterprise Crawler. Awaitable Tasks, блокирующиеся на синхронных вызовах, быстро упираются в лимиты конкурентности; async + вебхук освобождает слот в момент постановки запроса в очередь.

Справочник методов

Все клиентские классы, кроме Storage, имеют одинаковый интерфейс. Конструкторы принимают строку токена; глаголы идут парами sync + async и записывают состояние ответа в экземпляр api.

new Crawlbase.API(string token)
конструктор
Инициализирует клиент Crawling API. Та же форма у Crawlbase.ScraperAPI, Crawlbase.LeadsAPI, Crawlbase.ScreenshotsAPI, Crawlbase.StorageAPI.
api.Get(string url, Dictionary options = null)
метод
Отправляет GET (синхронно). Возвращает void; читайте ответ через свойства на api.
api.GetAsync(string url, Dictionary options = null)
метод
Отправляет GET (async). Возвращает Task. Та же модель ответа.
api.Post(...) / api.PostAsync(...)
метод
Отправить POST. data - это body: передайте Dictionary для form-encoded, строку для raw.

Состояние ответа - свойства экземпляра api после вызова:

api.StatusCode
int
HTTP-статус запроса SDK к Crawlbase.
api.CrawlbaseStatus
int
Вердикт Crawlbase по целевому ресурсу. Ветвитесь по нему для решений о повторных попытках.
api.OriginalStatus
int
HTTP-статус, который целевой ресурс вернул Crawlbase.
api.Body
string
Содержимое страницы (или JSON-строка, когда использовался format=json / scraper=). Для ScreenshotsAPI оно закодировано в base64 - декодируйте через Convert.FromBase64String(api.Body).
api.StorageURL / api.StorageRID
string
Заполняется, когда вызов содержал store=true. Используйте эти значения, чтобы получить сохранённый ответ обратно через StorageAPI.