2018-02-10

Google Apps Scriptで、正規表現でスクレイピングする方法を方法を初心者向けにまとめてみた




僕が作ったわけではないのですが、Google Apps ScriptによるスクレイピングというYoutubeの動画から写経して、Google App Scriptのスクレイピングコードを勉強します。
できることとしては、Ted talksの全動画のタイトル、スピーカーをすべてGoogle Spread Sheetに取り出すことができるというものです。(詳しくは動画を見てください。)


Google App Scriptを使って、スクレイピングするメリット


そもそも、Python使ってGoogle Apps Scriptかけばええでねぇかって話なので、なぜGoogle Apps Scriptを使うのかって話から。

- サーバーを準備せずとも、定期実行が簡単


普通にPythonなどで定期実行プログラムを組むと、どうしてもサーバー立てたりする必要があります。ちょっとしたスクレイピングをする定期実行プログラムを組むために、そこまではしたくない!みたいなときに超絶使えるのが、Google Apps Scriptです。


Googleがサーバーを用意してくれるので、定期実行するにしても、サーバーを自前で用意する必要がありません。まずcronが超簡単に使えるので、簡単な定期実行プログラムを組みたいのであれば、Python用意してサーバー立ててDBに保存したいみたいな場合でも、スプレッドシートで完結するので、非常に楽です。

- JavaScriptで書ける


Google Apps Scriptは、基本的にはJavascriptで書けるので、学習コストが低いです。
また、特別にLinuxがいじれたり、DBの構築も必要がありません。Javascriptしか書けないような人でも、かけてしまうと思います。ただし、すこし正規表現の知識がないと、スクレピングは難しいかもしれません。


- データの共有が簡単


スプレッドシートを共有するだけで、データの共有ができるので、さくっとスクレピングしてきたデータを共有するなんてことができます。


https://www.youtube.com/watch?v=xkk7OxsfTpQ



やろうとしていること

僕が作ったわけではないのですが、Google Apps ScriptによるスクレイピングというYoutubeの動画から写経して、Google App Scriptのスクレイピングコードを勉強します

1. データのスクレイピング


Ted日本語ページにアクセスし、ページャーの最後までの以下の値を取得します。

  • タイトル名
  • スピーカー名
  • 作成日時



該当するページのHTMLはこんな感じ。

<div class='media__message'>
  <h4 class='h12 talk-link__speaker'>Justin Baldoni</h4>
  <h4 class='h9 m5'>
    <a class=' ga-link' data-ga-context='talks' href='/talks/justin_baldoni_why_i_m_done_trying_to_be_man_enough?language=ja' lang='ja'>
    「男らしく」在らんとすることをやめた理由
    </a>
  </h4>
  <div class='meta'>
    <span class='meta__item'>
    Posted
    <span class='meta__val'>
    Dec 2017
    </span>
    </span>
    <span class='meta__row'>
    Rated
    <span class='meta__val'>
    Inspiring, Courageous
    </span>
    </span>
  </div>
</div>


タイトル名が

<a class=' ga-link' data-ga-context='talks' href='/talks/justin_baldoni_why_i_m_done_trying_to_be_man_enough?language=ja' lang='ja'>
    「男らしく」在らんとすることをやめた理由
    </a>

スピーカー名が

<h4 class='h12 talk-link__speaker'>Justin Baldoni</h4>


作成日時が

<span class='meta__val'>
    Dec 2017
    </span>

に該当します。


検索結果のページ番号が、URLのクエリに含まれています。

https://www.ted.com/talks?language=ja&page=2

であれば、page=2、つまり2番目のページが取得できます。

https://www.ted.com/talks?language=ja&page=3


であれば、同様に、3番目のページが取得できます。
なので、N番目のページが取得できるようにするには、最後のページの番号を取得し、一つずつ数字をあげていけば全部のページにアクセスできます。


2. スプレッドシートに書き込み


スクレイピングしたデータを、スプレッドシートに書き込みます。


3. 実際のソースコード


