ken_nogi/NodeJS/#ANDPAD/andpad_getPhotos.js
Kenichiro NOGI 88a402ce0f up
2026-07-10 18:13:30 +09:00

241 lines
9.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();