1. ホーム
  2. パイソン

Pythonによるhtmlのパース (BeautifulSoup)

2022-02-23 23:20:04

WeChat検索:"20人の学生"公開番号、成長の異なる道をたどることを歓迎する

BeautifulSoupの紹介

  BeautifulSoupはHTMLやXMLファイルからデータを抽出する効率的なウェブ解析ライブラリです。BeautifulSoupはHTML解析用、XML解析用、HTML5解析用など、異なるパーサーをサポートしています。一般的には、lxmlパーサーを使用することが多くなります。

BeautifulSoupのインストール

  BeautifulSoup3は現在更新が止まっており、現在のプロジェクトではBeautifulSoup4を使うことが推奨されていますが、bs4に移植されました。つまり、インポートする際にbs4をインポートする必要があるのです。インストールはpipかeasy_installのどちらかを使用します。以下はpipでのインストールです。

pip install beautifulsoup4

  pip install lxml

  モジュール "lxml" もインストールすることをお勧めします。BeautifulSoup は Python 標準ライブラリの HTML パーサー ("html.parser") と、いくつかのサードパーティのパーサーをサポートしていますが、インストールしない場合は Python のデフォルトパーサーを使用することになります。lxmlパーサはより強力で高速であり、推奨されています。

オブジェクトの作成

  インストール後、オブジェクトを作成します: soup = BeautifulSoup(markup='html-like string', 'lxml') # ここで注意点ですが、 "lxml" パーサーはノード名をすべて小文字に変換します! パーサーは、"xml" を使用すると、変換しません。ですから、XMLには"xml"を、HTMLには"lxml"を使ってください。

  出力の書式設定:soup.prettify()

BeautifulSoupの4つのオブジェクトタイプ

  BeautifulSoupは複雑なHTMLドキュメントを複雑なツリー構造に変換し、各ノードがPythonオブジェクトで、すべてのオブジェクトは4つのタイプにグループ化することが可能です。

  • BeautifulSoup (ドキュメント)
  • タグ(tag)
  • NavigableString (コンテンツ)
  • コメント(comment)

1. BeautifulSoupタイプ (ドキュメント)

  BeautifulSoupオブジェクトは、ドキュメントのコンテンツ全体を表します。

print soup.name 
# [document]

2. タグの種類(タグ)

  つまり、<title>タグを取得するなど、HTMLのタグ全体を指します。

print soup.title
#<title>The Dormouse's story</title>

  タグは2つの重要な属性を持っています:名前とattrsです。

名前

  つまり、HTMLタグの名前です。

print soup.name
#[document]
print soup.head.name
#head

属性

  つまり、タグの属性のHTML辞書です。

print soup.p.attrs
#{'class': ['title'], 'name': 'dromouse'}

  属性を個別に取得したい場合。

print soup.p['class']
#['title']

3. NavigableString 型(コンテンツ)

  さて、タグ全体ができたところで、タグの中のテキストを取得するにはどうすればいいのかという疑問が生じます。それは簡単で、文字列を使えばいいのです。

print soup.p.string
#The Dormouse's story

4. コメントタイプ(コメント)

  HTMLのコメントの内容です。コメント記号は含まれていないことに注意してください。まずそのタイプ、Commentタイプであるかどうかを判断し、その後、出力を印刷するなどの処理を行います。

if type(soup.a.string)==bs4.element.Comment:
    print soup.a.string
#<! -- Elsie -->

ドキュメントツリーをたどる

1. 子ノード

内容

  すべての子ノードを取得し、そのリストを返す。

print soup.head.contents 
#[<title>The Dormouse's story</title>]

子供たち

  すべての子ノードを取得し、リストジェネレータを返します。

print soup.head.children
#<listiterator object at 0x7f71457f5710>

## need to traverse
for child in soup.body.children:
    print child

## Result
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1"><! -- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>


<p class="story">... </p>

2. ノードの内容

文字列

  1つのテキストの内容を返します。タグの中にそれ以上タグがない場合、string はそのタグの内容を返します。タグの中にタグが1つしかない場合、stringは最も内側の内容も返します。タグの中に複数の子タグがある場合、タグはstringメソッドが呼び出すべき子タグを判断することができず、stringはNoneを返します。

print soup.head.string
print soup.title.string
#The Dormouse's story
#The Dormouse's storyprint soup.html.string# None

文字列

  空行や空白を含む複数のテキストコンテンツを返します。

ストリップドストリングス

  空行や空白を除いた複数のテキストコンテンツを返します。

for string in soup.stripped_strings:
    print(repr(string))
    # u"The Dormouse's story"
    # u"The Dormouse's story"
    # u'Once upon a time there were three little sisters; and their names were'
    # u'Elsie'
    # u','
    # u'Lacie'
    # u'and'
    # u'Tillie'
    # u';\nand they lived at the bottom of a well.'
    # u'...'

get_text() メソッド

  現在のノードと子ノードのテキストコンテンツを返します。

