morikomorou’s blog

自分が学んだことなどの備忘録的なやつ

【python】Webスクレイピングを体験してみよう!BeautifulSoup4+Requests入門


Googleニュースから自分の好きなキーワードに関するニュースの情報を最新の1日分取得する方法について解説します。

はじめに

Webスクレイピングに挑戦していきます。WebスクレイピングとはWebサイトから情報を抽出することです。自動で情報を集めることで、データの分析等に非常に役立ちます。
今回はスクレイピングでよく用いられるBeautifulSoup4というWebページ解析用のライブラリと、HTTP通信用のライブラリであるRequestsを使用します。
それではさっそくやっていきましょう。




スクレイピング対象のURLを調べる

まずは取得したいページのURLを調べるところからです。今回はGoogleニュースに自分の好きなキーワードを打ち込んで1日分で絞り込んだページをスクレイピング対象とします。
試しにChatGPTに関するニュースを調べてみます。



日付の指定は検索バーの右のとこから指定できますので指定して検索します。



その際のURLは下記のようになりました。

https://news.google.com/search?q=ChatGPT%20when%3A1d&hl=ja&gl=JP&ceid=JP%3Aja

いろいろキーワードを変えてみるとわかりますが、
q=*****&hl=ja&gl=JP&ceid=JP%3Ajaの*****にキーワードが入ります。
英数字以外はURLエンコーディングされるので半角スペースが%20、:が%3Aに変換されています。

なので好きなキーワードを*****のところに入れればスクレイピング対象のページのURLが取得できそうです。日本語のキーワードを入れたい場合はもちろんURLエンコーディングが必要です。pythonではurllib.parseモジュールをつかえばURLエンコーディングができるので試しにやってみましょう。

import urllib.parse

key_word = '日本株 when:1d'
print(urllib.parse.quote(key_word))

結果は以下です。
%E6%97%A5%E6%9C%AC%E6%A0%AA%20when%3A1d

これを先ほどのURLの*****の個所に入れてアクセスしてみると確かに狙ったキーワードで検索できています。

スクレイピング

ここからrequestsとbeautifulsoup4をつかって実際にスクレイピングしていきます。requestsを使ってurlからデータを取得し、beautifulsoup4を使って取得したデータからほしいデータを抜き出します。

今回取得したいのはそれぞれの記事のタイトルと、記事へのリンク、記事の出版元の3つの情報を抜き出します。

Requestsをもちいたデータの取得

requestsを使ってurlから情報を取得します。ついでに先ほど実装したキーワードをURLエンコーディングする処理も追加しちゃいましょう。

import requests
import urllib.parse

# URLを生成
key_word = '日本株 when:1d'
key_word_encoded = urllib.parse.quote(key_word)
url = 'https://news.google.com/search?q={}&hl=ja&gl=JP&ceid=JP%3Aja'.format(key_word_encoded)

# レスポンスを取得
res = requests.get(url) # urlにリクエストを送りレスポンスを取得
res.encoding = res.apparent_encoding # エンコーディング(そのままだと文字化けしてしまうので)
html_text = res.text # レスポンスのテキスト情報のみ抽出
print(html_text)

結果は以下です(膨大なので最初だけ)。

<!doctype html><html lang="ja" dir="ltr"><head><base href="https://news.google.com/"><meta name="referrer" content="origin"><link rel="canonical" href="https://news.google.com/search"><meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui"><meta name="google-site-verification" content="AcBy5YFny2HQgVUCR18tO5YUTf6MpVlcJqGTd-a9-SI"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="application-name" content="ニュース"><meta name="apple-mobile-web-app-title" content="ニュース"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="theme-color" content="white"><meta name="msapplication-tap-highlight" content="no"><link rel="shortcut icon" href="https://lh3.googleusercontent.com/-DR60l-K8vnyi99NZovm9HlXyZwQ85GMDxiwJWzoasZYCUrPuUM_P_4Rb7ei03j-0nRs0c4F=w16" sizes="16x16"><link rel="shortcut icon" href="https://lh3.googleusercontent.com/-DR60l-K8vnyi99NZovm9HlXyZwQ85GMDxiwJWzoasZYCUrPuUM_P_




BeautifulSoup4を用いた情報の抽出

これまでで情報が欲しいページのhtml情報まで抽出ができたのでここから必要な情報のみ抽出していきます。まずはほしい情報がどこにあるかhtmlを読み解いて確認します。

ページのhtml構造を調査する

