Pythonプログラミング：BeautifulSoup4を使ってlivedoor NEWSからニュースアクセスランキングを取得（スクレイピング）してみた

# はじめに

GPU搭載Windowsを購入して、PyTorchのコンテナイメージ（from NGC Catalog）を使えるようして、、、

それは、流行りの**自然言語処理（NLP; Natural Language Processing）**に取り組みたかったからです。
直近の投稿記事の全ては、この記事のためと言っても過言ではない。

目下、以下の記事を参考に試そうとしています。
[text2text-japaneseによる要約のファインチューニング](https://note.com/npaka/n/nfd1762c42f9d)

が、livedoor NEWSを収集するところで最新※の記事も使ってみたいなと思ったり。
というわけで、Pythonプログラムで[ニュースアクセスランキング](http://news.livedoor.com/ranking/)を取得（スクレイピング）する処理を実現してみようと思います。

※本稿執筆時点（2021/08/23）の情報に基づき、Code紹介と実行例を示します。

## 本稿で紹介すること

- ニュースアクセスランキング（livedoor NEWS）の取得


尚、筆者は以下のVersionで動作確認をしています。
  - Python: 3.6.10
  - BeautifulSoup4: 4.9.3

## 本稿で紹介しないこと

- Pythonライブラリのインストール方法および使い方
  - urllib.request
  - BeautifulSoup4
  - re
  - pandas
  - csv
  - traceback

# サンプルコード

Code量も多くないので、全体のCodeを紹介。
ポイントは2つ。

## 1. 名前付きグループの正規表現
参照先の記事では、ニュースIDをKeyに、3行要約と詳細本文を取得し利用している模様。
後の利用を鑑みて、ニュースIDも抽出しておくのがベター。

## 2. タグ要素の指定
各ページSourceを眺めて、タグ構成を鑑みて要素を指定し、BeautifulSoup4で情報を取得する必要あり。
多くの場合、タグに付与されたclass属性を指定し、目的のタグ（およびその内部のText）を取得する処理を実装することになります。
WebブラウザでページのSourceを眺めて、HTMLタグの構造を把握するのは簡単です。

## Codeを紹介
以下、全体のCodeです。

```python
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
import pandas as pd
import csv
import traceback

# ニュースアクセスランキング（ライブドアニュース）のURL
URL = 'http://news.livedoor.com/ranking/'
# ニュースID抽出用の正規表現（名前付きグループ）
REG_URL = r'(?P<L1>(https?://[^/]+/))(?P<L2>([^/]+))/(?P<L3>([^/]+))/(?P<L4>([^/]+))/'

try:
    with urlopen(URL) as res:
        # HTMLドキュメントからBeautifulSoupを初期化
        soup = BeautifulSoup(res.read().decode('euc_jp', 'ignore'), 'html.parser')
        # ニュースアクセスランキング部分を検索し、全て（20件）を取得
        soupNewsRanking = soup.find('ol', class_='articleList withRanking').find_all('li')

        articles = []
        for idx, soupNews in enumerate(soupNewsRanking):
            # 3行要約ページURLをHTMLタグの属性から抽出
            url = soupNews.a.get('href')
            # NewsBodyを検索し取得
            soupNewsBody = soupNews.find('div', class_='articleListBody')
            # NewsBodyから各種データを抽出
            article = {
                'url': url,
                # ニュースIDを3行要約ページURLから抽出
                'id': re.search(REG_URL, url).groupdict()['L4'],
                # タイトル／サマリ／提供元／公開日時をHTMLタグの本文から抽出
                'title': soupNewsBody.find('h3', class_='articleListTtl').text,
                'summary': soupNewsBody.find('p', class_='articleListSummary').text,
                'vender': soupNewsBody.find('p', class_='articleListVender').text,
                'datetime': soupNewsBody.find('time', class_='articleListDate').text
            }
            articles.append(article)
            print('%2d: %s' %(idx + 1, soupNewsBody.find('h3', class_='articleListTtl').text))

        df = pd.DataFrame(articles)
        # DataFrameからCSVファイルを生成
        # encoding='sjis'だとJupyterLab（CSVTable）上で表示不可なことに注意
        df.to_csv('livedoor_news_ranking.csv', encoding='utf-8', index=False, quotechar='"', quoting=csv.QUOTE_ALL)
except Exception as e:
    # エラー概要
    print('Exception: ', e)
    print('=====')
    # エラー詳細（スタックトレース）
    print(traceback.format_exc().rstrip())
    print('=====')
```

特にエラーが発生しなければ、以下のような実行結果を得られます。

```
 1: 天使のタトゥーをSNSに投稿した米女性「卑猥なデザイン」の声にショック
 2: 音喜多議員がモデルナ2回目の副反応を報告 接種から12時間で全身に悪寒
 3: エコバッグ万引きを詰められた客「逮捕」の言葉に動揺「工事があるから」
 4: 有吉弘行が中田翔の巨人移籍に皮肉「宮迫さんも太田プロ来てれば…」
 5: 若者に人気のウレタンマスク コロナ感染拡大の一因になっている可能性
 6: メリットのある人としか付き合わない？宮迫博之への違和感の正体
 7: コロナ発見からわずか1年で実用化「mRNAワクチン」高い効果の理由
 8: タバコ煙の成分に新型コロナの感染抑える効果？広島大学の教授らが研究
 9: コロナウイルス感染リスクの高い行動歴「感染経路不明」患者から判明
10: ハマのドン 菅首相やめるんじゃ
11: 若手の攻撃の的 働かない高給取りはなぜ存在するのか？
12: 「夏休みの宿題を最終日まで残したら」家族と自身の反応を自由研究に
13: 「基本週5日はフリー」M-1王者・トレエンたかしの行動に驚きの声
14: 24時間TVで圧倒的な歌声を披露した加山雄三 King & Princeへの反応と差
15: 長嶋一茂が番組中にパワハラ？ネットでは「張本勲化」懸念する声
16: 木梨憲武 石橋貴明のYouTubeチャンネル企画「24分間テレビ」に登場
17: 家庭ゴミ72kgを海に不法投棄か 技能実習生2人逮捕「日本は分別が細かすぎ」
18: 「最期は家がいい」末期がんで余命1カ月の夫の願い 妻は介護休業を決断
19: 速水もこみちとの結婚で話題となった平山あや 事務所との確執で仕事減少？
20: 関口宏「勉強する」語った直後に「女子としては…」発言が物議
```

以下、保管したCSVファイルを利用する例です。

```python:
# CSVファイルからDataFrameを生成
df = pd.read_csv('livedoor_news_ranking.csv', encoding='utf-8')
print(df.head())
```

DataFrameのサマリを表示しています。

```
            datetime        id                               summary  \
0    2021年8月23日 5時0分  20742362      しかし、見る人によっては「卑猥」なデザインだと勘違いされたという   
1    2021年8月22日 9時3分  20738878  接種から約12時間後、微熱ながら悪寒に襲われ歯がカチカチ鳴り始めたという   
2    2021年8月23日 9時5分  20743205      未精算の商品を詰め込み、店側に犯行を詰められた女性客の映像を公開   
3   2021年8月22日 21時8分  20741812        日ハムでは謹慎となっていた件に触れ、宮迫博之らについて言及も   
4  2021年8月23日 11時15分  20743887   ウレタンマスクの着用者が多いことが一因になっている可能性が高いと専門家   

                                 title  \
0  天使のタトゥーをSNSに投稿した米女性「卑猥なデザイン」の声にショック   
1  音喜多議員がモデルナ2回目の副反応を報告 接種から12時間で全身に悪寒   
2   エコバッグ万引きを詰められた客「逮捕」の言葉に動揺「工事があるから」   
3     有吉弘行が中田翔の巨人移籍に皮肉「宮迫さんも太田プロ来てれば…」   
4    若者に人気のウレタンマスク コロナ感染拡大の一因になっている可能性   

                                                 url       vender  
0  https://news.livedoor.com/topics/detail/20742362/  Techinsight  
1  https://news.livedoor.com/topics/detail/20738878/       BLOGOS  
2  https://news.livedoor.com/topics/detail/20743205/       スポーツ報知  
3  https://news.livedoor.com/topics/detail/20741812/       東スポWeb  
4  https://news.livedoor.com/topics/detail/20743887/       デイリー新潮  
```

以下、ニュースIDを利用する例です。

```python:
# DataFrameからニュースIDのリスト生成
idList = [str(x) for x in df['id'].tolist()]
print(idList)
```

ニュースIDのListを表示しています。

```
['20742362', '20738878', '20743205', '20741812', '20743887', '20742430', '20742604', '20742698', '20739474', '20741948', '20740080', '20742818', '20743581', '20742420', '20742413', '20741688', '20742972', '20742382', '20739621', '20744020']
```

後続処理で、ニュースIDをKeyに3行要約なり詳細本文なりを取得すれば良さそう。

# まとめ
BeautifulSoup4を使って[ニュースアクセスランキング](http://news.livedoor.com/ranking/)を取得（スクレイピング）する方法を紹介。