Skip to content

Commit ab98f68

Browse files
authored
chore: fix broken types in docs examples (#3287)
Closes #3277
1 parent c7899fb commit ab98f68

8 files changed

Lines changed: 142 additions & 131 deletions

File tree

docs/examples/file_download.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { FileDownload } from 'crawlee';
22

33
// Create a FileDownload - a custom crawler instance that will download files from URLs.
44
const crawler = new FileDownload({
5-
async requestHandler({ body, request, contentType, getKeyValueStore }) {
5+
async requestHandler({ request, response, contentType, getKeyValueStore }) {
66
const url = new URL(request.url);
77
const kvs = await getKeyValueStore();
88

9-
await kvs.setValue(url.pathname.replace(/\//g, '_'), body, { contentType: contentType.type });
9+
await kvs.setValue(url.pathname.replace(/\//g, '_'), response.body, { contentType: contentType.type });
1010
},
1111
});
1212

docs/examples/file_download_stream.ts

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,27 @@ function createProgressTracker({ url, log, totalBytes }: { url: URL; log: Log; t
2323

2424
// Create a FileDownload - a custom crawler instance that will download files from URLs.
2525
const crawler = new FileDownload({
26-
async streamHandler({ stream, request, log, getKeyValueStore }) {
26+
async requestHandler({ response, request, log, getKeyValueStore }) {
2727
const url = new URL(request.url);
2828

2929
log.info(`Downloading ${url} to ${url.pathname.replace(/\//g, '_')}...`);
3030

31-
await new Promise<void>((resolve, reject) => {
32-
// With the 'response' event, we have received the headers of the response.
33-
stream.on('response', async (response) => {
34-
const kvs = await getKeyValueStore();
35-
await kvs.setValue(
36-
url.pathname.replace(/\//g, '_'),
37-
pipeline(
38-
stream,
39-
createProgressTracker({ url, log, totalBytes: Number(response.headers['content-length']) }),
40-
(error) => {
41-
if (error) reject(error);
42-
},
43-
),
44-
{ contentType: response.headers['content-type'] },
45-
);
46-
47-
log.info(`Downloaded ${url} to ${url.pathname.replace(/\//g, '_')}.`);
48-
49-
resolve();
50-
});
51-
});
31+
if (!response.body) return;
32+
33+
const kvs = await getKeyValueStore();
34+
await kvs.setValue(
35+
url.pathname.replace(/\//g, '_'),
36+
pipeline(
37+
response.body,
38+
createProgressTracker({ url, log, totalBytes: Number(response.headers.get('content-length')) }),
39+
(error) => {
40+
if (error) log.error(`Failed to download ${url}: ${error.message}`);
41+
},
42+
),
43+
response.headers.get('content-type') ? { contentType: response.headers.get('content-type')! } : {},
44+
);
45+
46+
log.info(`Downloaded ${url} to ${url.pathname.replace(/\//g, '_')}.`);
5247
},
5348
});
5449

docs/guides/custom-http-client/implementation.ts

Lines changed: 6 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Readable } from 'node:stream';
1111
export class CustomHttpClient implements BaseHttpClient {
1212
async sendRequest<TResponseType extends keyof ResponseTypes = 'text'>(
1313
request: HttpRequest<TResponseType>,
14-
): Promise<HttpResponse<TResponseType>> {
14+
): Promise<Response> {
1515
const requestHeaders = new Headers();
1616
for (let [headerName, headerValues] of Object.entries(request.headers ?? {})) {
1717
if (headerValues === undefined) {
@@ -27,96 +27,20 @@ export class CustomHttpClient implements BaseHttpClient {
2727
}
2828
}
2929

30-
const response = await fetch(request.url, {
30+
return fetch(request.url, {
3131
method: request.method,
3232
headers: requestHeaders,
33-
body: request.body as string, // TODO implement stream/generator handling
33+
body: request.body as string,
3434
signal: request.signal,
35-
// TODO implement the rest of request parameters (e.g., timeout, proxyUrl, cookieJar, ...)
3635
});
37-
38-
const headers: Record<string, string> = {};
39-
40-
response.headers.forEach((value, headerName) => {
41-
headers[headerName] = value;
42-
});
43-
44-
return {
45-
complete: true,
46-
request,
47-
url: response.url,
48-
statusCode: response.status,
49-
redirectUrls: [], // TODO you need to handle redirects manually to track them
50-
headers,
51-
trailers: {}, // TODO not supported by fetch
52-
ip: undefined,
53-
body:
54-
request.responseType === 'text'
55-
? await response.text()
56-
: request.responseType === 'json'
57-
? await response.json()
58-
: Buffer.from(await response.text()),
59-
};
6036
}
6137

62-
async stream(request: HttpRequest, _onRedirect?: RedirectHandler): Promise<StreamingHttpResponse> {
63-
const fetchResponse = await fetch(request.url, {
38+
async stream(request: HttpRequest, _onRedirect?: RedirectHandler): Promise<Response> {
39+
return fetch(request.url, {
6440
method: request.method,
6541
headers: new Headers(),
66-
body: request.body as string, // TODO implement stream/generator handling
42+
body: request.body as string,
6743
signal: request.signal,
68-
// TODO implement the rest of request parameters (e.g., timeout, proxyUrl, cookieJar, ...)
6944
});
70-
71-
const headers: Record<string, string> = {}; // TODO same as in sendRequest()
72-
73-
async function* read() {
74-
const reader = fetchResponse.body?.getReader();
75-
76-
const stream = new ReadableStream({
77-
start(controller) {
78-
if (!reader) {
79-
return null;
80-
}
81-
return pump();
82-
function pump(): Promise<void> {
83-
return reader!.read().then(({ done, value }) => {
84-
// When no more data needs to be consumed, close the stream
85-
if (done) {
86-
controller.close();
87-
return;
88-
}
89-
// Enqueue the next data chunk into our target stream
90-
controller.enqueue(value);
91-
return pump();
92-
});
93-
}
94-
},
95-
});
96-
97-
for await (const chunk of stream) {
98-
yield chunk;
99-
}
100-
}
101-
102-
const response = {
103-
complete: false,
104-
request,
105-
url: fetchResponse.url,
106-
statusCode: fetchResponse.status,
107-
redirectUrls: [], // TODO you need to handle redirects manually to track them
108-
headers,
109-
trailers: {}, // TODO not supported by fetch
110-
ip: undefined,
111-
stream: Readable.from(read()),
112-
get downloadProgress() {
113-
return { percent: 0, transferred: 0 }; // TODO track this
114-
},
115-
get uploadProgress() {
116-
return { percent: 0, transferred: 0 }; // TODO track this
117-
},
118-
};
119-
120-
return response;
12145
}
12246
}

docs/guides/proxy_management.mdx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import InspectionPuppeteerSource from '!!raw-loader!./proxy_management_inspectio
3131
and most effective ways of preventing access to a website. It is therefore paramount for
3232
a good web scraping library to provide easy to use but powerful tools which can work around
3333
IP blocking. The most powerful weapon in our anti IP blocking arsenal is a
34-
[proxy server](https://en.wikipedia.org/wiki/Proxy_server).
34+
[proxy server](https://en.wikipedia.org/wiki/Proxy_server).
3535

3636
With Crawlee we can use our own proxy servers or proxy servers acquired from
3737
third-party providers.
@@ -105,7 +105,7 @@ You can also provide a list of proxy tiers to the `ProxyConfiguration` class. Th
105105
106106
:::warning
107107
108-
Note that the `tieredProxyUrls` option requires `ProxyConfiguration` to be used from a crawler instance ([see below](#crawler-integration)).
108+
Note that the `tieredProxyUrls` option requires `ProxyConfiguration` to be used from a crawler instance ([see below](#crawler-integration)).
109109
110110
Using this configuration through the `newUrl` calls will not yield the expected results.
111111
@@ -162,9 +162,7 @@ Our crawlers will now use the selected proxies for all connections.
162162

163163
## IP Rotation and session management
164164

165-
&#8203;<ApiLink to="core/class/ProxyConfiguration#newUrl">`proxyConfiguration.newUrl()`</ApiLink> allows us to pass a `sessionId` parameter. It will then be used to create a `sessionId`-`proxyUrl` pair, and subsequent `newUrl()` calls with the same `sessionId` will always return the same `proxyUrl`. This is extremely useful in scraping, because we want to create the impression of a real user. See the [session management guide](../guides/session-management) and <ApiLink to="core/class/SessionPool">`SessionPool`</ApiLink> class for more information on how keeping a real session helps us avoid blocking.
166-
167-
When no `sessionId` is provided, our proxy URLs are rotated round-robin.
165+
Each call to <ApiLink to="core/class/ProxyConfiguration#newUrl">`proxyConfiguration.newUrl()`</ApiLink> generates a new proxy URL. Crawler instances pair these URLs with `Session` instances and rotate those together with browser fingerprints, impersonated headers, and more. This is extremely useful in scraping, because we want to create the impression of a real user. See the [session management guide](../guides/session-management) and <ApiLink to="core/class/SessionPool">`SessionPool`</ApiLink> class for more information on how keeping a real session helps us avoid blocking.
168166

169167
<Tabs groupId="proxy_session_management">
170168
<TabItem value="http" label="HttpCrawler">

docs/guides/proxy_management_session_standalone.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,4 @@ const proxyConfiguration = new ProxyConfiguration({
44
/* opts */
55
});
66

7-
const sessionPool = await SessionPool.open({
8-
/* opts */
9-
});
10-
11-
const session = await sessionPool.getSession();
12-
13-
const proxyUrl = await proxyConfiguration.newUrl(session.id);
7+
const proxyUrl = await proxyConfiguration.newUrl();
Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BasicCrawler, ProxyConfiguration } from 'crawlee';
2-
import { gotScraping } from 'got-scraping';
2+
import { Impit } from 'impit';
3+
import { Cookie } from 'tough-cookie';
34

45
const proxyConfiguration = new ProxyConfiguration({
56
/* opts */
@@ -12,22 +13,19 @@ const crawler = new BasicCrawler({
1213
sessionPoolOptions: { maxPoolSize: 100 },
1314
async requestHandler({ request, session }) {
1415
const { url } = request;
15-
const requestOptions = {
16-
url,
17-
// We use session id in order to have the same proxyUrl
18-
// for all the requests using the same session.
19-
proxyUrl: await proxyConfiguration.newUrl(session?.id),
20-
throwHttpErrors: false,
16+
const client = new Impit({
17+
proxyUrl: await proxyConfiguration.newUrl(),
18+
ignoreTlsErrors: true,
2119
headers: {
2220
// If you want to use the cookieJar.
2321
// This way you get the Cookie headers string from session.
24-
Cookie: session?.getCookieString(url),
22+
Cookie: session?.getCookieString(url) ?? '',
2523
},
26-
};
24+
});
2725
let response;
2826

2927
try {
30-
response = await gotScraping(requestOptions);
28+
response = await client.fetch(url);
3129
} catch (e) {
3230
if (e === 'SomeNetworkError') {
3331
// If a network error happens, such as timeout, socket hangup, etc.
@@ -39,9 +37,9 @@ const crawler = new BasicCrawler({
3937
}
4038

4139
// Automatically retires the session based on response HTTP status code.
42-
session?.retireOnBlockedStatusCodes(response.statusCode);
40+
session?.retireOnBlockedStatusCodes(response.status);
4341

44-
if (response.body.includes('You are blocked!')) {
42+
if ((await response.text()).includes('You are blocked!')) {
4543
// You are sure it is blocked.
4644
// This will throw away the session.
4745
session?.retire();
@@ -51,6 +49,17 @@ const crawler = new BasicCrawler({
5149
// No need to call session.markGood -> BasicCrawler calls it for you.
5250

5351
// If you want to use the CookieJar in session you need.
54-
session?.setCookiesFromResponse(response);
52+
if (response.headers.has('set-cookie')) {
53+
const newCookies = response.headers
54+
.get('set-cookie')
55+
?.split(';')
56+
.map((x) => Cookie.parse(x));
57+
58+
newCookies?.forEach((cookie) => {
59+
if (cookie) {
60+
session?.cookieJar?.setCookie(cookie, url);
61+
}
62+
});
63+
}
5564
},
5665
});

docs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"typescript": "^5.9.3"
1111
},
1212
"dependencies": {
13+
"impit": "^0.7.1",
1314
"playwright-extra": "^4.3.6",
1415
"puppeteer-extra": "^3.3.6",
1516
"puppeteer-extra-plugin-stealth": "^2.11.2"

docs/yarn.lock

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ __metadata:
6969
version: 0.0.0-use.local
7070
resolution: "crawlee-docs@workspace:."
7171
dependencies:
72+
impit: "npm:^0.7.1"
7273
playwright-extra: "npm:^4.3.6"
7374
puppeteer-extra: "npm:^3.3.6"
7475
puppeteer-extra-plugin-stealth: "npm:^2.11.2"
@@ -157,6 +158,95 @@ __metadata:
157158
languageName: node
158159
linkType: hard
159160

161+
"impit-darwin-arm64@npm:0.7.1":
162+
version: 0.7.1
163+
resolution: "impit-darwin-arm64@npm:0.7.1"
164+
conditions: os=darwin & cpu=arm64
165+
languageName: node
166+
linkType: hard
167+
168+
"impit-darwin-x64@npm:0.7.1":
169+
version: 0.7.1
170+
resolution: "impit-darwin-x64@npm:0.7.1"
171+
conditions: os=darwin & cpu=x64
172+
languageName: node
173+
linkType: hard
174+
175+
"impit-linux-arm64-gnu@npm:0.7.1":
176+
version: 0.7.1
177+
resolution: "impit-linux-arm64-gnu@npm:0.7.1"
178+
conditions: os=linux & cpu=arm64 & libc=glibc
179+
languageName: node
180+
linkType: hard
181+
182+
"impit-linux-arm64-musl@npm:0.7.1":
183+
version: 0.7.1
184+
resolution: "impit-linux-arm64-musl@npm:0.7.1"
185+
conditions: os=linux & cpu=arm64 & libc=musl
186+
languageName: node
187+
linkType: hard
188+
189+
"impit-linux-x64-gnu@npm:0.7.1":
190+
version: 0.7.1
191+
resolution: "impit-linux-x64-gnu@npm:0.7.1"
192+
conditions: os=linux & cpu=x64 & libc=glibc
193+
languageName: node
194+
linkType: hard
195+
196+
"impit-linux-x64-musl@npm:0.7.1":
197+
version: 0.7.1
198+
resolution: "impit-linux-x64-musl@npm:0.7.1"
199+
conditions: os=linux & cpu=x64 & libc=musl
200+
languageName: node
201+
linkType: hard
202+
203+
"impit-win32-arm64-msvc@npm:0.7.1":
204+
version: 0.7.1
205+
resolution: "impit-win32-arm64-msvc@npm:0.7.1"
206+
conditions: os=win32 & cpu=arm64
207+
languageName: node
208+
linkType: hard
209+
210+
"impit-win32-x64-msvc@npm:0.7.1":
211+
version: 0.7.1
212+
resolution: "impit-win32-x64-msvc@npm:0.7.1"
213+
conditions: os=win32 & cpu=x64
214+
languageName: node
215+
linkType: hard
216+
217+
"impit@npm:^0.7.1":
218+
version: 0.7.1
219+
resolution: "impit@npm:0.7.1"
220+
dependencies:
221+
impit-darwin-arm64: "npm:0.7.1"
222+
impit-darwin-x64: "npm:0.7.1"
223+
impit-linux-arm64-gnu: "npm:0.7.1"
224+
impit-linux-arm64-musl: "npm:0.7.1"
225+
impit-linux-x64-gnu: "npm:0.7.1"
226+
impit-linux-x64-musl: "npm:0.7.1"
227+
impit-win32-arm64-msvc: "npm:0.7.1"
228+
impit-win32-x64-msvc: "npm:0.7.1"
229+
dependenciesMeta:
230+
impit-darwin-arm64:
231+
optional: true
232+
impit-darwin-x64:
233+
optional: true
234+
impit-linux-arm64-gnu:
235+
optional: true
236+
impit-linux-arm64-musl:
237+
optional: true
238+
impit-linux-x64-gnu:
239+
optional: true
240+
impit-linux-x64-musl:
241+
optional: true
242+
impit-win32-arm64-msvc:
243+
optional: true
244+
impit-win32-x64-msvc:
245+
optional: true
246+
checksum: 10c0/25032be7069d725273180c8f7de8c03a8572d786e196ebfaaa93e8fe591f85464ebd4bb766a1de59212ba6aef02e05e547dfcabf11e6e922cb1c58fc722edd2f
247+
languageName: node
248+
linkType: hard
249+
160250
"inflight@npm:^1.0.4":
161251
version: 1.0.6
162252
resolution: "inflight@npm:1.0.6"

0 commit comments

Comments
 (0)