PythonでBeautifulSoupを使ったHTMLパースガイド

Webスクレイピングとは、プログラムによって様々なWebサイトから情報を収集することです。Webデータを抽出するライブラリやフレームワークは様々な言語で提供されていますが、PythonはWebスクレイピングのためのオプションが豊富なため、長い間人気のある選択肢となっています。

この記事では、HTMLやXMLをパースする人気のPythonライブラリ、Beautiful Soupを使って、PythonでのWebスクレイピングのクラッシュコースを提供します。

倫理的なウェブスクレイピング

ウェブスクレイピングはどこにでもあり、APIで得られるようなデータを私たちに与えてくれます。しかし、インターネットの善良な市民として、スクレイピングを行うサイトオーナーを尊重することは私たちの責任です。ここでは、ウェブスクレイパーが守るべき原則をいくつか紹介します。

  • スクレイピングしたコンテンツを自分たちのものとして主張しない。ウェブサイトの所有者は、記事を作成したり、製品に関する詳細を収集したり、その他のコンテンツを収穫するのに長い時間を費やすことがあります。私たちは、彼らの労力とオリジナリティを尊重しなければなりません。
  • スクレイピングされることを望んでいないウェブサイトをスクレイピングしてはいけません。Webサイトには、「robots.txt」ファイルが付属していることがあります。これは、Webサイトのどの部分をスクレイピングできるかを定義したものです。また、多くのウェブサイトは、スクレイピングを許可しない利用規約を設けている場合があります。スクレイピングを望まないウェブサイトを尊重する必要があります。
  • APIはすでにあるのですか?素晴らしい、私たちがスクレイパーを書く必要はありません。API は、データの所有者によって定義された、制御された方法でデータへのアクセスを提供するために作成されます。私たちは、APIが利用可能な場合は、それを使用することをお勧めします。
  • Webサイトへのリクエストを行うことは、Webサイトのパフォーマンスに負担をかけることになります。Webサイトへのリクエストは、Webサイトのパフォーマンスを低下させる可能性があります。Webサイトの正常な機能を阻害しないよう、責任を持ってスクレイピングを行わなければなりません。

ビューティフル・スープ」の概要

WebページのHTMLコンテンツは、Beautiful Soupで解析してスクレイピングすることができます。以下では、Webページのスクレイピングに便利な関数を取り上げていきます。

Beautiful Soupが便利なのは、HTMLからデータを抽出するための関数が無数に用意されていることです。下の画像は、その中のいくつかの関数の例です。

では、実際にBeautiful Soupを使ってHTMLをパースしてみましょう。次のようなHTMLページが doc.html としてファイルに保存されているとします。

<html
<head
<titleHead's title</title
</head
<body
<p class="title"<bBody's title</b</p
<p class="story"line begins
    <a class="element" href="http://example.com/element1" id="link1"1</a
<a class="element" href="http://example.com/element2" id="link2"2</a
<a class="avatar" href="http://example.com/avatar1" id="link3"3</a
<p line ends</p
</p</body
</html


以下のコードスニペットは Ubuntu 20.04.1 LTS でテストされています。ターミナルで以下のコマンドを入力すると、BeautifulSoup モジュールがインストールされます。

$ pip3 install beautifulsoup4


HTMLファイル doc.html を用意する必要がある。ここでは、インタラクティブなPythonシェルを使用して、ページの特定の部分の内容を即座に表示できるようにします。

from bs4 import BeautifulSoup


with open("doc.html") as fp:
    soup = BeautifulSoup(fp, "html.parser")


これでBeautiful Soupを使ってウェブサイトを閲覧し、データを抽出できるようになりました。

特定のタグに移動する

From the soup object created in the previous section, let’s get the title tag of doc.html:

soup.head.title   # returns <titleHead's title</title


Here’s a breakdown of each component we used to get the title:

Beautiful Soup is powerful because our Python objects match the nested structure of the HTML document we are scraping.

To get the text of the first <a tag, enter this:

soup.body.a.text  # returns '1'


To get the title within the HTML’s body tag (denoted by the “title” class), type the following in your terminal:

soup.body.p.b     # returns <bBody's title</b


For deeply nested HTML documents, navigation could quickly become tedious. Luckily, Beautiful Soup comes with a search function so we don’t have to navigate to retrieve HTML elements.

タグの要素を検索する

find_all()メソッドは HTML タグを文字列の引数として受け取り、与えられたタグとマッチする要素のリストを返します。例えば、doc.htmlに含まれるすべてのa` タグが欲しい場合、以下のようになります。

soup.find_all("a")


このように、aタグのリストが出力される。

[<a class="element" href="http://example.com/element1" id="link1"1</a, <a class="element" href="http://example.com/element2" id="link2"2</a, <a class="element" href="http://example.com/element3" id="link3"3</a]


タグを検索するために使用した各コンポーネントの内訳は以下の通りです。

class_という引数を指定することで、特定のクラスのタグを検索することもできます。Beautiful Soupではclass_を使用していますが、これはclassがPythonの予約キーワードであるためです。例えば、"element" クラスを持つa` タグをすべて検索してみましょう。

