• トップ
  • ブログ一覧
  • 【GAS(第二弾)】Parserライブラリを使用してwebスクレイピングしてみた
  • 【GAS(第二弾)】Parserライブラリを使用してwebスクレイピングしてみた

    かねまさ(エンジニア)かねまさ(エンジニア)
    2024.03.03

    IT技術

    はじめに

    こんにちは!今回も前回の【GAS】instagram graph apiを使用してインサイトデータ入力を自動化に引き続きGASを使用した実装をおこなっていきたいと思います!

    構成

    1. 実装を行なった経緯
    2. GASとは???
    3. webスクレイピングとは???
    4. webスクレイピングをする前に知っておくこと
    5. 実装
    6. 終わりに

    実装を行なった経緯

    前回の【GAS】instagram graph apiを使用してインサイトデータ入力を自動化に引き続き、今回もシステム面で手伝えることを模索していると「キッチンカーの募集を探すのが面倒なのではないか」と思ったので、某情報サイトから情報を収集し一覧化→スプレットシートに自動入力という機能を作成しました。

    GASとは???

    Google App Scriptの略称。
    業務の進め方を最適化ができるGoogle Workspace の統合、自動化、拡張のためのビジネス ソリューションをすばやく簡単に構築するための唯一のローコード プラットフォームだと記載されています。

    webスクレイピングとは???

    自ら選択したURLにアクセスし、情報を取得すること

    webスクレイピングをする前に知っておくこと

    1. スクレイピングするサイトの利用規約等を確認し、スクレイピング行為が禁止されていないかを確認する
    2. /robots/txt にアクセスし、どのURLにはクロールしてよいかを確認する

    上記を念頭に置いた上でスクレイピングを行うようにしてください。
    犯罪になる可能性があります。

    実装

    Parserライブラリのインストール

    1. 「2」画像赤枠部分「+」ボタンを押下
    2. Parserライブラリにアクセスし、library_keyをコピー
    3. 「5」画像「スクリプトID」部分にコピーしたlibrary_keyをペースト
    4. 「7」画像「追加」を押下

    コード実装

    1function extractLink() {
    2  // 自動入力させたいスプシのシート名
    3  const sheetName = 'シート名';
    4  // 自動入力させたいスプシのURLに記載されているSSID
    5  const ssid = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
    6  const sheet = loadSheet(sheetName, ssid);
    7  // 他のシートに定義されているもの
    8  // function loadSheet(sheetName, ssid) {
    9  // let mySS = SpreadsheetApp.openById(ssid);
    10  // return mySS.getSheetByName(sheetName);
    11  // }
    12  deleteSheetContents(sheet);
    13  // 他のシートに定義されているもの
    14  // function deleteSheetContents(sheet) {
    15  //   // 最終行と最終列の取得
    16  //   var lastRow = sheet.getLastRow();
    17  //   var lastColumn = sheet.getLastColumn();
    18  //   // データが存在するかチェック
    19  //   if (lastRow > 1) { // データが1行以上存在する場合
    20  //   // 範囲を取得
    21  //   var range = sheet.getRange(2, 1, lastRow - 1, lastColumn); // カラム行はクリア対象外にしたいので -1
    22  //   // データをクリア(4パターン)
    23  //   range.clear(); // 値・書式
    24  //   // range.clearContent(); // 値のみ (これを有効にするかどうかは要件によります)
    25  //   range.clearFormat(); // 書式のみ
    26  //   range.clearDataValidations(); // 入力規則
    27  //   }
    28  // }
    29  const weekdays = ["日", "月", "火", "水", "木", "金", "土"];
    30  const currentDate = new Date();
    31  const currentDayOfWeek = weekdays[currentDate.getDay()];
    32  // 2024年02月01日(木) ⇦のような日付フォーマットに変更
    33  const formattedCurrentDate = Utilities.formatDate(currentDate, Session.getScriptTimeZone(), "yyyy年MM月dd日") + "(" + currentDayOfWeek + ")";
    34  // 情報を取得したいURLから情報を取得する
    35  const jmtyUrl = "https://xxxx.jp/saitama/event";
    36  const jmtyResponse = UrlFetchApp.fetch(jmtyUrl);
    37  const jmtyContent = jmtyResponse.getContentText(`UTF-8`);
    38  const filteredEvents = Parser.data(jmtyContent)
    39  // 所得したHTMLを分析し、p-articles-list-itemからp-articles-list-itemの間の文字列を配列で取得
    40    .from('p-articles-list-item')
    41    .to('p-articles-list-item')
    42    // iterate()は全て取得、build()は最初の1つのみ取得する
    43    .iterate()
    44    // ヒットした情報を1つずつeventElementとして回す
    45    .map(eventElement => {
    46      // マッチしたものがあれば変数に格納、なければnullを格納
    47      const titleMatch = eventElement.match(/<div class=\'p-item-title\'>\n<a[^>]+>([^<]+)<\/a>/);
    48      const eventName = titleMatch ? titleMatch[1].trim() : null;
    49      const eventUrlMatch = eventElement.match(/<a href=`([^`]+)`>/);
    50      const eventUrl = eventUrlMatch ? eventUrlMatch[1].trim() : null;
    51      // eventURLだった場合は詳細画面を取得
    52      const eventResponse = UrlFetchApp.fetch(String(eventUrl));
    53      const eventDetailContent = eventResponse.getContentText("UTF-8");
    54      // 詳細画面内の情報も取得なければ、nullを格納
    55      const openDay = checkAndFormatDate(eventDetailContent, '開催日');
    56      const closeDay = checkAndFormatDate(eventDetailContent, '終了日');
    57      const deadline = checkAndFormatDate(eventDetailContent, '募集期限');
    58      // 募集期限が今日よりも前だったら、null
    59      if (deadline !== "") {
    60        const formattedCurrentDatePart = formattedCurrentDate.match(/\d+/g).join("");
    61        const deadlineDatePart = deadline.match(/\d+/g).join("");
    62        if (formattedCurrentDatePart > deadlineDatePart) {
    63          return null; // 今日の日付より前の場合はスキップ
    64        }
    65      }
    66      // 取得した情報を配列に格納
    67      return [eventName, openDay, closeDay, deadline, eventUrl];
    68      // 次の要素の取得に向かう(繰り返す)
    69    })
    70    .filter(Boolean);
    71  // いらない要素が取得されてしまっていたので、削除
    72  if (filteredEvents.length > 0) {
    73    filteredEvents.pop(); // 最後の要素を削除(最初と重複するため)
    74  }
    75  const range = sheet.getRange(sheet.getLastRow() + 1, 1, filteredEvents.length, filteredEvents[0].length);
    76  range.setValues(filteredEvents);
    77}
    78function checkAndFormatDate(eventDetailContent, columnName) {
    79  const dateFormatPattern = /^\d{4}年\d{2}月\d{2}

    実行結果

    終わりに

    いかがだったでしょうか?
    GASでParserライブラリを使用する際の何かの頼りになれたらと思います。
    最後まで見ていただきありがとうございました!!

    参照URL

    1. https://workspace.google.co.jp/intl/ja/products/apps-script/
    かねまさ(エンジニア)

    かねまさ(エンジニア)

    おすすめ記事