Puppeteerの利用方法メモ
書いてあること
- Puppeteerの利用検証を行った際のメモ
環境
- Windows 10 Pro 1909
- Node.js v12.16.1
- Npm 6.14.3
- puppeteer 2.1.1
作成したプロジェクト
↓に置いてあります。
puppeteer-project
Puppeteerとは
Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.
環境構築
Node.jsをインストール
↓からインストーラを取得してインストール
Node.js
# バージョン確認
$ node --version
v12.16.1
$ npm --version
6.14.3
# npmを最新化
$ npm install -g npm
プロジェクト作成
# プロジェクトのディレクトリを作成・移動
$ mkdir puppeteer-project && cd puppeteer-project
# Git初期化
$ git init
# .gitignoreファイルを下記の通り作成
# プロジェクトを作成(対話形式はスキップ)
$ npm init -y
# package.jsonを下記の通り修正
# src、outディレクトリを作成
$ mkdir src out
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp
{
"name": "puppeteer-project",
"version": "1.0.0",
"description": "Puppeteer Project",
"author": "yoshi0518"
}
Puppeteerインストール
$ npm install --save puppeteer
ESLint、Prettier設定
必要なパッケージをインストール
$ npm install --save-dev prettier eslint eslint-plugin-prettier eslint-config-prettier babel-eslint
.eslintrc.jsを作成
module.exports = {
root: true,
parser: 'babel-eslint',
env: {
browser: true,
node: true,
es6: true,
},
extends: [
'eslint:recommended',
'plugin:prettier/recommended',
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'generator-star-spacing': 'off',
'prettier/prettier': 'error'
},
};
.eslintignoreを作成
/node_modules/
/*.js
/package.json
/package-lock.json
.prettierrc.jsを作成
module.exports = {
trailingComma: 'es5',
printWidth: 140,
tabWidth: 2,
singleQuote: true,
semi: true,
};
.prettierignoreを作成
/node_modules/
/*.js
/package.json
/package-lock.json
package.jsonにスクリプトを追加
{
"name": "puppeteer-project",
"version": "1.0.0",
"description": "Puppeteer Project",
"author": "yoshi0518",
+ "scripts": {
+ "lint": "eslint --ext .js --ignore-path .gitignore ./src",
+ "lint:fix": "eslint --ext .js --ignore-path .gitignore ./src --fix"
+ },
"dependencies": {
動作確認
sample.jsを作成
const puppeteer = require('puppeteer');
const path = require('path');
const URL = 'https://www.yahoo.co.jp/';
const PATH = './out';
const DEBUG = 0; // デバッグログなし
// const DEBUG = 1; // デバッグログあり
(async () => {
console.log('[Info] ■■■ Puppeteer動作確認 Start ■■■');
if (DEBUG) console.log('[Debug] Chromium起動');
const browser = await puppeteer.launch({
headless: false,
slowMo: 50,
});
if (DEBUG) console.log('[Debug] 新しいタブを開く');
const page = await browser.newPage();
if (DEBUG) console.log('[Debug] ビューポート/デバイスを指定');
await page.setViewport({
width: 1200,
height: 800,
});
if (DEBUG) console.log('[Debug] Yahooへ移動');
await page.goto(URL);
if (DEBUG) console.log('[Debug] スクリーンショットを保存');
await page.screenshot({
path: path.join(PATH, 'sample.png'),
fullPage: true,
});
console.log(`[Info] ファイル保存:sample.png`);
if (DEBUG) console.log('[Debug] Chromium終了');
await browser.close();
console.log('[Info] ■■■ Puppeteer動作確認 End ■■■');
})();
実行
下記コマンドを実行し、outディレクトリにsample.pngが作成されることを確認。
$ node ./src/sample.js
動作検証
2020/4/2にPuppeteer動作検証を行った際のソースです。
各サイトに負荷がかかりますので、試す場合は各自の責任で最小限ご利用ください。
事前準備
必要なパッケージをインストール
$ npm install --save dotenv csv-stringify iconv-lite request util child_process
Tesseract OCRをインストール
認証画像からキーワードを取得する際に利用するTesseract OCR(オープンソースOCRエンジン)をインストール
Mac
$ brew update
$ brew install tesseract
Windows
Tesseract OCRをWindowsにインストールする方法
共通処理を作成
const fs = require('fs');
const stringify = require('csv-stringify');
const iconv = require('iconv-lite');
const request = require('request');
const { promisify } = require('util');
// 指定したディレクトリ内のファイルを全て削除
exports.deleteFiles = (path) => {
fs.readdir(path, (error, files) => {
if (error) throw error;
for (const file of files) {
fs.unlink(`${path}/${file}`, (error) => {
if (error) throw error;
console.log(`[Info] ファイル削除:${file}`);
});
}
});
};
// パラメータの配列からCSVファイルを作成
exports.createCsv = async (data, filePath, charSet) => {
stringify(data, (error, csvData) => {
if (error) throw error;
const writableStream = fs.createWriteStream(filePath);
writableStream.write(iconv.encode(csvData, charSet));
});
};
// 指定した画像をダウンロード
exports.downloadImage = async (src, filePath) => {
const res = await promisify(request)({
method: 'GET',
uri: src,
encoding: null,
});
if (res.statusCode === 200) {
await promisify(fs.writeFile)(filePath, res.body, 'binary');
} else {
throw new Error(res.statusCode + ' Image download error');
}
};
YAHOO
const puppeteer = require('puppeteer');
const path = require('path');
const common = require('./common.js');
const URL = 'https://www.yahoo.co.jp/';
const PATH = './out/sample_yahoo';
const DEBUG = 0; // デバッグログなし
// const DEBUG = 1; // デバッグログあり
// 各ニュースページを開き、スクリーンショットを保存
const getChildPage = async (browser, href) => {
console.log(`[Info] ニュースページのURL:${href}`);
if (DEBUG) console.log('[Debug] 新しいタブを開き、各ニュースページに移動');
const childPage = await browser.newPage();
await childPage.goto(href);
if (DEBUG) console.log('[Debug] 各ニュースのタイトルを取得');
const title = await childPage.evaluate(() => document.querySelector('.pickupMain_articleTitle').innerHTML);
console.log(`[Info] タイトル:${title}`);
if (DEBUG) console.log('[Debug] 1.5秒待機');
await childPage.waitFor(1500);
if (DEBUG) console.log('[Debug] 「続きを読む」リンクをクリック');
await Promise.all([childPage.waitForNavigation({ waitUntil: 'load' }), childPage.click('.pickupMain_detailLink > a')]);
if (DEBUG) console.log('[Debug] スクリーンショットを保存');
await childPage.screenshot({
path: path.join(PATH, `${title}.png`),
fullPage: true,
});
console.log(`[Info] ファイル保存:${title}.png`);
if (DEBUG) console.log('[Debug] 1.5秒待機');
await childPage.waitFor(1500);
if (DEBUG) console.log('[Debug] 各ニュースページを閉じる');
await childPage.close();
};
(async () => {
console.log('[Info] ■■■ Yahooニュースを取得 Start ■■■');
if (DEBUG) console.log('[Debug] 前回実行時のファイルを削除');
await common.deleteFiles(PATH);
if (DEBUG) console.log('[Debug] Chromium起動');
const browser = await puppeteer.launch({
headless: false,
slowMo: 50,
});
if (DEBUG) console.log('[Debug] 新しいタブを開く');
const page = await browser.newPage();
if (DEBUG) console.log('[Debug] ビューポート/デバイスを指定');
await page.setViewport({
width: 1200,
height: 800,
});
if (DEBUG) console.log('[Debug] Yahooへ移動');
await page.goto(URL, { waitUntil: 'domcontentloaded' });
if (DEBUG) console.log('[Debug] ニュースタブからhrefを取得');
const hrefs = await page.evaluate(() =>
Array.from(document.querySelectorAll('#tabpanelTopics1 > div > div > ul > li > article > a')).map((a) => a.href)
);
if (DEBUG) console.log('[Debug] 取得したhrefだけ繰り返し');
for (const href of hrefs) {
if (DEBUG) console.log('[Debug] 各ニュースページを開き、スクリーンショットを保存');
await getChildPage(browser, href);
}
if (DEBUG) console.log('[Debug] Chromium終了');
await browser.close();
console.log('[Info] ■■■ Yahooニュースを取得 End ■■■');
})();
const puppeteer = require('puppeteer');
const path = require('path');
const common = require('./common.js');
const URL = 'https://www.google.com/?hl=ja';
const PATH = './out/sample_google';
const DEBUG = 0; // デバッグログなし
// const DEBUG = 1; // デバッグログあり
(async () => {
console.log('[Info] ■■■ Google検索結果を取得 Start ■■■');
if (DEBUG) console.log('[Debug] 前回実行時のファイルを削除');
await common.deleteFiles(PATH);
if (DEBUG) console.log('[Debug] Chromium起動');
const browser = await puppeteer.launch({
// headless: false,
slowMo: 50,
});
if (DEBUG) console.log('[Debug] 新しいタブを開く');
const page = await browser.newPage();
if (DEBUG) console.log('[Debug] ビューポート/デバイスを指定');
await page.setViewport({
width: 1200,
height: 800,
});
if (DEBUG) console.log('[Debug] Googleへ移動');
await page.goto(URL, { waitUntil: 'domcontentloaded' });
if (DEBUG) console.log('[Debug] 検索キーワードを入力');
await page.type('input[name="q"]', 'puppeteer');
if (DEBUG) console.log('[Debug] タイトルイメージにフォーカスを移動');
await page.focus('#hplogo');
if (DEBUG) console.log('[Debug] 検索ボタンをクリック');
await Promise.all([page.waitForNavigation({ waitUntil: 'load' }), page.click('#tsf > div > div > div > center > input[name="btnK"]')]);
if (DEBUG) console.log('[Debug] 検索結果からhrefを取得');
const results = await page.$$('.g > .rc > .r > a');
if (DEBUG) console.log('[Debug] 取得したhrefだけ繰り返し');
let data = [{ text: 'text', href: 'href' }];
let cnt = 0;
for (const result of results) {
const text = await (await result.getProperty('text')).jsonValue();
const href = await (await result.getProperty('href')).jsonValue();
if (DEBUG) console.log('[Debug] text、hrefを変数へ格納');
data.push({
text: text,
href: href,
});
console.log(`[Info] 検索結果ページのURL:${href}`);
console.log(`[Info] タイトル:${text}`);
if (DEBUG) console.log('[Debug] 3件までPDF出力');
if (cnt < 3) {
if (DEBUG) console.log('[Debug] 新しいタブを開き、検索結果ページに移動');
const childPage = await browser.newPage();
await childPage.goto(href);
if (DEBUG) console.log('[Debug] PDFを保存');
await childPage.pdf({
path: path.join(PATH, `${text}.pdf`),
});
console.log(`[Info] ファイル保存:${text}.pdf`);
if (DEBUG) console.log('[Debug] 1.5秒待機');
await childPage.waitFor(1500);
if (DEBUG) console.log('[Debug] 検索結果ページを閉じる');
await childPage.close();
cnt++;
}
}
if (DEBUG) console.log('[Debug] Chromium終了');
await browser.close();
if (DEBUG) console.log('[Debug] 検索結果をCSV出力');
await common.createCsv(data, path.join(PATH, 'data.csv'), 'UTF-8');
console.log('[Info] ■■■ Google検索結果を取得 End ■■■');
})();
価格.com
const puppeteer = require('puppeteer');
const path = require('path');
const common = require('./common.js');
const URL = 'https://kakaku.com/pc/pda/itemlist.aspx?pdf_se=2'; // 製品一覧(iPad)
const PATH = './out/sample_kakaku';
const DEBUG = 0; // デバッグログなし
// const DEBUG = 1; // デバッグログあり
// 各製品ページを開き、製品情報を取得
const getItemInfo = async (browser, href) => {
console.log(`[Info] 製品ページのURL:${href}`);
if (DEBUG) console.log('[Debug] 新しいタブを開き、製品ページに移動');
const childPage = await browser.newPage();
await childPage.goto(href, { waitUntil: 'domcontentloaded' });
if (DEBUG) console.log('[Debug] 1.5秒待機');
await childPage.waitFor(1500);
if (DEBUG) console.log('[Debug] 製品名を取得');
const itemTitle = await childPage.evaluate(() => document.querySelector('#titleBox h2').innerText);
console.log(`[Info] 製品名:${itemTitle}`);
if (DEBUG) console.log('[Debug] 製品の情報を取得①');
// 画像
const itemImgSrc = await childPage.evaluate(() => document.querySelector('#imgBox > a > img').src);
// 最安価格
const itemPrice = await childPage.evaluate(() => document.querySelector('#priceBox div.priceWrap span.priceTxt').innerText);
// 売れ筋ランキング
const itemRanking = (await childPage.evaluate(() => document.querySelector('#ovBtnBox li.ranking span.num').innerText)) + '位';
// 満足度・レビュー
const itemReview =
(await childPage.evaluate(() => document.querySelector('#ovBtnBox li.review span.num').innerText)) +
'点(' +
(await childPage.evaluate(() => document.querySelector('#ovBtnBox li.review span.sup').innerText)) +
')';
// クチコミ
const itemBbs = (await childPage.evaluate(() => document.querySelector('#ovBtnBox li.bbs span.num').innerText)) + '件';
if (DEBUG) console.log('[Debug] 「スペック情報」リンクをクリック');
await Promise.all([childPage.waitForNavigation({ waitUntil: 'load' }), childPage.click('#tab li:nth-child(4) a')]);
if (DEBUG) console.log('[Debug] 1.5秒待機');
await childPage.waitFor(1500);
if (DEBUG) console.log('[Debug] 製品の情報を取得②');
// OS種類
const itemOs = await childPage.evaluate(
() => document.querySelector('#mainLeft > table > tbody > tr:nth-child(2) td:nth-child(2)').innerText
);
// ネットワーク接続タイプ
const itemNetwork = await childPage.evaluate(
() => document.querySelector('#mainLeft > table > tbody > tr:nth-child(2) td:nth-child(4)').innerText
);
// CPU
const itemCpu = await childPage.evaluate(
() => document.querySelector('#mainLeft > table > tbody > tr:nth-child(4) td:nth-child(2)').innerText
);
// コア数
const itemCore = await childPage.evaluate(
() => document.querySelector('#mainLeft > table > tbody > tr:nth-child(4) td:nth-child(4)').innerText
);
// 画面サイズ
const itemDisplaySize = await childPage.evaluate(
() => document.querySelector('#mainLeft > table > tbody > tr:nth-child(14) td:nth-child(2)').innerText
);
// パネル種類
const itemDisplayType = await childPage.evaluate(
() => document.querySelector('#mainLeft > table > tbody > tr:nth-child(14) td:nth-child(4)').innerText
);
// 画面解像度
const itemDisplayResolution = await childPage.evaluate(
() => document.querySelector('#mainLeft > table > tbody > tr:nth-child(15) td:nth-child(2)').innerText
);
// 重量
const itemWeight = await childPage.evaluate(
() => document.querySelector('#mainLeft > table > tbody > tr:nth-child(30) td:nth-child(2)').innerText
);
// サイズ
const itemSize = await childPage.evaluate(
() => document.querySelector('#mainLeft > table > tbody > tr:nth-child(30) td:nth-child(4)').innerText
);
if (DEBUG) console.log('[Debug] 製品ページを閉じる');
await childPage.close();
if (DEBUG) console.log('[Debug] 製品画像をダウンロード');
const filePath = path.join(PATH, itemTitle + itemImgSrc.split('.').pop());
await common.downloadImage(itemImgSrc, filePath);
return [
itemTitle,
itemImgSrc,
itemPrice,
itemRanking,
itemReview,
itemBbs,
itemOs,
itemNetwork,
itemCpu,
itemCore,
itemDisplaySize,
itemDisplayType,
itemDisplayResolution,
itemWeight,
itemSize,
];
};
(async () => {
console.log('[Info] ■■■ 価格.com売れ筋製品を取得 Start ■■■');
if (DEBUG) console.log('[Debug] 前回実行時のファイルを削除');
await common.deleteFiles(PATH);
if (DEBUG) console.log('[Debug] Chromium起動');
const browser = await puppeteer.launch({
headless: false,
slowMo: 50,
});
if (DEBUG) console.log('[Debug] 新しいタブを開く');
const page = await browser.newPage();
if (DEBUG) console.log('[Debug] 指定した製品一覧ページへ移動');
await page.goto(URL, { waitUntil: 'domcontentloaded' });
if (DEBUG) console.log('[Debug] 1.5秒待機');
await page.waitFor(1500);
if (DEBUG) console.log('[Debug] 売れ筋1~10位までのhrefを取得');
let hrefs = await page.evaluate(() => Array.from(document.querySelectorAll('td.ckitemLink > a')).map((a) => a.href));
hrefs.splice(10);
if (DEBUG) console.log('[Debug] 1.5秒待機');
await page.waitFor(1500);
if (DEBUG) console.log('[Debug] 取得したhrefだけ繰り返し');
const items = [];
items.push([
'製品名',
'画像',
'最安価格',
'売れ筋ランキング',
'満足度・レビュー',
'クチコミ',
'OS種類',
'ネットワーク接続タイプ',
'CPU',
'コア数',
'画面サイズ',
'パネル種類',
'画面解像度',
'重量',
'サイズ',
]);
for (const href of hrefs) {
if (DEBUG) console.log('[Debug] 各製品ページを開き、製品情報を取得');
items.push(await getItemInfo(browser, href));
}
if (DEBUG) console.log('[Debug] 製品情報を表示');
console.log(items);
if (DEBUG) console.log('[Debug] Chromium終了');
await browser.close();
if (DEBUG) console.log('[Debug] 検索結果をCSV出力');
await common.createCsv(items, path.join(PATH, 'data.csv'), 'UTF-8');
console.log('[Info] ■■■ 価格.com売れ筋製品を取得 End ■■■');
})();
主なAPI仕様
Puppeteerクラス
launch:ブラウザを開く
headless
:デフォルトはブラウザ非表示。false
でブラウザを表示
devtools
:true
で開発者ツールを開く
slowMo
:全体的にゆっくり操作をさせる。50〜100程度で人間の操作に近い速度感と思われる
executablePath
:Chromiumではなく、インストール済のChromeを使う場合にパスを設定
defaultViewport
:デフォルトの画面サイズ等を変更
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
devices:デバイスのプリセットを利用
Browserクラス
close:ブラウザを閉じる
newPage:新しいタブを開く
Pageクラス
setViewport:ビューポートを指定
width
:幅
height
:高さ
deviceScaleFactor
:スケール
setUserAgent:ユーザーエージェントを指定
goto:ページを移動
url
:URL
timeout
:タイムアウト時間
waitUntil
:移動成功の判断基準。デフォルトはページ表示完了
waitFor:指定した時間、またはセレクターが表示されるまで、または関数が終了するまで待機
waitForSelector:セレクターが表示されるまで待機
waitForNavigation:ページ移動後の待機
waitUntil
:移動成功の判断基準
title:ページタイトルを取得
await page.title()
$$:セレクターに該当する要素すべてを取得
ElementHandleが返される
https://github.com/puppeteer/puppeteer/blob/v2.1.0/docs/api.md#pageselector-1
evaluate:関数を実行して結果を返す
screenshot:スクリーンショットを保存
path
:スクリーンショットの保存先
type
:拡張子を指定。デフォルトはpng
quality
:拡張子がjpegの場合の画像品質を0〜100で指定
fullPage
:スクロール可能な場合、true、で全体を取得。デフォルトはfalse
pdf:PDFファイルを保存
path
:PDFファイルの保存先
scale
:スケール。デフォルトは1
landscape
:用紙の向き
pageRanges
:印刷するページの範囲。デフォルトは全ページ
type:指定した要素に文字入力
click:指定した要素をクリック
ElementHandleクラス
getProperty:指定したプロパティの値を返す
その他
利用検証時や、実務での開発で利用した内容
セレクターで要素の取得確認
開発者ツールのConsoleタブで下記コマンドを入力することで、要素が取得できているかを確認可能
document.querySelectorAll('確認したいセレクター');
ダウンロードパスを指定
ダウンロードパスは絶対パスで指定。
フォルダがない場合は自動作成される。
const downloadPath = 'C:\\test\\puppeteer';
await page._client.send(
'Page.setDownloadBehavior',
{ behavior: 'allow', downloadPath: downloadPath }
);
ファイルアップロード(input type="file")
// input要素を取得し、アップロードするファイルを指定
const filePath = 'sample.csv';
const inputFileHandle = await page.$('input[name="f/filename"]');
await inputFileHandle.uploadFile(filePath);
// インポートボタンをクリック
await Promise.all([
page.waitForNavigation({ waitUntil: 'load' }),
page.click('input[value="インポート"]')
]);
画像認証のキーワード取得
// 認証画像のスクリーンショットを保存
const auth_img = await page.$('img[src="●●●●●"]');
await auth_img.screenshot({ path: path.join(PATH, 'auth_img.png') });
// 認証画像からキーワードを取得
execSync(`tesseract ${PATH}/auth_img.png ${PATH}/auth_img`);
const keyword = fs.readFileSync(`${PATH}/auth_img.txt`, 'utf-8').split('\n')[0];
参考
Puppeteer API v2.1.0
puppeteerでファイルダウンロードのダウンロードパスを設定する
VS CodeにPrettier・ESLint・Stylelintを導入してファイル保存時にコードを自動整形させる方法
eslint & prettier を整備したので設定についてまとめた
Author And Source
この問題について(Puppeteerの利用方法メモ), 我々は、より多くの情報をここで見つけました https://qiita.com/yoshi0518/items/0685700861fb7fd3f97b著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .