Перейти к основному содержимому

Использование пользовательского HTTP-клиента (Экспериментально)

Класс BasicCrawler позволяет настроить реализацию HTTP-клиента с помощью параметра конструктора httpClient. Это может быть полезно для тестирования или если вам нужно заменить стандартную реализацию на основе got-scraping на что-то другое, например, curl-impersonate или axios.

Реализация HTTP-клиента должна соответствовать интерфейсу BaseHttpClient. Для примера, вот базовая реализация с использованием стандартного интерфейса fetch:

import {
BaseHttpClient,
HttpRequest,
HttpResponse,
RedirectHandler,
ResponseTypes,
StreamingHttpResponse,
} from '@crawlee/core';
import { Readable } from 'node:stream';

class CustomHttpClient implements BaseHttpClient {
async sendRequest<TResponseType extends keyof ResponseTypes = 'text'>(
request: HttpRequest<TResponseType>,
): Promise<HttpResponse<TResponseType>> {
const requestHeaders = new Headers();
for (let [headerName, headerValues] of Object.entries(request.headers ?? {})) {
if (headerValues === undefined) {
continue;
}

if (!Array.isArray(headerValues)) {
headerValues = [headerValues];
}

for (const value of headerValues) {
requestHeaders.append(headerName, value);
}
}

const response = await fetch(request.url, {
method: request.method,
headers: requestHeaders,
body: request.body as string, // TODO реализовать обработку потоков/генераторов
signal: request.signal,
// TODO реализовать остальные параметры запроса (например, timeout, proxyUrl, cookieJar, ...)
});

const headers: Record<string, string> = {};

response.headers.forEach((value, headerName) => {
headers[headerName] = value;
});

return {
complete: true,
request,
url: response.url,
statusCode: response.status,
redirectUrls: [], // TODO реализовать ручное отслеживание перенаправлений
headers,
trailers: {}, // TODO не поддерживается fetch
ip: undefined,
body:
request.responseType === 'text'
? await response.text()
: request.responseType === 'json'
? await response.json()
: Buffer.from(await response.text()),
};
}

async stream(request: HttpRequest, onRedirect?: RedirectHandler): Promise<StreamingHttpResponse> {
const fetchResponse = await fetch(request.url, {
method: request.method,
headers: new Headers(),
body: request.body as string, // TODO реализовать обработку потоков/генераторов
signal: request.signal,
// TODO реализовать остальные параметры запроса (например, timeout, proxyUrl, cookieJar, ...)
});

const headers: Record<string, string> = {}; // TODO то же самое, что и в sendRequest()

async function* read() {
const reader = fetchResponse.body?.getReader();

const stream = new ReadableStream({
start(controller) {
if (!reader) {
return null;
}
return pump();
function pump() {
return reader!.read().then(({ done, value }) => {
// Когда больше нет данных для потребления, закройте поток
if (done) {
controller.close();
return;
}
// Поместите следующий фрагмент данных в наш целевой поток
controller.enqueue(value);
return pump();
});
}
},
});

for await (const chunk of stream) {
yield chunk;
}
}

const response = {
complete: false,
request,
url: fetchResponse.url,
statusCode: fetchResponse.status,
redirectUrls: [], // TODO реализовать ручное отслеживание перенаправлений
headers,
trailers: {}, // TODO не поддерживается fetch
ip: undefined,
stream: Readable.from(read()),
get downloadProgress() {
return { percent: 0, transferred: 0 }; // TODO реализовать отслеживание этого
},
get uploadProgress() {
return { percent: 0, transferred: 0 }; // TODO реализовать отслеживание этого
},
};

return response;
}
}

Затем вы можете создать экземпляр и передать его в конструктор краулера:

const crawler = new HttpCrawler({
httpClient: new CustomHttpClient(),
async requestHandler() {
/* ... */
},
});

Обратите внимание, что интерфейс является экспериментальным и, вероятно, будет изменен в версии Crawlee 4.