【python】簡単クローリング＆スクレイピングvol.2

 [前回(vol.1)](https://qiita.com/diagonal-m/items/6c7560ffc7cae6768003)よりも少しだけ複雑なものを書いてみたいと思います。前回と同じく[日経電子版](https://www.nikkei.com)を対象に行います。



## 目標

前回は指定したカテゴリーページのはじめの1ページ分の全記事タイトルを返すだけのものでした。

今回は**カテゴリー名**と**ページ数**を指定し、指定したカテゴリーの指定したページ数分の記事ジャンルがそれぞれ何件あるのかをキーが記事ジャンル、バリューが記事数の辞書型を返す関数をつくっていきます。

![クローリング_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481315/bbf73f62-ed5d-6c72-f54d-d52446e897f9.png)



上記の範囲だと、AI: 1, フィンテック: 1, コラム(テクノロジー): 1, IoT: 1, モバイル・5G: 1, 日経経済新聞: 1, 科学&新技術: 1となります 。



## コード

```python
from typing import Dict
import requests
from bs4 import BeautifulSoup
from time import sleep


def count_genre_nikkei(category: str, page: int) -> Dict[str, int]:
    """
    指定カテゴリーの指定ページ数のジャンルとその記事数を辞書型で返す関数

    :param category:記事カテゴリー
    :param page: ページ数
    :return: キー：記事カテゴリー、バリュー：記事数の辞書型
    """
    news = dict()
    base_url = 'https://www.nikkei.com/'
    base_url_2 = '/archive/'
    base_url_3 = '?bn='
    for i in range(page):
        sleep(1)
        try:
            response = requests.get('{}{}{}{}{}1'.format(base_url, category, base_url_2,
                                                         base_url_3, i))
            response.raise_for_status()

            response.encoding = response.apparent_encoding
            soup = BeautifulSoup(response.text, 'html.parser')

            # スクレイピング
            genre_list = soup.find_all('div', class_='m-miM09_keyword', reversed=False)

            for genre_1 in genre_list:
                genre_2 = genre_1.find_all('a')
                for genre_3 in genre_2:
                    genre = genre_3.text

                    if genre in news:
                        news[genre] += 1
                    else:
                        news[genre] = 1

        except requests.exceptions.HTTPError:
            print("{}カテゴリーページへのアクセス失敗".format(category))

        except Exception as e:
            print(e)

    return news
```

```python

d = count_genre_nikkei("technology", 2)
for key, value in d.items():
    print('{}: {}'.format(key, value))
```

実行結果

```
コラム（テクノロジー）: 3
科学&新技術: 11
IoT: 2
モバイル・5G: 4
BP速報: 6
AI: 2
シェアエコノミー: 1
フィンテック: 1
```



## コード解説

- `try~except`構文：発生した例外に応じたエラー処理ができる。`try`節の中でエラーが発生すると、そのエラーに対応した`except`節の処理へと移行する。
- `requests.get('url')`：指定したURLに対してHTTPリクエストを送信し、サーバから返却されるHTTPレスポンスを戻り値として返す。
- `response.raise_for_status()`：ステータスコードが200番台以外だった場合エラーを起こす

| HTTPステータスコード | 意味               |
| :------------------- | :----------------- |
| 100番台              | 処理中             |
| 200番台              | 成功               |
| 300番台              | リダイレクト       |
| 400番台              | クライアントエラー |
| 500番台              | サーバーエラー     |

- `response.encoding = response.apparent_encoding`：極力文字化けが起こらないようにコンテンツを取得できる
- `BeautifulSoup(response.text, "html.parser")`：第一引数にHTML、第二引数にHTMLパーサーを指定できる。最初から使える `html.parser` を指定している。HTMLの字句解析をして、タグなどを判断してデータ構造として取得するプログラムをHTMLパーサーという。
- `soup.find_all()`：タグの子孫要素を調べてフィルタにマッチする全ての子孫要素をリスト型で返す

- `except requests.exceptions.HTTPError:`：ステータスコードが200番台以外だった場合のエラー時の処理
- `except Exception as e:`：例外の内容を知る

```python
if genre in news:
	news[genre] += 1
else:
    news[genre] = 1
```

genreがすでに辞書newsに存在する場合そのバリューに1を足し、ない場合はそのgenreをキーに1をバリューとして追加