google chromeだと情報を抜き出したいページをデベロッパーモードで見るとhtmlの情報と一緒にページが見れるので便利です。

↑こんな感じで。

この構造を調べる際にhtmlの基礎知識が必要になりますので勉強しておきましょう。
ざっと調べた感じ以下のことがわかりました。

  • 各ニュースはそれぞれarticleタグにまとめられている
  • 各articleタグの中にはh3タグが一つあり、その中にaタグが1つある
  • 上のaタグのリンクテキストがタイトル。href属性が記事へのリンク
  • 記事へのリンクは相対URLなので実際にアクセスするときにはhttps://news.google.comを前につける必要あり
  • 各article内に2つのdivタグがあり、一つ目のdivタグの中のaタグのリンクテキストが出版元の情報となっている

これをもとに情報を吸い出していきましょう




情報の抽出

ほしい情報のありかがわかったのでBeautifulSoup4を用いて抽出していきます。

BeautifulSoupで解析するためには先ほど作成したレスポンスのテキスト情報を入れます。
find_all()メソッドでカッコ内にしていしたタグ名の要素をすべて抜き出してきます。

from bs4 import BeautifulSoup

soup = BeautifulSoup(html_text, 'html.parser') # html情報をBeautifulSoupで解析する
articles = soup.find_all('article') # articleタグをすべて抜き出す
# 一個目のarticlesタグを見てみる
print(articles[0])

結果は以下(長いので省略)

<article class="MQsxIb xTewfe R7GTQ keNKEd j7vNaf Cc0Z5d EjqUne" data-kind="13" data-n-cvid="i8" data-n-et="107" data-n-ham="true" jsaction=";rcuQ6b:npT2md; click:KjsqPd;" jscontroller="HyhIue" jsdata="oM6qxc;CBMiOGh0dHBzOi8vd3d3Lm5pa2thbi1nZW5kYWkuY29tL2FydGljbGVzL3ZpZXcvbW9uZXkvMzIyNTY00gEA;4" jslog="85008; 3:W251bGwsbnVsbCxudWxsLG51bGwsIiIsbnVsbCwxNDEsbnVsbCxudWxsLG51bGwsMzcsbnVsbCxbbnVsbCxudWxsLG51bGwsbnVsbCxudWxsLG51bGwsbnVsbCxudWxsLG51bGwsbnVsbCxudWxsLG51bGwsbnVsbCxudWxsLG51bGwsbnVs
...

先ほどまとめた情報だと下記の通りなので、その中のh3タグを抜き出し、さらにその中のaタグを抜き出してみます。

  • 各articleタグの中にはh3タグが一つあり、その中にaタグが1つある
  • 上のaタグのリンクテキストがタイトル。href属性が記事へのリンク

要素が一意に決まるときは.find()メソッドで指定したタグの要素を1個だけ抜き出せます。先ほど抜き出したarticles[0]の中のh3タグ、その中のaタグを抜き出すのでそれぞれの要素で.find()メソッドを使って抜き出します。

txt = articles[0].find('h3') # articleタグ内のh3タグを抜き出す
print(txt)
txt = txt.find('a') # 上のh3タグ内のaタグを抜き出す
print(txt)

h3タグの要素を抜き出した結果

<h3 class="ipQwMb ekueJc RD0gLb"><a class="DY5T1d RZIKme" href="./articles/CBMiOGh0dHBzOi8vd3d3Lm5pa2thbi1nZW5kYWkuY29tL2FydGljbGVzL3ZpZXcvbW9uZXkvMzIyNTY00gEA?hl=ja&amp;gl=JP&amp;ceid=JP%3Aja" target="_blank">米国が利下げを実施したら…過去に日本株はどう反応したのか ...</a></h3>

h3タグの中のaタグの要素を抜き出した結果

<a class="DY5T1d RZIKme" href="./articles/CBMiOGh0dHBzOi8vd3d3Lm5pa2thbi1nZW5kYWkuY29tL2FydGljbGVzL3ZpZXcvbW9uZXkvMzIyNTY00gEA?hl=ja&amp;gl=JP&amp;ceid=JP%3Aja" target="_blank">米国が利下げを実施したら…過去に
日本株はどう反応したのか ...</a>

このaタグの中のリンクテキストが記事タイトル。aタグのhref属性が記事の相対URLとなっていることがわかるかと思います。
リンクテキストはtxt.textで抜き出せます。
href属性はtext.get('href')で抜き出せます。

