BeautifulSoupを用いたHTMLデータの検索方法

#1．概要

データ解析用にいろいろなタグ情報をHTMLに埋め込み、「その埋め込んだデータが正しく表示されているか」を自動テストで検出できないか、いろいろ方法を調べてみました。

その中で、PythonのBeautifulSoupを用いて対象のHTMLデータを検索出来たので、その方法をまとめます。
**※本件は検索方法のみで解析については記載しておりません。**

#2．検索方法の考え方

BeautifulSoupで対象のHTMLデータを取得するには、まず起点となる<>で囲まれたデータを見つけます。そして、起点となるタグに含まれている情報を1つ1つ記載していくことで、HTMLデータを検索します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547663/ff8a78d7-26bc-1dc4-857b-f11f3511c442.png)

起点とすべきデータはユニークな値を持つものを指定すると、その項目のみを指定すればいいので、検索プログラムの記載がシンプルになります。


また、3章以降の例文を動作するには、以下のプログラムが記載されていることを前提としております。

```python

import requests
import re
from bs4 import BeautifulSoup

res = requests.get('ここに解析対象のURLを記載')
c=res.content
soup = BeautifulSoup(c,'html.parser')

#ここに例文を記載

print(elems)
```

#3．基本的なデータの取得方法

取得したいデータが1つの場合はfindを、複数の場合はfind_allを利用します（その他にselectという方法もありますが、今回除外します）。以下の例文ではfind_allを用いています。


###・直接タグを指定するパターン

```HTML
#検索したいタグの構造
<script>～</script>
```

```python
elems = soup.find_all("script")
```

単純なタグで囲まれた部分を検索する場合、利用します。



###・複数のタグを指定するパターン（リストを利用）

```HTML
#検索したいタグの構造
<h1>～</h1>
<div>～</div>
```

```python
elems = soup.find_all(["h1","div"])
```

タグで囲まれているデータを複数検索したい場合、利用します。


###・キーワードを指定するパターン（完全一致）

```HTML
#検索したいタグの構造
<a class = "test">～</a>
```

```python
elems = soup.find_all(class_="test")
```

各タグの中で「=」を用いて値が代入されている項目がある場合、利用します。
また、classを指定する場合は「class_」とする必要があります。（Pythonの予約語にclassが使われているので）
検索項目が2つある場合は、以下のように[]を使います。

```python
elems = soup.find_all(id=["test1", "test2"])
```

###・キーワードを指定するパターン（部分一致）

```HTML
#検索したいタグの構造
<a href="http://○○/△△.html">～<a>
```

```python
elems = soup.find_all(href=re.compile("http://"))
```

「=」で代入されている値を部分的に検索する場合などに利用します。


###・キーワードを指定するパターン（attrs属性を利用した完全一致）

```HTML
#検索したいタグの構造
<a href="http://○○/△△.html">～<a>
```

```python
elems = soup.find_all(attrs={"href":"http://○○/△△.html"})
```

HTML5のdataタグなど、キーワードとして使えないものがあるときは「attrs」を利用します

```python
#例）
×　elems = soup.find_all("meta",name="test")
⇒TypeError: find_all() got multiple values for argument 'name'

○　elems = soup.find_all("meta",attrs={"name":"test"})
```


###・項目のありなしを確認するパターン（値が入っていればTrue、入っていなければFalse）

```HTML
#検索したいタグの構造
<a href="http://○○/△△.html">～<a>
```

```python
elems = soup.find_all(href=True)
```

hrefなどのタグに何でもいいので値が入っているものを調べる場合、利用します。
値が入っていない場合は、以下のようにFalseを指定します

```python
elems = soup.find_all(id=False)
```


###・タグで囲まれているテキストを検索するパターン（完全一致）

```HTML
#検索したいタグの構造
<a href="http://○○/△△.html">ご利用案内<a> #ここのご利用案内のみ検索したい
```

```python
elems = soup.find_all(text='ご利用案内')
```

タグで囲まれた中のテキストのみ抽出したい場合、利用します。



###・タグで囲まれているテキストを検索するパターン（部分一致）

```HTML
#検索したいタグの構造
<a href="http://○○/△△.html">ご利用案内<a> #ここのご利用案内のみ検索したい
```

```python
elems = soup.find_all(text=re.compile("ご"))
```

タグで囲まれた中のテキストのみ抽出したい場合、利用します。こちらは完全一致ではなく、部分一致を用いています。


###・収集するタグの数を指定するパターン（find_allのみ利用できる）

```HTML
#検索したいタグの構造
<p>test1</p> #ここだけ取得したい
<p>test2</p>
```

```python
elems = soup.find_all('p', limit=1)
```

複数のタグから指定した数だけ取得したい場合、利用します。


#4．応用編

指定した構造データを見つけるには、**上記の基本パターンを組み合わせて使うこと**が多いです。

###・タグとキーワードを指定

```HTML
#検索したいタグの構造
<meta name="test">
```

```python
elems = soup.find_all("meta",attrs={"name":"test"})
```


metaというタグにname=testをしているタグを検索する場合、利用します。

###・タグとテキストを指定

```HTML
#検索したいタグの構造
<a href="http://○○/△△.html">テストです<a>
```

```python
elems = soup.find_all("a",text="テストです")
```

aタグで「テストです」とテキスト文が記載されているタグを検索する場合

###・attrsで項目を複数指定

```HTML
#検索したいタグの構造
<a href="http://○○/△△.html" title="test">テストです</a>
```

```python
elems = soup.find_all(attrs={"title":"test","href":"http://○○/△△.html"})
```

###・起点の前後のタグを指定

また、同じようなタグが複数あるため指定の構造データを検索できない場合は、**起点となる構造データを決めてそのデータの前後を検索するようにします。**前後の検索には、「next_element」と「previous_element」を利用します。
（next_elementは後の要素を検索する場合、previous_elementは前の要素を検索する場合に利用します）

```HTML
#検索したいタグの構造
<ui>
　<li>
　　<a href="http://○○/△△.html">test</a>
　</li>
</ui>

#取得したいのはここ
<ui>
　<li>
　　<a href="http://□□/☆☆.html">test2</a> #ここを起点にデータを取得する
　</li>
</ui>
```

```python
elems = soup.find_all("a",href="http://□□/☆☆.html")
elems2 = elems[0].previous_element.previous_element #.previous_elementを2回使い、<ui><li>までを含むようにする
```

###・関数を作成して指定

関数を作成し、複雑なタグ構造からデータを取得することもできます。

```HTML
#検索したいタグの構造
<a class="test">～</a> #ここだけ取得したい
<a id="test">～</a>
```

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

elems = soup.find_all(has_class_but_no_id)
```