皆さんは、WordPressでサイト運営をしていると、投稿を自動化したい場面などはありませんか?
おそらく、この記事を読んでいるということは、これからブログ記事の投稿を完全に自動化して、不労所得を得ようと企んでいるのではないでしょうか?
とは言うものの、自動化するために何をしたら良いのか分からず困っている人も多いかと思います。
そこで今回は、比較的簡単に誰でも扱えるNode.jsを使ってWordPressでブログ記事を自動投稿する方法について、スクレイピングのやり方から詳しく解説していきたいと思います。
ちなみに、スクレイピングする際は「1秒1アクセス」として、サーバーにあまり負荷を掛けすぎないように工夫しながらコーディングすることがポイントです。それでは詳しく見ていきましょう。
コーディングお役立ちツール
目次
WordPressで自動投稿する方法
まずは、スクレイピングしたデータをWordPressに自動投稿するためには「XML-RPC」と呼ばれる仕組みを使うことになりますので、プラグインなどで無効化してしまっている場合は有効化しておきましょう。
プラグインで無効化していない場合はデフォルトで有効になっていますので、そのままで大丈夫です。
また、自動投稿した記事を「IFTT」と呼ばれるサービスと連携することによって、ブログの投稿内容を自動でSNSに投稿できるようにもなりますので、極限まで作業時間を短縮したい人に、とてもオススメです。
詳しい、IFTTについての説明やプログラムでの自動化の流れについては下記解説を、ご覧ください。
IFTTT
IFTTT(イフト)は様々なWebアプリケーション同士を相互に連携させることができるサービスです。
日本語でのサービス提供はされていませんが、使い方がとてもシンプルなので英語の説明だけであっても簡単に設定完了することができます。
例えば今回は、WordPressに投稿された記事を自動でSNSにも投稿するという機能をIFTTTを使い実装するのですが、アカウント登録さえ終えてしまえばボタンを数回押すだけで直ぐに設定することができます。
詳しい設定方法については後々解説しますので、ひとまずアカウント登録だけしておきましょう。
プログラムでの自動化の流れ
次にプログラムを実際に書いて自動化の実装をする必要があるのですが、流れを明確にしておきましょう。
まずは、Node.jsでスクレイピングとWordPressに投稿するためのプログラムを書き、その後シェルスクリプトを書いてJavaScriptファイルを実行できるようにして、最後にcrontabを使い定期実行できるようになれば、プログラム側での自動化は完了になります。
ここで、簡単にまとめると以下のような流れになります。
- スクレイピングとWordPressに自動投稿するスクリプトを書く(Node.js)
- 作成したスクリプトファイルをシェルスクリプトで実行できるようにする
- crontabで定期実行できるようにする
WordPressに自動投稿するために必要な作業
WordPressに自動投稿するために必要な作業について説明していきます。
主にNode.jsを使ったコーディング作業が中心になりますので、パッケージ等のインストールや環境設定を初めの段階で済ませておく必要があります。
また、少しシェルスクリプトを書いたりcrontabなどの設定もありますので、比較的全体の作業は多めです。
簡単に必要な作業をまとめると以下のようになります。
- Node.jsで使うパッケージのインストール
- JavaScriptのコーディング
- シェルスクリプトの記述
- crontabの設定
- IFTTTでWordPressとSNSの連携
プロジェクトの作成とパッケージのインストール
まずは、プロジェクトの作成とコーディングで使うパッケージをインストールしていきます。
今回は5つのパッケージを使ってスクレイピングとWordPressへの自動投稿を行いたいと思います。
各パッケージの名前と、特徴については下のリストをご覧ください。
必要なパッケージ
- puppeteer (ブラウザを操作するパッケージ)
- delay (プログラムの動作を遅延させるためのパッケージ)
- node-wordpress (WordPressに投稿するためのパッケージ)
- colors (デバッグ時に色をつけて見やすくするためのパッケージ)
- dotenv (環境変数を読み込むためのパッケージ)
プロジェクトの作成
ターミナルを開き、デスクトップに移動した後に以下のコマンドでプロジェクトを作成します。
mkdir wp-auto-post
各種パッケージのインストール
作成したプロジェクトに移動して、パッケージをインストールしていきましょう。
以下のように「npm」コマンドを実行していけば、必要なパッケージをインストールできます。
「puppeteer」のインストール
npm install puppeteer
「delay」のインストール
npm install delay
「node-wordpress」のインストール
npm install node-wordpress
「colors」のインストール
npm install colors
「dotenv」のインストール
npm install dotenv
WordPressのローカル開発環境の構築
いきなり本番環境のWordPressでプログラムを動かすことは危険ですので、必ずローカル環境でテストを行ってから本番に移るようにしましょう。
まず本番と同等の環境を用意するために、データベースの情報をエクスポートすることと、FTPソフトなどを使ってWordPressで使用しているプログラムを、まとめてダウンロードしておく必要があります。
その後、ローカルにMAMP等のソフトを用意して、「wp-config.php」の編集と、データベースの情報を一部書き換える必要があります。詳しくは下記解説をご覧ください。
「wp-config.php」の編集
「wp-config.php」をダウンロードしたら、ローカル環境のデータベースのログイン情報に書き換える必要がありますので、下記の記述のある箇所を「root」に書き換えましょう。
/** MySQL データベースのユーザー名 */ define( 'DB_USER', 'root' ); /** MySQL データベースのパスワード */ define( 'DB_PASSWORD', 'root' );
データベースの設定
MAMPを起動した状態で「http://localhost/phpMyAdmin/」にアクセスして、ダウンロードしたデータベース情報をインポートしましょう。
その後、「wp_options」テーブルの「siteurl」と「home」を「http://localhost」に書き換えます。
.envファイルの記述
プロジェクトをgitで管理するようになった場合や、プログラムを動作させる環境に応じて振る舞いを変えたい場合などに便利な環境変数を記述していきます。
具体的にはWordPressなどにログインするための、URLやログイン情報などを記述していきます。
とは言ってもパスワード情報をファイルに記述しておくのは少々危険ですので、WordPressに標準で用意されている「アプリケーションパスワード」の機能を使っていきます。
アプリケーションパスワードはXML-RPCを実際のパスワードを入力しなくてもログインできようにするための認証方式なので、安全にコーディングを進めることができます。
アプリケーションパスワードの作成はWordPressの管理画面の「ユーザー > プロフィール」で行えます。
作成するとプログラムで使うためのパスワードが発行されますので、コピーしておきましょう。
step
1.envファイルの作成
空の「.env」ファイルを作成します。
touch .env
step
2.envファイルを編集
作成した「.env」ファイルを編集します。
vi .env
step
3環境変数の記述
「WP_URL」には投稿するWordPressサイトのアドレスを入力し、「WP_USERNAME」にはユーザー名を記述してください。
そして、先ほどコピーしたアプリケーションパスワードを「WP_PASSWORD」の右辺にペーストしてください。
それ以外は、以下の設定のままで大丈夫です。
#投稿サイト WP_URL = http://localhost WP_USERNAME = wpuser WP_PASSWORD = wpuser #デバイスの縦横幅、ブラウザを表示するか DEVICE_WIDTH = 1440 DEVICE_HEIGHT = 900 DEVICE_HEADLESS = false
JavaScriptで自動投稿用のプログラムを書く
インストールしたパッケージや、環境変数を使って自動化のプログラムを書いていきます。
最終的に以下の4つのJavaScriptファイルを作成することで自動化が実現します。
作成するファイル
- bot.js
- scraping.js
- wp.js
- main.js
bot.js
ブラウザを動かす時に使うモジュールを作成します。
ポイントは人間に近い挙動をするために、スクロールをしたりランダムな時間待機するなどの機能があるところです。
ただし、サイトにアクセスする時は最低1秒は待機するようにしておきましょう。
'use strict'; require("dotenv").config(); const puppeteer = require('puppeteer'); const colors = require("colors"); const delay = require("delay"); class Bot { constructor() { this.width = process.env.DEVICE_WIDTH; //デバイスの横幅 this.height = process.env.DEVICE_HEIGHT; //デバイスの縦幅 this.headless = Boolean(eval(process.env.DEVICE_HEADLESS)); //ヘッドレス this.dev = false; //開発者モード this.browser = null; this.page = null; //ページ this.delay_min_time = 1000; //処理の待ち時間(最小) this.delay_max_time = 5000; //処理の待ち時間(最大) } init() { //ブラウザを初期化 let self = this; return (async function() { self.browser = await puppeteer.launch({ headless: self.headless, slowMo: 10, defaultViewport: null, ignoreHTTPSErrors: true, timeout: 0, args: [ `--window-size=${self.width},${self.height}`, '--window-position=0,0', '--no-zygote', '--no-first-run', '--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--disable-infobars', //情報バー非表示 '--incognito', //シークレットモード ], devtools: self.dev //開発モード }); self.page = (await self.browser.pages())[0]; return self.page; })(); } auto_scroll(page) { //一番下までスクロール let self = this; return (async function() { await page.evaluate(async function() { let totalHeight = 0; let distance = window.innerHeight; let timer = setInterval(function() { let scrollHeight = document.body.scrollHeight window.scrollBy(0, distance); totalHeight += distance; if(totalHeight >= scrollHeight){ clearInterval(timer); resolve(); } }, 100); }); })(); } delay() { let self = this; return (async function() { let delay_time = Math.floor(Math.random() * (self.delay_max_time + 1 - self.delay_min_time)) + self.delay_min_time; /* ========== debug ========== */ //console.log(`\n${delay_time / 1000}秒停止\n`.yellow); await delay(delay_time); })(); } access(page, requestURL, referer) { //ページ遷移処理 let self = this; return (async function() { await self.auto_scroll(page); try { await page.setExtraHTTPHeaders({ 'referer': referer }); let response = await page.goto(requestURL, { timeout: 60000, waitUntil: 'domcontentloaded' }); await page.setCacheEnabled(false); console.log(`${colors.underline.blue(requestURL)}`); console.log('アクセス中....'.green); if(response.status() == 200) { console.log(`Status code: ${response.status()}`.green); console.log('完了\n'.green); return 1; } else { console.log(`Status code: ${response.status()}`.red); let access_flag = await self.reload(page, requestURL, referer); return access_flag; } } catch(error) { console.log(`${error}`.red); let access_flag = await self.reload(page, requestURL, referer); return access_flag; } })(); } reload(page, requestURL, referer) { //ネットワークエラーが起きた時に規定の回数同じURLにアクセスする let self = this; let delay_time = 60; let count = 0; let max_access = 3; //再度アクセスする回数 let access_flag = 0; return (async function() { //規定の回数同じURLにアクセス while(count < max_access) { /* ========== debug ========== */ console.log(`${delay_time}秒後に再度アクセスします\n`.red); await delay(delay_time * 1000); try { await page.setExtraHTTPHeaders({ 'referer': referer }); let response = await page.goto(requestURL, { timeout: 60000, waitUntil: 'domcontentloaded' }); await page.setCacheEnabled(false); console.log(`${colors.underline.blue(requestURL)}`); console.log('アクセス中....'.green); if(response.status() == 200) { console.log(`Status code: ${response.status()}`.green); console.log('完了\n'.green); access_flag = 1; } else { console.log(`Status code: ${response.status()}`.red); access_flag = 0; } } catch(error) { console.log(`${error}`.red); access_flag = 0; } //アクセスに成功したらループを終了 if(access_flag == 1) { break; } count += 1; } return access_flag; })(); } down() { //ブラウザを閉じる let self = this; return (async function() { self.browser.close(); })(); } } module.exports = new Bot();
scraping.js
次にAmazonの商品情報をスクレイピングするプログラムを書きます。
Amazonで販売されている本のURL、ASIN、新品の価格、中古の価格などの情報を先ほどのbot.jsを使い抽出するようになっています。
※Amazonへのスクレピングを推奨するプログラムではありませんので、あくまでスクレピングの参考用にご覧ください。
'use strict'; require("dotenv").config(); const colors = require('colors'); const delay = require('delay'); //other const bot = require('./bot.js'); class Scraping { constructor() { } mining(price_range = [1500, 3000], type = 86137051, month = new Date().getMonth(), year = new Date().getFullYear(), date_operation = 'during') { //詳細検索を利用して売れる商品を発掘 (セラー情報を取得しないバージョン) /* ========== debug ========== */ console.log('\nマイニング開始'.inverse); //価格幅が文字列だった場合は配列に変換 if(typeof price_range == 'string') { price_range = price_range.split(','); } let self = this; let search_price_range = price_range; //どの価格幅で検索するか let search_type = type; //判型 let search_month = month; //何月以前を検索するか let search_year = year; //何月以前を検索するか return (async function() { let max_pages = 1; //巡回する最大ページ数(最大75ページまで可能) let max_products = 5; //巡回する最大商品数(デバッグ用) let current_page = 1; //現在のページ番号 let itmes = []; //商品情報を格納した配列 let page = await bot.init(); while(current_page <= max_pages) { /* ========== debug ========== */ console.log(`\n========== 詳細検索${current_page}ページ目へアクセス ==========`.green); //詳細検索にアクセス let access_flag = await bot.access(page,`https://www.amazon.co.jp/s?i=stripbooks&rh=p_36%3A${search_price_range[0] * 100}-${search_price_range[1] * 100}&s=relevance-rank&page=${current_page}&Adv-Srch-Books-Submit.x=27&Adv-Srch-Books-Submit.y=13&__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&field-binding_browse-bin=${search_type}&field-datemod=${search_month}&field-dateop=${date_operation}&field-dateyear=${search_year}&page_nav_name=%E6%9C%AC%E3%83%BB%E3%82%B3%E3%83%9F%E3%83%83%E3%82%AF%E3%83%BB%E9%9B%91%E8%AA%8C&unfiltered=1&ref=sr_adv_b` ,'https://www.google.com'); if(access_flag == 0) { await bot.down(); } //一時停止 await bot.delay(); //商品ページへのリンクの総数 let product_links = await page.$$('.sg-col-inner div[data-component-type="s-search-result"] h2 > a'); /* ========== debug ========== */ console.log(`商品ページへの内部リンクの総数: ${product_links.length}`.red); for(let i = 0; i < product_links.length; i++) { console.log(colors.underline.blue(await (await product_links[i].getProperty('href')).jsonValue())); } let product_count = 0; while(product_count < max_products) { //max_products (デフォルト:product_links.length) /* ========== debug ========== */ console.log(`\n\n========== ${product_count + 1}商品目へアクセス ==========`.green); //商品のURLを新規タブで開く let product_detail_url = await (await product_links[product_count].getProperty("href")).jsonValue(); let new_tab = await bot.browser.newPage(); //商品詳細ページにアクセス access_flag = await bot.access(new_tab, product_detail_url, page.url()); if(access_flag == 0) { await bot.down(); } //一時停止 await bot.delay(); //中古の最安値の売価、新品の最安値の売価を取得 itmes.push((await self.itmes_info(new_tab))); //次の商品へ処理を移す product_count += 1; } current_page += 1; } await bot.down(); return itmes; })(); } itmes_info(page) { //URL、ASIN、中古の最安値の売価、新品の最安値の売価を返す let self = this; let result = null; return (async function() { //商品のURL let url = await page.url(); //Amazonの商品URLからASINコードを取得 let asin = self.product_asin((await page.url())); /* ========== debug ========== */ console.log(`ASINコード: ${asin}\n`.red); //中古商品の売価を包含している要素 let $olp_used = await page.$('#usedPrice'); let olp_used_value = null; //売価 if($olp_used !== null) { let value_match = /¥\d+/; //売価の正規表現 //テキストを抽出 let olp_used_text = await (await $olp_used.getProperty('textContent')).jsonValue(); olp_used_text = olp_used_text.replace(/,/g, ''); /* ========== debug ========== */ //console.log(olp_used_text); //売価を抽出 olp_used_value = olp_used_text.match(value_match); if(olp_used_value !== null) { olp_used_value = olp_used_value[0]; olp_used_value = Number(olp_used_value.replace(/¥/, '')); } /* ========== debug ========== */ console.log(`中古品の最安値の売価:${olp_used_value}`.red); } //新品商品の売価を包含している要素 let $olp_new = await page.$('#newBuyBoxPrice, #price'); let olp_new_value = null; //売価 if($olp_new !== null) { let value_match = /¥\d+/; //売価の正規表現 let length_match = /\d+.新品/; //出品数の正規表現 //テキストを抽出 let olp_new_text = await (await $olp_new.getProperty('textContent')).jsonValue(); olp_new_text = olp_new_text.replace(/,/g, ''); /* ========== debug ========== */ //console.log(olp_new_text); //売価を抽出 olp_new_value = olp_new_text.match(value_match); if(olp_new_value !== null) { olp_new_value = olp_new_value[0]; olp_new_value = Number(olp_new_value.replace(/¥/, '')); } /* ========== debug ========== */ console.log(`新品の最安値の売価:${olp_new_value}`.red); } //中古品の最安値の売価、新品の最安値の売価、新品の出品数を返す await page.close(); return result = { url: url, //商品ページのURL asin: asin, //ASINコード olp_used_value: olp_used_value, //中古品の最安値の売価 olp_new_value: olp_new_value, //新品の最安値の売価 }; })(); } product_asin(url) { //AmazonのURLからASINコードを取得 let result = null; let asin_match = /\/dp\/.{10}/; //商品URLからASINコード抽出するための条件 let match_array = url.match(asin_match); //条件にマッチしていた場合に返される配列 //条件に一致していた場合 if(match_array !== null) { let asin = match_array[0].replace(/dp/g, '').replace(/\//g, ''); result = asin; } return result; } } module.exports = Scraping;
wp.js
WordPressのXML-RPCの仕組みを使い、記事を投稿するためのプログラムです。
インストールした「node-wordpress」と最初に記述した「.env」ファイルを使うことによって投稿が可能になります。
また、カスタム投稿やカスタムフィールドを使った投稿も可能になっています。
'use strict'; require('dotenv').config(); const colors = require('colors'); const wordpress = require('wordpress'); class WP { constructor() { this.client = null; //サーバー情報を格納 this.url = process.env.WP_URL; this.username = process.env.WP_USERNAME; this.password = process.env.WP_PASSWORD; //初期化 this.init(); } init() { //投稿するサイトの情報を設定 this.client = wordpress.createClient({ url: this.url, username: this.username, password: this.password }); } post(dataset) { //Wordpressへ投稿 let self = this; return new Promise(function(resolve, reject) { if(dataset instanceof Object == true) { let status = dataset.status; let type = dataset.type; let author = dataset.author; let title = dataset.title; let content = dataset.content; let termNames = {}; if(dataset.termNames instanceof Object == true) { termNames = dataset.termNames; } let customFields = []; if(dataset.customFields instanceof Array == true) { customFields = dataset.customFields; } self.client.newPost({ status: status, type: type, author: author, title: title, content: content, termNames: termNames, customFields: customFields }, function(error, post_id) { if(post_id) { console.log(`${post_id}`.green); resolve(post_id); } if(error) { console.log(`${error}`.red); reject(); } }); } }); } } module.exports = new WP();
main.js
コーディングした「scraping.js」と「wp.js」を読み込んで、全体を動かすためのプログラムです。
最終的にスクレイピングしたデータを、ASINコードをタイトル、それ以外をHTMLのテーブル要素としてWordPress側に自動で投稿されるよようになっています。
また、main.jsの実行では引数を取ることにって、検索する価格帯を変更したりすることができます。
'use strict'; require("dotenv").config(); const colors = require('colors'); //other const Scraping = require('./scraping.js'); const wp = require('./wp.js'); class Main { constructor(options) { this.search_price_range = options[2]; //どの価格幅で検索するか this.search_type = options[3]; //判型 this.search_month = options[4]; //何月を検索するか this.search_year = options[5]; //何年出版の物を検索するか this.scraping = new Scraping(); this.run(); } run() { let self = this; (async function() { //詳細検索の中から、ライバルセラーに勝てる商品を発掘 let items = await self.scraping.mining(self.search_price_range, self.search_type, self.search_month, self.search_year); console.log(`\n${JSON.stringify(items, undefined, 1)}\n`); //WordPressサイトへ投稿 for(let i = 0; i < items.length; i++) { await wp.post({ status: 'publish', title: items[i].asin, content: `<table> <tr> <th>URL</th> <th>新品の最安値</th> <th>中古の最安値</th> </tr> <tr> <td>${items[i].url}</td> <td>${items[i].olp_new_value}</td> <td>${items[i].olp_used_value}</td> </tr> </table>`, }); } })(); } } let main = new Main(process.argv);
シェルスクリプトの記述
先ほどコーディングしたmain.jsをシェルスクリプを使い実行することによって、自動化する中で引数の値を変更しながらプログラムを動かすことができるので、違う本の価格帯や日付なども変更して勝手に検索してくれるようになります。
シェルスクリプトの詳細については下記プログラムをご覧ください。
wp-auto-post.sh
#!/bin/sh #何ヶ月前まで検索するか(現在の月も含める:デフォルト3+1) month_before=$((1 + 1)) #シェルスクリプトが実行された時刻 start_date=$(date) i=0 function wp_auto_post() { #現在の年 year=$(date "-v-"${i}"m" +"%Y") #現在の月(01~12) month=$(date "-v-"${i}"m" +"%m") echo "\n"${year}"年"${month}"月の詳細検索を処理します" /usr/local/bin/node main.js 1500,3000 86137051 $month $year if [ $? = 0 ] ; then echo "\n"${year}"年"${month}"月の詳細検索が完了しました" fi sleep 5s i=$(( i + 1 )) if [ $i -lt $month_before ] ; then wp_auto_post fi #規定回数の探索が終わった場合 if [ $i -gt $((month_before - 1)) ] ; then echo "全ての処理が完了しました" echo "開始時刻: "${start_date} echo "終了時刻: "$(date) exit fi } wp_auto_post
crontabの設定
crontabは決まった日時に登録したLinuxコマンドを実行してくれるようできるので、先ほど作成した「wp-auto-post.sh」を動かすコマンドを記述しておけば、自動でスクレイピングを行い、WordPressへ投稿してくれるようになります。
設定方法はターミナルを開いた状態で「crontab -i」と打ち込むことで編集できるようになります。
編集が終わると「esc」「:wq」と入力して保存を完了させましょう。
シェルスクリプトを12時間おきに実行するcrontabの設定は以下のようになります。
* */12 * * * /Users/user/wp-auto-post && sh wp-auto-post.sh
IFTTTでWordPressとSNSを連携させる
WordPressに自動で投稿できるようになりましたので、次にSNSにも自動で投稿するためにIFTTTを使う必要があります。
今回は代表的なSNSとしてTwitter、Facebook、Tumblrと連携したいと思います。
ただし、インスタグラムは現在非対応ですので、一旦この3つのSNSとの連携で進めていきましょう。
連携方法は「https://ifttt.com/create」にアクセスして、「If This」のボタンを押してWordPressサイトを登録し、「Then That」のボタンを押してSNSと連携させることができます。
連携するSNS
- Tumblr
まとめ
今回はスクレイピングする方法からWordPressに自動投稿したりSNSと連携するより方について解説してきましたが、いかがでしたでしょうか?
プログラミング初心者の方には少し難しい内容になってしまったかもしれませんが、すぐに理解できなくても大丈夫ですので、焦らずにゆっくりと開発を進めていきましょう。
また、スクレイピングはサイトによっては完全に禁止しているところもありますので、対象とするサイトには最大限運営の邪魔にならないよう配慮しながら開発をしましょう。
それでは皆さん、良いスクレイピングライフを!