7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【自分用メモ】TypeScript × Node.jsでスクレイピング基礎

7
Posted at

今回やること

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はシングルスレッド。

時間のかかる処理は:

  1. バックグラウンドへ渡す
  2. 他の処理を続ける
  3. 終わったら「続きの処理」が再開される

これが非同期。


実行方法

インストールが完了したら、以下で実行:

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 でこのファイルが実行される
7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?