function MainFrame() {

  var url = "https://www.ted.com/talks?language=ja"; //ted日本語のURL
  var request = UrlFetchApp.fetch(url) //http responseの取得
  var content = request.getContentText();

  //最終ページの数字を取得
  var step1 = content.match(/page=\d*/g)
  var pages = [];
  for (var i=0; i<=step1.length-1;i++){
    pages.push(step1[i].replace(/page=/,""));
  }
  var maxnum = Math.max.apply({},pages);

  //空の配列を作る

  var Speakers = [];
  var Titles = [];
  var Dates = [];

  //スクレイピング実行部分

  for (var i=1;i<=maxnum;i++){

    var url = "https://www.ted.com/talks?language=ja" + "&page=" + i;
    var request = UrlFetchApp.fetch(url);
    var content = request.getContentText();
    var speakers = SpeakersArray(content);
    var titles = TitlesArray(content);
    var dates = DateArray(content);

    for(var n=0;n<=speakers.length-1;n++){
      Speakers.push(speakers[n])
    }

    for(var n=0;n<=titles.length-1;n++){
      Titles.push(titles[n])
    }

    for(var n=0;n<=dates.length-1;n++){
      Dates.push(dates[n])
    }
  }

  //2次元配列を作成

  var Data = [];
  for(var n=0;n<=Speakers.length-1;i++){
    var data = [];
    data.push(Speakers[n]);
    data.push(Titles[n]);
    data.push(Dates[n]);
    Data.push(data);
  }  
  //スプレッドシートのid

  var id= "14OEIoSZ0RodyHgi3krhHuLDQd8k37CDy2Mm8Csrhsdk"
  var File = SpreadsheetApp.openById(id);
  var Sheet = File.getSheets();
  Sheet[0].getRange(1,1,Speakers.length,3).setValues(Data);
}

function SpeakersArray(x){

  /*実際のHTML
    <h4 class='h12 talk-link__speaker'>Justin Baldoni</h4>
  */

  //スピーカー名を配列で返す
  var step1 = x.match(/<h4\sclass='h12\stalk-link__speaker'>.*<\/h4>/g);
  var extract = []
  for (var i=0;i<=step1.length-1;i++){
    extract.push(step1[i]
  .replace(/<h4\sclass='h12\stalk-link__speaker'>/g,"")
  .replace(/<\/h4>/g,"")
  .replace(/\n/g));
  }
  return extract;

}

function TitlesArray(x){

  //スピーチタイトルを配列で返す

  /*実際のHTML
    <a class=' ga-link' data-ga-context='talks' href='/talks/justin_baldoni_why_i_m_done_trying_to_be_man_enough?language=ja' lang='ja'>
    「男らしく」在らんとすることをやめた理由
    </a>
  */

  var step1 = x.match(/<a\sclass='\sga-link'\sdata-ga-context='talks'\shref='\/talks\/.*\?language=ja'\slang='ja'>[\w\W]*?<\/a>/g,"")
  var extract = []
  for (var i=0;i<=step1.length-1;i++){
    extract.push(step1[i]
    .replace(/<a\sclass='\sga-link'\sdata-ga-context='talks'\shref='\/talks\/.*\?language=ja'\slang='ja'>/g,'')
    .replace(/<\/a>/g,"")
    .replace(/\n/g,""));
  }
  return extract;
}

function DateArray(x){

  //日付を配列で返す

  /*実際のHTML
    <span class='meta__val'>Dec 2017</span>
  */

  var step1 = x.match(/<span\sclass='meta__val'>\n*[A-z]{3}\s\d{4}\n*<\/span>/g,"")
  var extract = []
  for (var i=0;i<=step1.length-1;i++){
    extract.push(step1[i]
    .replace(/<span\sclass='meta__val'>/g,"")
    .replace(/<\/span>/g,"")
    .replace(/\n/g,""))
  }
  return extract;
}


実行すると、スプレッドシートにデータがすべてスクレイピングされ、スプレッドシートに反映されるようになります。ただし、私の環境ではメモリ不足になり確認できなかったので、ほかのタイミングで再現してみたいと思います。

関連資料


UrlFetchApp.fetch()
https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app

getContentText() : Gets the content of an HTTP response encoded as a string.
https://developers.google.com/apps-script/reference/url-fetch/http-response

match(): 正規表現を取得するメソッド
https://tonari-it.com/gas-regular-expression/

Math.max.apply:配列の最大値を取得するメソッド
https://qiita.com/YusukeHirao/items/e848f5de40beaa52e002










注目の投稿

 PythonのTweepyを利用して、Twitter APIを利用している。 その中で、ハマったポイントをメモしておく。 まず、Searchに関して。 Twitter検索は、クライアントアプリ側では、全期間の検索が可能になっている。 一方で、APIを利用する際は、過去1週間しか...