|
import os |
|
import re |
|
from playwright.sync_api import sync_playwright |
|
import requests |
|
import sys |
|
from PIL import Image, UnidentifiedImageError |
|
from io import BytesIO |
|
os.systems("python3 -m playwright install") |
|
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, |
|
format='%(asctime)s - %(levelname)s - %(message)s', |
|
handlers=[ |
|
logging.StreamHandler(sys.stdout), |
|
file_handler, |
|
] |
|
) |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
def generate_safe_folder_name(url): |
|
|
|
safe_name = re.sub(r'[^a-zA-Z0-9_\-]', '_', url) |
|
return safe_name |
|
|
|
|
|
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() |
|
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 |
|
|
|
|
|
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'): |
|
|
|
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("ページの読み込みが完了しました。") |
|
|
|
|
|
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 |
|
|
|
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: |
|
|
|
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 <URL>") |
|
sys.exit(1) |
|
|
|
url = sys.argv[1] |
|
folder_name = 'scraped_images' |
|
scrape_images_by_page(url, folder_name) |
|
|