Webスクレイピングでニュースサイトから文章データを抽出する

こんにちは。
株式会社クラスアクト インフラストラクチャ事業部の大塚です。

今回はWebスクレイピングを使ってBloomberg様のニュースサイトから文章のデータを自動で拝借させて頂きたいと思います。

Bloomberg様のサイトは以下となります。

https://www.bloomberg.co.jp/

# 今回用意したコード

https://colab.research.google.com/drive/106xXTqcktk7pQBoa4qVPH0a9UqdopPKC?authuser=4#scrollTo=Gz9hKFm2eQkj


# 用語
## Webスクレイピング
ChatGPTに聞いてみました。

>Webスクレイピングは、ウェブサイトやウェブページからデータを自動的に抽出するプロセスを指します。このプロセスでは、プログラムやスクリプトを使用して、特定のウェブページから情報を収集し、その情報を解析、保存、または他の用途に活用します

![DL-ページ5.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/4bfc919d-c7cc-8481-d419-78051184e4b9.png)


## BeautifulSoup4
PythonのWebスクレイピング用のライブラリ。名前お洒落ですよね～
公式サイト？は以下です。

https://www.crummy.com/software/BeautifulSoup/bs4/doc/

# robots.txt
ChatGPTに聞いてみました。

>robots.txtは、ウェブサイトの所有者がウェブクローラーやボットに対して、どのページをクロールして情報を収集してもよいかどうかを指示するための標準的なプロトコルです。robots.txtファイルは、ウェブサイトのルートディレクトリに配置され、クローラーがそのウェブサイトを訪れた際に読み取られます。このファイルには、クローラーがアクセスしてもよいページやディレクトリ、アクセスを制限するべきページなどに関する指示が含まれています。

Bloomberg様のrobots.txtは以下になります。

https://www.bloomberg.co.jp/robots.txt

例えば以下のrobots.txtがあった場合、"Googlebot"というクローラーに対して/private/ディレクトリ以下のページに対するアクセスはダメ。ということを示しているようです。Allowという表記は特段無いようで、Disallowに記載が無ければクローリングしても良いという事みたいです。
※最も、robots.txtだけではなくサービス条項も併せてみておくべきかと思います。他者に対する攻撃目的であったり、そもそも非公開の情報に対してクローリングは厳禁です。
```
User-agent: Googlebot
Disallow: /private/
```

# コード
## 処理対象ニュース記事のタイトルとURLをリストにする
### コード
```
import datetime
import requests
from bs4 import BeautifulSoup
import pandas as pd

# コード実行の年月日時を取得
t_delta = datetime.timedelta(hours=9)
JST = datetime.timezone(t_delta, 'JST')
now = datetime.datetime.now(JST)
d = now.strftime('%Y%m%d%H')

# Webスクレイピングに必要なリスト作成 
elements_title = [] 
elements_url = [] 

# Webスクレイピング処理 
def get_Bloomberg_news(): 
    url = "https://www.bloomberg.co.jp/" 
    response = requests.get(url) 
    html = response.text 
    soup = BeautifulSoup(html,"html.parser") 

    #titleについての処理 
    title_list = soup.select(".story-list-story__info__headline-link") 
    for title in title_list: 
        elements_title.append(title.text) 
        
    #urlについての処理 
    url_list = soup.select(".story-list-story__info__headline-link") 
    for i in url_list: 
        urls = i.get("href") 
        if "http" not in urls: 
            urls = "https://www.bloomberg.co.jp/" + urls 
            elements_url.append(urls) 
        else: 
            elements_url.append(urls) 

# Webスクレイピング処理呼び出し 
get_Bloomberg_news()

# pandas処理 
df = pd.DataFrame({"news_title":elements_title, 
                   "news_url":elements_url}) 
title_url_list = "Bloomberg" + str(d) + ".csv"
df.to_csv(title_url_list, index=False, encoding="utf-8")
```
このコードでは以下スクショの赤枠の部分あたりの記事タイトルとそのURLを抽出しています。
![test04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/fdee304b-1d7a-def1-94c8-c8c1b4fdba05.png)



### 出力結果_sample
出力ファイル名：Bloomberg2023100719.csv
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/bc462224-f148-f7f2-9740-01f8dfb7ec54.png)

## 作成したリストに対して文章データのみを取り出す処理
```
import requests
from bs4 import BeautifulSoup
import pandas as pd
import csv

# コード実行の年月日時を取得
t_delta = datetime.timedelta(hours=9)
JST = datetime.timezone(t_delta, 'JST')
now = datetime.datetime.now(JST)
d = now.strftime('%Y%m%d%H')
title_url_list = "Bloomberg" + str(d) + ".csv"

# csvデータのURL列をPythonのリスト型に変形する
# 変換したい列のインデックス
target_column_index = 1 

# リストを初期化
target_column_data = []

# CSVファイルを読み込む
with open(title_url_list, newline='') as title_url_list:
    csvreader = csv.reader(title_url_list)
    
    # 各行を読み込み、指定した列のデータをリストに追加
    for row in csvreader:
        if len(row) > target_column_index:
            target_column_data.append(row[target_column_index])

# 列のタイトルを削除しリストを表示
target_column_data = target_column_data[1:]

# Webスクレイピング処理 
for url in target_column_data:
    response = requests.get(url) 
    html = response.text 
    soup = BeautifulSoup(html,"html.parser") 

    contents_select = soup.select_one(".data-story-id") 
    contents_text = soup.select(".body-copy")
    soup = BeautifulSoup(html, 'html.parser')
    div_element = soup.find('div', class_='body-copy')
    paragraphs = [p.get_text() for p in div_element.find_all('p') if not p.text.startswith("原題：") and not "関連記事" in p.get_text()]

    # テキストファイルに書き込む
    with open('output_raw.txt', 'a', encoding='utf-8') as file:
      for paragraph in paragraphs:
        file.write(paragraph + '\n')

print("テキストデータがoutput.txtに書き込まれました。")
```
### 出力結果_sample
長くなるので最初の最後を数行ずつ取り出します。
```
全米自動車労組（ＵＡＷ）は、フォード・モーター、ゼネラル・モーターズ（ＧＭ）、ステランティスでのストライキ拡大を見送る。労働協約を巡る協議が進展した。
　　ＵＡＷのショーン・フェイン委員長は６日、ＵＡＷはテキサス州アーリントンのＧＭ工場へのスト拡大を準備していたが、新労働協約に新たなバッテリー合弁事業の従業員を含めることで同社と土壇場で合意したと述べた。

中略

ウェブブラウザーやスマートフォンのデフォルト検索エンジンとなるため、グーグルがアップルなどに数十億ドルを支払ってきたと米司法省は主張。連邦および州の反トラスト法執行当局によれば、こうした取引によって、マイクロソフトの「Bing」やダックダックゴーのような他の検索エンジンがユーザーを増やしグーグルと競い合うのを防いでいたとしている。
```
生成したcsvの"news_url"列のURLに対して1つずつアクセスし、HTMLファイルを取得し該当箇所だけを抽出しています。
普通にWebスクレイピングをしていると"原題："とか"関連記事"とかの文章データが邪魔そうに見えたので、コード内で除去するようにしています。