커스텀 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에서 변경될 수 있습니다.