BeautifulSoup徹底解説：HTML解析からデータ抽出まで（Python）


[![Group2265.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075813/481b38d5-bec0-48b9-b1b8-fb2671a29d9f.png)](https://leapcell.io/)


**[Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)**


# 包括的なHTML処理チュートリアル：解析からデータ抽出まで  

## I. はじめに  
Webページの基盤となる言語であるHTML（Hypertext Markup Language）は、Webデータ処理やWeb開発などの分野で幅広く利用されています。開発者がWeb構造を最適化する場合でも、データ分析者がWebページから情報を抽出する場合でも、HTML処理は欠かせません。本チュートリアルでは、HTMLの解析、修正、データ抽出などの核心的な操作に焦点を当て、読者がHTMLを扱う包括的な方法と技術を習得できるよう支援します。  

## II. HTML基礎の復習  
### 2.1 HTMLの基本構造  
標準的なHTML文書は`<!DOCTYPE html>`宣言で始まり、`<html>`ルート要素を含み、この要素の中に`<head>`と`<body>`の2つの主要なセクションがネストされています。`<head>`セクションには通常、ページのメタ情報（タイトル、文字コード、CSSスタイルシートへのリンクなど）が含まれます。`<body>`セクションには、テキスト、画像、リンク、フォームなどのページの表示されるコンテンツが含まれます。  

```html  
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>My Page</title>  
</head>  
<body>  
    <h1>Hello, World!</h1>  
    <p>This is a simple HTML page.</p>  
</body>  
</html>  
```  

### 2.2 HTML要素と属性  
HTMLはさまざまな要素で構成され、これらの要素はタグで表されます（例：段落の`<p>`、リンクの`<a>`）。要素には追加情報を定義する属性を含めることができます。たとえば、`<a href="https://example.com">`の`href`属性はリンクの目標アドレスを指定します。属性は通常「名前-値」のペアで記述し、属性値はクォートで囲む必要があります。  

## III. HTMLの解析  
### 3.1 解析ツールとライブラリ  
異なる開発環境では、複数のツールとライブラリを使用してHTMLを解析できます：  
- **ブラウザー**：ブラウザーには組み込みの強力なHTML解析エンジンが搭載されており、HTMLコードを視覚的なページにレンダリングします。ブラウザーの開発者ツール（例：Chrome DevTools）を使用すると、解析されたDOM（Document Object Model）構造を表示し、要素のスタイルや属性を分析できます。  
- **Pythonのライブラリ**：  
  - **BeautifulSoup**：Pythonで最もよく使われるHTML解析ライブラリの1つで、HTMLとXML文書を簡単に解析し、解析木をナビゲート、検索、修正するためのシンプルなAPIを提供します。  
  - **lxml**：libxml2とlibxsltライブラリをベースに構築されたPythonライブラリで、高速に解析でき、HTMLとXMLの両方の解析に対応しており、XPath式と組み合わせて効率的にデータ抽出が行えます。  
  - **html5lib**：このライブラリは現代のブラウザーと非常に類似した方法でHTMLを解析するため、規則正しくないHTMLコードの処理に適しています。  
- **JavaScript**：ブラウザー環境では、JavaScriptを使用して`document`オブジェクトが提供する`getElementById`や`getElementsByTagName`などのメソッドを直接使用してDOMを操作し、HTMLの解析と操作を行うことができます。Node.js環境では、`jsdom`などのライブラリを使用してブラウザー環境をシミュレートしてHTMLを解析できます。  

### 3.2 PythonによるHTMLの解析  
#### 3.2.1 BeautifulSoupによる解析の例  
まず、BeautifulSoupライブラリをインストールします：  
```bash  
pip install beautifulsoup4  
```  

以下はBeautifulSoupを使用してHTMLを解析する基本的なコードです：  
```python  
from bs4 import BeautifulSoup  

html_doc = """  
<html>  
  <head><title>Sample Page</title></head>  
  <body>  
    <p class="intro">This is an introductory paragraph.</p>  
    <p class="content">Here is some content.</p>  
  </body>  
</html>  
"""  
soup = BeautifulSoup(html_doc, 'html.parser')  # Pythonの組み込みパーサーを使用  
# 他のパーサー（例：lxml）を使用する場合：soup = BeautifulSoup(html_doc, 'lxml')  
print(soup.title.string)  # 出力：Sample Page  
```  

#### 3.2.2 lxmlによる解析の例  
lxmlライブラリをインストールします：  
```bash  
pip install lxml  
```  

lxmlを使用してHTMLを解析し、XPathによるデータ抽出を行います：  
```python  
from lxml import etree  

html = """  
<html>  
  <body>  
    <div class="box">  
      <p>First paragraph</p>  
      <p>Second paragraph</p>  
    </div>  
  </body>  
</html>  
"""  
tree = etree.HTML(html)  
paragraphs = tree.xpath('//div[@class="box"]/p/text()')  
print(paragraphs)  # 出力: ['First paragraph', 'Second paragraph']  
```  

## IV. HTML文書木のナビゲーションと検索  
### 4.1 HTML文書木のナビゲーション  
BeautifulSoupを例にとります：解析後、HTML文書は文書木を形成し、複数の方法でナビゲーションすることができます：  
- **子要素へのアクセス**：タグ名を直接使用して子要素にアクセスできます（例：`soup.body.p`は`<body>`要素の下にある最初の`<p>`要素にアクセスします）。また、`contents`属性を使用して子要素のリストを取得したり、`children`属性を使用して子要素をジェネレーターとして反復処理することもできます。  
- **親要素へのアクセス**：`parent`属性を使用して現在の要素の直接の親要素を取得し、`parents`属性を使用してすべての祖先要素を再帰的に辿ることができます。  
- **兄弟要素へのアクセス**：`next_sibling`と`previous_sibling`属性を使用して、次の兄弟要素と前の兄弟要素をそれぞれ取得します。`next_siblings`と`previous_siblings`属性を使用すると、すべての後続の兄弟要素と先行する兄弟要素を反復処理できます。  

### 4.2 HTML文書木の検索  
- **`find_all()`メソッド**：BeautifulSoupの`find_all()`メソッドを使用すると、指定された条件に一致するすべての要素を検索できます。これは、タグ名や属性などでフィルタリングすることができます。例：すべての`<p>`タグを検索するには`soup.find_all('p)`、クラス`content`を持つすべての要素を検索するには`soup.find_all(class_='content')`とします。  
- **`find()`メソッド**：`find()`メソッドは、条件に一致する最初の要素を返します（例：`soup.find('a')`は文書内の最初の`<a>`要素を返します）。  
- **CSSセレクター**：`select()`メソッドを使用してCSSセレクターの構文を用いて、より柔軟に要素を検索することができます。例：クラス`box`を持つすべての`<div>`要素を選択するには`soup.select('div.box)`、id`main`を持つ要素の下にあるすべての`<li>`要素を選択するには`soup.select('#main li)`とします。  

## V. HTMLの修正  
### 5.1 要素属性の修正  
PythonのライブラリとJavaScriptの両方で、HTMLの要素属性を簡単に修正できます。  
- **Python（BeautifulSoup）**：  
  ```python  
  from bs4 import BeautifulSoup  

  html = """  
  <html>  
    <body>  
      <a href="https://old-url.com">Old Link</a>  
    </body>  
  </html>  
  """  
  soup = BeautifulSoup(html, 'html.parser')  
  link = soup.find('a')  
  link['href'] = 'https://new-url.com'  # href属性を修正  
  print(soup.prettify())  
  ```  

- **JavaScript**：  
  ```html  
  <!DOCTYPE html>  
  <html lang="en">  
  <head>  
      <meta charset="UTF-8">  
  </head>  
  <body>  
      <img id="myImage" src="old-image.jpg" alt="Old Image">  
      <script>  
          const image = document.getElementById('myImage');  
          image.src = 'new-image.jpg';  // src属性を修正  
      </script>  
  </body>  
  </html>  
  ```  

### 5.2 要素の追加と削除  
- **Python（BeautifulSoup）**：  
  - **要素の追加**：  
    ```python  
    from bs4 import BeautifulSoup  

    html = """  
    <html>  
      <body>  
        <ul id="myList"></ul>  
      </body>  
    </html>  
    """  
    soup = BeautifulSoup(html, 'html.parser')  
    ul = soup.find('ul')  
    new_li = soup.new_tag('li')  
    new_li.string = 'New Item'  
    ul.append(new_li)  # 新しい要素を追加  
    ```  

  - **要素の削除**：  
    ```python  
    from bs4 import BeautifulSoup  

    html = """  
    <html>  
      <body>  
        <p id="removeMe">This paragraph will be removed.</p>  
      </body>  
    </html>  
    """  
    soup = BeautifulSoup(html, 'html.parser')  
    p = soup.find('p', id='removeMe')  
    p.decompose()  # 要素を削除  
    ```  

- **JavaScript**：  
  - **要素の追加**：  
    ```html  
    <!DOCTYPE html>  
    <html lang="en">  
    <head>  
        <meta charset="UTF-8">  
    </head>  
    <body>  
        <div id="parentDiv"></div>  
        <script>  
            const parentDiv = document.getElementById('parentDiv');  
            const newParagraph = document.createElement('p');  
            newParagraph.textContent = 'This is a new paragraph.';  
            parentDiv.appendChild(newParagraph);  // 新しい要素を追加  
        </script>  
    </body>  
    </html>  
    ```  

  - **要素の削除**：  
    ```html  
    <!DOCTYPE html>  
    <html lang="en">  
    <head>  
        <meta charset="UTF-8">  
    </head>  
    <body>  
        <p id="removeParagraph">This paragraph will be removed.</p>  
        <script>  
            const paragraph = document.getElementById('removeParagraph');  
            paragraph.remove();  // 要素を削除  
        </script>  
    </body>  
    </html>  
    ```  

## VI. HTMLのデータ抽出  
### 6.1 テキストコンテンツの抽出  
- **Python（BeautifulSoup）**：`string`属性または`get_text()`メソッドを使用して、要素内のテキストコンテンツを取得します。例：  
  ```python  
  from bs4 import BeautifulSoup  

  html = """  
  <html>  
    <body>  
      <p class="text">Extract this text.</p>  
    </body>  
  </html>  
  """  
  soup = BeautifulSoup(html, 'html.parser')  
  text = soup.find('p', class_='text').string  
  print(text)  # 出力: Extract this text.  
  ```  

- **JavaScript**：`textContent`または`innerText`属性を使用してテキストコンテンツを取得します（例：`const element = document.getElementById('myElement'); const text = element.textContent;`）。  

### 6.2 属性値の抽出  
PythonとJavaScriptの両方で、HTMLの要素属性値を簡単に抽出できます。例：`<a>`タグの`href`属性値を抽出する場合：  
- **Python（BeautifulSoup）**：`href = soup.find('a')['href']`  
- **JavaScript**：`const link = document.querySelector('a'); const href = link.getAttribute('href');`  

### 6.3 複雑なデータ抽出  
実際のアプリケーションでは、複雑なHTML構造からデータを抽出する必要がよくあります（例：商品リストのWebページから商品名、価格、リンクを抽出する場合）。このような場合、ループと条件文を上記のナビゲーションと検索メソッドと組み合わせて、必要なデータを巡回して抽出します：  
```python  
from bs4 import BeautifulSoup  
import requests  

url = "https://example.com/products"  
response = requests.get(url)  
soup = BeautifulSoup(response.text, 'html.parser')  

products = []  
for product_div in soup.find_all('div', class_='product'):  
    name = product_div.find('h2', class_='product-name').string  
    price = product_div.find('span', class_='product-price').string  
    link = product_div.find('a')['href']  
    products.append({'name': name, 'price': price, 'link': link})  

print(products)  
```  

## VII. 規則正しくないHTMLの処理  
実際には、HTMLコードには規則正しくない形式（閉じタグのないタグや属性のクォートが欠落しているなど）がよく見られます。異なるパーサーは規則正しくないHTMLの処理方法が異なります：  
- **html5lib**：このパーサーはブラウザーと同様の動作をし、誤った構造を修正しようとするため、規則正しくないHTMLの処理に優れています。  
- **lxml**：lxmlパーサーは比較的に厳格ですが、ある程度の容錯能力を備えています。非常に規則正しくないHTMLを処理する場合には、事前処理を
続けます。


必要がある場合は、`lxml.etree.HTMLParser` を `recover=True` パラメータとともに使用して回復モードを有効にすることもできます。  
- **BeautifulSoup**：選択したパーサーの特性に基づいて規則正しくないHTMLを処理します。複雑な規則正しくない文書の場合、html5libパーサーを優先的に使用することをおすすめします。  

## VIII. パフォーマンス最適化とベストプラクティス  
### 8.1 適切なパーサーの選択  
特定のニーズに基づいてパーサーを選択します：  
- **lxml**：HTMLが比較的標準化されている場合、速度重視の場合は最適です。  
- **html5lib**：規則正しくないHTMLの処理により適しています。  
- **html.parser（Python組み込み）**：シンプルでパフォーマンス要件が中等程度の基本的なニーズに対応します。  

### 8.2 冗長な解析の削減  
複数のHTML文書を処理する場合や、同じ文書で複数回操作を行う場合は、冗長な解析を避けます。解析結果をキャッシュするか、1回の解析で全ての関連操作を完了させます。  

### 8.3 検索メソッドの適切な使用  
要素を検索する際には、不必要なトラバーサルを減らすために、より正確なフィルタ条件を使用します。例えば、CSSセレクターやXPath式を使用すると、ターゲット要素をより効率的に特定できます。  

## IX. まとめ  
本チュートリアルを通じて、HTML処理の全般的な内容（基本構造、解析方法、文書木のナビゲーション、修正操作、データ抽出、規則正しくないHTMLの処理技術など）を学ぶことができました。実践においては、特定のシナリオに基づいて適切なツールと方法を選択し、パフォーマンス最適化とベストプラクティスに注意することで、より効率的にHTML処理タスクを完了することができます。Web開発やデータ収集のいずれの分野においても、HTML処理スキルを習得することで作業が大きく円滑化されるでしょう。  

本チュートリアルではHTML処理の主要な側面を網羅しています。学習中に特定のユースケースがある場合や、特定のセクションをさらに深掘りしたい場合は、いつでもお問い合わせください。  


# [Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)  

最後に、Pythonサービスのデプロイに最適なプラットフォーム **[Leapcell](https://leapcell.io/)** をご紹介します。  

[![brandpic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075813/0f9cf7a9-2d44-4888-a881-a868ff6a643e.png)](https://leapcell.io/)


### 🚀 お好きな言語で構築  
JavaScript、Python、Go、Rustで気軽に開発ができます。  

### 🌍 無料で無制限のプロジェクトをデプロイ  
使用分のみ課金 ― リクエストがなければ料金が発生しません。  

### ⚡ 使った分だけ課金、隠れた費用はありません  
アイドル料金は一切なし、シームレスなスケーラビリティが実現されています。  

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075813/fd23929a-6792-4e9d-8483-07f68150e20e.png)](https://leapcell.io/)


[📖 ドキュメントを確認する](https://docs.leapcell.io/)  

🔹 Twitterでフォローして最新情報を入手：[@LeapcellHQ](https://x.com/LeapcellHQ)