npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@d-zero/beholder

v0.1.28

Published

The tool for scraping and recording web pages

Readme

@d-zero/beholder

Webページのスクレイピングと記録を行うツールです。

名前の由来: Beholder という名前は、多くの目を持つ神話上の生物に由来しています。この生物のように、このツールは周囲のすべてを観察する能力を象徴しており、Webページを正確かつ徹底的にスクレイピングして記録し、詳細を見逃しません。

インストール

npm install @d-zero/beholder

または

yarn add @d-zero/beholder

概要

@d-zero/beholder は、Puppeteerを使用してWebページをスクレイピングし、ページのメタデータ、画像、リンク、およびHTML内容を収集するための強力なツールです。サブプロセスでスクレイピングを実行することで、メモリ効率的な運用が可能です。

エクスポートされるAPI

クラス

Scraper (デフォルトエクスポート)

メインのスクレイパークラスです。Puppeteerを使用してWebページをスクレイピングし、イベントを通じて進捗状況を通知します。

import Scraper from '@d-zero/beholder';
メソッド
scrapeStart(url: ExURL, options?: Partial<ScraperOptions>, isSkip?: boolean): Promise<PageData | void>

指定されたURLのスクレイピングを開始します。

パラメータ:

  • url (ExURL): スクレイピング対象のURL
  • options (Partial<ScraperOptions>, オプション): スクレイピングオプション
    • isExternal (boolean): 外部URLかどうか (デフォルト: false)
    • isGettingImages (boolean): 画像情報を取得するかどうか (デフォルト: true)
    • excludeKeywords (string[]): 除外するキーワードのリスト (デフォルト: [])
    • executablePath (string | null): Chromeの実行可能ファイルパス (デフォルト: null)
    • isTitleOnly (boolean): タイトルのみを取得するかどうか (デフォルト: false)
    • screenshot (string | null): スクリーンショットの保存パス (デフォルト: null)
    • disableQueries (boolean, オプション): クエリパラメータを無効にするかどうか
  • isSkip (boolean, オプション): スキップするかどうか (デフォルト: false)

戻り値:

  • Promise<PageData | void>: スクレイピング結果のページデータ、またはスキップされた場合は void

使用例:

const scraper = new Scraper();

// イベントリスナーを登録
scraper.on('scrapeEnd', async (event) => {
	console.log('スクレイピング完了:', event.result);
});

scraper.on('error', async (event) => {
	console.error('エラー発生:', event.error);
});

// スクレイピングを開始
const url = parseUrl('https://example.com');
await scraper.scrapeStart(url, {
	isGettingImages: true,
	excludeKeywords: ['広告', 'スポンサー'],
});

// 完了後にクリーンアップ
await scraper.destroy(false);
destroy(isExternal: boolean): Promise<void>

スクレイパーインスタンスを破棄し、ブラウザを閉じます。

パラメータ:

  • isExternal (boolean): 外部URLのスクレイピングかどうか

戻り値:

  • Promise<void>

使用例:

await scraper.destroy(false);
イベント

Scraper クラスは TypedAwaitEventEmitter を継承しており、以下のイベントを発行します:

  • ignoreAndSkip: ページがキーワードマッチングによりスキップされた場合
  • resourceResponse: リソースが取得された場合
  • scrapeEnd: スクレイピングが完了した場合
  • destroyed: スクレイパーが破棄された場合
  • error: エラーが発生した場合
  • changePhase: スクレイピングフェーズが変更された場合

イベントの使用例:

scraper.on('changePhase', async (event) => {
	console.log(`フェーズ: ${event.name} - ${event.message}`);
});

scraper.on('resourceResponse', async (event) => {
	console.log(`リソース取得: ${event.resource.url.href}`);
});

SubProcessRunner

サブプロセスでスクレイパーを実行するためのクラスです。メモリ効率的なスクレイピングを実現します。

import { SubProcessRunner } from '@d-zero/beholder';
コンストラクタ
new SubProcessRunner(resetTime: number)

パラメータ:

  • resetTime (number): サブプロセスをリセットするまでのスクレイピング回数
プロパティ
state: 'waiting' | 'running' (読み取り専用)

現在のサブプロセスの状態を取得します。

メソッド
start(url: ExURL, options: ScraperOptions, isSkip: boolean, interval: number): void

サブプロセスでスクレイピングを開始します。

パラメータ:

  • url (ExURL): スクレイピング対象のURL
  • options (ScraperOptions): スクレイピングオプション
  • isSkip (boolean): スキップするかどうか
  • interval (number): スクレイピング間隔(ミリ秒)

例外:

  • サブプロセスが既に実行中の場合はエラーをスローします

使用例:

const runner = new SubProcessRunner(10); // 10回ごとにリセット

runner.on('scrapeEvent', async (event) => {
	if (event.type === '@@scraper/scrapeEnd') {
		console.log('スクレイピング完了:', event.payload.result);
	}
});

