現場のスクレイピング

みなさん、スクレイピングしてますか？
この記事では現場でよく使う実践向けの内容をまとめました
- **idやclass属性が無くても抽出する方法**
- **ログインが必要なサイトの攻略法**
- **無限スクロールがあるサイトの攻略法**
- **```<iframe>```をスクレイピングする方法**
- **他、いろいろなユースケース**





## 使用するツール
| ツール | バージョン |
|:-|:-|
| Python | 3.10.6 |
| ChromeDriver | 107.0.5304.62 |
| selenium | 4.6.0 |
| beautifulsoup4 | 4.11.1 |

:::note warn
selenium 3系とselenium 4系でコードの書き方が異なるので注意
:::





## ツールを準備する
### Python 3.10.6
ダウンロード先：https://www.python.org/downloads/
インストールするときの注意点としては **```Add Python 3.x to PATH```** のチェックを入れ忘れないこと


### ChromeDriver
ダウンロード先：https://sites.google.com/chromium.org/driver/downloads
Google Chrome と ChromeDriver のバージョンを合わせる必要があるので
まずは Google Chrome のバージョンを確認してください。
確認する方法がいくつかありますが、得られる結果は同じです。

- Google Chromeの右上の```3点リーダー```から、```ヘルプ``` ＞ ```Google Chrome について```
- Google Chromeのアドレスバーに以下を入力して確認する
    - [chrome://version](chrome://version)
    - [chrome://settings/help](chrome://settings/help)

投稿時に確認したバージョンは```バージョン: 107.0.5304.106```でした。
次に、ChromeDriverのダウンロード先にアクセスしてください。
**Current Releases** の下に箇条書きがあるので条件に当てはまるバージョンをダウンロードします。

> **Current Releases** 
> - If you are using Chrome version 108, please download ChromeDriver 108.0.5359.22
> - If you are using Chrome version 107, please download ChromeDriver 107.0.5304.62
> - If you are using Chrome version 106, please download ChromeDriver 106.0.5249.61

ダウンロードしたファイルを解凍したら、任意の場所に移動させます。
自分はCドライブの直下に移動させました。
```C:\chromedriver_win32\chromedriver.exe```

:::note info
環境変数のススメ
```C:\chromedriver_win32```
上記のパスを環境変数に追加しておくと、短くなってスッキリします。
追加前：```service = ChromeService(executable_path="C:\chromedriver_win32\chromedriver.exe")```
追加後：```service = ChromeService()```
:::


### selenium, beautifulsoup4
コンソール内に以下の2行を入力してください。
```terminal:Console
pip install selenium
pip install beautifulsoup4
```

:::note info
スペース区切りで同時にインストールすることもできます
```pip install selenium beautifulsoup4```
:::

ここまで済ませたら準備完了です。





## スクレイピングの基本
下記コードを実行してスクレイピングしてみよう

```python:scrape1.py
import time

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.by import By

# ブラウザのオプション
options = Options()
options.add_argument("--blink-settings=imagesEnabled=false")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--disable-browser-side-navigation")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-extensions")
options.add_argument("--disable-gpu")
# options.add_argument("--headless")  # ブラウザを非表示で起動する
options.add_argument("--ignore-certificate-errors")
options.add_argument("--incognito")
options.add_argument("--no-sandbox")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("excludeSwitches", ["enable-logging"])
options.add_experimental_option("useAutomationExtension", False)

# ブラウザ起動
service = ChromeService(executable_path="C:\chromedriver_win32\chromedriver.exe")
driver = webdriver.Chrome(service=service, options=options)

# 要素が見つかるまで10秒待つ
driver.implicitly_wait(10)

# URLにアクセス
url = "https://www.google.com/"
driver.get(url)
time.sleep(1)

# 検索フォームを探す
input_tag = driver.find_element(By.NAME, "q")

# 検索フォームに文字を入力
input_tag.send_keys("Qiita 面白い記事")

# 検索ボタンをクリック
driver.find_element(By.NAME, "btnK").click()

# ブラウザのHTMLを取得
soup = BeautifulSoup(driver.page_source, features="html.parser")

# 検索結果１つ目のタイトルをターミナルに表示
print(soup.select_one("h3").text)
```

Google Chromeが起動してターミナルに```ネタ - Qiita```が表示されましたか？
![qiita1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1934278/41c136eb-483f-41a8-c236-5feb3e6bd5e8.png)
このコードを基本に実践的なスクレイピングを作っていきます。

::: note info
```# options.add_argument("--headless")  # ブラウザを非表示で起動する```について
実行テスト中やデバッグするときはコメントアウトして、問題や動作の確認ができるようにします。
スクレイピング中のブラウザを閉じてしまった、などの誤操作をしないようにするため
コードが完成したらコメントアウトを外して、ヘッドレスブラウザ(非表示)で起動しよう。
:::





## 要素の探し方
```python
# 検索フォームを探す
input_tag = driver.find_element(By.NAME, "q")
```

この要素をどこから持ってきたのか、**Google Chrome**を使って軽く説明します

1. ```Ctrl + Shift + V```で開発者ツールを出す
1. 欲しい要素の場所をクリックする
1. タグ内から指定する要素を決める（今回は赤枠内）
![qiita2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1934278/bfc43652-8219-6769-9da8-88c0e973ecff.png)
1. WebDriverやBeautifulSoupの引数に当てはめる

無理に覚えなくても多用するので自然と覚えます。
なので、最初は何となく覚えておけば大丈夫です。
<font color="gray">CSSセレクターで指定する方法もあるけど、ここでは省略</font>

ここまで、簡単な基礎となります。





## 実践的なスクレイピング
### ログインが不要なサイト
```python:scrape2.py
import time

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService

# ブラウザのオプション
options = Options()
options.add_argument("--blink-settings=imagesEnabled=false")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--disable-browser-side-navigation")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-extensions")
options.add_argument("--disable-gpu")
# options.add_argument("--headless")  # ブラウザを非表示で起動する
options.add_argument("--ignore-certificate-errors")
options.add_argument("--incognito")
options.add_argument("--no-sandbox")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("excludeSwitches", ["enable-logging"])
options.add_experimental_option("useAutomationExtension", False)

# ブラウザ起動
service = ChromeService(executable_path="C:\chromedriver_win32\chromedriver.exe")
driver = webdriver.Chrome(service=service, options=options)

# 要素が見つかるまで10秒待つ
driver.implicitly_wait(10)

# URLにアクセス
url = "https://books.toscrape.com/index.html"
driver.get(url)
time.sleep(1)

# ブラウザのHTMLを取得
soup = BeautifulSoup(driver.page_source, features="html.parser")

# １つ目の本のタイトルをターミナルに表示
print(soup.select_one("h3").text)
```

1つ目の本のタイトル(リンクテキスト)が表示されたらスクレイピングができています
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1934278/78aacd8c-2fa5-877b-bd28-b5d4e8ead89a.png)
上の```scrape2.py```を元にケース別のコードを書いていきます


#### 特定のテキストを含む要素を全部取得 (完全一致)
```python:ex1
button_tags = soup.find_all(text="Add to basket")
```

この方法だと```<td>Add to basket</td>```や```<p>Add to basket</p>```など
どんなタグであれ、該当するテキストは全部取得します。実務であまり使わないかな・・・


#### 特定のテキストを含む要素を全部取得＋タグ指定 (完全一致)
```python:ex2
button_tags = soup.find_all("button", text="Add to basket")
```


#### 特定のテキストを含む要素を全部取得＋タグ指定 (正規表現)
```python:ex3
import re
button_tags = soup.find_all("button", text=re.compile("Add"))
```

soup.find, soup.find_all で正規表現を使うには```import re```が必要です

:::note info
スクレイピングでよく使う正規表現たち( find系 )
前方一致：```soup.find_all("button", text=re.compile("^Add"))```
後方一致：```soup.find_all("button", text=re.compile("basket$"))```
ワイルドカード：```soup.find_all("button", text=re.compile("Add(.*)basket"))```
大文字・小文字を区別しない：```soup.find_all("button", text=re.compile("ADD", re.I))```
:::

:::note info
スクレイピングでよく使う正規表現たち( select系 )
前方一致：```soup.select('img[alt^="The"]')```
後方一致：```soup.select('img[alt$="The"]')```
ワイルドカード：```soup.select('img[alt*="The"]')```
大文字・小文字を区別しない：```soup.select('img[alt*="The" i]')```
:::


#### alt属性に```The```が含まれるimgタグを全部取得
```python:ex4
# soup.select
img_tags = soup.select('img[alt*="The"]')

# soup.find_all
import re
img_tags = soup.find_all("img", alt=re.compile("The"))
```


#### alt属性に```The```が含まれるimgタグを全部取得 (大文字・小文字を区別しない)
```python:ex5
# soup.select
img_tags = soup.select('img[alt*="The" i]')

# soup.find_all
import re
img_tags = soup.find_all("img", alt=re.compile("The", re.I))
```

**ex4** と **ex5** で2パターン書きましたが、自分のおすすめはCSSセレクターが書ける```soup.select```です。
```soup.select```や```soup.find_all```もそれぞれで使う場面があるので、好みがわかれるところ。


#### 20冊の本からリンク先のhrefを全部取得したい
```python:ex6
# soup.select
a_tags = soup.select("h3 > a")
href_list = [a.get("href") for a in a_tags]

# soup.find_all
h3_tags = soup.find_all("h3")
a_tags = [h3.find("a") for h3 in h3_tags]
href_list = [a.get("href") for a in a_tags]
```

:::note info
相対URLから絶対URLにする
```href_list```は```catalogue```から始まる相対URLのリストです
このまま使うことはほぼ無いので、絶対URLにしておくと◎

```python:ex6-1
from urllib.parse import urljoin
link_list = [urljoin(url, href) for href in href_list]
```
:::


#### 全てのh1タグを取得したい
```python:ex7
# soup.select
h1_tags = soup.select("h1")

# soup.find_all
h1_tags = soup.find_all("h1")
```


#### 全てのh1, h3タグを取得したい
```python:ex8
# soup.select
h13_tags = soup.select("h1, h3")

# soup.find_all
h13_tags = soup.find_all(["h1", "h3"])
```


#### 全てのhタグを取得したい
```python:ex9
# soup.select
# 公式ドキュメントざっと見した限りでは該当なし

# soup.find_all
h_tags = soup.find_all(re.compile("h[0-9]"))
```


#### とある要素の親要素を取得したい
```python:ex10
# soup.select_one
h1_parent = soup.select_one("h1").parent

# soup.find
h1_parent = soup.find("h1").parent
```

:::note warn
```parent```について
リストに対しては```AttributeError```となるので、1つの要素に対して使うようにしてください。
<font color="red">select, find_all</font>なら、複数の要素を<font color="red">**リスト**</font>で返す
<font color="blue">select_one, find</font>なら、1つの要素を<font color="blue">**変数**</font>で返す
なので、<font color="red">select, find_all</font> の場合は```for ～ in```やインデックス番号で1つの要素にしてあげる必要があります。
何がしたいのか、用途に応じて使い分けることが必要です。
:::


#### とある要素のn番目の子要素を取得したい
```python:ex11
# soup.select
p_child = soup.select_one("section > div:nth-of-type(2)")

# soup.find_all
# 公式ドキュメントざっと見した限りでは該当なし
```

<font color="gray">実務で使ったことない・・・</font>


#### 複数の属性ごとに条件、imgタグを全部取得
```python:ex12
# soup.select
img_tags = soup.select('button[type="submit"][data-loading-text="Adding..."]')

# soup.find_all
img_tags = soup.find_all('button', attrs={"type":"submit", "data-loading-text":"Adding..."})
```


#### 同じ属性に複数の条件、divタグを全部取得
```python:ex13
# soup.select
soup.select('div[class="image_container"], div[class="product_price"]')

# soup.find_all
soup.find_all('div', attrs={"class": ['image_container', 'product_price']})
```
**ex12** と **ex13** のコードは、正規表現を使った書き方もできます。





### ログインが必要なサイト
```python:scrape3.py
import time

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.by import By

# ブラウザのオプション
options = Options()
options.add_argument("--blink-settings=imagesEnabled=false")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--disable-browser-side-navigation")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-extensions")
options.add_argument("--disable-gpu")
# options.add_argument("--headless")  # ブラウザを非表示で起動する
options.add_argument("--ignore-certificate-errors")
options.add_argument("--incognito")
options.add_argument("--no-sandbox")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("excludeSwitches", ["enable-logging"])
options.add_experimental_option("useAutomationExtension", False)

# ブラウザ起動
service = ChromeService(executable_path="C:\chromedriver_win32\chromedriver.exe")
driver = webdriver.Chrome(service=service, options=options)

# 要素が見つかるまで10秒待つ
driver.implicitly_wait(10)

# URLにアクセス
url = "https://hotel.testplanisphere.dev/ja/"
driver.get(url)
time.sleep(1)

# ログインページにアクセス
try:
    driver.find_element(By.LINK_TEXT, "ログイン").click()
except NoSuchElementException:
    driver.find_element(By.CLASS_NAME, "navbar-toggler-icon").click()
    driver.find_element(By.LINK_TEXT, "ログイン").click()
finally:
    time.sleep(1)

# ユーザー名を入力
driver.find_element(By.ID, "email").send_keys("jun@example.com")

# パスワードを入力
driver.find_element(By.ID, "password").send_keys("pa55w0rd!")

# ログインをクリック
driver.find_element(By.ID, "login-button").click()

# トップページから宿泊予約へ
driver.get(url)
time.sleep(1)
try:
    driver.find_element(By.LINK_TEXT, "宿泊予約").click()
except NoSuchElementException:
    driver.find_element(By.CLASS_NAME, "navbar-toggler-icon").click()
    driver.find_element(By.LINK_TEXT, "宿泊予約").click()
finally:
    time.sleep(1)

# ブラウザのHTMLを取得
soup = BeautifulSoup(driver.page_source, features="html.parser")

# プランにヘッダーがある場合、ターミナルに表示
[print(i) for i in soup.select("div.card-header")]
```

おすすめプランと3つの会員限定プランが表示されましたか？
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1934278/c94a3199-8de3-10d8-c0c6-e1e38f0e0b86.png)

