ChatGPTと一緒にWebサイト制作① 調査編

## 生成AIをプロジェクトメンバーとして捉える

これからの時代は、ChatGPTなど生成AIと一緒に物事の取り組み、AIが得意な分野はAIに任せるという仕事のやり方が重要になると考えています。

ここでは、架空のプロジェクトを想定し、実際にAIと会話しながら進行をしてみようと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426622/9a808392-0025-9104-1eb6-2968e0d4b2e4.png)


## ChatGPTとのコミュニケーションと途中成果物

:::note info
冗長な部分や重要でない会話は省略して、一部を抜粋しています。
:::

### 私の指示

あなた（ChatGPT）と一緒にWebサイト制作を調査分析から構築まで進めていきたいと思います。

現在、下記の要件があります。

```markdown
案件概要: Webサイト制作会社のリニューアル
URL: https://www.add-more.co.jp/

まずは、上記のサイトの情報をまとめたいと考えています。

アプローチは下記のように考えています。

## Step1: wgetで対象サイトのクローリング

要件:

* wgetはサーバー負荷を与えないように1ページ巡回ごとに1秒間隔をあける
* 画像やCSSなどのアセットファイルは不要

## Step2: 情報解析プログラム開発

要件:

* wgetで取得したローカルのファイルを解析するNode.jsのプログラムを開発します
* URLに `feed` や `wp-json` 、クエリパラメータが含まれている場合は対象外とします
* ターミナル上から実行すると、ファイルを解析し、下記の情報をまとめます
  * ページ名称
  * path
  * title
  * meta description
  * コンテンツの概要
* `ページ名称` はtitle要素から取得しますが、`|` の区切り文字で区切った先頭の部分だけを採用する
* `title` はtitle要素のテキストノードそのままで良いです
* 解析した結果は再利用しやすいようにJSON形式で保持します

## Step3: Spreadsheetへ情報を転写

Step2で作成したJSONをSpreadsheetにコンテンツ一覧として情報をまとめたいと思います。
Spreadsheetに転写しやすいようCSV形式に変換するプログラムを作成してください。
```

まずは、Step1のwgetコマンドを教えて下さい。

### ChatGPTの回答

```bash
wget --wait=1 --limit-rate=200K --no-parent --adjust-extension --convert-links --restrict-file-names=windows --no-clobber --recursive --level=1 --exclude-directories=wp-content,feed,wp-json --accept html --domains add-more.co.jp --directory-prefix=add-more-site https://www.add-more.co.jp/
```

このコマンドの各オプションの説明は以下の通りです：

* `--wait=1`: 各リクエストの間に1秒間隔をあけます。
* `--limit-rate=200K`: ダウンロード速度を制限します（オプション）。
* `--no-parent`: 親ディレクトリをクロールしません。
* `--adjust-extension`: 保存するファイルに適切な拡張子を付けます。
* `--convert-links`: ローカルのHTMLファイルのリンクをローカルファイルに変換します。
* `--restrict-file-names=windows`: ファイル名をWindows互換にします。
* `--no-clobber`: 既存のファイルを上書きしません。
* `--recursive`: 再帰的にダウンロードします。
* `--level=1`: クロールの深さを1に制限します。
* `--exclude-directories=wp-content,feed,wp-json`: 特定のディレクトリを除外します。
* `--accept html`: HTMLファイルのみを受け入れます。
* `--domains add-more.co.jp`: 指定ドメインのみをクロールします。
* `--directory-prefix=add-more-site`: ダウンロード先ディレクトリを指定します。

このコマンドを実行することで、指定した要件に基づいて対象サイトをクロールできます。


### 私の指示

巡回完了しました。
次にStep2の情報解析プログラムの作成をお願いします。

### ChatGPTの回答

Step2の情報解析プログラムをNode.jsで開発します。このプログラムは、wgetで取得したローカルのファイルを解析し、指定された情報を抽出してJSON形式で保存します。

#### 必要な準備

Node.jsとnpmをインストールします。
必要なパッケージをインストールします。以下のコマンドを実行してください：

```bash
npm install cheerio fs path
```

#### プログラムの作成

以下に解析プログラムのコードを示します。`analyze.js`という名前で保存してください。

