ブラウザの開発者ツールのコンソールで JavaScript を実行してページ内の情報の一部を一括取得： connpass のページを例に

タイトルの通りで、何らかの Webサイトを開いた状態で、ブラウザの開発者ツールのコンソールに JavaScript のプログラムを書いて実行し、その Webサイトの情報をプログラムの処理で得るようなことをやる話です。

自分が上記の処理をやろうとしたのは、とある閉じたネットワーク内のサイトで情報の一括取得をプログラムでやると良さそうな状況があったためでした。（そのサイトを扱う PCの 環境的な事情で、ブラウザの自動操作系の外部プログラムとか、ブラウザ拡張機能は使えない状況でもあったり...）。
その閉じたネットワーク内でやった話の一部を、将来の自分のメモも兼ねて記事にしておきたかったのですが、そのサイトを扱う話は直接記事にはできないので、イベント主催・参加関連でお世話になっている connpass のサイト（※　以下の URLで見られる、自分自身のダッシュボードのページ）を題材にして、話を進めていくことにしました。

●ダッシュボード - connpass
　https://connpass.com/dashboard/

## 今回やること
ひとまず、 connpass のダッシュボード上に表示されたイベント情報のリストから、特定の情報を抜き出すようなことをやってみようと思います。

### 「あなたのイベント」のイベント名を抽出
今回は、ダッシュボードのトップに表示される「あなたのイベント」のイベント名を取得してみます。
![あなたのイベント](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/3a12c404-9911-b33d-1885-a98701e06e94.png)

#### ページ内の要素の下調べ
そのために、まずはブラウザの開発者ツールを使い、情報取得対象の情報を得ることにします。
（なお、この後は Google Chrome の開発者ツールを使って試した話になります）

開発者ツールで、「あなたのイベント」のイベント一覧が出ている要素を調べてみます。おおよそ、このあたりの divタグや ulタグの部分が該当するようです。
![「あなたのイベント」のリストを包含する要素](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/d06316b6-4c85-75ca-4058-5d8ef450b2d6.png)

さらに子要素を見ていくと、以下の pタグ（「event_title」というクラスのもの）が、イベントタイトルを保持しているもののようです。
![個別のイベントタイトル](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/81b60b29-12c5-f3d0-7847-490d1981e715.png)

そこで、開発者ツールのコンソールの上で JavaScript のプログラムを書き、このあたりの情報を取得してみます。

#### JavaScript のプログラムを書いていく： 「getElementsByClassName」を利用
やり方は一つではないと思いますが、ここでは「your_event_list」のクラスが設定されている ulタグを取得し、その中でさらに「event_title」というクラスが設定されている pタグを取得するやりかたで進めることにします。
その際、いっぺんに結果を得るのではなく、1ステップずつ情報取得を段階的に試す感じでやっていきます。

まずは、「your_event_list」のクラスが設定されている ulタグの取得のお試しから。

```javascript
document.getElementsByClassName("your_event_list");
```

実行結果は以下のとおりで、HTMLCollection が取得できています。
![「your_event_list」のクラスが設定された ulタグを取得](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/24f804fa-bbfe-4d54-428e-6d04486cd93e.png)

ページ内に 1つしかない要素だと思われるものですが、念のため確認してみます（長さを調べ、その後に 1つ目の要素を出力してみます）。
![「your_event_list」を持つ要素について調べる](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/45a29e47-04e5-5fbf-13fa-83b2864a5f84.png)
長さ「1」という結果が出てますので、想定通り、この要素はページ内に 1つのみのようです。

さらに、この下で「event_title」というクラスが設定されている pタグをまとめて取得してみます。
とりあえずは、お試しで要素を取得してみます。以下では、1行でまとめて書いてしまうパターンと、変数に途中段階のものを保持するパターンとを書いていますが、どちらでも OK です。

```javascript
document.getElementsByClassName("your_event_list")[0].getElementsByClassName("event_title");
```

```javascript
const eventList1 = document.getElementsByClassName("your_event_list")[0];
eventList1.getElementsByClassName("event_title");
```

