概要
Node.jsでWebページをMarkdownに変換するには?
Node.js 18以降の組み込みfetchを使います。Authorizationヘッダーにキーを入れてhttps://webreaderapi.com/api/v1/read?url=…を呼び出し、JSONの応答からmarkdownを読み取ります。WebReaderAPIはページの本文をタイトル、著者、日付とともに返します。1ページあたり1クレジット、ブラウザ使用時は2クレジットで、2026年10月時点で$0.07/1Kページから。KYC不要、暗号資産で支払い。
何が必要ですか?
Node.js 18以降(グローバルなfetch用)と、WEBREADER_API_KEY環境変数に設定したAPIキーです。例はESモジュールです:.mjs拡張子で保存するか、package.jsonで"type": "module"を設定して、トップレベルのawaitが動作するようにしてください。
JSDocコメントがレスポンスの形状を記述しているため、VS CodeなどのエディタではTypeScriptのビルド手順なしで補完が得られます。
/**
* @typedef {Object} Page
* @property {string} url
* @property {string} final_url
* @property {number} status_code
* @property {boolean} rendered
* @property {number} credits_used
* @property {string} [title]
* @property {string} [author]
* @property {string} [published_at]
* @property {string} [language]
* @property {string} [markdown]
* @property {number} word_count
*/
/** @returns {Promise<Page>} */
export async function readPage(url, options = {}) {
const params = new URLSearchParams({ url, ...options });
const res = await fetch(`https://webreaderapi.com/api/v1/read?${params}`, {
headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}` },
signal: AbortSignal.timeout(75_000),
});
const body = await res.json();
if (!res.ok) throw Object.assign(new Error(body.error.message), { code: body.error.code });
return body;
}
const page = await readPage('https://example.com/docs/getting-started', { max_chars: '20000' });
console.log(page.title, page.word_count, 'words,', page.credits_used, 'credit(s)');
レート制限に達せずにURLのリストを読み取るには?
共有キューからURLを取得する固定数のワーカーを実行します。リスト全体にPromise.allを使うより簡単です。後者はすべてのリクエストを一度に開始し、429 concurrency_limitに達してしまいます。すべてのアカウントには同時に読み取れるページ数の上限があります。上限はダッシュボードに表示され、サポートで引き上げられます。
以下のプールはエラーを結果の隣に保持するため、1ページが拒否されても実行は止まりません。blocked_by_robotsなどの拒否は確定で無料です。数を数えて次に進んでください。
import { readPage } from './read.mjs';
export async function readAll(urls, workers = 8) {
const queue = [...urls];
const results = [];
async function worker() {
while (queue.length) {
const url = queue.shift();
try {
const page = await readPage(url, { max_chars: '50000' });
results.push({ url, page });
} catch (err) {
results.push({ url, error: err.code ?? err.name });
}
}
}
await Promise.all(Array.from({ length: workers }, worker));
return results;
}
const urls = Array.from({ length: 30 }, (_, i) => `https://example.com/blog/post-${i + 1}`);
for (const r of await readAll(urls)) console.log(r.url, '->', r.error ?? r.page.title);
Node.jsでクロールジョブを追跡するには?
POST https://webreaderapi.com/api/v1/crawlでジョブを開始し、ループでhttps://webreaderapi.com/api/v1/jobs/{job_id}を読み取ります。next_cursorが返ってくるたびにcursorを渡します。カーソルがなくステータスがdoneであれば、ジョブは完了しています。空のポーリングの間には待機し、待機時間を最大30秒まで倍にします。サイトのCrawl-delayを尊重するクロールは遅くなることがあるためです。
バッチとクロールのページでは、クロールがページをどのように選択するか、limitが読み取る内容をどのように制限するかを説明しています。
const headers = {
Authorization: `Bearer ${process.env.WEBREADER_API_KEY}`,
'Content-Type': 'application/json',
};
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const start = await fetch('https://webreaderapi.com/api/v1/crawl', {
method: 'POST',
headers,
body: JSON.stringify({ url: 'https://docs.example.com/', limit: 300, include_paths: ['/docs/*'] }),
});
const { job_id } = await start.json();
const pages = [];
let cursor = '';
let wait = 2000;
for (;;) {
const url = `https://webreaderapi.com/api/v1/jobs/${job_id}${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ''}`;
const job = await (await fetch(url, { headers })).json();
pages.push(...job.results.filter((r) => !r.error));
if (job.next_cursor) {
cursor = job.next_cursor;
wait = 2000;
continue;
}
if (job.status === 'done') break;
await sleep(wait);
wait = Math.min(wait * 2, 30000);
}
console.log(pages.length, 'pages read, job', job_id);
結果をモデルに渡すには?
JSON全体ではなくMarkdownを送信します。モデルが読むべきはmarkdownフィールドで、メタデータは引用のために保持する方が適しています。モデルがソースを名指しできるよう、タイトルとfinal_urlを先頭に付け、max_charsをモデルのコンテキストに収まるサイズ(1ページあたり20,000文字など)に保ってください。
OpenAI function callingのページでは、Pythonでの完全なツールループを示しています。上記の同じreadPage関数をNode.jsエージェントのツール本体として使用できます。
Node.js:よくある質問
Deno、Bun、ブラウザで動作しますか?
DenoとBunは同じfetchとESモジュールをサポートしています。ブラウザのページからAPIを呼び出さないでください。キーがすべての訪問者に見えてしまいます。代わりにサーバーから呼び出してください。
npmパッケージはありますか?
いいえ。このページの関数が統合のすべてです。WebReaderAPIはプレーンなHTTPSとJSONなので、リトライ、タイムアウト、ロギングを自分で制御できます。
429 concurrency_limitを処理するには?
ワーカーの数を減らすか、毎回倍になる短い待機の後にリトライします。429にはコストはかからず、エラーのページにはすぐに使えるバックオフ関数があります。
10,000ページを読むとどれくらいのコストですか?
通常のページなら約10,000クレジット、一部でブラウザが必要な場合はそれ以上です。10,000ページのコストは$3.75です。プリペイドクレジットに有効期限はありません。未使用のページは使うまでアカウントに残ります。