print('タイトル: ', txt.text)
print('記事URL: ', txt.get('href'))

結果は以下

タイトル:  米国が利下げを実施したら…過去に日本株はどう反応したのか ...
記事URL:  ./articles/CBMiOGh0dHBzOi8vd3d3Lm5pa2thbi1nZW5kYWkuY29tL2FydGljbGVzL3ZpZXcvbW9uZXkvMzIyNTY00gEA?hl=ja&gl=JP&ceid=JP%3Aja

うまく抜き出すことができました。
記事URLは相対パスなので先頭の.をhttps://news.google.comに変えておきましょう。

続いて記事の出版元を取得します。

  • 各article内に2つのdivタグがあり、一つ目のdivタグの中のaタグのリンクテキストが出版元の情報となっている

上記の情報の通りなので、先ほどと同様に抜き出します。

source = articles[0].find_all('div')[0]
source_name = source.find('a').text
print('出版元: ', source_name)

結果は以下

出版元:  日刊ゲンダイDIGITAL

すべての情報が抜き出せました。後は同じことをほかのarticleタグでやればページのすべての情報が抜き出せます。

全コード

これまでのコードを使って全記事の情報を抜き出してみましょう。

from bs4 import BeautifulSoup
import requests
import urllib.parse

key_word = '日本株 when:1d'
key_word_encoded = urllib.parse.quote(key_word)
url = 'https://news.google.com/search?q={}&hl=ja&gl=JP&ceid=JP%3Aja'.format(key_word_encoded)

res = requests.get(url) # urlにリクエストを送りレスポンスを取得
res.encoding = res.apparent_encoding # エンコーディング
html_text = res.text # レスポンスのテキスト情報

soup = BeautifulSoup(html_text, 'html.parser') # html情報をBeautifulSoupで解析する
articles = soup.find_all('article') # articleタグをすべて抜き出す

base_url = 'https://news.google.com'
print('今日の記事数{}個'.format(len(articles)))
for article in articles:
    txt = article.find('h3') # articleタグ内のh3タグを抜き出す
    txt = txt.find('a') # 上のh3タグ内のaタグを抜き出す
    print('----------------------------')
    print(txt.text) # 記事タイトル
    print(base_url + txt.get('href')[1:]) # 記事urlの絶対URL
    source = article.find_all('div')[0] # articleタグ内の最初のdivタグを抜き出す
    source_name = source.find('a').text
    print(source_name) # 記事の出版元
    print('----------------------------')

結果は以下です。
抜き出したリンクもちゃんと機能するので成功です!

今日の記事数47個
----------------------------
米国が利下げを実施したら…過去に日本株はどう反応したのか ...
https://news.google.com/articles/CBMiOGh0dHBzOi8vd3d3Lm5pa2thbi1nZW5kYWkuY29tL2FydGljbGVzL3ZpZXcvbW9uZXkvMzIyNTY00gEA?hl=ja&gl=JP&ceid=JP%3Aja
日刊ゲンダイDIGITAL
----------------------------
----------------------------
「投資の神様」バフェット氏、株主と語った6時間 日本株にも言及 ...
https://news.google.com/articles/CBMiN2h0dHBzOi8vd3d3LmFzYWhpLmNvbS9hcnRpY2xlcy9BU1I1NzNSWjBSNTdVTEZBMDAxLmh0bWzSAQA?hl=ja&gl=JP&ceid=JP%3Aja
朝日新聞デジタル
----------------------------
----------------------------
バフェットもジム・ロジャーズも日本株に熱視線、なぜ天才投資家が日本復活に賭けている?狙っている3つの成長産業も=花輪陽子
https://news.google.com/articles/CBMiJGh0dHBzOi8vd3d3Lm1hZzIuY29tL3AvbW9uZXkvMTMwODM2NtIBKGh0dHBzOi8vd3d3Lm1hZzIuY29tL3AvbW9uZXkvMTMwODM2Ni9hbXA?hl=ja&gl=JP&ceid=JP%3Aja
MONEY VOICE
----------------------------
...

おわりに

GoogleニュースはURLがapiチックになっていたのでやりやすかったですね。
抜き出した情報を外部に保存するようにして、これをタスクスケジューラとかで毎日実行するようにすれば面白そうですね!

参考になった文献

様々な形式のサイトに使えるスクレイピング手法や、欲しいページまで自動的にサイトを辿っていってくれるクローラーの作成方法まで幅広く載っていて入門にオススメです!