一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

BeautifulSoup4を使ってスクレイピングしつつ、各メソッドを解説してみる

この記事は一休.comアドベントカレンダー2017の3日目です。

一休.comの開発基盤をやっていますid:akasakasです。

BeautifulSoup4でスクレイピング

スクレイピングでBeautifulSoup4を扱う機会が多いです。 BeautifulSoup4はいろんな便利機能が揃ってますが、自分は全部覚えられないし、使いこなせなません(苦笑)

正直、BeautifulSoup4にあるいくつかのメソッドがそれなりに使えれば、十分スクレイピングできます。

なので、今回はBeautifulSoup4を使い、スクレイピングをして、各種メソッドを紹介します。

スクレイピング対象

一休.com Advent Calendar 2017 - Qiita

やってみること

カレンダーから日付/担当者/タイトルを取得&出力してみます

f:id:akasakas:20171203122132p:plain

結果

day is 1
author is ninjinkun
title is 単純なコードでアプリ内のコンバージョン経路を計測する
----
day is 2
author is ryo511
title is 一休.comのJavaScriptユニットテスト環境
----

...

----
day is 24
author is zimathon
title is 開発組織の目的型組織への移行
----
day is 25
author is ninjinkun
title is 締めます
----

コード

from bs4 import BeautifulSoup
from urllib.request import urlopen

html = urlopen("https://qiita.com/advent-calendar/2017/ikyu")
soup = BeautifulSoup(html, "html.parser")

for advent_calendar_week in soup.tbody:
    for advent_calendar_day in advent_calendar_week.find_all("td", {"class": "adventCalendarCalendar_day"}):
        print(f"day is {advent_calendar_day.p.string}")
        print(f"author is {advent_calendar_day.find_all('div')[0].a.get_text().strip()}")
        print(f"title is {advent_calendar_day.find_all('div')[1].string}") 
        print("----")

1つずつブレイクダウン

下準備:htmlパース

これだけです

from bs4 import BeautifulSoup
from urllib.request import urlopen

html = urlopen("https://qiita.com/advent-calendar/2017/ikyu")
soup = BeautifulSoup(html, "html.parser")

soupオブジェクトにガツっと結果が入ってます。 このオブジェクトから日付・担当者・タイトルをピックアップします。

カレンダーを取得し、ループで回す

for advent_calendar_week in soup.tbody:
    for advent_calendar_day in advent_calendar_week.find_all("td", {"class": "adventCalendarCalendar_day"}):

soup.tbody でtbodyを取得しています。 この中にある <td class="adventCalendarCalendar_day"> が欲しいので、そこから、さらに find_all("td", {"class": "adventCalendarCalendar_day"})<td class="adventCalendarCalendar_day"> を全部ピックアップして、ループで回してます。

日付と担当者とタイトル

        print(f"day is {advent_calendar_day.p.string}")
        print(f"author is {advent_calendar_day.find_all('div')[0].a.get_text().strip()}")
        print(f"title is {advent_calendar_day.find_all('div')[1].string}") 

pタグに日付があるので、 p.string で日付が取得できます divタグの1つ目が担当者、2つ目がタイトルとなります。

個人的に感じたポイント

必要なデータは一括で取得

soup.{tag} で必要なデータは一括で取得できます

find_allで必要な情報だけうまく取得する

divタグの特定クラスをまとめて取得したい場合にfind_allが便利です。

上の例だと

find_all("td", {"class": "adventCalendarCalendar_day"})

で、tdタグの特定クラスだけをまとめて取得しています。


別法

下のリストから日付/担当者/タイトルを取得&出力してみます

f:id:akasakas:20171203122252p:plain

結果

day is 12 / 1
author is ninjinkun
title is 単純なコードでアプリ内のコンバージョン経路を計測する
----
day is 12 / 2
author is ryo511
title is 一休.comのJavaScriptユニットテスト環境
----

...

----
day is 12 / 24
author is zimathon
title is 開発組織の目的型組織への移行
----
day is 12 / 25
author is ninjinkun
title is 締めます
----

コード

from bs4 import BeautifulSoup
from urllib.request import urlopen

html = urlopen("https://qiita.com/advent-calendar/2017/ikyu")
soup = BeautifulSoup(html, "html.parser")

for advent_calendar_day in soup.find_all("div", {"class" : "container"})[4]:
    print(f"day is {advent_calendar_day.find('div', {'class' : 'adventCalendarItem_date'}).string}")
    print(f"author is {advent_calendar_day.a.get_text().strip()}")

    if advent_calendar_day.find('div', {'class' : 'adventCalendarItem_entry'}) is None:
        print(f"title is {advent_calendar_day.find('div', {'class' : 'adventCalendarItem_comment'}).string}")
    else:
        print(f"title is {advent_calendar_day.find('div', {'class' : 'adventCalendarItem_entry'}).get_text()}")
    
    print("----")

1つずつブレイクダウン

リストを取得し、ループで回す

for advent_calendar_day in soup.find_all("div", {"class" : "container"})[4]:

divタグの class="container" の中で、下のリストのオブジェクトを取得し、1行ずつ回しています。

投稿済みと未投稿の分類

以下のように、投稿済みと未投稿でタイトルのDOMが少々異なります。

投稿済み

<div class="adventCalendarItem_commentWrapper">
    <div class="adventCalendarItem_entry">
        <a data-confirm="Are you sure to follow a link to this website?
http://user-first.ikyu.com/entry/singleton-tracking" href="http://user-first.ikyu.com/entry/singleton-tracking" target="_blank">単純なコードでアプリ内のコンバージョン経路を計測する 
            <i class="fa fa-external-link"></i>
        </a>
    </div>
</div>

未投稿

<div class="adventCalendarItem_commentWrapper">
    <div class="adventCalendarItem_comment">BeautifulSoup4を実際に使ってみつつ、各メソッドを解説してみる</div>
</div>

<div class="adventCalendarItem_entry"> の有無で判定し、タイトルを取得しています。

    if advent_calendar_day.find('div', {'class' : 'adventCalendarItem_entry'}) is None:
        print(f"title is {advent_calendar_day.find('div', {'class' : 'adventCalendarItem_comment'}).string}")
    else:
        print(f"title is {advent_calendar_day.find('div', {'class' : 'adventCalendarItem_entry'}).get_text()}")

まとめ

BeautifulSoup4を使い、スクレイピングをしてみました。 ここでご紹介した機能はごく一部になりますが、それでも使えれば十分スクレイピングができました。

明日は id:kentana20 さんによる「宿泊サービスにおけるUI改善の取り組み」です。

参考

BeautifulSoup4公式ドキュメント