:::note info
```# ログインページにアクセス```について
画面サイズによってレイアウトが変わるページなのでログインボタンがクリックできない場合があります。
```try-except```でクリックできなかったときの例外処理を書きましたが、
```driver.set_window_size(x,y)```などでウィンドウサイズを変えてしまうのもアリ。
:::





### 無限スクロールがあるサイト
スクロールするたびに、画面外の非表示だった部分を表示させる。
**Lazy Load**や**遅延ロード**なんて言ったりします。
YoutubeやTwitterなんかでも使われてますね。

```python:scrape4.py
import time

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService

# ブラウザのオプション
options = Options()
options.add_argument("--blink-settings=imagesEnabled=false")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--disable-browser-side-navigation")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-extensions")
options.add_argument("--disable-gpu")
# options.add_argument("--headless")  # ブラウザを非表示で起動する
options.add_argument("--ignore-certificate-errors")
options.add_argument("--incognito")
options.add_argument("--no-sandbox")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("excludeSwitches", ["enable-logging"])
options.add_experimental_option("useAutomationExtension", False)

# ブラウザ起動
service = ChromeService(executable_path="C:\chromedriver_win32\chromedriver.exe")
driver = webdriver.Chrome(service=service, options=options)

# 要素が見つかるまで10秒待つ
driver.implicitly_wait(10)

# URLにアクセス
url = "https://infinite-scroll.com/demo/full-page/"
driver.get(url)
time.sleep(1)

# ブラウザのHTMLを取得
soup = BeautifulSoup(driver.page_source, features="html.parser")

# 要素の数をターミナルに表示
length = len(soup.select('article[class="article"]'))
print(f"articleの数は{length}")
```

