今日はWebスクレイピング初学者がとりあえず通るという道「Yahoo!ニュースのトップページの見出しをスクレイピング」をやった。
— ゆーさん (@fukannk0423) August 2, 2020
Webスクレイピングのやり方をググるとだいたいどのサイトもYahoo!ニュースを例に出して記事見出しを取得するコードを紹介している。
そんなWebスクレイピング初学者がとりあえず通るというプログラムを組んでみた。
requestsでWebページの情報を取得
詳しい使い方は分からないので、これさえ覚えておけばデータが取得できるというコマンドを覚え書きしておく。
-
requests
get()
メソッドでurlを指定することでWebページのデータをまるっと取得することができる。
import requests
html = requests.get('https://news.yahoo.co.jp/')
html
という変数を用意してそこに代入しておくのが慣例みたい。
requests.get()
の第一引数にurlを指定するとResponse
オブジェクトが取得できる。
Response
オブジェクトはさまざまな属性を持っているが、content
属性でWebページのソースコードと同じような内容を取得できる。
(参照:https://note.nkmk.me/python-requests-usage/ )
今回はrequests
で取得したcontent
属性のResponse
オブジェクトをPythonの標準ライブラリであるhtml.parser
でパースしてBeautifulSoup
で抽出する。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html.content, "html.parser")
こちらはsoup
という変数に代入しておくのが慣例みたい。
ところでパースとは?
HTMLのタグや属性などを解析して機械が読めるデータにしておくこと
なんぞや??という方向け(主にぼくのため)のまとめ
今までのコードをコピペすると
soup
という変数に指定したurlのソースコードみたいなものが入る。
次はお楽しみのスクレイピング。取得したデータからいらない部分を削ぎ落とし、欲しい情報だけを抜き出していく。
BeautifulSoupでスクレイピング
-
BeautifulSoup
HTMLファイルから欲しい情報だけを抜き出すために使うライブラリ
さっき取得した変数soup
の中身は、ページのソースコードみたいな内容になっていて余計なものがたくさん入っているので、いらない情報をscrape(削ぎ落とす)する。
~スクレイピングの理想的な流れ~
- 欲しい情報がHTMLのどこに収納されているか調べるために、ページのソースを表示し、コーヒーを淹れながらゆっくりと眺める。
- そのうち、欲しい情報の近くにのみ存在するHTMLのタグや属性に気付く。
- それらを
select()
やfind()
メソッドを用いて抜き出して整理する。 - 結果を.txtや.csvで出力してスクレイピング完了
- 出来上がったファイルを眺めてニヤニヤする
~初心者に突き付けられる現実~
- まずソースコードが長すぎて欲しい情報を見失う
- 情報の位置を特定するための手がかりがつかめない
- 仕方がないので<td>や<p>などの汎用的なタグを一括で抜き出す
- 膨大なHTMLタグに囲まれたちっぽけな情報を見て軽く絶望する
- テキストだけ抜き出そうと思っても
.text
は複数の要素を抜き出せませんとPythonに怒られてしょげる - その処理のために
for
文を勉強することになる - なんとかして
.text
で抜き出したリストを作成するが、欲しい情報が何番目の要素に入っているか分からない - 整理するためにさまざまなリストの扱い方を勉強することに
- 課題が派生していき本来の目的を見失い、生ける屍と化す
ま、この辺は軽い冗談ですけども
プログラミングは本当に時間泥棒ですなあ。。。
実際に行ったこと
- 今回欲しい情報は、「Yahoo!ニュースの最新記事見出しとそのurl」なので、まずはその情報がHTMLのどこに収納されているかを調べる。
- Yahoo!ニュースのトップページを開いて、右クリック→「ページのソースを表示」でソースコードを表示
- 眺めていると、主要記事の見出しやリンクは
<li class="topicsListItem">
のタグの中に入っていることに気付いたので、select()
で抽出 - テキスト部分とurl部分を抜き出すためにfor文を使う
書いたスクリプト
extract = soup.select(".topicsListItem") #class="topicsListItem"を含むタグの中身を抽出
for i in extract:
print(i.text) #タグの中身の文章だけを抽出してforで取り出す。
#国内1300人超感染 連日千人超
#お盆帰省「今週判断」西村氏
#帰省だけ自粛は矛盾?識者見解
#都のコロナ死者 平均年齢79歳
#琵琶湖で遊泳 31歳男性死亡
#雌雄半々? 珍クワガタに驚き
#復活V「笑える日くると信じ」
#俳優の立石涼子さん死去 68歳
for i in extract:
print(i.find('a')['href']) #<a href="">の中身のリンクだけを抽出してforで取り出す。
#https://news.yahoo.co.jp/pickup/6367239
#https://news.yahoo.co.jp/pickup/6367235
#https://news.yahoo.co.jp/pickup/6367234
#https://news.yahoo.co.jp/pickup/6367237
#https://news.yahoo.co.jp/pickup/6367241
#https://news.yahoo.co.jp/pickup/6367243
#https://news.yahoo.co.jp/pickup/6367233
#https://news.yahoo.co.jp/pickup/6367238
見出しのテキストと記事リンクを取得できた(2020.8.2とかのニュースなのでリンク切れしてます)。
zip()
関数を使うとこれらをまとめて取得することができる。
2021.1.14追記
半年ほど経って自分の記事を見直してたら恥ずかしいミスをしてたのでここは訂正します。
同じリストに対する処理なので、zip使う必要ないですね。
zip関数は異なったリストなどから並行に処理をしたい時に使う関数です。
下記のコードもzip使わない形に編集しました。
ついでにopen()
関数を使ってtxtファイルを開き、.write()
で書き込んでおく。
open()
したら.close()
しておくのを忘れないように。
f = open('yahootopicnews.txt', 'w', encoding='utf-8')
for i in extract:
txt = i.text + ' ' + i.find('a')['href']
print(txt)
f.write(txt + '\n')
f.close()
#国内1300人超感染 連日千人超 https://news.yahoo.co.jp/pickup/6367239
#お盆帰省「今週判断」西村氏 https://news.yahoo.co.jp/pickup/6367235
#帰省だけ自粛は矛盾?識者見解 https://news.yahoo.co.jp/pickup/6367234
#都のコロナ死者 平均年齢79歳 https://news.yahoo.co.jp/pickup/6367237
#琵琶湖で遊泳 31歳男性死亡 https://news.yahoo.co.jp/pickup/6367241
#雌雄半々? 珍クワガタに驚き https://news.yahoo.co.jp/pickup/6367243
#復活V「笑える日くると信じ」 https://news.yahoo.co.jp/pickup/6367233
#俳優の立石涼子さん死去 68歳 https://news.yahoo.co.jp/pickup/6367238
これでYahoo!ニュースの主要記事の見出しとリンクをまとめてtxtファイルに出力することができた。
毎朝取得して自分のメールアドレスに送ったりできたら面白いかもですね。
結論
スクレイピングは取ってきたデータをどう処理するかが一番の楽しみどころであり処理の仕方は腕が問われますね。クレバーにやれるようになりたいなと思いました。