Pythonプログラミング：SeleniumとBeautifulSoup4を使ってニュース記事を取得（クローリング）してみた

# はじめに

大学時代から株価やニュース記事を取得して溜め込むといったことは、研究室のPCを使って取り組んでいました。
が、最近、職場で**「英語」**ニュース記事を取得して溜め込むといったことにチャレンジする必要がでてきた。

というわけで、Pythonプログラムで「英語」ニュース記事を取得する処理を実現してみようと思います。
今回、ニュースSourceとしては**[Reuters](https://www.reuters.com/)**を対象とします。

## 本稿で紹介すること

- Reutersからのヘッドライン（タイトル、サマリ）の取得
- Reutersからの記事本文の取得

以下のリンクに記載されたコードを見本とし、“NEWS HEADLINES”のリンク先である記事本文を取得するコードを追加しました。

[How to scrape news headlines from Reuters?](https://medium.com/analytics-vidhya/how-to-scrape-news-headlines-from-reuters-27c0274dc13c)
[Business News Headlines](https://www.reuters.com/news/archive/businessnews)

尚、筆者は以下のVersionで動作確認をしています。
- Python: 3.6.8
- Google Chrome: 86.0.4240.75（Official Build）（64 ビット）
- Selenium: 3.141.0
- chromedriver-binary: 86.0.4240.22.0
- BeautifulSoup4: 4.9.1

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

- Pythonライブラリのインストール方法および使い方
  - Selenium
  - requests
  - BeautifulSoup4

Seleniumのインストールについては、以下の記事を参考としました。
[[selenium向け] ChromeDriverをpipでインストールする方法（パス通し不要、バージョン指定可能）](https://qiita.com/hanzawak/items/2ab4d2a333d6be6ac760)

# サンプルコード

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

## 1. 明示的な待機
**アクセス先に負荷を与えないため**にも、待機処理（Sleep）を実装するのはマスト。
と、WebブラウザによってURL（ページ）が読み込まれるまで時間を要することを想定し、待機処理を実装するのがベター。

以下の記事を参考としました。
[【Python】Seleniumの使用方法メモ](https://qiita.com/motoki1990/items/a59a09c5966ce52128be)
[Seleniumで待機処理するお話](https://qiita.com/uguisuheiankyo/items/cec03891a86dfda12c9a)
[Seleniumを安定稼働させるために行うべき３つの設定(Headlessモードにも対応)](https://tanuhack.com/stable-selenium/)

## 2. タグ要素の指定
各ページSourceを眺めて、タグ構成を鑑みて要素を指定し、SeleniumもしくはBeautifulSoup4で情報を取得するのがマスト。
今回は、ヘッドラインをSeleniumで、記事本文をBeautifulSoup4で、各々取得しています。

## Codeを紹介
Seleniumを使って処理をする部分は概ね[参照先コード](https://medium.com/analytics-vidhya/how-to-scrape-news-headlines-from-reuters-27c0274dc13c)に同じです。
各記事本文のリンク（href属性）を取得する処理、そして、記事本文を取得する処理を追加実装したかたちです。

コードを実行すると、**outputdirpath**に指定したフォルダにCSVファイルが出力されます。（CSVファイルはページ単位で）
Errorと文字コードの扱いをまじめに実装していないのが、少し懸念ですが。

```python:crawler_reuters.py
import chromedriver_binary
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import dateutil.parser
import time
import os
import datetime
import csv
import io
import codecs
import requests
from bs4 import BeautifulSoup

'''
# 以下、職場とか社内ネットワーク（プロキシ環境）向け。(2020/11/02 Update)
os.environ["HTTP_PROXY"] = "http://${ProxyサーバのIPアドレス}:${ProxyサーバのPort番号}/"
os.environ["HTTPS_PROXY"] = "http://${ProxyサーバのIPアドレス}:${ProxyサーバのPort番号}/"
'''

def createOutputDirpath():
  workingdirpath = os.getcwd()
  outputdirname = 'article_{0:%Y%m%d}'.format(datetime.datetime.now())
  outputdirpath = "..\\data\\%s" %(outputdirname)
  if not os.path.exists(os.path.join(workingdirpath, outputdirpath)):
    os.mkdir(os.path.join(workingdirpath, outputdirpath))
  return os.path.join(workingdirpath, outputdirpath)

def getArticleBody(url):
  html = requests.get(url)
  #soup = BeautifulSoup(html.content, "html.parser")
  soup = BeautifulSoup(html.content, "lxml")
  wrapper = soup.find("div", class_="ArticleBodyWrapper")
  paragraph = [element.text for element in wrapper.find_all("p", class_="Paragraph-paragraph-2Bgue")]
  #paragraph = []
  #for element in wrapper.find_all("p", class_="Paragraph-paragraph-2Bgue"):
  #  paragraph.append(element.text)
  return paragraph

outputdirpath = createOutputDirpath()
driver = webdriver.Chrome()
driver.implicitly_wait(10)
driver.get('https://www.reuters.com/news/archive/businessnews?view=page&page=5&pageSize=10')

count = 0
for x in range(5):
  try:
    print("=====")
    print(driver.current_url)
    print("-----")
    #f = open(os.path.join(outputdirpath, "reuters_news.csv"), "w", newline = "")
    f = codecs.open(os.path.join(outputdirpath, "reuters_news_%s.csv" %(x)), "w", "UTF-8")
    writer = csv.writer(f, delimiter=',', quoting=csv.QUOTE_ALL, quotechar="\"")
    WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, "control-nav-next")))
    loadMoreButton = driver.find_element_by_class_name("control-nav-next") # or "control-nav-prev"
    # driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")
    #news_headlines = driver.find_elements_by_class_name("story-content")
    news_headlines = driver.find_elements_by_class_name("news-headline-list")[0].find_elements_by_class_name("story-content")
    for headline in news_headlines:
      #print(headline.text)
      #print(headline.get_attribute("innerHTML"))
      href = headline.find_element_by_tag_name("a").get_attribute("href")
      title = headline.find_element_by_class_name("story-title").text
      smry = headline.find_element_by_tag_name("p").text
      stmp = headline.find_element_by_class_name("timestamp").text
      body = getArticleBody(href)
      print(href)
      #print(title)
      #print(smry)
      #print(stmp)
      #print(body)      
      writer.writerow([href, title, smry, stmp, '\r\n'.join(body)])
      time.sleep(1)
    f.close()
    count += 1
    loadMoreButton.click()
    time.sleep(10)
  except Exception as e:
    print(e)
    break
```

やはり便利ですね、Python。
ReutersのURLパラメータ（ページ番号とページあたりの記事数）を変えて職場でも使ってみようかな。

が、Java版のSeleniumの方が使い勝手が良いのだろうか。。。？

# まとめ
SeleniumとBeautifulSoup4を使ってニュース記事（Reuters記事）を取得（クローリング）する方法を紹介。