```javascript
const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');

const targetDir = './add-more-site'; // wgetでダウンロードしたディレクトリ
const outputFilePath = './output.json';

function readFiles(dir, files) {
  fs.readdirSync(dir).forEach(file => {
    const fullPath = path.join(dir, file);
    if (fs.statSync(fullPath).isDirectory()) {
      readFiles(fullPath, files);
    } else if (file.endsWith('.html')) {
      files.push(fullPath);
    }
  });
}

function extractInfo(filePath) {
  const content = fs.readFileSync(filePath, 'utf-8');
  const $ = cheerio.load(content);

  const title = $('title').text();
  const metaDescription = $('meta[name="description"]').attr('content') || '';
  const pageName = title.split('|')[0].trim();
  const summary = $('body').text().replace(/\s+/g, ' ').substring(0, 200); // コンテンツの概要（最初の200文字）

  return {
    pageName,
    path: filePath.replace(targetDir, ''),
    title,
    metaDescription,
    summary
  };
}

function analyze() {
  const files = [];
  readFiles(targetDir, files);

  const result = files
    .filter(file => !file.includes('feed') && !file.includes('wp-json') && !file.includes('?'))
    .map(file => extractInfo(file));

  fs.writeFileSync(outputFilePath, JSON.stringify(result, null, 2));
  console.log(`解析結果が${outputFilePath}に保存されました`);
}

analyze();
```

#### プログラムの実行

ターミナルから以下のコマンドを実行して、プログラムを実行します：

```bash
node analyze.js
```

これにより、`add-more-site`ディレクトリ内のHTMLファイルを解析し、指定された情報を抽出して`output.json`に保存します。
次に、Step3でこのJSONをCSV形式に変換するプログラムを作成します。

### 私の指示

無事に実行しJSONファイル生成を確認できました。

Step2の追加要件として下記の修正をお願いできますか？

* `path`の先頭に毎回`add-more-site/www.add-more.co.jp`が入ってしまうので入らないようにしたい
* `summary`は`main`という`id`属性を持つ要素の中身を抽出するようにしたい
* `summary`は`img`要素が含まれる場合は`alt`のテキストを使用してほしい
* `summary`に`noscript`要素の中身や`script`要素の中身が入らないようにしたい
* `summary`に文字制限をつけないでほしい

### ChatGPTの回答

Step2のプログラムを追加要件に従って修正します。

以下に修正したプログラムを示します。

```javascript
const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');

const targetDir = './add-more-site'; // wgetでダウンロードしたディレクトリ
const outputFilePath = './output.json';

function readFiles(dir, files) {
  fs.readdirSync(dir).forEach(file => {
    const fullPath = path.join(dir, file);
    if (fs.statSync(fullPath).isDirectory()) {
      readFiles(fullPath, files);
    } else if (file.endsWith('.html')) {
      files.push(fullPath);
    }
  });
}

function extractSummary($) {
  const mainContent = $('#main');
  if (!mainContent.length) return '';

  mainContent.find('script, noscript').remove();

  mainContent.find('img').each((_, img) => {
    const altText = $(img).attr('alt') || '';
    $(img).replaceWith(altText);
  });

  return mainContent.text().replace(/\s+/g, ' ').trim();
}

function extractInfo(filePath) {
  const content = fs.readFileSync(filePath, 'utf-8');
  const $ = cheerio.load(content);

  const title = $('title').text();
  const metaDescription = $('meta[name="description"]').attr('content') || '';
  const pageName = title.split('|')[0].trim();
  const summary = extractSummary($);

  return {
    pageName,
    path: filePath.replace(targetDir + '/www.add-more.co.jp', ''),
    title,
    metaDescription,
    summary
  };
}

function analyze() {
  const files = [];
  readFiles(targetDir, files);

  const result = files
    .filter(file => !file.includes('feed') && !file.includes('wp-json') && !file.includes('?'))
    .map(file => extractInfo(file));

  fs.writeFileSync(outputFilePath, JSON.stringify(result, null, 2));
  console.log(`解析結果が${outputFilePath}に保存されました`);
}

analyze();
```

※説明は省略

### 私の指示

`path`の置換がうまく動作しなかったので、下記のように変更しました。

```javascript
const targetDir = 'add-more-site';
```

また、Step2の追加要件が発生しました。
上記の修正の取り込みと、追加要件の対応をお願いします。

* `path`フィールドに`index.html`のようなデフォルトドキュメントの指定が入らないように省略して記載する

### ChatGPTの回答

