素人がPythonでWebスクレイピングを実装する7

よこのじ(@yokonoji_work)です。

素人がPythonでWebスクレイピングを実装する」の第7回です。第6回ではh2タグのスクレイピングの結果、本来欲しい記事タイトル以外も抽出してしまいましたので、今回はスクレイピング対象を絞る方法をみてみます。

find_all()の使い方

スクレイピング対象を絞るには、Beautiful Soupのfind_all()メソッドで指定できる引数を知る必要があります。

find_all(name, attrs, recursive, text, limit, **kwargs)

(コードエリアの文字色と背景色をQiita風にしてみました)

対象のすべてを返すfind_all()の他に、1つだけを返すfind()メソッドもあります。find_all() は何もみつけられないときは空リストを返すのに対して、find() は何もみつけられないときは、 None を返します。

name引数

find_all() の name 引数に値を渡すと、タグの名前だけを対象に検索が行われます。

soup.find_all("title")
# [<title>The Dormouse's story</title>]

name 引数は文字列、正規表現、リスト、True値、関数を指定することができます。

attrs引数

HTMLタグが持つCSSのclassで検索できます。

soup.find_all("a", class_="sister")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

attrs引数では、class_ に文字列、正規表現、True値、関数を指定できます。

soup.find_all(class_=re.compile("itl"))
# [<p class="title"><b>The Dormouse's story</b></p>]

def has_six_characters(css_class):
    return css_class is not None and len(css_class) == 6
soup.find_all(class_=has_six_characters)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

Noneを扱うとき、”== None”ではなく “is None”を使うべきです。動作に問題が出ることはほとんどないようですが、isの方が処理が速いです。
Python: “is None” vs “==None”

複数のclassを検索対象にする場合は注意が必要です。

class 属性の値は、文字列で検索できます。bodyとstrikeoutというclassを持つ要素を検索するには次のように指定します。

css_soup.find_all("p", class_="body strikeout")
# [<p class="body strikeout"></p>]

しかし、文字列として検索するため、strikeout、bodyの順番では検索できません。

css_soup.find_all("p", class_="strikeout body")
# []

2つ以上のclassと一致する要素を検索する場合は、CSSセレクタで指定します。

css_soup.select("p.strikeout.body")
# [<p class="body strikeout"></p>]

recursive引数

例えば、title.find_all() を実行すると、Beautiful Soupは titleタグ の全ての子孫要素を調べます。直下の子要素のみ調べる場合は、recursive=False という引数を渡します。

soup.html.find_all("title")
# [<title>The Dormouse's story</title>]

soup.html.find_all("title", recursive=False)
# [] ← htmlタグの直下にtitleタグがないと、何も返ってこない

text引数

タグに挟まれている文字列を検索します。文字列、正規表現、リスト、True値、関数が使えます。

soup.find_all(text="Elsie")
# [u'Elsie']

soup.find_all(text=["Tillie", "Elsie", "Lacie"])
# [u'Elsie', u'Lacie', u'Tillie']

soup.find_all(text=re.compile("Dormouse"))
# [u"The Dormouse's story", u"The Dormouse's story"]

text 引数はテキスト文字列の検索ですが、タグの検索を組みわせることもできます。次の例は、文字列 “Elsie”を持つ<a>タグをみつけます。

soup.find_all("a", text="Elsie")
# [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]

limit引数

limit 引数で、取得する数を指定(制限)することができます。次の例では、aタグが3つあったとしても取得数は2つとなります。

soup.find_all("a", limit=2)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

キーワード引数

キーワード引数 id に値を渡すと、’id’属性に対してフィルタリングを行います。

soup.find_all(id='link2')
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

キーワード引数 href に値を渡すと、’href’属性に対してフィルタリングを行います。

soup.find_all(href=re.compile("elsie"))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

キーワード引数は、文字列、正規表現、リスト、True値、関数を指定することもできます。次の例では、id 属性に値が入っている全てのタグをみつけます。

soup.find_all(id=True)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

複数のキーワード引数を一度に渡すことによって、複数の属性についてフィルタリングすることもできます。

soup.find_all(href=re.compile("elsie"), id='link1')
# [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]

フィルターの種類

name引数、attrs引数、text引数、キーワード引数は「文字列」「正規表現」「リスト」「True値」「関数」でフィルターをかけることができます。

文字列

指定した文字列と一致する文字列を検索します。次の例では、name引数に’b’を指定して<b>タグをみつけます。

soup.find_all('b')
# [<b>The Dormouse's story</b>]

