個人的(私的利用の範囲内で)に、めざましテレビの占いをLibreOfficeのCalcを使い、簡易的なデータベースとしてまとめ始めたが、月~土にめざましテレビの占いページを見て移していく作業は、若干面倒だったり、忘れてしまったりで、あまりよろしくない感じがしていました。
以前からGoogle Apps Script(以下GAS)は気になっていたが、使ってこなかったので、GASでデータベース化する事にしました。
今回はPuppeteerを利用して、めざましテレビの占いページから情報を取得します。
PuppeteerはそのままではGASでは利用できないため、Google Cloud Functionsを利用して使用するか、自前の環境で使用するかになりますが、折角VPSをレンタルしているので、WindowsサーバーのVPSを利用して構築していきます。
構築作業
まずは、Googleドライブでめざましテレビの占い用のスプレッドシートを作成。
次に、拡張機能からApps Scriptを選択し、コードエディタを開きます。
JavaScriptベースのコードを書く。
最終的に出来上がったコード例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
function fetchFortuneFromVPS() { const url = 'http://example.com/get-fortune'; // HTTPSで接続時に403になる場合はHTTP接続してください const options = { 'method': 'GET', 'headers': { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0' }, 'muteHttpExceptions': true // 403エラー時もレスポンス全体を取得できるようにする }; const response = UrlFetchApp.fetch(url, options); Logger.log(response.getContentText()); // ステータスコードとレスポンス内容をログ出力 Logger.log('HTTP Response Code: ' + response.getResponseCode()); Logger.log('Response Content: ' + response.getContentText()); const data = JSON.parse(response.getContentText()); // スプレッドシートのIDを指定 const spreadsheetId = 'スプレッドシートのID'; const sheet = SpreadsheetApp.openById(spreadsheetId).getActiveSheet(); // データを書き込む先の行数を計算 const lastRow = sheet.getLastRow(); // カラムヘッダーがなければ追加 if (lastRow === 0) { sheet.appendRow(['日付', '順位', '星座', 'テキスト', 'ポイント']); } // APIレスポンスのデータを書き込み (APIレスポンスのフォーマットに応じて処理) data.forEach(fortune => { const row = [ fortune.date, // 日付 fortune.rank, // 順位 fortune.starSign, // 星座 fortune.text, // テキスト fortune.point // ポイント ]; sheet.appendRow(row); }); Logger.log(data); } |
Windowsサーバーでの作業
Windowsサーバーでプロジェクトフォルダを作成し、Puppeteerをインストール
1 |
npm install puppeteer |
スクレイピングをするためのgetFortune.js
を作成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const page = await browser.newPage(); // ページにアクセスし、完全に読み込まれるまで待機 await page.goto('めざましテレビの占いページURL', { waitUntil: 'networkidle2' }); // 日付を取得し、整形する const date = await page.evaluate(() => { const dateElement = document.querySelector('p.date'); if (dateElement) { const rawDate = dateElement.innerText.trim(); // "9月19日(木)" のような形式を取得 const match = rawDate.match(/(\d+)[^\d]+(\d+)/); // 正規表現で "月" と "日" を抽出 if (match) { const year = new Date().getFullYear(); // 現在の年を使用 const month = match[1]; // 月 const day = match[2]; // 日 return `${year}/${month}/${day}`; // "2024/9/19" の形式に整形 } } return '日付不明'; // 日付が取得できなかった場合のフォールバック }); //console.log(`取得した日付: ${date}`); // 取得された日付をログに出力 // 占い結果を取得 const fortunes = await page.evaluate(() => { const results = []; const fortuneElements = document.querySelectorAll('.uranaiList li'); fortuneElements.forEach(element => { const rank = element.querySelector('.rank').textContent.trim(); const starSign = element.querySelector('.name').textContent.trim(); const text = element.querySelector('.text').textContent.trim().replace(/\n/g, " "); const point = element.querySelector('.point').textContent.trim().replace(/\n/g, " "); results.push({ rank, starSign, text, point }); }); return results; }); // 日付を各占いデータに追加 const fortuneWithDate = fortunes.map(fortune => ({ date, // 取得した日付を追加 ...fortune // 既存の占いデータを展開して追加 })); // JSON形式で出力 console.log(JSON.stringify(fortuneWithDate, null, 2)); // ブラウザを閉じる await browser.close(); })(); |
次に、Node.jsを使ってPuppeteerスクリプトを呼び出すための簡単なAPIサーバー、Expressのインストール
1 |
npm install express |
また、ExpressアプリケーションでCORSを有効にするためにCORSをインストール
1 |
npm install cors |
APIサーバーのコード(app.js)を作成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
const express = require('express'); const cors = require('cors'); const { exec } = require('child_process'); const app = express(); app.use(cors()); // CORSを有効化 // Puppeteerスクリプトを呼び出すエンドポイント app.get('/get-fortune', (req, res) => { exec('node getFortune.js', (error, stdout, stderr) => { if (error) { console.error(`Error: ${error}`); console.error(`stderr: ${stderr}`); // 標準エラー出力をログに出力 return res.status(500).send(`Error running Puppeteer script: ${stderr}`); } res.send(stdout); // Puppeteerの出力をそのまま返す }); }); const port = 3000; // Node.jsアプリはポート3000で稼働 const host = '0.0.0.0'; // すべてのIPからアクセスを許可 app.listen(port, host, () => { console.log(`Server running on http://${host}:${port}`); }); |
IISのリバースプロキシ設定
IISでURL RewriteモジュールとApplication Request Routing (ARR)をインストール
IISマネージャーから設置するサイトのドメインを選択、URL 書き換え
をダブルクリック、規則の追加
をクリックして開き、リバースプロキシ
を選択してOK
受信規則
のHTTP 要求が転送されるサーバー名または IP アドレスを入力してください:
にlocalhost:3000と入力、OK
をクリック。
出来上がった受信規則をダブルクリックして開くか、選択して編集をクリックして開き、URL の一致
のパターン
に^get-fortune$、アクションのプロパティ
のURL の書き換え
にhttp://localhost:3000/get-fortuneを入力して適用
をクリック。
Winserを使用して永続化
Winserを使用して、WindowsサービスとしてNode.jsアプリケーションを登録し、システム起動時やシステムが動作している間、確実にアプリケーションが実行され続けるように設定する
Winserをグローバルにインストールする
1 |
npm install winser -g |
アプリケーションのルートディレクトリにあるpackage.jsonファイルにstartスクリプトを追加
1 2 3 4 5 6 7 8 9 10 |
{ "name": "get-fortune", "version": "1.0.0", "scripts": { "start": "node app.js" }, "dependencies": { ... } } |
管理者としてコマンドプロンプトまたはPowerShellを開き、サービスを登録する
1 2 |
cd /your/application/path winser -i |
Winserで作成したサービスを起動するユーザーを自分に指定
サービスのプロパティから、ログオン
タブを選択し、アカウント
を選択し、ユーザーとパスワードを指定してOK
ファイアウォールでポート3000を許可
セキュリティが強化された Windows Defender ファイアウォール
を開き、受信の規則
、ポート
を選択、TCP
を選択、特定のローカルポート
を選択、3000を入力し次へ
、接続を許可する
を選択し次へ
、ドメイン、プライベート、パブリックにチェックを入れて次へ
、名前を入力して完了。
ブラウザで確認
一旦、ブラウザでhttp://example.com/get-fortune
にアクセスして、JSON形式で文字列が表示されたら大丈夫でしょう。
末尾に/を付けてアクセスして、末尾に/付きにリダイレクトされて403エラーが出る場合は、ブラウザのキャッシュをクリアする事でアクセスできるようになる場合もあります。
GASで実行とスケジュール設定
GASのApps Scriptエディタで実行し、エラーが無く無事にスプレッドシートまで更新されたら、トリガーの追加からトリガーを追加します。
月~金までは午前7時に更新されるので、各曜日、週ベースのタイマーで午前8~9時を設定します。
土曜日は放送後の更新になるので、週ベースのタイマーで午前9時~10時を設定します。
指定した時間帯にタイマーが作動し、エラーが無く更新出来たらOKです。
まとめ
毎回、PCを起動してCalcに転記する必要が無いので、今後の集計が楽になりそうです。
Webスクレイピングをする時は、各サイトのポリシーに従ってください。
私的利用の範囲を超えて使用したい場合は、必ず権利者の許可を取ってください。
コメント