#GAS #スクレイピング を便利にする「cheeriogs」を使ってスプレッドシートに登録する

JQueryを使っていると、スクレイピングでcheerioにはすごくお世話になりますが、GASでも使えるとは思わなかったです。

スクリプトIDは

1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0

なので、これを使っていきましょう。

# 実践

ソースコードで語ります。

## Before

var yearsname2ad = {
 "元年": 2019,
};
const URL = PropertiesService.getScriptProperties().getProperties().url;

function __scraping ()
{
 var html = UrlFetchApp.fetch( 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 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();

## After

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, "" );
   }
 } );

 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() ) );
} );

行数はそんなに変わっていないのに、メンテナンス性が明らかに違いますね。
パターンマッチングを辞めたのは非常に大きいです。

※getDateとgetYMDは似たような事をやってるので統合しました。

# ヨタ話

Momentjsの出どころは私も気になっていました。
見た感じだとここが一番まともっぽい話をしていたのと、ソースが見れたので本スクリプトでも採用しています。

# 戻る


のむらがあなたの役に立つ記事を書くためのコーヒーを一杯奢ってくれませんか? サポートをすると、のむらの記事を使って、あなたの記事を盛り上げてみませんか? また、有料記事などいただいた収益はすべて、あなたの代わりにアプリやツールを買って色々検証をして記事にするために使っています。