正規表現

正規表現を指定するとsearch()メソッドを用いて、その正規表現と一致するものを検索します。次の例では、”b”で始まるタグを検索します。

import re
for tag in soup.find_all(re.compile("^b")):
    print(tag.name)
# body
# b

この例では、タグ名に”t”を含むものをみつけます。

for tag in soup.find_all(re.compile("t")):
    print(tag.name)
# html
# title

reモジュールは、Perl などと同様の正規表現マッチング操作を行うものです。import reでモジュールを読み込んで使用します。

リスト

リストで引数を渡すと、そのリスト内のいずれかと一致した要素を返します。 次の例では、<a>タグと<b>タグをみつけます。

soup.find_all(["a", "b"])
# [<b>The Dormouse's story</b>,
#  <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

True値

True値は対象の引数が存在するとその要素を返します。キーワード引数にid=Trueを指定すると、id属性を持つ要素を検索します。次の例ではname引数にTrueを指定して、すべてのタグをみつけます。

for tag in soup.find_all(True):
    print(tag.name)
# html
# head
# title
# body
# p
# b
# p
# a
# a
# a
# p

関数

上記のフィルターで機能が足りなければ、自分で関数を定義することもできます。その関数は、引数がマッチしたときは True を、そうでないときは False を返します。

次の関数では、HTMLタグが “class” 属性を持ち、”id”属性を持たない場合に True を返します。

def has_class_but_no_id(tag):
    return tag.has_attr('class') and not tag.has_attr('id')

soup.find_all(has_class_but_no_id)
# [<p class="title"><b>The Dormouse's story</b></p>,
#  <p class="story">Once upon a time there were...</p>,
#  <p class="story">...</p>]

その他の指定方法

次のように、bodyタグの中のbタグのようにパースツリーを階層上に指定することができます。

soup.body.b
# <b>The Dormouse's story</b>

CSSセレクタで指定することもできます。

この例では、bodyタグの中にあるすべてのaタグを抽出しています(直下だけではない)。

soup.select("body a")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie"  id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

この例では、pタグ直下のaタグを抽出します。

soup.select("p > a")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie"  id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

「class名」で該当タグをみつける。

soup.select(".sister")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

「id名」で該当タグをみつける。

soup.select("#link1")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

「指定の属性の有無」で該当タグをみつける。

soup.select('a[href]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

「属性が持つ値」で該当タグをみつける。

soup.select('a[href="http://example.com/elsie"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

soup.select('a[href^="http://example.com/"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select('a[href$="tillie"]')
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select('a[href*=".com/el"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

対象のテキスト部分を抽出する方法はこちらです。

# タグ直下にある文字列を取得できる
soup.title.string

# タグ内のすべての文字列を取得できる
soup.get_text("|", strip=True)

記事名のみを抽出

これまでの内容を踏まえて、方法はいくつかあると思いますが、次のようなコードを書いてみました。これにより、このブログのホームに表示されている記事のタイトルのみをスクレイピングできました。

h2タグclass名により対象を絞っています。h2タグの直下にaタグがあり、その中に記事名がある構造ですが、h2タグまで抽出してget_text(“|”, strip=True)でその中のテキストを取得しました。

soup = BeautifulSoup(r.content, 'lxml')
elements = soup.find_all('h2', class_="heading heading-archive heading-leftImg")

for e in elements:
    print(e.get_text("|", strip=True))

# 素人がPythonでWebスクレイピングを実装する7
# GoogleドキュメントからWordPressに直接投稿する方法
# WebAssembly(wasm)とはなにか?Googleやアップルも関わる期待の技術
# 素人がPythonでWebスクレイピングを実装する6
# IoTでの活用が期待されるメッシュネットワークとはなにか?
# 素人がPythonでWebスクレイピングを実装する5
# 素人がPythonでWebスクレイピングを実装する4
# Google Apps Script(GAS)でGoogleカレンダーからスプレッドシートに予定を取り込む
# 素人がPythonでWebスクレイピングを実装する3
# 素人がPythonでWebスクレイピングを実装する2

 

 

Pythonは応用範囲が広いので、学ぶのが楽しいです

素人がPythonでWebスクレイピングを実装する1
素人がPythonでWebスクレイピングを実装する2
素人がPythonでWebスクレイピングを実装する3
素人がPythonでWebスクレイピングを実装する4
素人がPythonでWebスクレイピングを実装する5
素人がPythonでWebスクレイピングを実装する6
素人がPythonでWebスクレイピングを実装する8