const { Builder, By, until } = require('selenium-webdriver'); const fs = require('fs-extra'); const path = require('path'); const unzipper = require('unzipper'); const chrome = require('selenium-webdriver/chrome'); // 変数定義 const URL = 'https://andpad.jp/my/orders'; const LOGIN_ID = 'k.nogi@next-hd.co.jp'; const LOGIN_PASSWORD = 'Next79324$#'; // ORDER_NUMBERはコマンドライン引数で指定 const ORDER_NUMBER = process.argv[2]; if (!ORDER_NUMBER) { console.error('エラー: オーダー番号をコマンドライン引数で指定してください。\n例: node andpad_getPhotos.js 21404611'); process.exit(1); } const BASE_FOLDER = 'C:\\Users\\k.nogi\\Desktop\\デザイノANDPAD'; // Chrome default download folder let driver; async function main() { try { const options = new chrome.Options(); options.addArguments('--log-level=3'); // エラーのみ表示 options.addArguments('--disable-logging'); // Seleniumドライバ初期化 driver = await new Builder() .forBrowser('chrome') .setChromeOptions(options) .build(); // ログイン処理 await loginWithDriver(driver); // URLを開く const photoUrl = `${URL}/${ORDER_NUMBER}/photos/folder`; await driver.get(photoUrl); // 件名を取得 const subject = await getSubject(); console.log('件名:', subject); // ログイン後の要素が現れるまで待機(タイムアウト20秒に延長) await driver.wait(until.elementLocated(By.xpath('//*[contains(text(), "フォルダ")]')), 20000); // フォルダリンククリック await driver.executeScript(`$('a:contains("フォルダ")')[0].click()`); await driver.sleep(2000); try { const photoTopLink = await driver.findElement(By.xpath('//a[contains(text(), "写真トップ")]')); await photoTopLink.click(); await driver.sleep(1000); } catch (e) { // "写真トップ"リンクがなければスキップ } // 写真トップの写真を保存 try { await driver.findElement(By.xpath('//a[contains(text(), "すべて選択")]')); // hidden inputからphotoTagIdを取得 const photoTagIdInput = await driver.findElement(By.css('input[name="current_photo_tag_id"]')); const photoTagId = await photoTagIdInput.getAttribute('value'); const photoTagLabel = '写真トップ'; console.log(`処理開始: ${photoTagLabel} (ID: ${photoTagId})`); // フォルダ構造を作成 const folderPath = path.join(BASE_FOLDER, subject, photoTagLabel); await fs.ensureDir(folderPath); console.log('フォルダ作成:', folderPath); // 写真ダウンロード処理 await photoDownload(folderPath, photoTagId); } catch (e) { // "すべて選択"がなければスキップ } // フォトフォルダリストを取得 const folderElements = await driver.findElements(By.css('div.grid-photo-folder__element')); console.log('フォルダ数:', folderElements.length); // driver.quit()はループ後に実行 // 各要素を処理 for (let element of folderElements) { const photoTagId = await element.getAttribute('data-photo-tag-id'); const photoTagLabel = await element.getAttribute('data-photo-tag-label'); // 要素数を取得 const countElement = await element.findElement(By.css('div.grid-photo-folder-element__count')); const count = parseInt(await countElement.getText()); if (count >= 1) { console.log(`処理開始: ${photoTagLabel} (ID: ${photoTagId})`); // フォルダ構造を作成 const folderPath = path.join(BASE_FOLDER, subject, photoTagLabel); await fs.ensureDir(folderPath); console.log('フォルダ作成:', folderPath); // 写真ダウンロード処理 await photoDownload(folderPath, photoTagId); } } await driver.quit(); } catch (error) { console.error('エラー:', error); } // finally節でdriver.quit()を呼ばないことで、Chromeを自動終了させない } async function loginWithDriver(driver) { // Chromeウインドウを最大化 await driver.manage().window().maximize(); // ログイン画面へ遷移 await driver.get('https://andpad.jp/login'); // 「ログイン画面へ」ボタンをクリック try { const loginPageBtn = await driver.findElement(By.xpath('//input[@value="ログイン画面へ"]')); await loginPageBtn.click(); await driver.sleep(1000); } catch (e) { // ボタンがなければスキップ } // ログインIDとパスワード入力 const loginIdInput = await driver.findElement(By.id('email')); await loginIdInput.clear(); await loginIdInput.sendKeys(LOGIN_ID); const passwordInput = await driver.findElement(By.id('password')); await passwordInput.clear(); await passwordInput.sendKeys(LOGIN_PASSWORD); // ログインボタン押下 const loginButton = await driver.findElement(By.css('button[type="submit"]')); await loginButton.click(); // ログイン後のURLとタイトルを出力 await driver.sleep(1000); } async function getSubject() { const subjectInput = await driver.findElement(By.css('input[placeholder="台帳名"]')); return await subjectInput.getAttribute('value'); } async function photoDownload(folderPath, photoTagId) { const tagUrl = `${URL}/${ORDER_NUMBER}/photo_tags/${photoTagId}`; // Chrome Preferencesで自動ダウンロード設定 const chrome = require('selenium-webdriver/chrome'); const prefs = { 'download.default_directory': folderPath, 'download.prompt_for_download': false, 'safebrowsing.enabled': false }; const options = new chrome.Options(); options.setUserPreferences(prefs); options.addArguments('--log-level=3'); // エラーのみ表示 options.addArguments('--disable-logging'); // Selenium起動(driver再起動) let subDriver = await new Builder() .forBrowser('chrome') .setChromeOptions(options) .build(); await loginWithDriver(subDriver); // 指定タグURLへ遷移 await subDriver.get(tagUrl); await subDriver.sleep(2000); // 「写真をもっと見る」ボタンが表示されている間クリックを繰り返す while (true) { try { // ng-hideが親divに無い場合のみクリック const moreDiv = await subDriver.findElement(By.xpath('//div[contains(@ng-show,"hasNextPage()") and not(contains(@class,"ng-hide"))]')); const moreBtn = await moreDiv.findElement(By.xpath('.//a[contains(text(),"写真をもっと見る")]')); await moreBtn.click(); await subDriver.sleep(5000); // ロード待ち } catch (e) { // ボタンが見つからない場合は終了 break; } } // "すべて選択"と書かれているaタグをクリック await subDriver.findElement(By.xpath('//a[contains(text(), "すべて選択")]')).click(); await subDriver.sleep(1000); // "ダウンロード"と書かれているaタグをクリック await subDriver.findElement(By.xpath('//a[contains(text(), "ダウンロード")]')).click(); await subDriver.sleep(1000); // "ダウンロードする"と書かれているinputタグをクリック await subDriver.findElement(By.xpath('//input[@value="ダウンロードする"]')).click(); await subDriver.sleep(2000); // ブラウザを閉じる await subDriver.quit(); // ダウンロードしたzipファイルを解凍 await extractDownloadedZip(folderPath); } // 指定フォルダ内の最新のzipファイルをそのフォルダに解凍 async function extractDownloadedZip(folderPath) { const fsPromises = require('fs').promises; const files = await fsPromises.readdir(folderPath); // zipファイルのみ抽出 const zipFiles = files.filter(f => f.endsWith('.zip')); if (zipFiles.length === 0) { console.log('zipファイルが見つかりません:', folderPath); return; } // 最も新しいzipファイルを選択 let latestZip = zipFiles[0]; let latestTime = (await fsPromises.stat(path.join(folderPath, latestZip))).mtime; for (const zip of zipFiles) { const stat = await fsPromises.stat(path.join(folderPath, zip)); if (stat.mtime > latestTime) { latestZip = zip; latestTime = stat.mtime; } } const zipPath = path.join(folderPath, latestZip); console.log('解凍開始:', zipPath); await fs.createReadStream(zipPath) .pipe(unzipper.Extract({ path: folderPath })) .promise(); console.log('解凍完了:', folderPath); } main();