ページ全体のarticleの数は**15**ですが、いくつでしたか？　**2**？
画面外が読み込まれていないので、ある意味正解なのですが欲しいのは**15**です。
ちょっと手直ししたコードが下のようになります。

```python:scrape5.py
import time

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.by import By

# ブラウザのオプション
options = Options()
options.add_argument("--blink-settings=imagesEnabled=false")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--disable-browser-side-navigation")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-extensions")
options.add_argument("--disable-gpu")
# options.add_argument("--headless")  # ブラウザを非表示で起動する
options.add_argument("--ignore-certificate-errors")
options.add_argument("--incognito")
options.add_argument("--no-sandbox")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("excludeSwitches", ["enable-logging"])
options.add_experimental_option("useAutomationExtension", False)

# ブラウザ起動
service = ChromeService(executable_path="C:\chromedriver_win32\chromedriver.exe")
driver = webdriver.Chrome(service=service, options=options)

# 要素が見つかるまで10秒待つ
driver.implicitly_wait(10)

# URLにアクセス
url = "https://infinite-scroll.com/demo/full-page/"
driver.get(url)
time.sleep(1)

# ブラウザのHTMLを取得
soup = BeautifulSoup(driver.page_source, features="html.parser")

# 要素の数をターミナルに表示
length = len(soup.select('article[class="article"]'))
print(f"スクロールする前のarticleの数は{length}")

# ページの最下部へ移動する
old_size = 0
html_body = driver.find_element(By.XPATH, "/html/body")
for _ in range(1, 50):
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(1)
    new_size = html_body.size
    if new_size == old_size:
        print(f"{_}回スクロールしました。")
        break
    old_size = new_size

# ブラウザのHTMLを取得
soup = BeautifulSoup(driver.page_source, features="html.parser")

# 要素の数をターミナルに表示
length = len(soup.select('article[class="article"]'))
print(f"スクロールした後のarticleの数は{length}")
```
articleの数がスクロールする前は**2**、スクロールした後は**15**になりましたか？