```javascript
const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');

const targetDir = 'add-more-site'; // wgetでダウンロードしたディレクトリ
const outputFilePath = './output.json';

function readFiles(dir, files) {
  fs.readdirSync(dir).forEach(file => {
    const fullPath = path.join(dir, file);
    if (fs.statSync(fullPath).isDirectory()) {
      readFiles(fullPath, files);
    } else if (file.endsWith('.html')) {
      files.push(fullPath);
    }
  });
}

function extractSummary($) {
  const mainContent = $('#main');
  if (!mainContent.length) return '';

  mainContent.find('script, noscript').remove();

  mainContent.find('img').each((_, img) => {
    const altText = $(img).attr('alt') || '';
    $(img).replaceWith(altText);
  });

  return mainContent.text().replace(/\s+/g, ' ').trim();
}

function extractInfo(filePath) {
  const content = fs.readFileSync(filePath, 'utf-8');
  const $ = cheerio.load(content);

  const title = $('title').text();
  const metaDescription = $('meta[name="description"]').attr('content') || '';
  const pageName = title.split('|')[0].trim();
  const summary = extractSummary($);

  // pathフィールドの修正
  let relativePath = filePath.replace(targetDir + '/www.add-more.co.jp', '');
  if (relativePath.endsWith('/index.html')) {
    relativePath = relativePath.replace('/index.html', '/');
  }

  return {
    pageName,
    path: relativePath,
    title,
    metaDescription,
    summary
  };
}

function analyze() {
  const files = [];
  readFiles(targetDir, files);

  const result = files
    .filter(file => !file.includes('feed') && !file.includes('wp-json') && !file.includes('?'))
    .map(file => extractInfo(file));

  fs.writeFileSync(outputFilePath, JSON.stringify(result, null, 2));
  console.log(`解析結果が${outputFilePath}に保存されました`);
}

analyze();
```

※ 説明は省略

### 私の指示

想定通りのJSONファイルとなりました。
では、Step3に入ります。

`output.json`をもとにSpreadsheetへ読み込ませるCSVを生成するプログラムを作成してください。

### ChatGPTの回答

Step3のプログラムを作成します。`output.json`をCSV形式に変換するプログラムを以下に示します。`convertToCSV.js`という名前で保存してください。

#### プログラムの作成

まず、必要なパッケージをインストールします。

```bash
npm install fs
```

次に、`convertToCSV.js`の内容を以下のようにします。

```javascript
const fs = require('fs');
const path = require('path');

const inputFilePath = './output.json';
const outputFilePath = './output.csv';

// JSONからCSVに変換する関数
function jsonToCSV(json) {
  const items = JSON.parse(json);
  const header = ['pageName', 'path', 'title', 'metaDescription', 'summary'];

  const csv = [
    header.join(','), // ヘッダー行
    ...items.map(item => [
      `"${item.pageName}"`,
      `"${item.path}"`,
      `"${item.title}"`,
      `"${item.metaDescription}"`,
      `"${item.summary}"`
    ].join(','))
  ].join('\n');

  return csv;
}

// JSONファイルを読み込み、CSVに変換して保存する
function convertToCSV() {
  const json = fs.readFileSync(inputFilePath, 'utf-8');
  const csv = jsonToCSV(json);
  fs.writeFileSync(outputFilePath, csv);
  console.log(`CSVファイルが${outputFilePath}に保存されました`);
}

convertToCSV();
```

#### プログラムの実行

ターミナルから以下のコマンドを実行して、プログラムを実行します：

```bash
node convertToCSV.js
```

これにより、`output.json`から`output.csv`が生成されます。このCSVファイルをSpreadsheetに読み込むことができます。

※詳細の説明は省略

### 私の指示

ありがとうございます。
想定通りに変換できていることを確認できました。

しかし、今のタイミングでサイト全体が巡回出来ていないことに気付きました。
wgetの`level`と`restrict-file-names`オプションの指定を取り除いて、再実行しました。

こちらで想定しているStep3までの作業は完了しました。

### ChatGPTの回答

お役に立てて何よりです。作業が無事に完了してよかったです。

引き続き何かサポートが必要な場合や、別のプロジェクトで手助けが必要な場合は、いつでもご連絡ください。お疲れさまでした！

## 今回得た最終成果物とまとめ

下記は、インポートしたCSVを`path`でソートしたものです。

![ss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426622/c3c2d7d9-c589-b436-91a2-3acfcf3fe8c4.png)

人の手作業でこのような資料を作成すると精度に懸念が残るのと、多くの労力もかかるため、今回のように簡易なプログラムを作成するのがおすすめです。

今回のように具体的な道筋をたてて、場合によっては動作しないコードを微修正する知識があれば、ChatGPTに依頼せずともおそらく自力でコードを書くことが出来るはずです。

しかし、このように会話しながら、状況に応じて追加要件を伝えて修正していくやり方は、自分ひとりで進めた場合より遥かに効率が上がります。

エンジニアの働き方はこれからこのような進め方がスタンダードとなるのではないでしょうか。

## ChatGPTと一緒にWebサイト制作シリーズ

* [ChatGPTと一緒にWebサイト制作① 調査編](https://qiita.com/koji-yamauchi/items/1615a7479d482b899fee)
* [ChatGPTと一緒にWebサイト制作② ヒアリング編](https://qiita.com/koji-yamauchi/items/e01bb5b59917545a1dea)
* [ChatGPTと一緒にWebサイト制作③ UML編](https://qiita.com/koji-yamauchi/items/774d0d7a445ae8562052)
* [ChatGPTと一緒にWebサイト制作④ サイトマップ作成編](https://qiita.com/koji-yamauchi/items/4dd67868dc5b0bf903c3)