はじめに
前回記事に続き、データ収集に関する投稿です。
- ニュースアクセスランキング編
- 今日のニュース編
- 話題のニュース編 ★本稿
- キーワード検索結果のニュース編
今回は、話題のニュースが対象です。
というわけで、Pythonプログラムで取得(スクレイピング)する処理を実現してみようと思います。
※本稿執筆時点(2021/10/07)の情報に基づき、Code紹介と実行例を示します。
本稿で紹介すること
- 話題のニュース(livedoor NEWS)の取得
尚、筆者は以下のVersionで動作確認をしています。
- Python: 3.6.10
- BeautifulSoup4: 4.9.3
本稿で紹介しないこと
- Pythonライブラリのインストール方法および使い方
- urllib.request
- BeautifulSoup4
- re
- pandas
- csv
- traceback
- os
サンプルコード
Code量も多くないので、全体のCodeを紹介。
ポイントは4つ。
1. 日付の指定
話題のニュースなのですが、Webブラウザから見える範囲だと当日含め直近1週間分です。
が、URLに日付(YYYY-MM-DD)を付与することにより、過去日に遡及して取得可能です。
ただし、それでも限界はあって、ざっくり1年半前までが取得可能という仕組みです。
2. User-Agentの設定
HTTPリクエストヘッダーにUserAgentを設定するのがベター。
自宅PC(個人環境)なので不要ですが、会社PC(企業環境)であればProxyの設定も同じようにヘッダーに設定するのがベター。
3. 名前付きグループの正規表現
後々、ニュースIDをKeyに記事全文を取得することも視野に、ニュースIDも抽出しておくのがベター。
4. タグ要素の指定
各ページSourceを眺めて、タグ構成を鑑みて要素を指定し、BeautifulSoup4で情報を取得する必要あり。
多くの場合、タグに付与されたclass属性を指定し、目的のタグ(およびその内部のText)を取得する処理を実装することになります。
WebブラウザでページのSourceを眺めて、HTMLタグの構造を把握するのは簡単です。
Codeを紹介
以下、全体のCodeです。
from urllib.request import Request, urlopen
from bs4 import BeautifulSoup
import re
import pandas as pd
import csv
import traceback
import datetime
import os
# 保管先フォルダの名称
OUTPUT_DIR = 'livedoor_news'
# 話題のニュース(ライブドアニュース)のURL
URL = 'https://news.livedoor.com/social_reaction/%s/'
# ニュースID抽出用の正規表現(名前付きグループ)
REG_URL = r'(?P<L1>(https?://[^/]+/))(?P<L2>([^/]+))/(?P<L3>([^/]+))/(?P<L4>([^/]+))/'
# ニュースアーカイブの遡及可能な日数
DAYS_AGO_MAX = 570
# UserAgent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36'
def create_folder():
if not os.path.exists(OUTPUT_DIR):
# フォルダが存在しなければ、フォルダを作成
os.makedirs(OUTPUT_DIR)
def get_links(delta=None):
# 基準日を実行時点で初期化
target = datetime.date.today()
#print(target.strftime('%Y-%m-%d'))
if delta in list(range(-DAYS_AGO_MAX, 1)):
# delta日前を求めるtimedeltaを作成
td = datetime.timedelta(days=delta)
# 基準日のdelta日前を計算して取得
target = target + td
#print(target.strftime('%Y-%m-%d'))
try:
print(URL %target.strftime('%Y-%m-%d'))
# HTTPリクエストヘッダーにUser Agentを設定
req = Request(URL %target.strftime('%Y-%m-%d'), data=None, headers={'User-Agent': USER_AGENT})
with urlopen(req) as res:
# HTMLドキュメントからBeautifulSoupを初期化
soup = BeautifulSoup(res.read().decode('euc_jp', 'ignore'), 'html.parser')
# 話題のニュース部分を検索し、全てを取得
soupNewsTopic = soup.find('ol', class_='socialReactionList').find_all('li')
articles = []
for idx, soupNews in enumerate(soupNewsTopic):
# ICON(FacebookとTwitter)を除外
if len(soupNews.contents) == 0 or soupNews.contents[0].name == 'i':
continue
# 詳細ページURLをHTMLタグの属性から抽出
#url = soupNews.a.get('href')
url = soupNews.find('a')['href']
# NewsBodyを検索し取得
soupNewsBody = soupNews.find('div', class_='socialReactionBody')
# NewsBodyから各種データを抽出
article = {
'url': url,
# ニュースIDを詳細ページURLから抽出
'id': re.search(REG_URL, url).groupdict()['L4'],
# タイトル/サマリをHTMLタグの本文から抽出
'title': soupNewsBody.find('h3', class_='socialReactionTtl').text,
'summary': soupNewsBody.find('p', class_='socialReactionDesc').text
}
articles.append(article)
#print('%2d: %s' %(idx + 1, soupNewsBody.find('h3', class_='socialReactionTtl').text))
df = pd.DataFrame(articles)
# DataFrameからCSVファイルを生成
# encoding='sjis'だとJupyterLab(CSVTable)上で表示不可なことに注意
df.to_csv('%s/livedoor_news_topic_%s.csv' %(OUTPUT_DIR, target.strftime('%Y-%m-%d')), encoding='utf-8', index=False, quotechar='"', quoting=csv.QUOTE_ALL)
except Exception as e:
# エラー概要
print('Exception: ', e)
print('=====')
# エラー詳細(スタックトレース)
print(traceback.format_exc().rstrip())
print('=====')
ちなみに、2020年4月の緊急事態宣言が発令された翌日だと、以下のような記事があり、社会情勢を反映していますね。
まとめ
BeautifulSoup4を使って話題のニュースを取得(スクレイピング)する方法を紹介。