runner.start(
	url,
	{
		isExternal: false,
		isGettingImages: true,
		excludeKeywords: [],
		executablePath: null,
		isTitleOnly: false,
		screenshot: null,
	},
	false,
	1000,
);
destory(): void

サブプロセスを破棄します。

注意: メソッド名は destory (typo) ですが、パッケージの互換性のため維持されています。

使用例:

runner.destory();
kill(): void

サブプロセスを強制終了します(SIGKILL)。

使用例:

runner.kill();
getUndeadPid(): number[]

終了できなかった(ゾンビ)プロセスのPIDリストを取得します。

戻り値:

  • number[]: ゾンビプロセスのPIDリスト

使用例:

const zombiePids = runner.getUndeadPid();
console.log('ゾンビプロセス:', zombiePids);
イベント
  • reset: サブプロセスがリセットされた場合
  • scrapeEvent: スクレイピングイベントが発生した場合
  • changePhase: フェーズが変更された場合
  • error: エラーが発生した場合

型定義

ExURL

拡張されたURL情報を含む型です。

type ExURL = {
	href: string; // 完全なURL
	_originUrlString: string; // パース前の元のURL文字列
	withoutHash: string; // ハッシュなしのURL
	withoutHashAndAuth: string; // ハッシュと認証情報なしのURL
	protocol: string; // プロトコル(例: "https:")
	isHTTP: boolean; // HTTPまたはHTTPSかどうか
	isSecure: boolean; // HTTPSかどうか
	username: string | null; // 認証ユーザー名
	password: string | null; // 認証パスワード
	hostname: string; // ホスト名
	port: string | null; // ポート番号
	pathname: string | null; // パス
	paths: string[]; // パスの配列
	depth: number; // パスの深さ
	dirname: string | null; // ディレクトリ名
	basename: string | null; // ベース名(拡張子なしのファイル名)
	isIndex: boolean; // インデックスページかどうか
	extname: string | null; // ファイル拡張子
	query: string | null; // クエリ文字列
	hash: string | null; // ハッシュ
};

PageData

スクレイピング結果のページデータです。

type PageData = {
	url: ExURL; // ページのURL
	redirectPaths: string[]; // リダイレクトパス
	isTarget: boolean; // ターゲットページかどうか
	isExternal: boolean; // 外部ページかどうか
	status: number; // HTTPステータスコード
	statusText: string; // HTTPステータステキスト
	contentType: string | null; // コンテンツタイプ
	contentLength: number | null; // コンテンツ長
	responseHeaders: Record<string, string | string[] | undefined> | null; // レスポンスヘッダー
	meta: Meta; // メタ情報
	anchorList: AnchorData[]; // アンカーリスト
	imageList: ImageElement[]; // 画像リスト
	html: string; // HTML内容
	isSkipped: false; // スキップされたかどうか
};

Meta

ページのメタデータです。

type Meta = {
	lang?: string; // 言語
	title: string; // タイトル
	description?: string; // 説明
	keywords?: string; // キーワード
	noindex?: boolean; // noindexタグの有無
	nofollow?: boolean; // nofollowタグの有無
	noarchive?: boolean; // noarchiveタグの有無
	canonical?: string; // 正規URL
	alternate?: string; // 代替URL
	'og:type'?: string; // Open Graph: type
	'og:title'?: string; // Open Graph: title
	'og:site_name'?: string; // Open Graph: site_name
	'og:description'?: string; // Open Graph: description
	'og:url'?: string; // Open Graph: url
	'og:image'?: string; // Open Graph: image
	'twitter:card'?: string; // Twitter Card: card
};

AnchorData

アンカー要素のデータです。

type AnchorData = {
	href: ExURL; // href属性の値
	textContent: string; // アクセシブルな名前
};

ImageElement

画像要素のデータです。

type ImageElement = {
	src: string; // src属性
	currentSrc: string; // 現在のsrc
	alt: string; // alt属性
	width: number; // 表示幅
	height: number; // 表示高さ
	naturalWidth: number; // 実際の幅
	naturalHeight: number; // 実際の高さ
	isLazy: boolean; // 遅延読み込みかどうか
	viewportWidth: number; // ビューポート幅
	sourceCode: string; // ソースコード
};

NetworkLog

ネットワークログの情報です。

type NetworkLog = {
	url: ExURL; // リクエストURL
	status: number | null; // ステータスコード
	contentLength: number; // コンテンツ長
	contentType: string; // コンテンツタイプ
	isError: boolean; // エラーかどうか
	request: {
		ts: number; // タイムスタンプ
		headers: Record<string, string>; // リクエストヘッダー
		method: string; // HTTPメソッド
	};
	response?: {
		ts: number; // タイムスタンプ
		status: number; // ステータスコード
		statusText: string; // ステータステキスト
		fromCache: boolean; // キャッシュから取得したかどうか
		headers: Record<string, string>; // レスポンスヘッダー
	};
};

Resource

リソースの情報です。

