前提
モダンなWebサイトでは、ページの初期読み込み時間を短縮するため、画像の遅延読み込み(Lazy Loading)が割と広めに採用されています。data-src
やdata-original
属性に実際のURLが設定されていたり、JavaScriptによってスクロール時にsrc
属性に移動されるような仕組みだったりします。通常のrequestでは、JavaScript実行前のHTMLしか取得できないため、これらの画像URLを見逃してしまいます。
どんな時に使えるのか
- 自社ECサイトの商品画像の一括取得・品質チェック
- 社内ポートフォリオサイトの作品画像収集
- 企業ギャラリーサイトの画像アーカイブ作成
- 自社メディアサイトのコンテンツ監査
- 社内システムの画像データ移行作業
【重要】スクレイピング実行前に確認しよう
1. 法的確認事項
- 対象サイトの利用規約・robots.txtを必ず確認
- 著作権・肖像権などの知的財産権を尊重
- 個人情報保護法(GDPR等)の遵守
2. 技術的マナー
- 適切な間隔(1-3秒)でのリクエスト実行
- User-Agentの適切な設定
- サーバー負荷を考慮した同時接続数の制限
3. 推奨される使用場面
- 自社サイト・関連サイトでの利用
- 明示的な許可を得たサイト
- パブリックAPI代替としての社内利用
注意:本コードは教育・研究目的および自社サイト運用での使用を想定しています
コードの内容
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
import time
import random
def scroll_and_wait_for_images(driver, url, max_scrolls=5, scroll_pause_time=1.0, image_wait_time=2.0):
"""画像読み込み待機付きスクロール処理"""
try:
driver.get(url)
# 初期読み込み完了を待機
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
print(f"ページを読み込み完了: {url}")
# スクロール前の画像数を記録
initial_img_count = len(driver.find_elements(By.TAG_NAME, "img"))
print(f"初期画像数: {initial_img_count}個")
last_height = driver.execute_script("return document.body.scrollHeight")
scroll_count = 0
while scroll_count < max_scrolls:
# 段階的なスクロール(人間らしい動作でBot検出回避)
scroll_steps = random.randint(3, 7)
for step in range(scroll_steps):
scroll_position = (step + 1) * (last_height / scroll_steps)
driver.execute_script(f"window.scrollTo(0, {scroll_position});")
time.sleep(random.uniform(0.2, 0.8))
# 最下部までスクロール
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(scroll_pause_time)
# 遅延読み込み画像の強制読み込み
force_load_lazy_images(driver)
time.sleep(image_wait_time)
# 現在の画像数を確認
current_img_count = len(driver.find_elements(By.TAG_NAME, "img"))
print(f"スクロール {scroll_count + 1}: {current_img_count}個の画像")
# ページ高さの変化を確認
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
print(f"スクロール完了 (回数: {scroll_count + 1})")
break
last_height = new_height
scroll_count += 1
return driver.page_source
except Exception as e:
print(f"スクロール処理エラー: {e}")
return None
def force_load_lazy_images(driver):
"""遅延読み込み画像を強制的に読み込む"""
try:
# JavaScriptで遅延読み込み画像を即座に読み込み
script = """
// data-src属性を持つ画像を強制読み込み
var lazyImages = document.querySelectorAll('img[data-src], img[data-original], img[data-lazy]');
console.log('遅延読み込み画像を検出:', lazyImages.length, '個');
lazyImages.forEach(function(img, index) {
setTimeout(function() {
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
} else if (img.dataset.original) {
img.src = img.dataset.original;
img.removeAttribute('data-original');
} else if (img.dataset.lazy) {
img.src = img.dataset.lazy;
img.removeAttribute('data-lazy');
}
}, index * 50); // 50ms間隔で段階的に読み込み
});
// Intersection Observer APIを無効化(一部サイト用)
if (window.IntersectionObserver) {
window.IntersectionObserver = function() {
return {
observe: function() { console.log('IntersectionObserver.observe() - bypassed'); },
unobserve: function() { console.log('IntersectionObserver.unobserve() - bypassed'); },
disconnect: function() { console.log('IntersectionObserver.disconnect() - bypassed'); }
};
};
}
"""
driver.execute_script(script)
time.sleep(2.0) # 画像読み込み完了を待機
except Exception as e:
print(f"遅延読み込み画像処理エラー: {e}")
# 使用例
def extract_images_from_page(url, output_file="extracted_images.txt"):
"""ページから画像URLを抽出してファイルに保存"""
# Chrome設定(画像読み込み有効)
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(options=chrome_options)
try:
# 画像読み込み待機付きスクロール
page_source = scroll_and_wait_for_images(driver, url)
if page_source:
# BeautifulSoupで画像URL抽出
soup = BeautifulSoup(page_source, 'html.parser')
images = soup.find_all('img', src=True)
image_urls = []
for img in images:
src = img.get('src')
if src and not src.startswith('data:'):
if src.startswith('//'):
src = 'https:' + src
elif src.startswith('/'):
from urllib.parse import urljoin
src = urljoin(url, src)
image_urls.append(src)
# 重複除去
unique_urls = list(set(image_urls))
# ファイルに保存
with open(output_file, 'w', encoding='utf-8') as f:
for url in unique_urls:
f.write(f"{url}\n")
print(f"抽出完了: {len(unique_urls)}個の画像URLを {output_file} に保存")
return unique_urls
finally:
driver.quit()
# 実行例
if __name__ == "__main__":
# 注意: 実際の使用時には規約の確認などはしっかりと。
target_url = "https://your-site.com/gallery"
extract_images_from_page(target_url)