:::note info
```# ページの最下部へ移動する```について
seleniumでは、javascriptを実行するexecute_script()関数があります。
ここでは```body```のheightを取得して、一番下までスクロールするという動きを実現しています。
```old_size (スクロール前)```と```new_size (スクロール後)```のサイズが一致＝これ以上はスクロールできないので```break```で抜ける処理を書きました。
:::





### BASIC認証を突破する方法
BASIC認証とは、対象URLにアクセスした後に出てくるコレのこと
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1934278/69524f26-8a7e-fe08-286e-a95f0ca73e46.png)
下記の形式でBASIC認証を通過する事が可能です。

```python:ex
# 通常のアクセス
driver.get("http://hogehoge.jp")

# BASIC認証＋アクセス
driver.get("http://username:password@hogehoge.jp")
```





### iframeがあるサイト
見ているページ(```<iframe>```があるページ)は１つだけの部屋に見えるが
実際は、基本のHTMLといくつかの```<iframe>```で別々の部屋として構成されている、と抽象的に捉えておけばok。
例)
<table>
  <tr>
    <th>ブラウザ</th><th colspan="2">HTML内</th>
  </tr>
  <tr>
    <td rowspan="3">見ているページ</td> <td>基本</td> <td> </td>
  </tr>
  <tr>
    <td> </td> <td>iframe</td>
  </tr>
  <tr>
    <td> </td> <td>iframe</td>
  </tr>
