今回やること
TypeScriptとNode.jsを用いてスクレイピングを試す。
- TypeScriptを書く
- Nodeで実行
- WebサイトからHTMLを取得
- 必要なデータだけ抽出する
用語の整理
■ TypeScriptとは?
JavaScriptに「型」を追加した言語。
- string や number を明示できる
- 型エラーを事前に防げる
- 大規模開発向き
TypeScriptはそのままでは動かない。
TypeScriptコード
↓
tsc(コンパイラ)
↓
JavaScriptコードに変換
↓
Nodeで実行
この変換ルールを決めるのが tsconfig.json です。
ts-node は内部で「tscでコンパイル → nodeで実行」を自動で行っています。
そのため今回は ts-node を使うことで、
tsc を手で実行しなくても「変換 → 実行」をまとめて行えます。
■ Node.jsとは?
JavaScriptをブラウザ外で動かす実行環境。
Nodeを使うと、
- Webサーバーを作れる
- CLIツールを作れる
- バッチ処理を書ける
今回はその中のCLIアプリ(ターミナル実行型) を作ります。
■ スクレイピングとは?
WebサイトからHTMLを取得し、必要なデータだけ抽出すること。
流れ:
HTTPリクエスト
↓
HTML取得
↓
DOM解析
↓
要素抽出
↓
データ整形
■ DOMとは?
DOM = Document Object Model
簡単に言うと:
HTMLを「木構造(ツリー構造)」として表したもの。
例:HTML
<html>
<body>
<h1>Hello</h1>
<a href="https://example.com">Link</a>
</body>
</html>
これをDOMにすると:
html
└── body
├── h1
│ └── "Hello"
└── a (href="https://example.com")
└── "Link"
HTMLはただの文字列。
でも一度構造化することで要素を検索可能になる。
環境構築
プロジェクト作成
cd ~/Projects
mkdir ts-scraper
cd ts-scraper
npmが package.json を使って、
プロジェクト単位で依存関係を管理します。
npm初期化
npm init -y
package.jsonが生成される。
package.jsonの役割
- プロジェクト情報
- 依存関係管理
- 実行コマンド管理
RailsでいうGemfileのような存在。
TypeScript導入
npm install typescript ts-node @types/node --save-dev
| パッケージ | 役割 |
|---|---|
| typescript | TSをJSに変換 |
| ts-node | TSを直接実行 |
| @types/node | Nodeの型定義 |
cheerio(チアリオ)導入
npm install cheerio
cheerioはHTMLをjQueryのように扱えるライブラリ。
ブラウザなしでDOM操作できる。
tsconfig作成
npx tsc --init
tsconfigとはTypeScriptのルールブック。
- どのJSバージョンに変換するか
- import/exportの扱い
- 型チェックの厳しさ
最小設定:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "node",
"strict": true,
"skipLibCheck": true
}
}
非同期(Promise)理解
Promiseとは?
「未来に値が返ってくる箱」
const promise = fetch("https://example.com")
まだ結果は入っていない。
状態は3つ:
- pending
- fulfilled
- rejected
await(アウェイト)とは?
Promiseが完了するまで待つ。
const res = await fetch(...)
awaitがないと、結果を扱えない。
async(アシンク)とは?
awaitを使えるようにする宣言(awaitはasync関数の中でしか使えない)
非同期の内部挙動
JavaScriptはシングルスレッド。
時間のかかる処理は:
- バックグラウンドへ渡す
- 他の処理を続ける
- 終わったら「続きの処理」が再開される
これが非同期。
実行方法
インストールが完了したら、以下で実行:
npm run dev
これは:
NODE_TLS_REJECT_UNAUTHORIZED=0
をつけて
ts-node index.ts
を実行しています。
これは開発時のみの一時的な対応です。
NODE_TLS_REJECT_UNAUTHORIZED=0 を指定すると、
HTTPS証明書の検証を無効化します。
⚠ 本番環境では絶対に使用しないでください。
つまり、
- TypeScriptを直接実行
- HTML取得
- DOM解析
- 結果出力
までを一気に行います。
index.ts
import * as cheerio from "cheerio"
type LinkItem = {
text: string
href: string
}
async function main() {
const res = await fetch("https://example.com")
const html = await res.text()
console.log("===== HTML(先頭300文字) =====")
console.log(html.slice(0, 300))
const $ = cheerio.load(html)
const links: LinkItem[] = []
$("a").each((_i, el) => {
const text = $(el).text().trim()
const href = ($(el).attr("href") ?? "").trim()
if (text && href) {
links.push({ text, href })
}
})
console.log("===== 抽出結果 =====")
console.table(links)
}
main()
実行すると何が起きる?(HTML取得)
あなたのPC
↓
Node
↓
fetch
↓
example.com
↓
HTML取得
html変数にHTML文字列が入る。
DOM解析(スクレイピング)
解析の流れ:
HTML文字列
↓
cheerio.load(仮想DOM作成)
↓
$("a")(aタグ取得)
↓
each(ループ)
↓
配列
↓
console.table(表で表示)
今回はCLIアプリなので、
$ npm run dev
の実行結果はターミナルに表示されます。
取得しているサイトは問題ない?
example.comは公式にサンプル用途として提供されているドメイン。
実際にスクレイピングする際は:
- 利用規約を確認
- robots.txt確認
- 過剰アクセスしない
- 個人情報を取得しない
これが重要。
もっと“目に見えて”わかる方法は?
今はCLI表示なので結果表示は地味です。
分かりやすくする方法として以下のようなものがあります:
① console.table(最も簡単)
console.table(links)
配列が表形式で表示される。
CLIでも視覚的に分かりやすくなるため、
最も手軽な改善方法。
② JSONファイルに保存する
import { writeFileSync } from "fs"
writeFileSync("result.json", JSON.stringify(links, null, 2))
実行すると、プロジェクト直下に
result.json
が生成される。
APIとして使える
データ確認が楽になる
デバッグに便利
③ CSV出力する
import { writeFileSync } from "fs"
const csv = links
.map(l => `${l.text},${l.href}`)
.join("\n")
writeFileSync("result.csv", csv)
生成された
result.csv
はExcelで開ける。
データ分析や一覧確認に向いている。
④ 簡易Web画面に表示(Express)
まずインストール:
npm install express
動く最小例
import express from "express"
import * as cheerio from "cheerio"
const app = express()
app.get("/", async (_, res) => {
const response = await fetch("https://example.com")
const html = await response.text()
const $ = cheerio.load(html)
const links: { text: string; href: string }[] = []
$("a").each((_i, el) => {
const text = $(el).text().trim()
const href = ($(el).attr("href") ?? "").trim()
if (text && href) links.push({ text, href })
})
res.json(links)
})
app.listen(3000, () => {
console.log("http://localhost:3000")
})
実行:
npm run dev
ブラウザで
http://localhost:3000
にアクセスするとJSONが表示される。
⑤ Next.jsで表示(概念)
より実践的な方法。
1.APIルートでスクレイピングを書く
2.フロントエンドでfetchして表示する
構成例:
pages/api/scrape.ts
pages/index.tsx
これにより、
- UI付き表示
- フィルタリング
- 検索機能
なども実装可能。
今回の内容の汎用性
今回作ったのは「スクレイピング処理の最小構成(骨組み)」です。
変わるのは:
- URL
- CSSセレクタ
構造は同じ。
まとめ
- TypeScript:型付きJavaScript
- Node:JS実行環境
- Promise:未来の値
- await:待つ
- cheerio:DOM解析
- スクレイピング:HTMLからデータ抽出
今回はその最小構成を作りました。
おまけ(私用のコード分解メモ)
# package.json
{
// プロジェクト基本情報ブロック =========================
"name": "ts-scraper", // プロジェクト名(npm管理用の識別子)
// npmに公開する場合の名前
// フォルダ名とは必ずしも一致しなくてよい
"version": "1.0.0", // バージョン番号
// セマンティックバージョニング(major.minor.patch)
"description": "", // プロジェクト説明文
// npm公開時に表示される概要
"main": "index.js", // エントリーポイント
// node . と実行したときに読み込まれるファイル
// 今回はts-node実行のため実質未使用
"keywords": [], // npm検索用キーワード
"author": "", // 作成者情報
"license": "ISC", // ライセンス種別
// ISCはMITに近いシンプルなオープンライセンス
// 実行コマンド定義ブロック ================================
"scripts": {
"dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 ts-node index.ts",
// 開発用実行コマンド
// npm run dev で実行される
// NODE_TLS_REJECT_UNAUTHORIZED=0 → HTTPS証明書検証を無効化(開発用)
// ts-node → TypeScriptをコンパイルせず直接実行
"start": "ts-node index.ts"
// 通常実行コマンド
// npm start で実行される
// TLS検証無効化なしでTypeScriptを直接実行
},
// 開発時に必要なパッケージブロック ========================
"devDependencies": {
"@types/node": "^25.3.2",
// Node.jsの型定義ファイル
// fsやprocessなどNode標準APIをTypeScriptで扱うために必要
"ts-node": "^10.9.2",
// TypeScriptをコンパイルせずに直接実行できるツール
// tsc → node の工程をまとめて実行してくれる
"typescript": "^5.9.3"
// TypeScriptコンパイラ本体
// .tsファイルをJavaScriptへ変換する役割
},
// 実行時に必要なパッケージブロック ========================
"dependencies": {
"cheerio": "^1.2.0"
// HTMLをDOM構造として扱うためのライブラリ
// jQuery風のCSSセレクタで要素抽出を可能にする
// 今回のスクレイピング処理の中核ライブラリ
}
}
# tsconfig
{
// TypeScriptコンパイラ全体設定ブロック ====================
"compilerOptions": {
// 出力ターゲット設定 ------------------------------------
"target": "ES2020",
// TypeScriptをどのJavaScript仕様に変換するか
// ES2020 → async/awaitなどモダン構文が使える
// Node22環境なら十分対応可能
// モジュール方式設定 ------------------------------------
"module": "CommonJS",
// import/export をどの形式に変換するか
// CommonJSはNode標準のモジュール形式(requireベース)
// モジュール解決方法 ------------------------------------
"moduleResolution": "node",
// import文で読み込むパッケージを
// Node.jsと同じルールで探す設定
// ESModule互換設定 --------------------------------------
"esModuleInterop": true,
// CommonJSとESModuleの互換を許可する
// これがないと
// import axios from "axios"
// がエラーになることがある
// 型チェック厳格モード ----------------------------------
"strict": true,
// TypeScriptの型チェックを厳しくする
// nullやundefinedの扱いも厳格化
// 実務ではほぼ必須レベル
// ライブラリ型チェック省略 -------------------------------
"skipLibCheck": true
// node_modules内の型チェックをスキップ
// コンパイル速度向上
// 実務でもよく使われる
}
}
# index.ts
// ライブラリ読み込みブロック ===============================
import * as cheerio from "cheerio"
// cheerioを読み込む
// HTML文字列をDOMとして扱えるようにするためのライブラリ
// 型定義ブロック ===========================================
type LinkItem = {
text: string
href: string
}
// 抽出するデータの型を定義
// links配列の1件分の形を固定する
// TypeScriptによる型安全を確保
// メイン処理ブロック =======================================
async function main() {
// HTML取得ブロック ---------------------------------------
const res = await fetch("https://example.com")
// WebサイトへHTTPリクエストを送る
// fetchはPromiseを返すためawaitで待つ
// resはResponseオブジェクト
const html = await res.text()
// レスポンスから本文(HTML文字列)を取り出す
// htmlはただの文字列
// HTML確認ブロック ---------------------------------------
console.log("===== HTML(先頭300文字) =====")
console.log(html.slice(0, 300))
// HTML全文は長いため先頭300文字だけ表示
// 実際に取得できているかを確認するためのデバッグ出力
// DOM解析ブロック ----------------------------------------
const $ = cheerio.load(html)
// HTML文字列をDOM構造に変換
// $はjQueryのようにDOMを操作できる関数
// 配列初期化ブロック --------------------------------------
const links: LinkItem[] = []
// LinkItem型の配列を用意
// 型が固定される
// 要素抽出ブロック ----------------------------------------
$("a").each((_i, el) => {
// aタグをすべて取得しループ処理
const text = $(el).text().trim()
// aタグ内の表示テキスト取得
// trimで前後空白を除去
const href = ($(el).attr("href") ?? "").trim()
// href属性を取得
// ?? "" でundefined対策
// trimで整形
if (text && href) {
// textとhrefが両方存在する場合のみ
links.push({ text, href })
// 配列に追加
}
})
// 出力ブロック --------------------------------------------
console.log("===== 抽出結果 =====")
console.table(links)
// console.tableで表形式表示
// CLIでも視覚的に分かりやすくなる
}
// 実行ブロック =============================================
main()
// main関数を呼び出してプログラムを実行
// npm run dev でこのファイルが実行される