soup.find_all("a", class_="element")


element “クラスを持つリンクは2つしかないので、このように出力されます。

[<a class="element" href="http://example.com/element1" id="link1"1</a, <a class="element" href="http://example.com/element2" id="link2"2</a]


もし、aタグの中に埋め込まれているリンクを取得したい場合はどうすればよいでしょうか。find()オプションを使用して、リンクのhref属性を取得してみましょう。これはfind_all()` と同じように動作しますが、リストの代わりに最初にマッチした要素を返します。シェルで次のようにタイプしてください。

soup.find("a", href=True)["href"] # returns http://example.com/element1


find()find_all()関数は、文字列の代わりに正規表現も受け付けます。裏側では、コンパイルされた正規表現のsearch()` メソッドを使ってテキストがフィルタリングされます。For example:

import re


for tag in soup.find_all(re.compile("^b")):
    print(tag)


The list upon iteration, fetches the tags starting with the character b which includes <body and <b:

<body
<p class="title"<bBody's title</b</p
<p class="story"line begins
       <a class="element" href="http://example.com/element1" id="link1"1</a
<a class="element" href="http://example.com/element2" id="link2"2</a
<a class="element" href="http://example.com/element3" id="link3"3</a
<p line ends</p
</p</body
<bBody's title</b


We’ve covered the most popular ways to get tags and their attributes. Sometimes, especially for less dynamic web pages, we just want the text from it. Let’s see how we can get it!

全文取得

get_text()` 関数は、HTML ドキュメントからすべてのテキストを取得します。それでは、HTML ドキュメントの全テキストを取得してみましょう。

soup.get_text()


出力はこのようになるはずです。

Head's title


Body's title
line begins
      1
2
3
 line ends


時々、改行文字が出力されるので、出力はこのようになるかもしれません。

"

Head's title


Body's title
line begins
    1
2
3
 line ends

"


Beautiful Soupの使い方を理解したところで、Webサイトをスクレイピングしてみましょう。

美しいスープの実践-ブックリストのかき集め

Beautiful Soupのコンポーネントをマスターしたところで、いよいよ学習したことを実践してみましょう。https://books.toscrape.com/ からデータを抽出し、CSV ファイルに保存するスクレイパーを構築してみましょう。このサイトには本に関するランダムなデータが含まれており、Webスクレイピングのテクニックを試すのに最適な場所です。

まず、scraper.pyというファイルを新規に作成します。このスクリプトに必要なすべてのライブラリをインポートしましょう。

import requests
import time
import csv
import re
from bs4 import BeautifulSoup


上記のモジュールの中で

  • requests – URLリクエストを実行し、WebサイトのHTMLを取得します。
  • time – ページを一度にスクレイピングする回数を制限します。
  • csv – スクレイピングしたデータを CSV ファイルにエクスポートします。
  • re – 正規表現を書くことができる。パターンからテキストを選ぶのに便利だ
  • bs4 – あなた自身、HTMLをパースするためのスクレイピングモジュールです。

すでに bs4 がインストールされていると思いますが、 time, csv, re はPythonのビルトインパッケージです。このように、 requests モジュールを直接インストールする必要があります。

$ pip3 install requests


始める前に、ウェブページのHTMLがどのように構成されているかを理解する必要があります。ブラウザで、http://books.toscrape.com/catalogue/page-1.html にアクセスしてみましょう。そして、スクレイピングするウェブページのコンポーネントを右クリックし、下図のようにタグの階層を理解するために inspect ボタンをクリックします。

これで、インスペクトしている内容の基礎となるHTMLが表示されます。次の図は、これらの手順を示しています。

HTML を検査することで、本の URL、表紙画像、タイトル、評価、価格、その他のフィールドにアクセスする方法を学ぶことができます。では、本の項目をスクレイピングしてデータを抽出する関数を書いてみましょう。

def scrape(source_url, soup):  # Takes the driver and the subdomain for concats as params
    # Find the elements of the article tag
    books = soup.find_all("article", class_="product_pod")


# Iterate over each book article tag
    for each_book in books:
        info_url = source_url+"/"+each_book.h3.find("a")["href"]
        cover_url = source_url+"/catalogue" + 
            each_book.a.img["src"].replace("..", "")


title = each_book.h3.find("a")["title"]
        rating = each_book.find("p", class_="star-rating")["class"][1]
        # can also be written as : each_book.h3.find("a").get("title")
        price = each_book.find("p", class_="price_color").text.strip().encode(
            "ascii", "ignore").decode("ascii")
        availability = each_book.find(
            "p", class_="instock availability").text.strip()


# Invoke the write_to_csv function
        write_to_csv([info_url, cover_url, title, rating, price, availability])


上のスニペットの最後の行は、スクレイピングされた文字列のリストをCSVファイルに書き出す関数を示しています。では、その関数を追加してみましょう。

def write_to_csv(list_input):
    # The scraped info will be written to a CSV here.
    try:
        with open("allBooks.csv", "a") as fopen:  # Open the csv file.
            csv_writer = csv.writer(fopen)
            csv_writer.writerow(list_input)
    except:
        return False


ページをスクレイピングしてCSVにエクスポートする関数ができたので、ページ分割されたウェブサイトをクロールして、各ページのブックデータを収集する別の関数が必要です。

これを実現するために、このスクレイパーを書く URL を見てみましょう。

"http://books.toscrape.com/catalogue/page-1.html"


このURLの中で変化する要素はページ番号だけです。URLを動的にフォーマットすることで、シードURLにすることができます。

"http://books.toscrape.com/catalogue/page-{}.html".format(str(page_number))


このページ番号を含む文字列フォーマットのURLは、メソッド requests.get() を使って取得することができます。そして、新しい BeautifulSoup オブジェクトを作成することができます。スープオブジェクトを取得するたびに、”next” ボタンがあるかどうかがチェックされるので、最後のページで停止することができます。ページ番号のカウンターを記録しておき、ページのスクレイピングに成功したら1だけインクリメントするようにしています。

def browse_and_scrape(seed_url, page_number=1):
    # Fetch the URL - We will be using this to append to images and info routes
    url_pat = re.compile(r"(http://.*.com)")
    source_url = url_pat.search(seed_url).group(0)


# Page_number from the argument gets formatted in the URL &amp; Fetched
    formatted_url = seed_url.format(str(page_number))


try:
        html_text = requests.get(formatted_url).text
        # Prepare the soup
        soup = BeautifulSoup(html_text, "html.parser")
        print(f"Now Scraping - {formatted_url}")


# This if clause stops the script when it hits an empty page
        if soup.find("li", class_="next") != None:
            scrape(source_url, soup)     # Invoke the scrape function
            # Be a responsible citizen by waiting before you hit again
            time.sleep(3)
            page_number += 1
            # Recursively invoke the same function with the increment
            browse_and_scrape(seed_url, page_number)
        else:
            scrape(source_url, soup)     # The script exits here
            return True
        return True
    except Exception as e:
        return e


上記の関数 browse_and_scrape() は、関数 soup.find("li",class_="next")None を返すまで再帰的に呼び出されています。この時点で、コードはウェブページの残りの部分をスクレイピングして終了します。

パズルの最後のピースとして、スクレイピングフローを開始します。seed_urlを定義し、browse_and_scrape()を呼び出してデータを取得します。これはif name == “main“` ブロックの下で行われます。