type Resource = {
	url: ExURL; // リソースURL
	isExternal: boolean; // 外部リソースかどうか
	isError: boolean; // エラーかどうか
	status: number | null; // ステータスコード
	statusText: string | null; // ステータステキスト
	contentType: string | null; // コンテンツタイプ
	contentLength: number | null; // コンテンツ長
	compress: false | CompressType; // 圧縮タイプ
	cdn: false | CDNType; // CDNタイプ
	headers: Record<string, string | string[] | undefined> | null; // ヘッダー
};

SkippedPageData

スキップされたページのデータです。

type SkippedPageData = {
	isSkipped: true; // スキップされたかどうか
	url: ExURL; // URL
	matched: {
		type: 'keyword' | 'path'; // マッチタイプ
		text?: string; // マッチしたテキスト(keywordの場合)
		excludeKeywords?: string[]; // 除外キーワード(keywordの場合)
		excludes?: string[]; // 除外パス(pathの場合)
	};
};

イベント型

ScrapeEventTypes
type ScrapeEventTypes = {
	ignoreAndSkip: {
		pid: number | undefined;
		url: ExURL;
		reason: {
			matchedText: string;
			excludeKeywords: string[];
		};
	};
	resourceResponse: {
		pid: number | undefined;
		url: ExURL;
		log: NetworkLog;
		resource: Omit<Resource, 'uid'>;
	};
	scrapeEnd: {
		pid: number | undefined;
		url: ExURL;
		timestamp: number;
		result: PageData;
	};
	destroyed: {
		pid: number | undefined;
	};
	error: {
		pid: number | undefined;
		url: ExURL;
		shutdown: boolean;
		error: {
			name: string;
			message: string;
			stack?: string;
		};
	};
	changePhase: ChangePhaseEvent;
};
SubProcessRunnerEventTypes
type SubProcessRunnerEventTypes = {
	reset: {
		pid: number | undefined;
	};
	scrapeEvent: Action<AnyScrapeEvent>;
	changePhase: SubProcessChangeEvent;
	error: {
		pid: number | undefined;
		url: ExURL;
		shutdown: boolean;
		error: {
			name: string;
			message: string;
			stack?: string;
		};
	};
};

イベントアクションクリエーター

scraperEvent

スクレイパーイベントを作成するためのアクションクリエーターです。

import { scraperEvent } from '@d-zero/beholder';

// 利用可能なイベント:
// - scraperEvent.ignoreAndSkip
// - scraperEvent.resourceResponse
// - scraperEvent.scrapeEnd
// - scraperEvent.destroyed
// - scraperEvent.error
// - scraperEvent.changePhase

subProcessEvent

サブプロセスイベントを作成するためのアクションクリエーターです。

import { subProcessEvent } from '@d-zero/beholder';

// 利用可能なイベント:
// - subProcessEvent.start
// - subProcessEvent.destroy

完全な使用例

基本的なスクレイピング

import Scraper from '@d-zero/beholder';
import { parseUrl } from '@d-zero/shared/parse-url';

const scraper = new Scraper();

// イベントハンドラーを設定
scraper.on('changePhase', async (event) => {
	console.log(`[${event.pid}] ${event.name}: ${event.message}`);
});

scraper.on('scrapeEnd', async (event) => {
	const { result } = event;
	console.log('タイトル:', result.meta.title);
	console.log('ステータス:', result.status);
	console.log('アンカー数:', result.anchorList.length);
	console.log('画像数:', result.imageList.length);
});

scraper.on('error', async (event) => {
	console.error('エラー:', event.error.message);
});

// スクレイピングを実行
const url = parseUrl('https://example.com');
const result = await scraper.scrapeStart(url, {
	isGettingImages: true,
	excludeKeywords: ['広告'],
	isExternal: false,
});

// クリーンアップ
await scraper.destroy(false);

サブプロセスでのスクレイピング

import { SubProcessRunner } from '@d-zero/beholder';
import { parseUrl } from '@d-zero/shared/parse-url';

const runner = new SubProcessRunner(5); // 5回ごとにリセット

// イベントハンドラーを設定
runner.on('scrapeEvent', async (action) => {
	if (action.type === '@@scraper/scrapeEnd') {
		console.log('完了:', action.payload.result.url.href);
	}
	if (action.type === '@@scraper/error') {
		console.error('エラー:', action.payload.error.message);
	}
});

runner.on('changePhase', async (event) => {
	console.log(`[${event.pid}] ${event.name}`);
});

// URLリストをスクレイピング
const urls = [
	'https://example.com',
	'https://example.com/about',
	'https://example.com/contact',
];

for (const urlString of urls) {
	const url = parseUrl(urlString);

	// サブプロセスが待機状態になるまで待つ
	while (runner.state === 'running') {
		await new Promise((resolve) => setTimeout(resolve, 100));
	}

	runner.start(
		url,
		{
			isExternal: false,
			isGettingImages: false,
			excludeKeywords: [],
			executablePath: null,
			isTitleOnly: true,
			screenshot: null,
		},
		false,
		1000,
	);
}

// 完了後にクリーンアップ
runner.destory();

ライセンス

MIT

作者

D-ZERO