본문으로 건너뛰기

커스텀 HTTP 클라이언트 사용하기 (실험적 기능)

BasicCrawler 클래스는 생성자 옵션의 httpClient를 통해 HTTP 클라이언트 구현을 설정할 수 있습니다. 이는 테스트를 하거나 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 same as in 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에서 변경될 수 있습니다.