@d-zero/puppeteer-scroll 3.1.18-alpha.2 → 4.0.0

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.
package/README.md CHANGED
@@ -21,3 +21,72 @@ await page.goto('https://example.com');
21
21
 
22
22
  await scrollAllOver(page);
23
23
  ```
24
+
25
+ ## API
26
+
27
+ ### `scrollAllOver(page, options?)`
28
+
29
+ ページを上から下までスクロールします。`body.scrollHeight`に到達するか、スクロールが進行しない状態(スクロールジャック等)が3イテレーション続いた時点で終了します。
30
+
31
+ #### オプション
32
+
33
+ | オプション | 型 | デフォルト | 説明 |
34
+ | ---------- | ------------------------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
35
+ | `interval` | `number \| DelayOptions` | `{ random: { min: 200, max: 500 } }` | 各スクロール間のミリ秒。固定値またはランダム範囲(`{ random: ... }`)を指定可能。 |
36
+ | `distance` | `number \| DelayOptions` | 未指定時は`clientHeight × random(0.5, 1.0)` | 1ステップで進むピクセル数。固定値またはランダム範囲を指定可能。未指定時はビューポート高さの50〜100%の範囲でステップごとに変動。 |
37
+ | `logger` | `(scrollY, scrollHeight, message) => void` | なし | スクロール進捗のログ用コールバック。 |
38
+
39
+ `DelayOptions`の型定義は[`@d-zero/shared/delay`](../shared/src/delay.md)を参照。
40
+
41
+ #### デフォルト挙動
42
+
43
+ オプションを渡さない場合、人間のスクロールに近い揺らぎを付けるため、`interval`と`distance`の両方がランダム化されます:
44
+
45
+ - **`interval`**: 200〜500ms の範囲で毎ループ均一分布からサンプリング
46
+ - **`distance`**: ビューポート高さの 50〜100% を毎ループブラウザ側`Math.random()`でサンプリング(`clientHeight`に比例するためモバイル・デスクトップを問わず安全)
47
+
48
+ 決定論的な挙動が必要な場合(テスト、レコーディング、比較など)は、必ず`interval`と`distance`を明示的に指定してください:
49
+
50
+ ```ts
51
+ // 完全固定(ランダム要素なし)。distance はピクセル値を直接指定する
52
+ await scrollAllOver(page, {
53
+ interval: 300,
54
+ distance: 800,
55
+ });
56
+ ```
57
+
58
+ > **メモ**: `distance`に`0`以下の値を渡しても内部で最小`1`px に丸められます(スクロールジャック誤検出の防止)。`distance`はピクセル値であり、ビューポート比率では指定できません。
59
+
60
+ #### 使用例
61
+
62
+ ```ts
63
+ import { scrollAllOver } from '@d-zero/puppeteer-scroll';
64
+
65
+ // すべてデフォルト(ランダム挙動)
66
+ await scrollAllOver(page);
67
+
68
+ // interval だけ固定
69
+ await scrollAllOver(page, { interval: 300 });
70
+
71
+ // interval と distance の両方をランダム化(distance はピクセル値)
72
+ await scrollAllOver(page, {
73
+ interval: { random: { min: 200, max: 800 } },
74
+ distance: { random: { min: 300, max: 900 } },
75
+ });
76
+
77
+ // 進捗ログ
78
+ await scrollAllOver(page, {
79
+ logger: (scrollY, scrollHeight, message) => {
80
+ console.log(`${message}: ${scrollY}/${scrollHeight}`);
81
+ },
82
+ });
83
+ ```
84
+
85
+ ### スクロールジャック検出
86
+
87
+ `scrollBy`が呼ばれても`scrollY`が変化しない状態(fullpage.jsなどスクロールジャック系のライブラリが原因)を検出した場合、3イテレーション連続で進行がない時点で自動的に終了します。終了時には`logger`に`'Scroll stuck, bailing out'`が通知されます。
88
+
89
+ ## 関連パッケージ
90
+
91
+ - [`@d-zero/puppeteer-page-scan`](../puppeteer-page-scan/README.md) — `scrollAllOver`を内包し、`scrollInterval` / `scrollDistance`オプション経由で挙動をカスタマイズ可能
92
+ - [`@d-zero/shared/delay`](../shared/src/delay.md) — `DelayOptions`型と確率分布のサポート
@@ -0,0 +1,11 @@
1
+ import type { DelayOptions } from '@d-zero/shared/delay';
2
+ /**
3
+ * Synchronously resolves a `number | DelayOptions` to a concrete number.
4
+ *
5
+ * Why: `delay()` resolves the same shape but actually waits. For values
6
+ * that need to be passed into a browser `evaluate` call (e.g. scroll step
7
+ * distance), we need the sampled number without any time elapsing.
8
+ * @param value - The value to resolve.
9
+ * @returns A concrete number.
10
+ */
11
+ export declare function resolveValue(value: number | DelayOptions): number;
@@ -0,0 +1,20 @@
1
+ import { sampleDistribution } from '@d-zero/shared/sample-distribution';
2
+ /**
3
+ * Synchronously resolves a `number | DelayOptions` to a concrete number.
4
+ *
5
+ * Why: `delay()` resolves the same shape but actually waits. For values
6
+ * that need to be passed into a browser `evaluate` call (e.g. scroll step
7
+ * distance), we need the sampled number without any time elapsing.
8
+ * @param value - The value to resolve.
9
+ * @returns A concrete number.
10
+ */
11
+ export function resolveValue(value) {
12
+ if (typeof value === 'number') {
13
+ return value;
14
+ }
15
+ const random = value.random;
16
+ if (typeof random === 'number') {
17
+ return sampleDistribution(random);
18
+ }
19
+ return sampleDistribution({ min: random.min, max: random.max }, random.distribution);
20
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Page } from 'puppeteer';
2
2
  import { type DelayOptions } from '@d-zero/shared/delay';
3
3
  export type Options = {
4
- distance?: number;
4
+ distance?: number | DelayOptions;
5
5
  interval?: number | DelayOptions;
6
6
  logger?: (scrollY: number, scrollHeight: number, message: string) => void;
7
7
  };
@@ -14,8 +14,12 @@ export type Options = {
14
14
  * progress.
15
15
  * @param page - The Puppeteer page object.
16
16
  * @param options - Optional parameters for scrolling.
17
- * @param options.distance - The distance to scroll on each iteration (default: 100).
18
- * @param options.interval - The interval between each scroll iteration in milliseconds (default: 300).
17
+ * @param options.distance - The distance to scroll on each iteration in pixels.
18
+ * Accepts a fixed number or a random range via `{ random: ... }`. When omitted,
19
+ * each step uses `clientHeight × random(0.5, 1.0)` so it adapts to the viewport.
20
+ * @param options.interval - The interval between each scroll iteration in
21
+ * milliseconds. Accepts a fixed number or a random range via `{ random: ... }`.
22
+ * When omitted, defaults to a random range of 200–500 ms.
19
23
  * @param options.logger - A function that logs messages.
20
24
  */
21
25
  export declare function scrollAllOver(page: Page, options?: Options): Promise<void>;
@@ -1,13 +1,27 @@
1
1
  import { delay } from '@d-zero/shared/delay';
2
+ import { resolveValue } from './resolve-value.js';
2
3
  /**
3
4
  * Number of consecutive iterations without scroll progress before bailing out.
4
5
  *
5
6
  * Scroll-jacking libraries (e.g. fullpage.js) can block `scrollBy` while
6
7
  * `body.scrollHeight` remains larger than the viewport, causing an infinite
7
- * loop. Three stuck iterations (≈ 900 ms at the default interval) is enough
8
- * to confirm that scrolling is genuinely blocked.
8
+ * loop. Three stuck iterations (≈ 1.05 s at the default interval mean) is
9
+ * enough to confirm that scrolling is genuinely blocked.
9
10
  */
10
11
  const MAX_STUCK_ITERATIONS = 3;
12
+ /**
13
+ * Default interval range (ms) used when `options.interval` is omitted.
14
+ * Randomized to mimic human-like reading pauses while staying close to the
15
+ * historical 300 ms fixed default.
16
+ */
17
+ const DEFAULT_INTERVAL = { random: { min: 200, max: 500 } };
18
+ /**
19
+ * Default scroll-step ratio range applied to `clientHeight` when
20
+ * `options.distance` is omitted. Sampled in the browser context per iteration
21
+ * so it adapts to the current viewport height.
22
+ */
23
+ const DEFAULT_DISTANCE_RATIO_MIN = 0.5;
24
+ const DEFAULT_DISTANCE_RATIO_MAX = 1;
11
25
  /**
12
26
  * Scrolls the page vertically until the end or a maximum height is reached.
13
27
  *
@@ -17,26 +31,37 @@ const MAX_STUCK_ITERATIONS = 3;
17
31
  * progress.
18
32
  * @param page - The Puppeteer page object.
19
33
  * @param options - Optional parameters for scrolling.
20
- * @param options.distance - The distance to scroll on each iteration (default: 100).
21
- * @param options.interval - The interval between each scroll iteration in milliseconds (default: 300).
34
+ * @param options.distance - The distance to scroll on each iteration in pixels.
35
+ * Accepts a fixed number or a random range via `{ random: ... }`. When omitted,
36
+ * each step uses `clientHeight × random(0.5, 1.0)` so it adapts to the viewport.
37
+ * @param options.interval - The interval between each scroll iteration in
38
+ * milliseconds. Accepts a fixed number or a random range via `{ random: ... }`.
39
+ * When omitted, defaults to a random range of 200–500 ms.
22
40
  * @param options.logger - A function that logs messages.
23
41
  */
24
42
  export async function scrollAllOver(page, options) {
25
- const interval = options?.interval ?? 300;
43
+ const interval = options?.interval ?? DEFAULT_INTERVAL;
44
+ const distance = options?.distance;
26
45
  let currentScrollY = 0;
27
46
  let scrollHeight = await page.evaluate(() => document.body.scrollHeight);
28
47
  let prevScrollY = -1;
29
48
  let stuckCount = 0;
30
49
  while (Math.ceil(currentScrollY) < Math.ceil(scrollHeight)) {
31
- [currentScrollY, scrollHeight] = await page.evaluate(() => {
32
- // Move the scroll position to the bottom of the page.
33
- globalThis.scrollBy(0, document.documentElement.clientHeight);
34
- // Return the current scroll position.
50
+ // Force a minimum of 1 px so a user-supplied 0/negative distance
51
+ // cannot stall the loop into the stuck-detection bail out.
52
+ const stepDistance = distance === undefined ? null : Math.max(1, resolveValue(distance));
53
+ [currentScrollY, scrollHeight] = await page.evaluate((step, ratioMin, ratioMax) => {
54
+ // When step is null, sample a random fraction of the viewport
55
+ // height so each scroll feels less mechanical.
56
+ const actualStep = step ??
57
+ Math.max(1, Math.floor(document.documentElement.clientHeight *
58
+ (ratioMin + Math.random() * (ratioMax - ratioMin))));
59
+ globalThis.scrollBy(0, actualStep);
35
60
  return [
36
61
  Math.ceil(globalThis.scrollY + globalThis.innerHeight),
37
62
  Math.ceil(document.body.scrollHeight),
38
63
  ];
39
- });
64
+ }, stepDistance, DEFAULT_DISTANCE_RATIO_MIN, DEFAULT_DISTANCE_RATIO_MAX);
40
65
  options?.logger?.(currentScrollY, scrollHeight, 'Scrolling');
41
66
  if (currentScrollY === prevScrollY) {
42
67
  stuckCount++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d-zero/puppeteer-scroll",
3
- "version": "3.1.18-alpha.2",
3
+ "version": "4.0.0",
4
4
  "description": "Scroll function for puppeteer",
5
5
  "author": "D-ZERO",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "clean": "tsc --build --clean"
24
24
  },
25
25
  "dependencies": {
26
- "@d-zero/shared": "0.21.3-alpha.2"
26
+ "@d-zero/shared": "0.22.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "puppeteer": "24.37.5"
@@ -36,5 +36,5 @@
36
36
  "url": "https://github.com/d-zero-dev/tools.git",
37
37
  "directory": "packages/@d-zero/puppeteer-scroll"
38
38
  },
39
- "gitHead": "74b262438ed5e263d89a066029adfadf1ec27e0b"
39
+ "gitHead": "2d24e08c0cb516b7ea9d07a4301eb991193cca11"
40
40
  }