[GCP]CloudFunctionsでpuppeteerを使う
puppeteer(ぱぺてぃあ)を使った業務の自動化。
時々やってくるエラー通知。そのころにはpuppeteerのことを忘れていて調べるのに苦労することが多いので、今まで勉強したことをちゃんとまとめておこうと思う。
不定期追記予定
よくある対策
ページ遷移が遅くて30秒タイムアウトしてしまう時
・遷移後のページにしか存在しないDOM要素の出現を検知するまで待つ。
await page.waitForSelector('input[name="xxx"]');
・DOM要素が遷移後のページにしかないことの確認方法は、GoogleChromeのディベロッパーツールのConsoleタブで、$$('input[name="xxx"]')と入力し、遷移前のページでは空配列が返ってくること。
ダウンロードファイルをGCSにアップロードする
・ダウンロード先を /tmpフォルダに指定する
・/tmpフォルダからGCSへアップロードする
const {Storage} = require('@google-cloud/storage');
const downloadPath = '/tmp';
const projectId = 'project_id';
const bucketName = 'bucket1';
const upload_file = 'filename_yyyymmdd.csv';
const storage = new Storage({"projectId": projectId});
const bucket = storage.bucket(bucketName);
exports.download_csv = async (req, res) => {
const browser = await puppeteer.launch({--(省略)--});
const page = await browser.newPage();
const client = await page.target().createCDPSession();
await client.send( //download先を指定
'Page.setDownloadBehavior',
{behavior : 'allow', downloadPath: downloadPath}
);
// 「ダウンロード」ボタンを押す
await navigationPromise;
const downloadLink = "//[contains(@href,'filename1.csv')]";
await page.waitForXPath(downloadLink);
const downloadLinkClick = await page.$x(downloadLink);
await downloadLinkClick[0].click();
await waitDownloadComplete(downloadPath)
.catch((err) => console.error(err));
console.log('-- download success');
// GCSへアップロード
await bucket.upload(upload_file)
.then(res => {
console.log(`-- ${upload_file} uploaded`);
})
.catch(err => {
console.error('-- ERROR:', err);
});
エラー内容調査用にスクリーンショットを取ってGCSにアップロード
pngpath = downloadPath +'/screenshot_xx.png';
await page.screenshot({ path: pngpath, fullPage:true});
await bucket.upload(pngpath)
.then(res => fs.unlinkSync(pngpath));
DOM要素検索の基礎
idで探す→#
await page.waitForSelector('#newbook');
classで探す→. (ドット)
await page.waitForSelector(.main-article');
ある要素の子要素を指定→スペースで区切る
//id="head_news"の要素の子要素で、かつ、class="main-article"
await page.waitForSelector('#head_news .main-article');
タグや属性名で探す
(例1) await page.waitForSelector('input');
(例2) await page.waitForSelector('input.gsc-input'); //class="gsc-input"を持つinputタグ
(例3) await page.waitForSelector('input[name="xxx"]');
(例4) await page.waitForSelector('[data-ship-id="12345"]');
自動化操作 page
URLからページ遷移 .goto
await page.goto(url);
await page.goto(url, {waitUntil: "domcontentloaded"});
await page.waitForSelector('div[class="xxx"]');
クリック .click
await page.click('input[class="xxx"]');
await page.waitForSelector('yyy');
テキスト入力 .type
await page.type('input[type="text"]', username);
await page.type('input[type="password"]', password);
コード例
package.json
{
"name": "test_func",
"version": "0.0.1",
"dependencies": {
"puppeteer": "^*"
,"delay": "^*"
,"fs": "^*"
,"@google-cloud/storage": "^*"
}
}
index.js
// import module
const fs = require('fs');
const puppeteer = require('puppeteer');
const {Storage} = require('@google-cloud/storage');
// variable
const url = 'https://xxx';
const username = 'tt';
const password = 'pw';
const downloadPath = '/tmp';
const projectId = 'project_id';
const bucketName = 'bucket1';
// GCSのバケットを指定
const storage = new Storage({"projectId": projectId});
const bucket = storage.bucket(bucketName);
// main
exports.download_csv = async (req, res) => {
// Puppeteer起動
const browser = await puppeteer.launch({
headless: true,
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-setuid-sandbox',
'--no-first-run',
'--no-sandbox',
'--no-zygote',
'--single-process'
]
});
// 新しい空のページを開く
const page = await browser.newPage();
const client = await page.target().createCDPSession();
await client.send( //download先を指定
'Page.setDownloadBehavior',
{behavior : 'allow', downloadPath: downloadPath}
);
// view portの設定.
await page.setViewport({
width: 1200,
height: 800
});
// 待ち時間の設定
const navigationPromise = page.waitForNavigation(); // 30秒でタイムアウトしてしまうので要注意
// URLにアクセス
await page.goto(url);
await page.goto(url, {waitUntil: "domcontentloaded"});
// ログイン情報を入力してログイン
await navigationPromise;
await page.type('input[type="text"]', username);
await page.type('input[type="password"]', password);
await page.click('button[class="xxx"]');
await page.waitForSelector('div[class="xxx"]');
console.log('-- login success');
// ダウンロードページへ移動
await navigationPromise;
await page.goto(url_friends, {waitUntil: "domcontentloaded"});
await page.waitForSelector('a[class="xxx"]');
console.log('-- ダウンロードページへ');
pngpath = downloadPath +'/screenshot_friend.png';
await page.screenshot({ path: pngpath, fullPage:true});
await bucket.upload(pngpath)
.then(res => fs.unlinkSync(pngpath));
// 「ダウンロード」ボタンを押す
await navigationPromise;
const downloadLink = "//a[contains(@href,'filename_ym.csv')]";
await page.waitForXPath(downloadLink);
const downloadLinkClick = await page.$x(downloadLink);
await downloadLinkClick[0].click();
await waitDownloadComplete(downloadPath)
.catch((err) => console.error(err));
console.log('-- download success');
// GCSへアップロード
await bucket.upload(upload_file)
.then(res => {
console.log(`-- ${upload_file} uploaded`);
})
.catch(err => {
console.error('-- ERROR:', err);
});
//ブラウザを閉じる
await page.waitForTimeout(5000);
await browser.close();
console.log('-- close browser');
};
const waitDownloadComplete = async (path, waitTimeSpanMs = 1000, timeoutMs = 60 * 1000) => {
return new Promise((resolve, reject) => {
const wait = (waitTimeSpanMs, totalWaitTimeMs) => setTimeout(
() => isDownloadComplete(path).then(
(completed) => {
if (completed) {
resolve();
} else {
const nextTotalTime = totalWaitTimeMs + waitTimeSpanMs;
if (nextTotalTime >= timeoutMs) {
reject('timeout');
}
const nextSpan = Math.min(
waitTimeSpanMs,
timeoutMs - nextTotalTime
);
wait(nextSpan, nextTotalTime);
}
}
).catch(
(err) => { reject(err); }
),
waitTimeSpanMs
);
wait(waitTimeSpanMs, 0);
});
};
const isDownloadComplete = async (path) => {
return new Promise((resolve, reject) => {
fs.readdir(path, (err, files) => {
if (err) {
reject(err);
} else {
if (files.length === 0) {
resolve(false);
return;
}
for(let file of files){
// .crdownloadがあればダウンロード中のものがある
if (/.*\.crdownload$/.test(file)) {
resolve(false);
return;
}
}
resolve(true);
}
});
});
};
exports.waitDownloadComplete = waitDownloadComplete;
exports.isDownloadComplete = isDownloadComplete;
この記事が気に入ったらサポートをしてみませんか?