📝

GASでスクレイピングするなら「Cheeriosgs」が便利!最低賃金APIを作ってみる

2023/08/29に公開

https://qiita.com/items/f157a03e83761b0920ec


お詫び

Qiitaの元記事にて、区切り線を「---」で書いている場所があり、これがZennの記法に干渉して一部うまく表示できない記事がある事を認識しています。
全ての記事を精査しきれていないため、お手数ですがお見かけの際は教えていただけると大変喜びます。


本家の更新が2018年で止まっていたので、最新版に対応しつつGASでも作ってみる。

GASでスクレイピングするなら「Cheeriosgs」が便利!最低賃金APIを作ってみる

いつもどおり、ソースはGithub

<details><summary>ソースコード</summary><div>

  /**
   * ユーザー定義
   */
  const years = [
    {
      name: "令和",
      start: 2019,
    },
  ];

  /**
   * データクレンジング
   */    
  function replaces ( str )
  {
    return getDate( str ).trim()
      .replace( / /g, "" )
      .replace( / /g, "" )
      .replace( /\)/g, "" )
      .replace( /\(/g, "" )
      .replace( /[0-9]/g, function ( word )
      {
        return String.fromCharCode( word.charCodeAt( 0 ) - 0xFEE0 )
      } );
  }
  function getDate ( str )
  {
    // 日付でなければやらない
    if ( str.indexOf( "年" ) == -1 ) return str;

    // 年号を西暦に直す。
    var ad = -1;  // 元年分を減らしておくため-1
    years.forEach( function ( year )
    {
      if ( str.indexOf( year.name ) > -1 )
      {
        ad += year.start;
        str = str.replace( year.name, "" );
      }
    } );

    // 元年は1年
    str = str.replace( "元", 1 )

    // 西暦を算出して-でつなげる
    var split = str.split( "年" );  // 2桁以上の検出に対応
    return ( Number( split[ 0 ] ) + ad )
      + "-"
      + split[ 1 ].replace( "月", "-" ).replace( "日", "" );
  }

  /**
   * メイン
   */
  const URL = PropertiesService.getScriptProperties().getProperties().url;
  const content = UrlFetchApp.fetch( URL ).getContentText();
  const $ = Cheerio.load( content );

  var result = [];
  var pointer = -1;
  const EXCLUDE_COLUMN = 3; // 最初の行だけおかしなものがあるので除外
  const COLUMNS = 4;
  $( "td" ).each( function ( i, td )
  {
    if ( i < EXCLUDE_COLUMN ) return;
    if ( i % COLUMNS == EXCLUDE_COLUMN )
    {
      result.push( [] );
      pointer++;
    }
    result[ pointer ].push( replaces( $( td ).text() ) );
  } );

</div></details>

スクレイピングでつらいのは、スクレイピングの処理ではなく、取得したデータのクレンジング。
具体的には、

  • 必要な部分の括り出し
  • 不正な値やデータを取り除く、置き換える

これを如何に頑張らないかが重要だと考える。
pythonだととりあえずpandas辺りに入れてしまえ!となるがJavascriptにそこまで求めるのは酷だろうか。

Cheeriogs

公式

使い方はCheerioと同じなのでJQueryでスクレイピングをしている人にとっては馴染みやすい。

当初案

Github(History)

<details><summary>ソースコード</summary><div>

  /**
   * ユーザー定義
   */
  var yearsname2ad = {
    "元年": 2019,
  };
  const PROPERTIES = PropertiesService.getScriptProperties().getProperties();

  /**
   * システムロジック
   */

  function __scraping ()
  {
    var html = UrlFetchApp.fetch( PROPERTIES.url ).getContentText( 'UTF-8' ).replace( /\r?\n/g, "" ).replace( /[0-9]/g, function ( word )
    {
      return String.fromCharCode( word.charCodeAt( 0 ) - 0xFEE0 )
    } );
    var start = "<tbody>";
    var end = "</tbody>";
    return __cut( html, start, end );
  }

  var __match = {
    tr: /\<tr \w(.*?)\<\/tr\>/g,
    td: /\<td \w(.*?)\<\/td\>/g,
    year: /[\d元](.*)年/g,
    month: /年\d(.*)月/g,
    day: /月\d(.*)日/g,
    run: function ( str, pattern ) { return str.match( __match[ pattern ] ) },
  }
  function __cut ( str, sep )
  {
    return str.substring( str.indexOf( sep ) + sep.length, str.length );
  }
  function __rsubstring ( str, sep )
  {
    return str.substring( 0, str.indexOf( sep ) );
  }

  function __getYMD ( str, pattern )
  {
    var tmp = __match.run( str, pattern )[ 0 ]

    switch ( pattern )
    {
      case "year":
        tmp = yearsname2ad[ tmp ];
        break;

      case "month":
      case "day":
        tmp = tmp.substring( 1, tmp.length - 1 );
        if ( tmp.length == 1 ) tmp = "0" + tmp;
        break;
    }
    return tmp;
  }
  function __getDate ( str )
  {
    var year = __getYMD( str, "year" );
    var month = __getYMD( str, "month" );
    var day = __getYMD( str, "day" );
    return year + "-" + month + "-" + day;
  }

  /**
   * メイン
   */

  var table = __scraping()
  //  var table = test()

  /**
   * データクレンジング
   */
  var tr_items = __match.run( table, "tr" );
  var td_items = tr_items.map( function ( tr )
  {
    var tds = __match.run( tr, "td" );
    return tds.map( function ( td )
    {
      var clean_td = __rsubstring( __cut( td, ">" ), "<" )
        .trim()
        .replace( / /g, "" )
        .replace( / /g, "" )
        .replace( /\(/g, "" )
        .replace( /\)/g, "" )
        ;
      if ( clean_td.indexOf( "年" ) > -1 ) clean_td = __getDate( clean_td );
      return clean_td;
    } );
  } );
  td_items.shift();

</div></details>

このように、うまいパターンマッチングを考えるしか方法が思いつかなかった。

なお、getDate関連とstr.replaceをリファクタリングしているので、そこは目を瞑ってほしい。

動くもの

Web版

簡易人月シミュレーター.png

Chrome拡張機能にも対応している。

手引

最低賃金APIを使って簡易人月計算機をChrome拡張機能で作ってみた

GitHubで編集を提案

Discussion