if __name__ == "__main__":
    seed_url = "http://books.toscrape.com/catalogue/page-{}.html"
    print("Web scraping has begun")
    result = browse_and_scrape(seed_url)
    if result == True:
        print("Web scraping is now complete!")
    else:
        print(f"Oops, That doesn't seem right!!! - {result}")


if name == “main“` ブロックについてもっと知りたい場合は、このブロックの動作に関するガイドを参照してください。

ターミナルで以下のようなスクリプトを実行すると、次のような出力が得られます。

$ python scraper.py


Web scraping has begun
Now Scraping - http://books.toscrape.com/catalogue/page-1.html
Now Scraping - http://books.toscrape.com/catalogue/page-2.html
Now Scraping - http://books.toscrape.com/catalogue/page-3.html
.
.
.
Now Scraping - http://books.toscrape.com/catalogue/page-49.html
Now Scraping - http://books.toscrape.com/catalogue/page-50.html
Web scraping is now complete!


スクレイピングされたデータは、現在の作業ディレクトリにある allBooks.csv というファイル名で見ることができます。以下は、そのファイルの内容のサンプルです。

http://books.toscrape.com/a-light-in-the-attic_1000/index.html,http://books.toscrape.com/catalogue/media/cache/2c/da/2cdad67c44b002e7ead0cc35693c0e8b.jpg,A Light in the Attic,Three,51.77,In stock
404 Not Found
the Velvet,One,53.74,In stock
404 Not Found
stock

お疲れ様でした。もしスクレイパーのコード全体を見たい場合は、GitHubで見つけることができます。

結論

In this tutorial, we learned the ethics of writing good web scrapers. We then used Beautiful Soup to extract data from an HTML file using the Beautiful Soup’s object properties, and it’s various methods like find(), find_all() and get_text(). We then built a scraper than retrieves a book list online and exports to CSV.

Web scraping is a useful skill that helps in various activities such as extracting data like an API, performing QA on a website, checking for broken URLs on a website, and more. What’s the next scraper you’re going to build?

タイトルとURLをコピーしました