from bs4 import BeautifulSoup

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
    <p class="title"><b>The Dormouse's story</b></p>
    <p class="story">Once upon a time there were three little sisters; and their names were
        <a href="http://example.com/elsie" class="sister1" id="link1">Elsie</a>,
        <a href="http://example.com/lacie" class="sister2" id="link2">Lacie</a> and
        <a href="http://example.com/tillie" class="sister3" id="link3">Tillie</a>;
        and they lived at the bottom of a well.
    </p>
    <p class="story">... </p>
</body>
</html>
"""

soup = BeautifulSoup(markup=html_doc,features='lxml')

node_p_text=soup.find('p',class_='story').get_text() # note class_ with underscore
print(node_p_text)

# Results
Once upon a time there were three little sisters; and their names were
        Elsie,
        Lacie and
        Tillie;
        and they lived at the bottom of a well.


3. 親ノード

  ノードの直接の親を返します。

p = soup.p
print p.parent.name
#body

両親

  あるノードの親以上のすべてのノードを返します。

content = soup.head.title.string
for parent in content.parents:
    print parent.name

## Results
title
head
html
[document]

4. 兄弟ノード

次の兄弟

  next_sibling属性は、ノードの次の兄弟を取得します。通常は文字列か空白になりますが、空白や改行もノードとして扱われるためです。

前兄弟

  previous_sibling 属性は、そのノードの前の兄弟を取得します。

print soup.p.next_sibling
# The actual space is blank
print soup.p.prev_sibling
# None There is no previous sibling node, return None
print soup.p.next_sibling.next_sibling
#<p class="story">Once upon a time there were three little sisters; and their names were
#<a class="sister" href="http://example.com/elsie" id="link1"><<! -- Elsie --></a>,
#<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
#<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
#and they lived at the bottom of a well.</p>
#The next sibling of the next node is the node we can see

次の兄弟姉妹, 前の兄弟姉妹

  すべての兄弟ノードを取得するために反復する。

5. ビフォーアフターノード

次要素, 前要素

  は兄弟ノードに特化したものではなく、前ノードや次ノードの階層に関係なく、すべてのノードに存在する。

次要素, 前要素

  前ノードと次ノードをすべて取得するために反復する。

ドキュメントツリーを検索する

1.find_all(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)

  find_all()メソッドは、現在のタグのすべての子タグを検索し、フィルタの条件が満たされているかどうかを判断します。

パラメータの説明

name パラメータ

  nameパラメータは非常に強力で、様々な方法で渡すことができ、名前nameを持つすべてのタグを見つけることができ、文字列オブジェクトは自動的に無視されます。

(a) タグ署名を渡す 最も簡単なフィルタは、タグ名です。検索メソッドにタグ名の引数を渡すと、BeautifulSoupは、文書内のすべての<a>タグを見つけるための次の例のように、タグ名と完全に一致するものを探します。

print soup.find_all('a')
#[<a class="sisters" href="http://example.com/elsie" id="link1"><! -- Elsie -- ></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a& gt;, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

結果リストの中で、まだBeautifulSoupオブジェクトである要素を返します。

(b) 正規表現を渡す

  パラメータとして正規表現を渡すと、BeautifulSoupは正規表現のmatch()でコンテンツをマッチングさせます。次の例では、bで始まるすべてのタグを見つけるので、<body> と <b> の両方のタグが見つかるはずです。

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

(c)リストを渡す

  リストパラメータを渡すと、BeautifulSoupはリスト内の任意の要素にマッチするコンテンツを返します。以下のコードでは、ドキュメント内のすべての <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="sisters" href="http://example.com/tillie" id="link3">Tillie</a>]

(d) パス トゥルー

  Trueはどんな値にもマッチします。次のコードはすべてのタグを見つけますが、文字列nodeは返しません。

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

(e) 関数を渡す

  適切なフィルターがない場合は、要素のパラメータを1つだけ受け取るメソッドを定義することもできます。このメソッドは、現在の要素がマッチして見つかれば True を、見つからなければ False を返します。

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>]

keyword parameter 与えられた名前のパラメータが組み込みの検索パラメータの1つでない場合、検索はそのパラメータを与えられた名前のタグの属性であるかのように検索し、idという名前のパラメータを含む場合、BeautifulSoupは各タグの"id" 属性を検索しますことに注意してください。

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

  hrefパラメータを渡すと、Beautiful Soupは各タグの"href"の属性を検索します。

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


  タグの複数の属性を同時にフィルタリングするために、指定した名前の複数のパラメータを使用します。

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

  ここではクラスでフィルタリングしたいのですが、クラスはpythonのキーワードなのでどうでしょうか。アンダースコアをつけると次のようになります。

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="sisters" href="http://example.com/tillie" id="link3">Tillie</a>]

attrs パラメータ

  HTML5では"など、検索で利用できないタグ属性があります。  data-*  "カスタム属性です。

data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
data_soup.find_all(data-foo="value")
# SyntaxError: keyword can't be an expression

## But you can define a dictionary parameter to search for tags with special attributes by using the attrs parameter of the find_all() method
data_soup.find_all(attrs={"data-foo": "value"})
# [<div data-foo="value">foo!</div>]

テキストパラメータ

  textパラメータを使用すると、文書内の文字列の内容を検索することができます。name パラメータのオプション値と同様に、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")) # Fuzzy lookup
[u"The Dormouse's story", u"The Dormouse's story"]

limit パラメータ

  find_all()メソッドは検索構造全体を返すので、ドキュメントツリーが大きい場合は遅くなることがあります。すべての結果が必要でない場合は、limit パラメータを使って返される結果の数を制限することができます。これは SQL の limit キーワードと似たようなもので、結果の数が上限に達すると検索結果の返送を停止します。

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>]

再帰的パラメータ

  タグのfind_all()メソッドを呼び出すと、BeautifulSoupは現在のタグのすべての子タグを取得します。タグの直接の子だけを検索したい場合は、recursive=Falseというパラメータを使用します。

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

soup.html.find_all("title", recursive=False)
# []

2.find( name , attrs , recursive , text , **kwargs )

  find_all()メソッドとの唯一の違いは、find()メソッドが結果を直接返すのに対して、find_all()メソッドは1つの要素を含む値のリストを返すことです。

3. find_parents()とfind_parent()

  find_all()とfind()は、現在のノードのすべての子や孫などを検索します。find_parents()とfind_parent()は、通常のタグと同じように現在のノードの親ノードを検索し、文書が含んでいるものを検索するために使用されます。

4. find_next_siblings()およびfind_next_sibling()  

  これら二つのメソッドは、タグが.next_siblings属性を通して渡された場合、そのタグの後に解決された全ての兄弟タグノードを繰り返し処理します。 find_next_siblings()は適格な全ての後の兄弟ノード、一方 find_next_sibling() は適格な後の最初のタグノードのみを返します。

5. find_previous_siblings()および find_previous_sibling()

  find_previous_siblings()メソッドは、対象となる全ての先行兄弟を返し、 find_previous_sibling()メソッドは、最初の対象となる先行兄弟を返します。

6. find_all_next()とfind_next()

  これらの2つのメソッドは、.next_elements属性を使用して、現在のタグの後のタグと文字列を繰り返し処理します。 find_all_next()は対象となるすべてのノードを返し、find_next()は最初の対象となるノードを返します。

7. find_all_previous()とfind_previous()

  これらの2つのメソッドは、.previous_elements属性を使用して、現在のノードの前のタグと文字列を繰り返し処理します。 find_all_previous()は対象となるすべてのノードを返し、 find_previous()は対象となる最初のノードを返します。

 CSSセレクタ

   CSSでは、タグ名はそのまま、クラス名はドットで、id名は#で記述しますが、ここでも同様の考え方で、要素をフィルタリングすることができます。  スープ.select()や 戻り値の型は  のリストです。

タグ名で探す

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

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

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

クラス名で探す


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

ID名で検索

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

複合的なルックアップ

  組み合わせ検索は、クラスファイルを書くときのタグ名とクラス名、id名の組み合わせと同じで、例えばidがlink1に等しいpタグを探すには、2つを空白で区切る必要があるのです。

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

  サブタグを直接検索します。

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

属性ルックアップ

  attribute要素もルックアップに追加することができます。属性は括弧で囲む必要があります。また、属性とタグは同じノードに属しているため、間にスペースを入れてはいけません。

print soup.select('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& gt;, <a class="sisters" href="http://example.com/tillie" id="link3">Tillie</a>]

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

  繰り返しになりますが、属性はまだ上記のルックアップと組み合わせることができ、同じノード上にないスペースで区切られ、で同じノード上のスペースなしで区切られています。

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

上記の select メソッドは結果をリスト形式で返すので、それを反復して出力し、その内容を string または get_text() メソッドで取得することができます: 。

soup = BeautifulSoup(html, 'lxml')
print type(soup.select('title'))
print soup.select('title')[0].get_text()

for title in soup.select('title'):
    print title.get_text()

ドキュメントツリーを変更する

1. .stringを修正する

タグの.stringプロパティに値を代入することは、元のコンテンツを現在のコンテンツに置き換えることと同じです。(現在のTagが他のTagを含んでいる場合、その.string属性に値を代入すると、サブTagを含むオリジナルのコンテンツがすべて上書きされます)。

例:soup.find('title').string = '新しいタイトル'

2.append()

Tag.append()メソッドは、Pythonのリストに対するappend()メソッドと同じように、タグにサブコンテントを追加するメソッドです。

例:soup.find('p').append('パラグラフの内容')

3.new_tag()

Tagを作成する最良の方法は、ファクトリーメソッドBeautifulSoup.new_tag()を呼び出すことです。

例えば、soup.find('p').append(soup.new_tag('a', href='http://www.baidu.com'))のようになります。

4.insert()、insert_before()、insert_after()

指定された位置に要素を挿入します。

例:soup.find('p').insert_after(soup.new_tag('a', href='http://www.baidu.com'))

5.decompose()

Tag.decompose()メソッドは、現在のノードを完全に削除します。

6.クリア()

Tag.clear()メソッドは、現在のノードの内容を消去します。