@crawlee/utils 4.0.0-beta.16 → 4.0.0-beta.160

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +14 -14
  2. package/index.d.ts +7 -15
  3. package/index.js +6 -14
  4. package/internal.d.ts +9 -0
  5. package/internal.js +8 -0
  6. package/internals/blocked.d.ts +0 -1
  7. package/internals/blocked.js +0 -1
  8. package/internals/cheerio.d.ts +1 -3
  9. package/internals/cheerio.js +4 -5
  10. package/internals/extract-microdata.d.ts +24 -0
  11. package/internals/extract-microdata.js +118 -0
  12. package/internals/extract-urls.d.ts +5 -1
  13. package/internals/extract-urls.js +23 -18
  14. package/internals/general.d.ts +0 -25
  15. package/internals/general.js +2 -110
  16. package/internals/iterables.d.ts +9 -70
  17. package/internals/iterables.js +40 -111
  18. package/internals/open_graph_parser.d.ts +2 -3
  19. package/internals/open_graph_parser.js +8 -9
  20. package/internals/robots.d.ts +38 -12
  21. package/internals/robots.js +72 -48
  22. package/internals/schemas.d.ts +114 -0
  23. package/internals/schemas.js +114 -0
  24. package/internals/sitemap.d.ts +69 -8
  25. package/internals/sitemap.js +239 -75
  26. package/internals/social.d.ts +1 -2
  27. package/internals/social.js +7 -5
  28. package/internals/url.d.ts +70 -2
  29. package/internals/url.js +120 -2
  30. package/internals/validation.d.ts +25 -0
  31. package/internals/validation.js +140 -0
  32. package/package.json +9 -8
  33. package/index.d.ts.map +0 -1
  34. package/index.js.map +0 -1
  35. package/internals/blocked.d.ts.map +0 -1
  36. package/internals/blocked.js.map +0 -1
  37. package/internals/cheerio.d.ts.map +0 -1
  38. package/internals/cheerio.js.map +0 -1
  39. package/internals/chunk.d.ts +0 -2
  40. package/internals/chunk.d.ts.map +0 -1
  41. package/internals/chunk.js +0 -40
  42. package/internals/chunk.js.map +0 -1
  43. package/internals/debug.d.ts +0 -32
  44. package/internals/debug.d.ts.map +0 -1
  45. package/internals/debug.js +0 -41
  46. package/internals/debug.js.map +0 -1
  47. package/internals/extract-urls.d.ts.map +0 -1
  48. package/internals/extract-urls.js.map +0 -1
  49. package/internals/general.d.ts.map +0 -1
  50. package/internals/general.js.map +0 -1
  51. package/internals/iterables.d.ts.map +0 -1
  52. package/internals/iterables.js.map +0 -1
  53. package/internals/open_graph_parser.d.ts.map +0 -1
  54. package/internals/open_graph_parser.js.map +0 -1
  55. package/internals/robots.d.ts.map +0 -1
  56. package/internals/robots.js.map +0 -1
  57. package/internals/sitemap.d.ts.map +0 -1
  58. package/internals/sitemap.js.map +0 -1
  59. package/internals/social.d.ts.map +0 -1
  60. package/internals/social.js.map +0 -1
  61. package/internals/system-info/cpu-info.d.ts +0 -64
  62. package/internals/system-info/cpu-info.d.ts.map +0 -1
  63. package/internals/system-info/cpu-info.js +0 -211
  64. package/internals/system-info/cpu-info.js.map +0 -1
  65. package/internals/system-info/memory-info.d.ts +0 -28
  66. package/internals/system-info/memory-info.d.ts.map +0 -1
  67. package/internals/system-info/memory-info.js +0 -118
  68. package/internals/system-info/memory-info.js.map +0 -1
  69. package/internals/system-info/ps-tree.d.ts +0 -18
  70. package/internals/system-info/ps-tree.d.ts.map +0 -1
  71. package/internals/system-info/ps-tree.js +0 -145
  72. package/internals/system-info/ps-tree.js.map +0 -1
  73. package/internals/typedefs.d.ts +0 -5
  74. package/internals/typedefs.d.ts.map +0 -1
  75. package/internals/typedefs.js +0 -9
  76. package/internals/typedefs.js.map +0 -1
  77. package/internals/url.d.ts.map +0 -1
  78. package/internals/url.js.map +0 -1
package/README.md CHANGED
@@ -1,23 +1,23 @@
1
1
  <h1 align="center">
2
2
  <a href="https://crawlee.dev">
3
3
  <picture>
