例えば、以下のようなhtmlがあって、aタグの「サンプルリンク」文字列までたどり着きたい場合、
<div> <article> <p id="main"> <a href="https://example.com">サンプルリンク</a> </p> </article> <div>
beautifulsoupでは色々な道順が用意されていて面白いです。
パーサーはlxmlを使用しています。
soup = BeautifulSoup(response.body, "lxml") #以降は省略 #findで要素を取得 link_name = soup.find('p', id='main').a.string print('link_name', link_name) #link_name サンプルリンク #CSSセレクタで指定して要素を取得(戻り値はリスト) link_name = soup.select('#main > a')[0].string print('link_name', link_name) #link_name サンプルリンク #ドット繋ぎで要素を取得 link_name = soup.div.article.p.a.string print('link_name', link_name) #link_name サンプルリンク
stringとtextの違い
<p id="main"> <a href="https://example.com">サンプルリンク</a> </p>
_a = soup.find('p', id='main').a print('_a.string', _a.string) #_a.string サンプルリンク print('type(_a.string)', type(_a.string)) #type(_a.string) <class 'bs4.element.NavigableString'> print('_a.text', _a.text) #_a.text サンプルリンク print('type(_a.text)', type(_a.text)) #type(_a.text) <class 'str'>
stringはNavigableStringオブジェクトというやつで、textは文字列です。
NavigableStringは、htmlをツリー構造化したbeautifulsoup独自のクラスだそうです。
例えば以下の様に、span要素の中にimg要素が入っているといった、文字列以外の要素が含まれている場合などは、spanのstringを表示させようとしてもNoneになります。
こういう場合はtextを使います。
textはタグを除去して全テキストを抽出します。
<div> <span class="new">spanコンテンツ<img src="https://example.com/img/img.jpg" /></span> <div>
span_str = soup.div.span.string print('span_str', span_str) #span_str None span_text = soup.div.span.text print('span_text', span_text) #span_text spanコンテンツ
もしくはこんな方法もあります。
extractでdivからimg要素を除去します。
stringは、タグの子要素が1つだけ(NavigableString)なら利用できるので、この場合はdivからimgが除去されて「spanコンテンツ」というNavigableStringオブジェクトだけが残るので、条件が整うことになります。
div = soup.div img = div.img.extract() span_str = div.span.string print('span_str', span_str) #span_str spanコンテンツ
文字列の空白除去
よくあるのが、タグの中に空白や改行が混ざっているケースです。
そーいう場合は、pythonのstrip関数で先頭と末尾の空白を削除します。
<div class="new"> divコンテンツ </div>
div = soup.div print('div.string', div.string) #div.string # divコンテンツ # strip_str = str(div.string).strip() print('strip_str', strip_str) #strip_str divコンテンツ
兄弟要素
同じ階層の要素を兄弟要素と言います。
以下だと、dl同士は兄弟要素で、dtとddも兄弟要素です。
<div class="news_list"> <dl> <dt><span class="date">2018年1月1日</span></dt> <dd><a href="https://example.com/1">コンテンツ1</a></dd> </dl> <dl> <dt><span class="date">2018年1月2日</span></dt> <dd><a href="https://example.com/2">コンテンツ2</a></dd> </dl> <dl> <dt><span class="date">2018年1月3日</span></dt> <dd><a href="https://example.com/3">コンテンツ3</a></dd> </dl> </div>
#classによる検索 #「class」はpythonの予約語なため「class_」を使う news_list = soup.find('div', class_='news_list') for dl in news_list.find_all('dl'): dd = dl.dd content = dd.a.text #ddの一つ前のdt兄弟要素を取得する date = dd.find_previous_sibling('dt').text print(date, content) #2018年1月1日 コンテンツ1 #2018年1月2日 コンテンツ2 #2018年1月3日 コンテンツ3
previous_siblingという属性も使えますが、</dt>と<dd>の間の改行(\n)を要素として取得するので、もっとシビアな調整が必要になりそうです。
二番目の要素の取得
<article> <div>コンテンツ1</div> <div>コンテンツ2</div> <div>コンテンツ3</div> </article>
二番目の「コンテンツ2」って要素が取得したい場合はこれでいいのかな?
なんかちょっとブサイクなような気もするんだけど……。
second_div = soup.article.div.find_next_sibling('div') print('second_div.text', second_div.text) #second_div.text コンテンツ2
あ、こんな感じでもいけるか。
divs = soup.article.find_all('div') print('divs[1].text', divs[1].text) #second_div.text コンテンツ2
属性の存在チェック・取得
<article> <div id="id_content">IDコンテンツ</div> <div class="class_content">Classコンテンツ</div> </article>
divs = soup.article.find_all('div') print(divs[0].has_attr('id')) #True print(divs[0].has_attr('class')) #False print(divs[0].get('id')) #id_content print(divs[0].get('class')) #None print(divs[1].has_attr('id')) #False print(divs[1].has_attr('class')) #True print(divs[1].get('id')) #None print(divs[1].get('class')) #['class_content']
has_attrで属性の存在確認ができます。
getで属性を取得した時に、idと違いclassの場合はリストが返ってきます。
最強のドキュメント
kondou.com - Beautiful Soup 4.2.0 Doc. 日本語訳 (2013-11-19最終更新)