はじめに
スクレイピングとはWebサイトからデータを収集すること.その収集するロボットのことをクローラーと呼ぶ.
やっていいサイト,やっていけないサイトあるので注意.
ログインとかボタン押すなどの自動操作も可能(Seleniumライブラリなど).Web画面のスクショもできる.
このペーじ,pythonのライブラリBeautifu SoupとSeleniumの説明をする.
Beautiful Soup
名前は不思議の国のアリスから.
HTML,XMLを解析し,データを取得してくれる.
公式
https://www.crummy.com/software/BeautifulSoup/bs4/doc/(2023年1月)
解析するためのはじめの一歩
解析するやつをパーサーと呼ぶ.
python標準ライブラリに含まれるHTMLパーサ以外にも,サードpythonパーサをサポートしてる.
ここら辺はよくわからん.
やり方
ドキュメントの解析はSoupオブジェクトが行う.
BeautifulSoupコンストラクタに渡して,Soupオブジェクトを作成する.
コンストラクタによって,Unicodeに変換される.
HTMLファイルのSoupオブジェクトを作成
from bs4 import BeautifulSoup
with open("index.html") as fp:
soup = BeautifulSoup(fp, 'html.parser')
"html.pearser"
はpython標準ライブラリに含まれるパーサ.
HTML文字列のSoupオブジェクトを作成
soup = BeautifulSoup("<html>a web page</html>", 'html.parser')
扱うオブジェクト
なんと4つのみのオブジェクトを扱うだけでHTMLを操れる.
それぞれprettify()メソッドでインデントつけて表示してくれる.
BeautifulSoupオブジェクト
解析したドキュメントの全体のオブジェクト.
name属性は''[document]'
.
Tagオブジェクト
BeautifulSoupオブジェクトから,HTMLのタグと同じ名前のTagオブジェクトを扱える.
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser')
tag = soup.b
type(tag)
# <class 'bs4.element.Tag'>
name属性
HTMLの各タグ名.
上書き可能で,上書きしたタグになる.
attrs属性
そのTagオブジェクトにあるHTMLの全属性.辞書型.
HTMLの属性の参照
辞書のように属性を扱える.
tag = BeautifulSoup('<b id="boldest">bold</b>', 'html.parser').b
tag['id']
# 'boldest'
これも追加,削除,変更が可能.
リストとして取得することもできる.
id_soup.p.get_attribute_list('id')
# ["my id"]
HTMLの属性の値が複数ある場合
複数の値をもてるHTMLの属性(class, rel, rev, accept-charset, headers, accesskey等)の値はリストとして扱うことができる.
BeautifulSoupで複数の値を設定したい場合も,リストで渡す.
css_soup = BeautifulSoup('<p class="body strikeout"></p>', 'html.parser')
css_soup.p['class']
# ['body', 'strikeout']
リストではなく,文字列のまま扱うためには,BeautifulSoupオブジェクトのコンストラクタに multi_valued_attributes=None
を渡す.
no_list_soup = BeautifulSoup('<p class="body strikeout"></p>', 'html.parser', multi_valued_attributes=None)
no_list_soup.p['class']
# 'body strikeout'
NavigableStringオブジェクト
テキストのオブジェクト.
Tagオブジェクトのstringから取得可能.
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser')
tag = soup.b
tag.string
# 'Extremely bold'
type(tag.string)
# <class 'bs4.element.NavigableString'>
これは上書きができない.
文字列の変換
tag.string.replace_with("No longer bold")
tag
# <b class="boldest">No longer bold</b>
Commentオブジェクト
NavigableStringオブジェクトの特別ver. みたいなものだそう.
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup, 'html.parser')
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>
うぇぶぺーじを解析する
許可不許可のかくにん
URLで次のように '/robots.txt'
を追加して検索すると大体宣言ページがある.
https://hogehoge.com/robots.txt
クローラ(スクレイピングするマシーン)の宣言なので,不許可でもやろうと思えばできます.
例:とあるサイトのrobots.txtページ
User-agent: *
Allow: /wp-admin/admin-ajax.php
Disallow: /wp-admin/
User-agent: *
全てのユーザ対象.
Allow
クローラOKぺーじです!
Disallow
クローラ対象外ぺーじです!
うぇぶぺーじからデータとってくる
requestライブラリをつかうのでインストールしましょう.
例:hogehoge.comの情報とってくる
from bs4 import BeautifulSoup
import requests
url = "https://hogehoge.com"
req = requests.get(url)
soup = BeautifulSoup(req.text, 'html.parser')
print(soup.text)
基本的な情報検索メソッド
// はじめの一個のみ
soup.find(name, attrs, recursive, string, **kwargs)
// 合致したもの全て(数はlimit引数で指定可能)
soup.find_all(name, attrs, recursive, string, limit, **kwargs)
name:
タグの名前
**kwargs
属性を検索できるattr='value'
の形で引数に渡す.
なお,class
はpythonの予約語にあるので, class属性を指定する場合は,'class_'
で指定する.
クラス属性の内,一つでも合致したら取得される.class='ho ge'
に対して,find_all(class='ho ge')
は合致しない.
attrs
一部属性(name,data-*等)はキーワード引数として使用できないものもある.
これらに対して,辞書にしてattrs引数に渡す.
soup.find_all(attrs={"data-foo": "value"})
string
.stringが一致するものを取得する.
limit: int
最大取得数.
recursive=True:
全ての子孫を調べるか.
子のみにしたいならFalseを指定する.
うぇぶぺーじから取得するデータに関して,フィルターをかけることができる.
string,正規表現
それに合致するもの,
4.4.0以降.以前はtextと呼ばれていた.
リスト
それに含まれるもの(stringはできる,正規表現,関数等も良いのか?)と合致するタグ名のもの.
関数
「class属性がありかつid属性がないもの」等の条件つけられる.boolean型のものでないとだめ.
True
全て合致.
使用例例
titleタグ検索
soup.find_all('title')
id="hoge"を検索
soup.find_all(id='hoge')
hrefに"hoge"が含まれるものを検索
soup.find_all(href=re.compile('hoge'))
id属性があるものを検索
soup.find_all(id=True)
複数の属性をAND条件で検索
soup.find_all(id='hoge-id', href=re.compile('hoge'))
複数のクラス属性をAND条件で検索するには,find_all()とは別のselect()を使う.CSSセレクタで指定できる.
soup.select("p.ho.ge")
stringで文字列を検索
// 完全一致
soup.find_all(string='hoge')
// 部分一致
soup.find_all(string=re.complie('ho')
stringのみ検索では文字列のみが返却されるので注意.
一つ目の例だと['hoge']
が返却される.
タグとstringでAND条件で検索??
soup.find_all('a', string='hoge')
引数string=""ではなく,text=""ってやった方が良いのかも.
それ以前/以後にある同階層にあるタグの検索
// それ以前,複数および1個のみ
find_previous_siblings(name, attrs, string, limit, **kwargs)
find_previous_sibling(name, attrs, string, **kwargs)
// それ以降,複数および1個のみ
find_next_siblings(name, attrs, string, limit, **kwargs)
find_next_sibling(name, attrs, string, **kwargs)
CSSセレクタで検索する
特定の階層構造を検索するならこっちの方が簡単そうな?
例:
// タグの下のタグ
soup.select('body a')
// タグの直下のタグ
soup.select('p > a')
// タグの同階層
soup.select('#link1 ~ .sister')
ツリー間を辿っていく
タグ名の属性があるので,それを辿れる.
同じ名前のタグがある場合は,はじめの一個目が返される.
1番の親はBeautfulSoupオブジェクト
soup.body.b
複数の子を辿る
リストで取得する場合
soup.contens
イテレートする場合
for child in soup.children:
print(tag)
直下でない子供も辿る
for child in soup.descendants:
print(child)
子供のStringを見る
Stringがある唯一の子供を持つ場合
親の.Stringは子の.Stringと一致する
<head> <title> HOGE </title> </head>
head_tag.string
> 'HOGE'
複数の子供がいる場合は,どれを返せば良いかわからんのでNoneになる.
全てのStringを表示する(ジェネレータ)
// 改行文字も表示する
for string in soup.strings:
print(repr(string))
// 改行文字を表示しない
for string in soup.stripped_strings:
print(repr(string))
親を辿る
title_tag.parent
BeuautifulSoupオブジェクトの親はNone
兄弟を辿る
sibling_soup.next_sibling
sibling_soup.previous_sibling
タグ以外に文字列(改行文字含む)があると,それも兄弟判定.
よって,隣のタグをたどりたい場合は,2回siblingを呼び出す必要あり.
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
link = link.a
link
# <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
link.next_sibling
# '\n'
link.next_sibling.next_sibling
# <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
イテレートする
複数にするとジェネレータになる?
for sib in sibling_soup.next_siblings:
print(sib)
要素を辿る
soup.next_element
soup.previous_element
例:
.html
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="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
.py
for element in last_a_tag.next_elements:
print(repr(element))
# 'Tillie'
# ';\nand they lived at the bottom of a well.'
# '\n'
# <p class="story">...</p>
# '...'
# '\n'