4
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/apify/crawlee/master/website/static/img/crawlee-dark.svg?sanitize=true">
5
- <img alt="Crawlee" src="https://raw.githubusercontent.com/apify/crawlee/master/website/static/img/crawlee-light.svg?sanitize=true" width="500">
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/apify/crawlee/master/website/static/img/crawlee-dark.svg?sanitize=true" />
5
+ <img alt="Crawlee" src="https://raw.githubusercontent.com/apify/crawlee/master/website/static/img/crawlee-light.svg?sanitize=true" width="500" />
6
6
  </picture>
7
7
  </a>
8
- <br>
8
+ <br />
9
9
  <small>A web scraping and browser automation library</small>
10
10
  </h1>
11
11
 
12
- <p align=center>
13
- <a href="https://trendshift.io/repositories/5179" target="_blank"><img src="https://trendshift.io/api/badge/repositories/5179" alt="apify%2Fcrawlee | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
12
+ <p align="center">
13
+ <a href="https://trendshift.io/repositories/5179" target="_blank"><img src="https://trendshift.io/api/badge/repositories/5179" alt="apify%2Fcrawlee | Trendshift" width="250" height="55"/></a>
14
14
  </p>
15
15
 
16
- <p align=center>
17
- <a href="https://www.npmjs.com/package/@crawlee/core" rel="nofollow"><img src="https://img.shields.io/npm/v/@crawlee/core.svg" alt="NPM latest version" data-canonical-src="https://img.shields.io/npm/v/@crawlee/core/next.svg" style="max-width: 100%;"></a>
18
- <a href="https://www.npmjs.com/package/@crawlee/core" rel="nofollow"><img src="https://img.shields.io/npm/dm/@crawlee/core.svg" alt="Downloads" data-canonical-src="https://img.shields.io/npm/dm/@crawlee/core.svg" style="max-width: 100%;"></a>
19
- <a href="https://discord.gg/jyEM2PRvMU" rel="nofollow"><img src="https://img.shields.io/discord/801163717915574323?label=discord" alt="Chat on discord" data-canonical-src="https://img.shields.io/discord/801163717915574323?label=discord" style="max-width: 100%;"></a>
20
- <a href="https://github.com/apify/crawlee/actions/workflows/test-ci.yml"><img src="https://github.com/apify/crawlee/actions/workflows/test-ci.yml/badge.svg?branch=master" alt="Build Status" style="max-width: 100%;"></a>
16
+ <p align="center">
17
+ <a href="https://www.npmjs.com/package/@crawlee/core" rel="nofollow"><img src="https://img.shields.io/npm/v/@crawlee/core.svg" alt="NPM latest version" data-canonical-src="https://img.shields.io/npm/v/@crawlee/core/next.svg" /></a>
18
+ <a href="https://www.npmjs.com/package/@crawlee/core" rel="nofollow"><img src="https://img.shields.io/npm/dm/@crawlee/core.svg" alt="Downloads" data-canonical-src="https://img.shields.io/npm/dm/@crawlee/core.svg" /></a>
19
+ <a href="https://discord.gg/jyEM2PRvMU" rel="nofollow"><img src="https://img.shields.io/discord/801163717915574323?label=discord" alt="Chat on discord" data-canonical-src="https://img.shields.io/discord/801163717915574323?label=discord" /></a>
20
+ <a href="https://github.com/apify/crawlee/actions/workflows/test-ci.yml"><img src="https://github.com/apify/crawlee/actions/workflows/test-ci.yml/badge.svg?branch=master" alt="Build Status" /></a>
21
21
  </p>
22
22
 
23
23
  Crawlee covers your crawling and scraping end-to-end and **helps you build reliable scrapers. Fast.**