ここで利用している「getElementsByClassName」は、「Elements」という名称のとおり、複数の要素を取得するものです（上で書いたとおり、中身は長さ 1 の単一の要素ではあるものの）。そのため、「[0]」をつけて先頭の 1つの要素を指定した上で、さらにその中で「getElementsByClassName」を使った要素の取得を行っています。
![「event_title」というクラスが設定されている pタグをまとめて取得](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/3ca50d72-6268-262f-0cb1-b3d792da0f95.png)

どうやら、5つの要素が HTMLCollection として取得できているようです。
あとは、その 5つの要素のイベントタイトルを含む部分を取得するだけです。

さて [forEach](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) とか使って、などと進めたいところですが、これはエラーになります。

```javascript
// エラーになるもの
document.getElementsByClassName("your_event_list")[0].getElementsByClassName("event_title").forEach(element => console.log(element));
```

![HTMLCollection で forEach](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/5e94b663-04d6-ac73-6cd9-408832caa8a5.png)
これは、HTMLCollection　に対して、直接 forEach を使うことはできないためです。

その対策は、以下のページ等に書かれているとおり、「Array.from や スプレッド構文で配列に変換」という方法になります。
　●JavaScript NodeList, HTMLCollection は配列(Array)ではない - かもメモ
　　https://chaika.hatenablog.com/entry/2019/09/09/083000

今回は、スプレッド構文を使うことにしてみます。1行にまとめると長くなるので、変数を使いつつ、2行に分かれるような形で書きます。

```javascript
const event_list = document.getElementsByClassName("your_event_list")[0].getElementsByClassName("event_title");
[...event_list].forEach(element => console.log(element));
```

さて、以下のように目的の情報を含む pタグを一括で取得できました。
![pタグをまとめて取得](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/98c1f09f-0f9c-ff3a-7038-ca2e6befcddf.png)

欲しい情報はイベント名なので、pタグの中のイベントタイトル部分を抜き出します。つい先ほど実行したプログラムの、スプレッド構文を使って forEach を実行した行を、以下に変更します。

```javascript
[...event_list].forEach(element => console.log(element.textContent));
```

これを実行すると、無事にイベントタイトルを記載した部分のテキストを取得できると思います。

#### JavaScript のプログラムを書いていく： 「querySelectorAll」を利用
以下は、上記と少しやり方が異なる別パターンの一例で、[querySelectorAll](https://developer.mozilla.org/ja/docs/Web/API/Element/querySelectorAll) を使ったものになります。

「your_event_list」を取得する部分までは同じやり方にして、その後の部分を「querySelectorAll」を使った内容にしてみます。その際、クラスとタグの両方を指定する形にしてみます。

```javascript
const event_list = document.getElementsByClassName("your_event_list")[0].querySelectorAll("p.event_title");
```

実行結果は以下の通りで、この場合は、上で出てきた HTMLCollection ではなく NodeList が取得できます。
![querySelectorAll を使ったパターン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/ddf82091-c49c-6fbd-0ee2-9c9c4de5bb0b.png)
NodeList は HTMLCollection と違って、直接 forEach を扱えます。そのため、以下の書き方で今回得たい最終結果を得られます。

```javascript
document.getElementsByClassName("your_event_list")[0].querySelectorAll("p.event_title").forEach(element => console.log(element.textContent));
```

## おわりに
HTMLCollection と NodeList、挙動が分かってないと少し混乱させられそうなところです...

### 余談
今回、「your_event_list」のクラスが設定された要素を取得し、その下でさらに要素を取得する処理を行う形にしました。これを、 `ocument.querySelectorAll("p.event_title")` といった形でやると、どうなるでしょうか？

以下を実行してみると、「あなたのイベント」の下のイベント名以外のものも取得されてしまいます。

```javascript
document.querySelectorAll("p.event_title").forEach(element => console.log(element.textContent));
```

これは、ページの下のほうにある「おすすめのイベント」のリストでも、「event_title」のクラスが設定された pタグが用いられているためです。

![おすすめのイベントの部分](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/dea56755-d38a-df72-5787-8e4a44859351.png)