import os import re from playwright.sync_api import sync_playwright import requests import sys from PIL import Image, UnidentifiedImageError from io import BytesIO log_file = "app_log.txt" # ログファイルのパス # ログフォーマットの定義 log_format = '%(asctime)s - %(levelname)s - %(message)s' import logging file_handler = logging.FileHandler(log_file, encoding='utf-8') # ログの設定 logging.basicConfig( level=logging.INFO, # ログレベルをINFOに設定 format='%(asctime)s - %(levelname)s - %(message)s', # ログのフォーマットを指定 handlers=[ logging.StreamHandler(sys.stdout), # 標準出力にログを出力 file_handler, ] ) logger = logging.getLogger(__name__) # 安全なフォルダ名を生成する関数 def generate_safe_folder_name(url): # URLから安全なフォルダ名を生成(ファイル名に使えない文字を除去) safe_name = re.sub(r'[^a-zA-Z0-9_\-]', '_', url) return safe_name # 画像を保存する関数 (JPG 80%の品質で保存) def save_image_as_jpg(image_url, save_folder, image_name): if not os.path.exists(save_folder): os.makedirs(save_folder) logger.info(f"フォルダを作成しました: {save_folder}") try: response = requests.get(image_url, timeout=10) response.raise_for_status() # HTTPエラーが発生した場合例外を投げる except requests.exceptions.RequestException as e: logger.error(f"画像のダウンロード中にエラーが発生しました: {e}") return try: image = Image.open(BytesIO(response.content)) except UnidentifiedImageError: logger.warning(f"未識別の画像ファイル: {image_url}. スキップします。") return except Exception as e: logger.error(f"画像のオープン中にエラーが発生しました: {e}") return # 保存時に JPG に変換し、品質80%で保存 image_path = os.path.join(save_folder, image_name) try: image.convert("RGB").save(image_path, "JPEG", quality=80) logger.info(f"画像を保存しました: {image_path}") except Exception as e: logger.error(f"画像の保存中にエラーが発生しました: {e}") # 画像の再帰的取得 def scrape_images_by_page(url, folder_name='scraped_images'): # URLが"/"で終わっている場合、スラッシュを削除 original_url = url url = url.rstrip('/') logger.info(f"処理するURL: {url}") with sync_playwright() as p: browser = p.chromium.launch(headless=False) # ブラウザを表示して操作 page = browser.new_page() # 初期ページにアクセス page.goto(url) logger.info(f"ページにアクセスしました: {url}") # ページが完全に読み込まれるまで待機 page.wait_for_load_state('networkidle') logger.info("ページの読み込みが完了しました。") # lazy-loading属性を無効にするためのJavaScriptを挿入 try: page.evaluate(""" document.querySelectorAll('img[loading="lazy"]').forEach(img => { img.setAttribute('loading', 'eager'); img.src = img.src; // 画像を強制的にリロード }); """) logger.info("lazy-loadingを無効化しました。") except Exception as eval_error: logger.warning(f"JavaScriptの評価中にエラーが発生しました: {eval_error}") # フォルダ名を生成 safe_folder_name = generate_safe_folder_name(url) folder_path = os.path.join(folder_name, safe_folder_name) logger.info(f"保存先フォルダ: {folder_path}") # ページ数を取得 try: # ページ数が格納されているセレクタからテキストを取得 page_count_selector = 'div.tag-container:nth-child(8) > span:nth-child(1) > a:nth-child(1) > span:nth-child(1)' page_count_text = page.locator(page_count_selector).text_content().strip() num_pages = int(re.search(r'\d+', page_count_text).group()) logger.info(f"セレクタ '{page_count_selector}' からページ数を取得: {num_pages}") except Exception as e: logger.warning(f"セレクタ '{page_count_selector}' からページ数を取得できませんでした: {e}") # セレクタが見つからない場合のフォールバック try: fallback_selector = 'section.reader-bar:nth-child(2) > div:nth-child(2) > button:nth-child(3) > span:nth-child(3)' page.wait_for_selector(fallback_selector, timeout=5000) num_pages_text = page.locator(fallback_selector).text_content().strip() num_pages = int(re.search(r'\d+', num_pages_text).group()) logger.info(f"セレクタ '{fallback_selector}' からページ数を取得: {num_pages}") except Exception as e2: logger.error(f"ページ数の取得に失敗しました: {e2}") num_pages = 1 # デフォルトで1ページとする logger.info(f"総ページ数: {num_pages}") # 各ページにアクセスして画像を取得 for i in range(1, num_pages + 1): page_url = f"{url}/{i}" page.goto(page_url) logger.info(f"ページにアクセスしました: {page_url}") # ページが完全に読み込まれるまで待機 page.wait_for_load_state('networkidle') logger.info(f"ページ {i} の読み込みが完了しました。") try: # 画像を取得するセレクタ img_selector = '#image-container > a > img' img_elements = page.locator(img_selector) img_count = img_elements.count() logger.info(f"ページ {i} の画像数: {img_count}") if img_count == 0: logger.warning(f"ページ {i} に画像が見つかりません。") continue for j in range(img_count): try: image_element = img_elements.nth(j) image_url = image_element.get_attribute('src') if not image_url: # data-srcなどに画像URLが格納されている場合 image_url = image_element.get_attribute('data-src') logger.info(f"取得した画像URL (ページ {i}, 画像 {j + 1}): {image_url}") if image_url: # ファイル名にページ番号と画像番号を含め、位取りを適用 image_name = f'page_{str(i).zfill(5)}_img_{str(j + 1).zfill(5)}.jpg' save_image_as_jpg(image_url, folder_path, image_name) except Exception as e: logger.error(f"ページ {i}, 画像 {j + 1} の処理中にエラーが発生しました: {e}") continue except Exception as e: logger.error(f"ページ {i} の画像取得中にエラーが発生しました: {e}") continue browser.close() logger.info("ブラウザを閉じました。") if __name__ == "__main__": if len(sys.argv) < 2: logger.error("使用方法: python scrape_images_worker.py ") sys.exit(1) url = sys.argv[1] # コマンドライン引数でURLを受け取る folder_name = 'scraped_images' # デフォルトのフォルダ名 scrape_images_by_page(url, folder_name)