@@ -89,7 +89,7 @@ By default, Crawlee stores data to `./storage` in the current working directory.
89
89
  We provide automated beta builds for every merged code change in Crawlee. You can find them in the npm [list of releases](https://www.npmjs.com/package/crawlee?activeTab=versions). If you want to test new features or bug fixes before we release them, feel free to install a beta build like this:
90
90
 
91
91
  ```bash
92
- npm install crawlee@3.12.3-beta.13
92
+ npm install crawlee@next
93
93
  ```
94
94
 
95
95
  If you also use the [Apify SDK](https://github.com/apify/apify-sdk-js), you need to specify dependency overrides in your `package.json` file so that you don't end up with multiple versions of Crawlee installed:
@@ -98,9 +98,9 @@ If you also use the [Apify SDK](https://github.com/apify/apify-sdk-js), you need
98
98
  {
99
99
  "overrides": {
100
100
  "apify": {
101
- "@crawlee/core": "3.12.3-beta.13",
102
- "@crawlee/types": "3.12.3-beta.13",
103
- "@crawlee/utils": "3.12.3-beta.13"
101
+ "@crawlee/core": "$crawlee",
102
+ "@crawlee/types": "$crawlee",
103
+ "@crawlee/utils": "$crawlee"
104
104
  }
105
105
  }
106
106
  }
package/index.d.ts CHANGED
@@ -1,19 +1,11 @@
1
- export * from './internals/blocked.js';
2
- export * from './internals/cheerio.js';
3
- export * from './internals/chunk.js';
4
- export * from './internals/extract-urls.js';
5
- export * from './internals/general.js';
6
- export * from './internals/debug.js';
1
+ export { htmlToText } from './internals/cheerio.js';
2
+ export { downloadListOfUrls, extractUrls } from './internals/extract-urls.js';
3
+ export { EnqueueStrategy } from './internals/url.js';
4
+ export type { DownloadListOfUrlsOptions, ExtractUrlsOptions } from './internals/extract-urls.js';
5
+ export { sleep, expandShadowRoots } from './internals/general.js';
7
6
  export * as social from './internals/social.js';
8
- export * from './internals/typedefs.js';
7
+ export * from './internals/extract-microdata.js';
9
8
  export * from './internals/open_graph_parser.js';
10
9
  export * from './internals/robots.js';
11
10
  export * from './internals/sitemap.js';
12
- export * from './internals/iterables.js';
13
- export * from './internals/robots.js';
14
- export * from './internals/sitemap.js';
15
- export * from './internals/url.js';
16
- export { getCurrentCpuTicksV2, CpuSample } from './internals/system-info/cpu-info.js';
17
- export { getMemoryInfo, MemoryInfo } from './internals/system-info/memory-info.js';
18
- export { Dictionary, Awaitable, Constructor } from '@crawlee/types';
19
- //# sourceMappingURL=index.d.ts.map
11
+ export * from './internals/validation.js';
package/index.js CHANGED
@@ -1,18 +1,10 @@
1
- export * from './internals/blocked.js';
2
- export * from './internals/cheerio.js';
3
- export * from './internals/chunk.js';
4
- export * from './internals/extract-urls.js';
5
- export * from './internals/general.js';
6
- export * from './internals/debug.js';
1
+ export { htmlToText } from './internals/cheerio.js';
2
+ export { downloadListOfUrls, extractUrls } from './internals/extract-urls.js';
3
+ export { EnqueueStrategy } from './internals/url.js';
4
+ export { sleep, expandShadowRoots } from './internals/general.js';
7
5
  export * as social from './internals/social.js';
8
- export * from './internals/typedefs.js';
6
+ export * from './internals/extract-microdata.js';
9
7
  export * from './internals/open_graph_parser.js';
10
8
  export * from './internals/robots.js';
11
9
  export * from './internals/sitemap.js';
12
- export * from './internals/iterables.js';
13
- export * from './internals/robots.js';
14
- export * from './internals/sitemap.js';
15
- export * from './internals/url.js';
16
- export { getCurrentCpuTicksV2 } from './internals/system-info/cpu-info.js';
17
- export { getMemoryInfo } from './internals/system-info/memory-info.js';
18
- //# sourceMappingURL=index.js.map
10
+ export * from './internals/validation.js';
package/internal.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * from './internals/blocked.js';
2
+ export type { CheerioAPI, Cheerio, Element } from './internals/cheerio.js';
3
+ export { extractUrlsFromCheerio } from './internals/cheerio.js';
4
+ export { tryAbsoluteURL } from './internals/extract-urls.js';
5
+ export { URL_NO_COMMAS_REGEX, URL_WITH_COMMAS_REGEX } from './internals/general.js';
6
+ export * from './internals/iterables.js';
7
+ export * from './internals/url.js';
8
+ export * from './internals/validation.js';
9
+ export * as schemas from './internals/schemas.js';
package/internal.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from './internals/blocked.js';
2
+ export { extractUrlsFromCheerio } from './internals/cheerio.js';
3
+ export { tryAbsoluteURL } from './internals/extract-urls.js';
4
+ export { URL_NO_COMMAS_REGEX, URL_WITH_COMMAS_REGEX } from './internals/general.js';
5
+ export * from './internals/iterables.js';
6
+ export * from './internals/url.js';
7
+ export * from './internals/validation.js';
8
+ export * as schemas from './internals/schemas.js';
@@ -7,4 +7,3 @@ export declare const RETRY_CSS_SELECTORS: string[];
7
7
  * Content of proxy errors that should trigger a retry, as the proxy is likely getting blocked / is malfunctioning.
8
8
  */
9
9
  export declare const ROTATE_PROXY_ERRORS: string[];
10
- //# sourceMappingURL=blocked.d.ts.map
@@ -17,4 +17,3 @@ export const ROTATE_PROXY_ERRORS = [
17
17
  'ERR_TUNNEL_CONNECTION_FAILED',
18
18
  'Proxy responded with',
19
19
  ];
20
- //# sourceMappingURL=blocked.js.map
@@ -1,5 +1,4 @@
1
1
  import type { CheerioAPI } from 'cheerio';
2
- export type CheerioRoot = CheerioAPI;
3
2
  export type { CheerioAPI, Cheerio } from 'cheerio';
4
3
  export type { Element } from 'domhandler';
5
4
  /**
@@ -29,7 +28,7 @@ export type { Element } from 'domhandler';
29
28
  * @param htmlOrCheerioElement HTML text or parsed HTML represented using a [cheerio](https://www.npmjs.com/package/cheerio) function.
30
29
  * @return Plain text
31
30
  */
32
- export declare function htmlToText(htmlOrCheerioElement: string | CheerioRoot): string;
31
+ export declare function htmlToText(htmlOrCheerioElement: string | CheerioAPI): Promise<string>;
33
32
  /**
34
33
  * Extracts URLs from a given Cheerio object.
35
34
  *
@@ -40,4 +39,3 @@ export declare function htmlToText(htmlOrCheerioElement: string | CheerioRoot):
40
39
  * @return An array of absolute URLs
41
40
  */
42
41
  export declare function extractUrlsFromCheerio($: CheerioAPI, selector?: string, baseUrl?: string): string[];
43
- //# sourceMappingURL=cheerio.d.ts.map
@@ -1,4 +1,3 @@
1
- import * as cheerio from 'cheerio';
2
1
  import { tryAbsoluteURL } from './extract-urls.js';
3
2
  // NOTE: We are skipping 'noscript' since it's content is evaluated as text, instead of HTML elements. That damages the results.
4
3
  const SKIP_TAGS_REGEX = /^(script|style|canvas|svg|noscript)$/i;
@@ -30,10 +29,11 @@ const BLOCK_TAGS_REGEX = /^(p|h1|h2|h3|h4|h5|h6|ol|ul|li|pre|address|blockquote|
30
29
  * @param htmlOrCheerioElement HTML text or parsed HTML represented using a [cheerio](https://www.npmjs.com/package/cheerio) function.
31
30
  * @return Plain text
32
31
  */
33
- export function htmlToText(htmlOrCheerioElement) {
32
+ export async function htmlToText(htmlOrCheerioElement) {
33
+ const { load } = await import('cheerio');
34
34
  if (!htmlOrCheerioElement)
35
35
  return '';
36
- const $ = typeof htmlOrCheerioElement === 'function' ? htmlOrCheerioElement : cheerio.load(htmlOrCheerioElement);
36
+ const $ = typeof htmlOrCheerioElement === 'function' ? htmlOrCheerioElement : load(htmlOrCheerioElement);
37
37
  let text = '';
38
38
  const process = (elems) => {
39
39
  const len = elems ? elems.length : 0;
@@ -42,7 +42,7 @@ export function htmlToText(htmlOrCheerioElement) {
42
42
  if (elem.type === 'text') {
43
43
  // Compress spaces, unless we're inside <pre> element
44
44
  let compr;
45
- if (elem.parent && elem.parent.tagName === 'pre')
45
+ if (elem.parent?.tagName === 'pre')
46
46
  compr = elem.data;
47
47
  else
48
48
  compr = elem.data.replace(/\s+/g, ' ');
@@ -107,4 +107,3 @@ export function extractUrlsFromCheerio($, selector = 'a', baseUrl = '') {
107
107
  })
108
108
  .filter(Boolean);
109
109
  }
110
- //# sourceMappingURL=cheerio.js.map
@@ -0,0 +1,24 @@
1
+ import type { CheerioAPI } from 'cheerio';
2
+ /** The value of a microdata property: either text or a nested item. */
3
+ export type MicrodataValue = string | MicrodataItem;
4
+ /** A single schema.org item extracted from a document. */
5
+ export interface MicrodataItem {
6
+ /** Tokens of the item's `itemtype` attribute. */
7
+ type?: string[];
8
+ /** The item's `itemid` attribute. */
9
+ id?: string;
10
+ /** Values keyed by `itemprop` name, an array where the property repeats. */
11
+ properties: Record<string, MicrodataValue | MicrodataValue[]>;
12
+ }
13
+ /**
14
+ * Easily parse all schema.org microdata from a page with just a `CheerioAPI` object or raw HTML,
15
+ * following the [microdata processing model](https://html.spec.whatwg.org/multipage/microdata.html#microdata).
16
+ *
17
+ * Text values are trimmed and their inner whitespace collapsed. URL-valued attributes are returned
18
+ * verbatim rather than resolved against the document's base URL.
19
+ *
20
+ * @param htmlOrCheerioElement A `CheerioAPI` object, or a string of raw HTML.
21
+ * @returns The document's top-level items. Nested items are the property values of their parent.
22
+ */
23
+ export declare function extractMicrodata(raw: string): Promise<MicrodataItem[]>;
24
+ export declare function extractMicrodata($: CheerioAPI): Promise<MicrodataItem[]>;
@@ -0,0 +1,118 @@
1
+ import { isTag } from 'domhandler';
2
+ export async function extractMicrodata(htmlOrCheerioElement) {
3
+ // Dynamic so that importing `@crawlee/utils` does not pull in cheerio - see #3836.
4
+ const { load } = await import('cheerio');
5
+ const $ = typeof htmlOrCheerioElement === 'string' ? load(htmlOrCheerioElement) : htmlOrCheerioElement;
6
+ const context = { $ };
7
+ return $('[itemscope]')
8
+ .toArray()
9
+ .filter((element) => !('itemprop' in element.attribs))
10
+ .map((element) => parseItem(context, element, new Set()));
11
+ }
12
+ function parseItem(context, element, ancestors) {
13
+ const item = { properties: {} };
14
+ const type = uniqueTokens(element.attribs.itemtype);
15
+ const id = element.attribs.itemid;
16
+ if (type.length > 0) {
17
+ item.type = type;
18
+ }
19
+ if (id) {
20
+ item.id = id.trim();
21
+ }
22
+ ancestors.add(element);
23
+ for (const propertyElement of collectPropertyElements(context, element)) {
24
+ const value = getPropertyValue(context, propertyElement, ancestors);
25
+ for (const name of uniqueTokens(propertyElement.attribs.itemprop)) {
26
+ addProperty(item.properties, name, value);
27
+ }
28
+ }
29
+ ancestors.delete(element);
30
+ return item;
31
+ }
32
+ function collectPropertyElements(context, scope) {
33
+ const elements = [];
34
+ collectFromNodes(scope.children, elements);
35
+ for (const id of uniqueTokens(scope.attribs.itemref)) {
36
+ const referenced = (context.idIndex ??= indexIds(context.$)).get(id);
37
+ if (referenced) {
38
+ collectFromNodes([referenced], elements);
39
+ }
40
+ }
41
+ return elements;
42
+ }
43
+ function collectFromNodes(nodes, elements) {
44
+ for (const node of nodes) {
45
+ if (!isTag(node)) {
46
+ continue;
47
+ }
48
+ if ('itemprop' in node.attribs) {
49
+ elements.push(node);
50
+ }
51
+ // A nested item owns everything below it, so its subtree is not part of the enclosing item.
52
+ if (!('itemscope' in node.attribs)) {
53
+ collectFromNodes(node.children, elements);
54
+ }
55
+ }
56
+ }
57
+ function indexIds($) {
58
+ const index = new Map();
59
+ for (const element of $('[id]').toArray()) {
60
+ // Duplicate ids are invalid HTML; `getElementById` resolves them to the first element.
61
+ if (!index.has(element.attribs.id)) {
62
+ index.set(element.attribs.id, element);
63
+ }
64
+ }
65
+ return index;
66
+ }
67
+ function getPropertyValue(context, element, ancestors) {
68
+ if ('itemscope' in element.attribs) {
69
+ // `itemref` can point back at an enclosing item, which the spec treats as an error.
70
+ return ancestors.has(element) ? { properties: {} } : parseItem(context, element, ancestors);
71
+ }
72
+ const { attribs } = element;
73
+ switch (element.tagName.toLowerCase()) {
74
+ case 'meta':
75
+ return attribs.content ?? '';
76
+ case 'audio':
77
+ case 'embed':
78
+ case 'iframe':
79
+ case 'img':
80
+ case 'source':
81
+ case 'track':
82
+ case 'video':
83
+ return attribs.src ?? '';
84
+ case 'a':
85
+ case 'area':
86
+ case 'link':
87
+ return attribs.href ?? '';
88
+ case 'object':
89
+ return attribs.data ?? '';
90
+ case 'data':
91
+ case 'meter':
92
+ return attribs.value ?? '';
93
+ case 'time':
94
+ return attribs.datetime ?? context.$(element).text().replace(/\s+/g, ' ').trim();
95
+ default:
96
+ return context.$(element).text().replace(/\s+/g, ' ').trim();
97
+ }
98
+ }
99
+ function addProperty(properties, name, value) {
100
+ const existing = properties[name];
101
+ if (existing === undefined) {
102
+ properties[name] = value;
103
+ }
104
+ else if (Array.isArray(existing)) {
105
+ existing.push(value);
106
+ }
107
+ else {
108
+ properties[name] = [existing, value];
109
+ }
110
+ }
111
+ /** `itemtype`, `itemprop` and `itemref` are all unordered sets of unique space-separated tokens. */
112
+ function uniqueTokens(value) {
113
+ if (!value) {
114
+ return [];
115
+ }
116
+ const tokens = value.split(/\s+/).filter(Boolean);
117
+ return tokens.length > 1 ? [...new Set(tokens)] : tokens;
118
+ }
@@ -1,3 +1,4 @@
1
+ import type { BaseHttpClient } from '@crawlee/http-client';
1
2
  export interface DownloadListOfUrlsOptions {
2
3
  /**
3
4
  * URL to the file
@@ -16,6 +17,10 @@ export interface DownloadListOfUrlsOptions {
16
17
  urlRegExp?: RegExp;
17
18
  /** Allows to use a proxy for the download request. */
18
19
  proxyUrl?: string;
20
+ /**
21
+ * Custom HTTP client to use for downloading the file.
22
+ */
23
+ httpClient?: BaseHttpClient;
19
24
  }
20
25
  /**
21
26
  * Returns a promise that resolves to an array of urls parsed from the resource available at the provided url.
@@ -41,4 +46,3 @@ export declare function extractUrls(options: ExtractUrlsOptions): string[];
41
46
  * Helper function used to validate URLs used when extracting URLs from a page
42
47
  */
43
48
  export declare function tryAbsoluteURL(href: string, baseUrl: string): string | undefined;
44
- //# sourceMappingURL=extract-urls.d.ts.map
@@ -1,38 +1,44 @@
1
- import { gotScraping } from 'got-scraping';
2
- import ow from 'ow';
1
+ import { FetchHttpClient } from '@crawlee/http-client';
2
+ import { z } from 'zod';
3
3
  import { URL_NO_COMMAS_REGEX } from './general.js';
4
+ import { httpClient as httpClientSchema } from './schemas.js';
5
+ import { parseArgument } from './validation.js';
6
+ const downloadListOfUrlsOptionsSchema = z.strictObject({
7
+ url: z.url(),
8
+ encoding: z.string().default('utf8'),
9
+ urlRegExp: z.instanceof(RegExp).default(URL_NO_COMMAS_REGEX),
10
+ proxyUrl: z.string().optional(),
11
+ httpClient: httpClientSchema.default(() => new FetchHttpClient()),
12
+ });
13
+ const extractUrlsOptionsSchema = z.strictObject({
14
+ string: z.string(),
15
+ urlRegExp: z.instanceof(RegExp).default(URL_NO_COMMAS_REGEX),
16
+ });
4
17
  /**
5
18
  * Returns a promise that resolves to an array of urls parsed from the resource available at the provided url.
6
19
  * Optionally, custom regular expression and encoding may be provided.
7
20
  */
8
21
  export async function downloadListOfUrls(options) {
9
- ow(options, ow.object.exactShape({
10
- url: ow.string.url,
11
- encoding: ow.optional.string,
12
- urlRegExp: ow.optional.regExp,
13
- proxyUrl: ow.optional.string,
14
- }));
15
- const { url, encoding = 'utf8', urlRegExp = URL_NO_COMMAS_REGEX, proxyUrl } = options;
22
+ const { url, encoding, urlRegExp, proxyUrl, httpClient } = parseArgument(options, downloadListOfUrlsOptionsSchema);
16
23
  // Try to detect wrong urls and fix them. Currently, detects only sharing url instead of csv download one.
17
- const match = url.match(/^(https:\/\/docs\.google\.com\/spreadsheets\/d\/(?:\w|-)+)\/?/);
24
+ const match = /^(https:\/\/docs\.google\.com\/spreadsheets\/d\/(?:\w|-)+)\/?/.exec(url);
18
25
  let fixedUrl = url;
19
26
  if (match) {
20
27
  fixedUrl = `${match[1]}/gviz/tq?tqx=out:csv`;
21
28
  }
22
- const { body: string } = await gotScraping({ url: fixedUrl, encoding, proxyUrl });
29
+ const response = await httpClient.sendRequest(new Request(fixedUrl, { method: 'GET' }), {
30
+ proxyUrl,
31
+ });
32
+ const string = new TextDecoder(encoding).decode(new Uint8Array(await response.arrayBuffer()));
23
33
  return extractUrls({ string, urlRegExp });
24
34
  }
25
35
  /**
26
36
  * Collects all URLs in an arbitrary string to an array, optionally using a custom regular expression.
27
37
  */
28
38
  export function extractUrls(options) {
29
- ow(options, ow.object.exactShape({
30
- string: ow.string,
31
- urlRegExp: ow.optional.regExp,
32
- }));
33
- const lines = options.string.split('\n');
39
+ const { string, urlRegExp } = parseArgument(options, extractUrlsOptionsSchema);
40
+ const lines = string.split('\n');
34
41
  const result = [];
35
- const urlRegExp = options.urlRegExp ?? URL_NO_COMMAS_REGEX;
36
42
  for (const line of lines) {
37
43
  result.push(...(line.match(urlRegExp) ?? []));
38
44
  }
@@ -49,4 +55,3 @@ export function tryAbsoluteURL(href, baseUrl) {
49
55
  return undefined;
50
56
  }
51
57
  }
52
- //# sourceMappingURL=extract-urls.js.map
@@ -8,25 +8,6 @@ export declare const URL_NO_COMMAS_REGEX: RegExp;
8
8
  * Note, however, that this may prevent parsing URLs from comma delimited lists, or the URLs may become malformed.
9
9
  */
10
10
  export declare const URL_WITH_COMMAS_REGEX: RegExp;
11
- /**
12
- * Returns a `Promise` that resolves to true if the code is running in a Docker container.
13
- */
14
- export declare function isDocker(forceReset?: boolean): Promise<boolean>;
15
- /**
16
- * Detects if crawlee is running in a containerized environment.
17
- */
18
- export declare function isContainerized(): Promise<boolean>;
19
- export declare function isLambda(): boolean;
20
- /**
21
- * gets the cgroup version by checking for a file at /sys/fs/cgroup/memory
22
- * @returns "V1" or "V2" for the version of cgroup or null if cgroup is not found.
23
- */
24
- export declare function getCgroupsVersion(forceReset?: boolean): Promise<"V1" | "V2" | null>;
25
- /**
26
- * Computes a weighted average of an array of numbers, complemented by an array of weights.
27
- * @ignore
28
- */
29
- export declare function weightedAvg(arrValues: number[], arrWeights: number[]): number;
30
11
  /**
31
12
  * Returns a `Promise` that resolves after a specific period of time. This is useful to implement waiting
32
13
  * in your code, e.g. to prevent overloading of target website or to avoid bot detection.
@@ -44,14 +25,8 @@ export declare function weightedAvg(arrValues: number[], arrWeights: number[]):
44
25
  * @param millis Period of time to sleep, in milliseconds. If not a positive number, the returned promise resolves immediately.
45
26
  */
46
27
  export declare function sleep(millis?: number): Promise<void>;
47
- /**
48
- * Converts SNAKE_CASE to camelCase.
49
- * @ignore
50
- */
51
- export declare function snakeCaseToCamelCase(snakeCaseStr: string): string;
52
28
  /**
53
29
  * Traverses DOM and expands shadow-root elements (created by custom components).
54
30
  * @ignore
55
31
  */
56
32
  export declare function expandShadowRoots(document: Document): string;
57
- //# sourceMappingURL=general.d.ts.map
@@ -1,108 +1,14 @@
1
- import fs from 'node:fs/promises';
2
1
  import { setTimeout } from 'node:timers/promises';
3
2
  /**
4
3
  * Default regular expression to match URLs in a string that may be plain text, JSON, CSV or other. It supports common URL characters
5
4
  * and does not support URLs containing commas or spaces. The URLs also may contain Unicode letters (not symbols).
6
5
  */
7
- export const URL_NO_COMMAS_REGEX = /https?:\/\/(www\.)?([\p{L}0-9]|[\p{L}0-9][-\p{L}0-9@:%._+~#=]{0,254}[\p{L}0-9])\.[a-z]{2,63}(:\d{1,5})?(\/[-\p{L}0-9@:%_+.~#?&/=()]*)?/giu;
6
+ export const URL_NO_COMMAS_REGEX = /https?:\/\/(www\.)?([\p{L}0-9]|[\p{L}0-9][-\p{L}0-9@:%._+~#=]{0,254}[\p{L}0-9])\.[a-z]{2,63}(:\d{1,5})?(\/[-\p{L}0-9@:%_+.~#?&/=()'*]*)?/giu;
8
7
  /**
9
8
  * Regular expression that, in addition to the default regular expression `URL_NO_COMMAS_REGEX`, supports matching commas in URL path and query.
10
9
  * Note, however, that this may prevent parsing URLs from comma delimited lists, or the URLs may become malformed.
11
10
  */
12
- export const URL_WITH_COMMAS_REGEX = /https?:\/\/(www\.)?([\p{L}0-9]|[\p{L}0-9][-\p{L}0-9@:%._+~#=]{0,254}[\p{L}0-9])\.[a-z]{2,63}(:\d{1,5})?(\/[-\p{L}0-9@:%_+,.~#?&/=()]*)?/giu;
13
- let isDockerPromiseCache;
14
- async function createIsDockerPromise() {
15
- const promise1 = fs
16
- .stat('/.dockerenv')
17
- .then(() => true)
18
- .catch(() => false);
19
- const promise2 = fs
20
- .readFile('/proc/self/cgroup', 'utf8')
21
- .then((content) => content.includes('docker'))
22
- .catch(() => false);
23
- const [result1, result2] = await Promise.all([promise1, promise2]);
24
- return result1 || result2;
25
- }
26
- /**
27
- * Returns a `Promise` that resolves to true if the code is running in a Docker container.
28
- */
29
- export async function isDocker(forceReset) {
30
- // Parameter forceReset is just internal for unit tests.
31
- if (!isDockerPromiseCache || forceReset)
32
- isDockerPromiseCache = createIsDockerPromise();
33
- return isDockerPromiseCache;
34
- }
35
- let isContainerizedResult;
36
- /**
37
- * Detects if crawlee is running in a containerized environment.
38
- */
39
- export async function isContainerized() {
40
- // Value is very unlikley to change. Cache the result after the first execution.
41
- if (isContainerizedResult !== undefined) {
42
- return isContainerizedResult;
43
- }
44
- // return false if running in aws lambda
45
- if (isLambda()) {
46
- isContainerizedResult = false;
47
- return isContainerizedResult;
48
- }
49
- const dockerenvCheck = fs
50
- .stat('/.dockerenv')
51
- .then(() => true)
52
- .catch(() => false);
53
- const cgroupCheck = fs
54
- .readFile('/proc/self/cgroup', 'utf8')
55
- .then((content) => content.includes('docker'))
56
- .catch(() => false);
57
- const [dockerenvResult, cgroupResult] = await Promise.all([dockerenvCheck, cgroupCheck]);
58
- isContainerizedResult = dockerenvResult || cgroupResult || !!process.env.KUBERNETES_SERVICE_HOST;
59
- return isContainerizedResult;
60
- }
61
- export function isLambda() {
62
- return !!process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE;
63
- }
64
- let _cgroupsVersion;
65
- /**
66
- * gets the cgroup version by checking for a file at /sys/fs/cgroup/memory
67
- * @returns "V1" or "V2" for the version of cgroup or null if cgroup is not found.
68
- */
69
- export async function getCgroupsVersion(forceReset) {
70
- // Parameter forceReset is just internal for unit tests.
71
- if (_cgroupsVersion !== undefined && !forceReset) {
72
- return _cgroupsVersion;
73
- }
74
- try {
75
- // If this directory does not exists, cgroups are not available
76
- await fs.access('/sys/fs/cgroup/');
77
- }
78
- catch (e) {
79
- _cgroupsVersion = null;
80
- return null;
81
- }
82
- _cgroupsVersion = 'V1';
83
- try {
84
- // If this directory does not exists, assume the container is using cgroups V2
85
- await fs.access('/sys/fs/cgroup/memory/');
86
- }
87
- catch (e) {
88
- _cgroupsVersion = 'V2';
89
- }
90
- return _cgroupsVersion;
91
- }
92
- /**
93
- * Computes a weighted average of an array of numbers, complemented by an array of weights.
94
- * @ignore
95
- */
96
- export function weightedAvg(arrValues, arrWeights) {
97
- const result = arrValues
98
- .map((value, i) => {
99
- const weight = arrWeights[i];
100
- const sum = value * weight;
101
- return [sum, weight];
102
- })
103
- .reduce((p, c) => [p[0] + c[0], p[1] + c[1]], [0, 0]);
104
- return result[0] / result[1];
105
- }
11
+ export const URL_WITH_COMMAS_REGEX = /https?:\/\/(www\.)?([\p{L}0-9]|[\p{L}0-9][-\p{L}0-9@:%._+~#=]{0,254}[\p{L}0-9])\.[a-z]{2,63}(:\d{1,5})?(\/[-\p{L}0-9@:%_+,.~#?&/=()'*]*)?/giu;
106
12
  /**
107
13
  * Returns a `Promise` that resolves after a specific period of time. This is useful to implement waiting
108
14
  * in your code, e.g. to prevent overloading of target website or to avoid bot detection.
@@ -122,19 +28,6 @@ export function weightedAvg(arrValues, arrWeights) {
122
28
  export async function sleep(millis) {
123
29
  return setTimeout(millis ?? undefined);
124
30
  }
125
- /**
126
- * Converts SNAKE_CASE to camelCase.
127
- * @ignore
128
- */
129
- export function snakeCaseToCamelCase(snakeCaseStr) {
130
- return snakeCaseStr
131
- .toLowerCase()
132
- .split('_')
133
- .map((part, index) => {
134
- return index > 0 ? part.charAt(0).toUpperCase() + part.slice(1) : part;
135
- })
136
- .join('');
137
- }
138
31
  /**
139
32
  * Traverses DOM and expands shadow-root elements (created by custom components).
140
33
  * @ignore
@@ -164,4 +57,3 @@ export function expandShadowRoots(document) {
164
57
  replaceShadowDomsWithHtml(document.body);
165
58
  return document.documentElement.outerHTML;
166
59
  }
167
- //# sourceMappingURL=general.js.map