概要
seleniumで、指定した要素をスクロールしてスクリーンショットを撮るためのメモ。
背景と目的
seleniumを使って画面キャプチャをしていたところ、テーブルのすべての値をキャプチャするのにスクロールが必要だった。そこで、その方法をメモした。
なお、ブラウザのウインドウサイズを大きくすればすべて表示される作りになっている画面は対象外とする。ブラウザサイズを変えればよいだけなので。
詳細
0. 環境
以下は、縦、横ともスクロールバーが発生するテーブルのサンプル。表示領域が1000×200pxに限定されていて、いくつかの行、列が隠れている。
<html>
<head>
<style>
.table-wrap {
overflow: scroll;
width: 1000px;
height: 200px;
}
.table {
border-collapse: collapse;
white-space: nowrap;
}
.table th,
.table td {
border: 2px solid #eee;
padding: 4px 8px;
}
</style>
</head>
<body>
<div class="table-wrap" id="scroll_area">
<table class="table">
<tr>
<th>サンプルテキスト00</th>
<td>これはサンプルテキストです。00</td>
<td>これはサンプルテキストです。01</td>
: 中略
<td>これはサンプルテキストです。09</td>
</tr>
: 中略
<tr>
<th>サンプルテキスト90</th>
<td>これはサンプルテキストです。90</td>
<td>これはサンプルテキストです。91</td>
: 中略
<td>これはサンプルテキストです。99</td>
</tr>
</table>
</div>
</body>
</html>
参考: https://webukatu.com/wordpress/blog/7058/#overflow
2.0 キャプチャ対象要素を選択
q = driver.find_element(By.ID, "scroll_area")
2.1 対象要素をスクロール
特定の要素をスクロールするには、WebDriverのexecute_scriptを使い、javascript側のDOM操作をする。第一引数には実行したいスクリプト(javascript)、第2引数以降はスクリプトに渡す引数。スクリプト側では、
arguments[n]
として、第n+2引数を参照できる。seleniumのElementを渡せば、DOMとして受け取られるので、スクリプト側でDOMの各種操作が実行可能。
以下に、スクロールに関連するDOMのプロパティを操作する例を列挙。※javascript側の仕様なのでそちらを調べた方がよいが。
driver.execute_script("arguments[0].scrollTo(10,10);", q)
driver.execute_script("arguments[0].scrollTop = 10;", q)
driver.execute_script("arguments[0].scrollLeft= 10;", q)
driver.execute_script("arguments[0].scrollBy(10,10);", q)
driver.execute_script("arguments[0].scrollWidth;", q)
driver.execute_script("arguments[0].scrollHeight;", q)
q.size
2.2 指定した子要素が表示されるように親をスクロール
javascript側でscrollIntoViewというメソッドがある。それも呼び出して使える。
サンプルとして、最終行の7列目セルを表示ターゲットとする。
: 中略
<tr>
<th>サンプルテキスト90</th>
<td>これはサンプルテキストです。90</td>
<td>これはサンプルテキストです。91</td>
: 中略
<td id="target">スクロールターゲット</td>
: 中略
<td>これはサンプルテキストです。99</td>
</tr>
以下のように、当該セル.scrollIntoViewを呼び出せば、親(#scroll_area)がスクロールされる。
q = driver.find_element(By.ID, "scroll_area")
driver.execute_script("arguments[0].scrollIntoView();", q)
安直な方法として、上下左右に少しずつスクロールして複数枚のスクリーンショットを撮ることにする。scrollIntoViewは使っていない。
ちょっと注意が必要なのは、
- 表示領域には、スクロールバーの幅が考慮されないので、sizeで取得した値よりも少し狭く考える
- スクロールをする前に、Elementを再度取得する。しないとエラーが出るため。
selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: stale element not found
コードは以下。
height = q.size["height"] - 30
width = q.size["width"] - 30
scrollWidth = driver.execute_script("return arguments[0].scrollWidth;", q)
scrollHeight = driver.execute_script("return arguments[0].scrollHeight;", q)
horizontalScrollCount = int(scrollWidth / width) + 1
verticalScrollCount = int(scrollHeight / height) + 1
horizontalScrollAmount = int(scrollWidth / horizontalScrollCount)
verticalScrollAmount = int(scrollHeight / verticalScrollCount)
driver.execute_script("arguments[0].scrollTo(0, 0);", q)
for i in range(verticalScrollCount):
for j in range(horizontalScrollCount):
q = driver.find_element(By.ID, "scroll_area")
driver.execute_script(
f"arguments[0].scrollTo({j * horizontalScrollAmount}, {i * verticalScrollAmount});",
q,
)
png = q.screenshot_as_png
with open(f"./img_{i}_{j}.png", "wb") as f:
f.write(png)
撮れたスクリーンショットは以下。
上段
中段
下段
まとめと今後の課題
seleniumで、指定した要素をスクロールして、要素内すべてのスクリーンショットを撮るスクリプトを作ることができた。