</table>

いろいろな記事の中に埋め込まれているTwitterやYoutubeなども```<iframe>```ですね。
なので、取りたいデータの部屋に移動する必要があります。
tak-dcxiさんが作ったCODEPENをお借りしてスクレイピングします

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="JjKKzLm" data-user="tak-dcxi" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/tak-dcxi/pen/JjKKzLm">
  a, button, divで作ったボタンの比較</a> by tak-dcxi (<a href="https://codepen.io/tak-dcxi">@tak-dcxi</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

```python:scrape6.py
import time

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.by import By

# ブラウザのオプション
options = Options()
options.add_argument("--blink-settings=imagesEnabled=false")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--disable-browser-side-navigation")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-extensions")
options.add_argument("--disable-gpu")
# options.add_argument("--headless")  # ブラウザを非表示で起動する
options.add_argument("--ignore-certificate-errors")
options.add_argument("--incognito")
options.add_argument("--no-sandbox")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("excludeSwitches", ["enable-logging"])
options.add_experimental_option("useAutomationExtension", False)

# ブラウザ起動
service = ChromeService(executable_path="C:\chromedriver_win32\chromedriver.exe")
driver = webdriver.Chrome(service=service, options=options)

# 要素が見つかるまで10秒待つ
driver.implicitly_wait(10)

# URLにアクセス
url = "https://qiita.com/kawagoe6884/items/889f8f2cf816439c026b"
driver.get(url)
time.sleep(1)

# 対象までスクロールして読み込み
scroll = driver.find_element(By.ID, "iframeがあるサイト")
driver.execute_script("arguments[0].scrollIntoView();", scroll)

# データを取りたいiframeを指定
iframe = driver.find_element(By.CLASS_NAME, "cp_embed_iframe ")

# CODEPENの外枠に移動
driver.switch_to.frame(iframe)

# ブラウザのHTMLを取得
soup = BeautifulSoup(driver.page_source, features="html.parser")

# 3つのボタンを取得したい
li_tags = soup.select("li.code-type")
print(li_tags)

# 元々いた部屋に戻りたい場合は以下のコード
driver.switch_to.default_content()

# ブラウザのHTMLを取得
soup = BeautifulSoup(driver.page_source, features="html.parser")

# 3つのボタンを取得したかった
li_tags = soup.select("li.code-type")
print(li_tags)
```

3つの```<li class="code-type">```が表示されたあと、空のリストが表示されましたか？
※元々いた部屋には```<iframe>```が無いので空のリストになります。

移動するときは、```driver.switch_to.frame(移動先の要素)```
戻るときは、```driver.switch_to.default_content()```


#### iframeが入れ子になっているとき
入れ子になっている数だけ```driver.switch_to.frame(移動先の要素)```するだけでok

```python:scrape7.py
import time

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.by import By

# ブラウザのオプション
options = Options()
options.add_argument("--blink-settings=imagesEnabled=false")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--disable-browser-side-navigation")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-extensions")
options.add_argument("--disable-gpu")
# options.add_argument("--headless")  # ブラウザを非表示で起動する
options.add_argument("--ignore-certificate-errors")
options.add_argument("--incognito")
options.add_argument("--no-sandbox")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("excludeSwitches", ["enable-logging"])
options.add_experimental_option("useAutomationExtension", False)

# ブラウザ起動
service = ChromeService(executable_path="C:\chromedriver_win32\chromedriver.exe")
driver = webdriver.Chrome(service=service, options=options)

# 要素が見つかるまで10秒待つ
driver.implicitly_wait(10)

# URLにアクセス
url = "https://qiita.com/kawagoe6884/items/889f8f2cf816439c026b"
driver.get(url)
time.sleep(1)

# 対象までスクロールして読み込み
scroll = driver.find_element(By.ID, "iframeがあるサイト")
driver.execute_script("arguments[0].scrollIntoView();", scroll)

# データを取りたいiframeを指定
iframe = driver.find_element(By.CLASS_NAME, "cp_embed_iframe ")
# CODEPENの枠に移動
driver.switch_to.frame(iframe)

# データを取りたいiframeを指定
iframe = driver.find_element(By.CLASS_NAME, "result-iframe  ")
# Resultの枠に移動
driver.switch_to.frame(iframe)

# 1つ目のボタンをクリックする
driver.find_element(By.XPATH, '//a[text()="a要素で作ったボタン"]').click()


# ```driver.implicitly_wait(10)```を設定しているので10秒後、エラーになるよ
driver.switch_to.default_content()
driver.find_element(By.XPATH, '//a[text()="a要素で作ったボタン"]').click()
# driver.switch_to.frame()で移動しないとクリックできない！
# selenium.common.exceptions.NoSuchElementException:
# Message: no such element: Unable to locate element:
# {"method":"xpath","selector":"//a[text()="a要素で作ったボタン"]"}
```

プッシュされました！が表示された後、エラーが発生しましたか？





## おまけ：スクレイピングしてCSVで保存するまで
ここまでつらつら書きましたが、それぞれのポイントをまとめただけなので
全体像があるとより分かりやすいのかなと思ったので作ってみました
このコードを動かすには```pip install pandas```のインストールが必要です

```python:scrape8.py
import getpass
import time
from urllib.parse import urljoin

import pandas as pd
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.by import By

# ブラウザのオプション
options = Options()
options.add_argument("--blink-settings=imagesEnabled=false")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--disable-browser-side-navigation")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-extensions")
options.add_argument("--disable-gpu")
# options.add_argument("--headless")  # ブラウザを非表示で起動する
options.add_argument("--ignore-certificate-errors")
options.add_argument("--incognito")
options.add_argument("--no-sandbox")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("excludeSwitches", ["enable-logging"])
options.add_experimental_option("useAutomationExtension", False)

# ブラウザ起動
service = ChromeService(executable_path="C:\chromedriver_win32\chromedriver.exe")
driver = webdriver.Chrome(service=service, options=options)

# 要素が見つかるまで10秒待つ
driver.implicitly_wait(10)

# スクレイピングするURLにアクセス
url = "https://books.toscrape.com/"
driver.get(url)
time.sleep(1)


# 商品タイトルのリスト
title_list = []
# 商品評価のリスト
rating_list = []
# 商品価格のリスト
price_list = []
# 在庫状態のリスト
stock_list = []
# 商品URLのリスト
link_list = []
# nextのボタンがある限りスクレイピングをする
while True:
    # ブラウザのHTMLを取得
    soup = BeautifulSoup(driver.page_source, features="html.parser")

    # 商品タイトルをリストに追加
    a_tags = soup.select("h3 > a")
    title_list += [a.get("title") for a in a_tags]

    # 商品URLをリストに追加
    href_list = [a.get("href") for a in a_tags]
    href_list = [href.replace("catalogue/", "") for href in href_list]
    link_list += [urljoin(url, "/catalogue/" + href) for href in href_list]

    # 商品の評価をリストに追加
    p_tags = driver.find_elements(By.CSS_SELECTOR, 'p[class^="star-rating"]')
    for p in p_tags:
        rating = ""
        i_tags = p.find_elements(By.CLASS_NAME, "icon-star")
        for i in i_tags:
            if i.value_of_css_property("color") == "rgba(230, 206, 49, 1)":
                rating += "★"
            else:
                rating += "☆"
        rating_list.append(rating)

    # 商品価格をリストに追加
    p_tags = soup.select("p.price_color")
    price_list += [p.get_text(strip=True) for p in p_tags]

    # 在庫の状態をリストに追加
    p_tags = soup.select("p.instock.availability")
    stock_list += [p.get_text(strip=True) for p in p_tags]

    # ※※※※※※※※※※※※※※※
    break
    # ※※※※※※※※※※※※※※※
    try:
        driver.find_element(By.CSS_SELECTOR, 'li[class="next"] > a').click()
        time.sleep(1)
    except NoSuchElementException:
        break


# 商品カテゴリのリスト
cate_list = []
# 商品画像URLのリスト
img_list = []
# 商品説明のリスト
desc_list = []
# 商品詳細のデータフレーム
df_info = pd.DataFrame()
# 商品URLのリストから一つ一つアクセスしてスクレイピングをする
for link_url in link_list:
    # 商品URLにアクセス
    driver.get(link_url)
    time.sleep(1)

    # ブラウザのHTMLを取得
    soup = BeautifulSoup(driver.page_source, features="html.parser")

    # 商品カテゴリをリストに追加
    li_tag = soup.select_one('a[href^="../category/books/"]')
    cate_list.append(li_tag.get_text(strip=True))

    # 商品画像URLをリストに追加
    img_tag = soup.select_one("div.item.active > img")
    img_list.append(urljoin(url, img_tag.get("src")))

    # 商品説明をリストに追加
    p_tag = soup.find(lambda tag: tag.name == "p" and not tag.attrs)
    # ```soup.find("p", class_=False)``` でも同じことができるが汎用的なのは上記のコードか。
    #
    # Product Descriptionが無い場合は空文字を追加
    if p_tag is None:
        desc_list.append("")
    else:
        desc_list.append(p_tag.get_text(strip=True))

    # 商品詳細をデータフレームに追加
    table_tag = soup.select("table.table.table-striped")
    df = pd.read_html(str(table_tag))[0]
    # 取得したいテーブルが決まってるなら、soupで探してstr型で渡すのも◎
    # ```df = pd.read_html(driver.page_source)``` でも問題ない
    #
    # 行/列の入れ替え
    df = df.transpose()
    # 1行目を列名にする
    df.columns = df.iloc[0]
    # 列名にした1行目を削除
    df.drop(0, inplace=True)
    # dfを行方向に追加していく
    df_info = pd.concat([df_info, df], axis="rows")


# 取得したデータをまとめる
df_info = df_info.assign(商品URL=link_list)
df_info = df_info.assign(画像URL=img_list)
df_info.insert(0, "在庫", stock_list)
df_info.insert(0, "価格", price_list)
df_info.insert(0, "評価", rating_list)
df_info.insert(0, "説明", desc_list)
df_info.insert(0, "タイトル", title_list)
df_info.insert(0, "カテゴリ", cate_list)


# CSV形式でデスクトップに保存する
file_path = f"C:\\Users\\{getpass.getuser()}\\Desktop\\"
df_info.to_csv(f"{file_path}info.csv", index=False, encoding="utf_8_sig")
```

:::note info
```~~~.get_text(strip=True)```は先頭と末尾から空白を除いてくれます
:::

:::note info
①と②は同じ処理です。①を**内包表記**といって処理が早い上に見栄えも◎
```python:①
title_list += [a.get("title") for a in a_tags]
```
```python:②
for a in a_tags:
    title_list.append(a.get("title"))
```
:::

:::note warn
※※※※※※※※※※※※※※※ の間にある```break```について
コメントアウトすることで全てのページをスクレイピングできます。
その代わりに、処理が終わるまで27分くらい掛かります。
テストで動かしてみたい、という場合はコメントアウト**非推奨**です
:::

出来上がったCSVはこんな感じになります
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1934278/476f3a9c-5b4d-6d0d-19da-fffa0561ef3d.png)





## 動作テストができるサイト

http://test-sites.octoparse.com/

https://books.toscrape.com/index.html

https://webscraper.io/test-sites

https://hotel.testplanisphere.dev/ja/





## あとがき
昔々あるところに1000Contributionsを超える有用な記事がありました。
その記事は、皆からの情報や質問を元により良い記事になっていきました。
とある日、その記事は突然'404'を表示するようになりました。記事を移転や移行するわけでもなく。<font color="gray">※1</font>
仕方ないので古より伝わる秘儀、wayback machine で記事を見ることにしました。<font color="gray">※2</font>
しかし、 wayback machine を知らない人はもとより、URLが分からない人たちは見ることができません。
後世のためにも有用な記事を徒労に帰す訳にはいかない。と思い立った投稿主は、新たに記事を書き起こすことにしましたとさ。

●何か理由があって、削除したと思われるので元となった記事には触れません●
<font color="gray">※1「移転や移行」：良さげな記事を見つけて「おっ」となり、アクセスしたら "移転・移行" の文字。「はあ～～～～(ため息)」(ブラウザバック)</font>
<font color="gray">※2「wayback machine」：7,540億以上のアーカイブされたWebページを見ることができるサイト。20年前のYahooやGoogleが見れたりする。</font>