学科サイトの静的化の裏側

これは琉大 Advent Calendar 2020の5日目の記事です。

昨日はid:anatofuzさんのvirshの学科ラッパーie-virshをRustで再実装したでした。

まだまだ琉大アドベントカレンダーは空きがあるので皆さんの参加お待ちしております!!!

学科サイトのリプレイス

以前こちらのエントリで書いたように、今までWordPressで動作していた学科サイトを今年のシステム更新に合わせて静的サイト化しました!

anatofuz.hatenablog.com

このエントリはその際のログとなっています

いきなりのhugo化 -> 断念

静的サイト化する場合直接HTMLを編集しないといけなくなるのは避けたいところです。 そのためmarkdownなどからHTMLを生成する静的サイトジェネレーターを使って運用を行いたいと考えました。

システム管理チームの1年生にwebの練習としてhugoテーマを作成してもらおうと思いましたが、難易度が高すぎたようで結局学科システムの更新タイミングまでテーマ作成が間に合いませんでした。 しかしシステム更新後はWordPressを動かしてはいけないというレギュレーションになっていたため、とりあえず現状のWordPressコンテンツをHTMLに変換するという方向で作業を行いました。

wordpressの静的サイト化プラグイン

前回のエントリで紹介したhugo形式のmarkdownWordPressのコンテンツを変換してくれるプラグインですが、学科サイトではプラグインの問題か変換が出来ませんでした。

github.com

似たようなツールにWP2Staticが存在しますが、こちらも件数が多すぎて学科サイトは変換できませんでした。

これらのプラグインは小規模なWordPressサイトでは変換が可能なのですが、学科サイトは様々な問題がありこれらのプラグインが使えなかったのです。

学科サイトのつらみ

学科サイトと読んでいるものは https://ie.u-ryukyu.ac.jpのサイトです。

主に受験生・外部の方向けの https://ie.u-ryukyu.ac.jp/prospective の様なパスと、学科の学生・スタッフ向けの https://ie.u-ryukyu.ac.jp/studentsのパスが大まかなコンテンツパスとなっています。

それだけなら変換は楽そうなのですが、実は学科サイトは https://ie.u-ryukyu.ac.jp/e155730の様に学籍番号をURLにつけると、マルチサイト化された各学生のWordPressページが使われるように設定されています。

同様にnewsサイトはhttps://ie.u-ryukyu.ac.jp/news-ieになっていたり、各講義で使っていたページなども存在するため、ざっと2000件ほどのWordPressサイトが存在しています。

しかもこれらWordPressサイトは、歴史的な理由で数年前からマルチサイト化されており、それ以前はすべて同じWordPressサイトとして運用されていました。そのため、学科トップページを静的化しようとすると、これらのほかユーザーなどのURLもプラグインが同時に変換しに行くため、変換プラグインの負荷がかかり、基本的に変換できないという状況になっていました。

気合で静的サイト化する

プラグインが使えないなら気合でどうにかするしかありません。幸いWP2Staticはページを生成するところまではしませんでしたが、ページのURLをクロールして発見してくれました。

発見してくれたパスの一覧はこんな感じです。

Pending   /robots.txt   Note: initial_crawl_list
Pending   /sitemap/   Note: initial_crawl_list
Pending   /sitemap.xml   Note: initial_crawl_list
Pending   /students/   Note: initial_crawl_list
Pending   /students/get-a-job/   Note: initial_crawl_list
Pending   /students/graduate/   Note: initial_crawl_list
Pending   /students/graduate/%e5%a4%a7%e5%ad%a6%e9%99%a2%e4%bf%ae%e5%ad%a6%e3%81%ae%e6%89%8b%e5%bc%95%e3%81%8d/   Note: initial_crawl_list
Pending   /students/graduate/%e5%a4%a7%e5%ad%a6%e9%99%a2%e5%85%a5%e8%a9%a6%e3%82%a2%e3%83%89%e3%83%9f%e3%83%83%e3%82%b7%e3%83%a7%e3%83%b3%e3%83%9d%e3%83%aa%e3%82%b7%e3%83%bc/   Note: initial_crawl_list
Pending   /students/graduate/%e5%a4%a7%e5%ad%a6%e9%99%a2%e7%a0%94%e7%a9%b6%e8%a8%88%e7%94%bb%e6%9b%b8/   Note: initial_crawl_list
Pending   /students/graduate-students/   Note: discovered on: /
Pending   /students/graduate-students2019/   Note: initial_crawl_list
Pending   /students/graduation-study/   Note: initial_crawl_list
Pending   /students/info-ethics-video/   Note: initial_crawl_list
Pending   /students/install/   Note: initial_crawl_list
Pending   /students/install/event-calendar/   Note: initial_crawl_list
Pending   /students/install/forward-email/   Note: initial_crawl_list
Pending   /students/install/homebrew/   Note: initial_crawl_list
Pending   /students/install/ip-address/   Note: initial_crawl_list
Pending   /students/install/mail/   Note: initial_crawl_list
Pending   /students/install/mercurial/   Note: initial_crawl_list
Pending   /students/install/newsie/   Note: initial_crawl_list
Pending   /students/install/printer/   Note: initial_crawl_list
Pending   /students/install/rss-2/   Note: initial_crawl_list
Pending   /students/install/server-access/   Note: initial_crawl_list
Pending   /students/install/web-server/   Note: initial_crawl_list
Pending   /students/install/wifi/   Note: initial_crawl_list
Pending   /students/office-hour/   Note: initial_crawl_list
Pending   /students/rental/   Note: initial_crawl_list
Pending   /students/request/   Note: initial_crawl_list
Pending   /students/request/guest-id/   Note: initial_crawl_list
Pending   /students/request/guest-wifi/   Note: initial_crawl_list
Pending   /students/request/vm/   Note: initial_crawl_list
Pending   /students/security-policy/   Note: initial_crawl_list
Pending   /students/software/   Note: initial_crawl_list
Pending   /students/study-guide/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/risyuu/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/risyuu/%e6%95%99%e5%93%a1%e5%85%8d%e8%a8%b1%e3%82%b3%e3%83%bc%e3%82%b9/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/risyuu/%e7%9f%a5%e8%83%bd%e6%83%85%e5%a0%b1%e3%82%b3%e3%83%bc%e3%82%b9%e5%b1%a5%e4%bf%ae%e8%a6%8f%e5%ae%9a/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/risyuu/%e7%9f%a5%e8%83%bd%e6%83%85%e5%a0%b1%e3%82%b3%e3%83%bc%e3%82%b9%e5%b1%a5%e4%bf%ae%e8%a8%88%e7%94%bb%e8%a1%a8/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/risyuu/2017%e5%b9%b4%e5%ba%a6-%e5%be%8c%e6%9c%9f/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/shinro/   Note: initial_crawl_list
Pending   /students/study-teaching-licence/shinro/%e3%82%a4%e3%83%b3%e3%82%bf%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%83%e3%83%97/   Note: initial_crawl_list
Pending   /students/timetable/   Note: initial_crawl_list
Pending   /students/timetable/2018-latter/   Note: initial_crawl_list
Pending   /students/timetable/2018-previous/   Note: initial_crawl_list
Pending   /students/timetable/2019-latter/   Note: initial_crawl_list
Pending   /students/timetable/2019-previous/   Note: initial_crawl_list
Pending   /students/timetable/2020-previous/   Note: initial_crawl_list
Pending   /survey-ob/   Note: discovered on: /%e5%ad%a6%e5%86%85%e5%90%91%e3%81%91/
Pending   /tag/%e3%82%af%e3%83%a9%e3%82%b9%e3%82%bf/   Note: initial_crawl_list
Pending   /tag/%e3%83%a1%e3%83%bc%e3%83%ab/   Note: initial_crawl_list
Pending   /tag/%e3%83%aa%e3%83%b3%e3%82%af%e9%9b%86/   Note: initial_crawl_list
Pending   /tag/%e3%83%ad%e3%82%b0%e3%82%a4%e3%83%b3%e3%82%b7%e3%82%a7%e3%83%ab/   Note: initial_crawl_list
Pending   /tag/%e4%bb%ae%e6%83%b3os/   Note: initial_crawl_list
Pending   /tag/%e5%89%b2%e3%82%8a%e5%bd%93%e3%81%a6/   Note: initial_crawl_list
Pending   /tag/%e6%89%80%e5%b1%9evlan/   Note: initial_crawl_list
Pending   /tag/bashou/   Note: initial_crawl_list
Pending   /tag/editor/   Note: initial_crawl_list
Pending   /tag/emacs/   Note: initial_crawl_list
Pending   /tag/imac/   Note: initial_crawl_list
Pending   /tag/mac/   Note: initial_crawl_list
Pending   /tag/procmailrc/   Note: initial_crawl_list
Pending   /tag/spam/   Note: initial_crawl_list
Pending   /tag/spamassassin/   Note: initial_crawl_list
Pending   /tag/tcshrc/   Note: initial_crawl_list
Pending   /tag/torque/   Note: initial_crawl_list
Pending   /tag/vi/   Note: initial_crawl_list
Pending   /tag/xen/   Note: initial_crawl_list
Pending   /wp-admin/   Note: discovered on: /
Pending   /wp-content/plugins/contact-form-7/includes/js/scripts.js   Note: discovered on: /
Pending   /wp-content/plugins/wc-shortcodes/public/assets/css/font-awesome.min.css   Note: discovered on: /
Pending   /wp-content/plugins/wc-shortcodes/public/assets/css/style.css   Note: discovered on: /
Pending   /wp-content/plugins/wc-shortcodes/public/assets/js/rsvp.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-browsing-history-master/jquery.cookie.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-browsing-history-master/wp-browsing-history-display.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-browsing-history-master/wp-browsing-history-logging.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-browsing-history-master/wp-browsing-history-variables.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-browsing-history-master/wp-browsing-history.css   Note: discovered on: /
Pending   /wp-content/plugins/wp-rss-aggregator/css/colorbox.css   Note: discovered on: /
Pending   /wp-content/plugins/wp-rss-aggregator/css/styles.css   Note: discovered on: /
Pending   /wp-content/plugins/wp-rss-aggregator/js/custom.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-rss-aggregator/js/jquery.colorbox-min.js   Note: discovered on: /
Pending   /wp-content/plugins/wp-to-twitter/css/twitter-feed.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/js/main.js   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/bootstrap/css/bootstrap.min.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/bootstrap/js/bootstrap.min.js   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/kf-icons/css/style.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/OwlCarousel2-2.2.1/assets/owl.carousel.min.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/OwlCarousel2-2.2.1/assets/owl.theme.default.min.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster/assets/vendors/OwlCarousel2-2.2.1/owl.carousel.min.js   Note: discovered on: /
Pending   /wp-content/themes/education-booster/style.css   Note: discovered on: /
Pending   /wp-content/themes/education-booster-child/style.css   Note: discovered on: /
Pending   /wp-includes/js/imagesloaded.min.js   Note: discovered on: /
Pending   /wp-includes/js/jquery/jquery-migrate.min.js   Note: discovered on: /
Pending   /wp-includes/js/jquery/jquery.js   Note: discovered on: /
Pending   /wp-includes/js/jquery/jquery.masonry.min.js   Note: discovered on: /
Pending   /wp-includes/js/masonry.min.js   Note: discovered on: /
Pending   /wp-json/   Note: discovered on: /
Pending   /wp-json/oembed/1.0/embed   Note: discovered on: /

なんかデバッグ情報が付与されているので、perlスクリプトを書いて解決しました。 Perlだと__DATA__以下に書いたテキストをwhile (my $line = <DATA>)の用にして取得できるので便利です。

github.com

この結果こんな感じのパスが生成されました

students/install/newsie
students/install/printer
students/install/rss-2
students/install/server-access
students/install/web-server
students/install/wifi
students/office-hour
students/rental
students/request
students/request/guest-id
students/request/guest-wifi
students/request/vm
students/security-policy
students/software
students/study-guide
students/study-teaching-licence
students/study-teaching-licence/risyuu
students/study-teaching-licence/risyuu/%e6%95%99%e5%93%a1%e5%85%8d%e8%a8%b1%e3%82%b3%e3%83%bc%e3%82%b9
students/study-teaching-licence/risyuu/%e7%9f%a5%e8%83%bd%e6%83%85%e5%a0%b1%e3%82%b3%e3%83%bc%e3%82%b9%e5%b1%a5%e4%bf%ae%e8%a6%8f%e5%ae%9a
students/study-teaching-licence/risyuu/%e7%9f%a5%e8%83%bd%e6%83%85%e5%a0%b1%e3%82%b3%e3%83%bc%e3%82%b9%e5%b1%a5%e4%bf%ae%e8%a8%88%e7%94%bb%e8%a1%a8
students/study-teaching-licence/risyuu/2017%e5%b9%b4%e5%ba%a6-%e5%be%8c%e6%9c%9f
students/study-teaching-licence/shinro
students/study-teaching-licence/shinro/%e3%82%a4%e3%83%b3%e3%82%bf%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%83%e3%83%97
students/timetable
students/timetable/2018-latter
students/timetable/2018-previous
students/timetable/2019-latter
students/timetable/2019-previous
students/timetable/2020-previous
survey-ob
tag/%e3%82%af%e3%83%a9%e3%82%b9%e3%82%bf
tag/%e3%83%a1%e3%83%bc%e3%83%ab
tag/%e3%83%aa%e3%83%b3%e3%82%af%e9%9b%86

このパスをもとにひたすらwget的なことをして、HTMLをダウンロードしてくれば人まずは静的化できそうです。

このURL一覧に画像やPDFファイルなどは含まれていませんが、それはこのURLから落としてきたHTMLをパースして発見すれば良いでしょう。

ダウンローダーの実装

なんとなくgoroutineを使って解決したかったのでgolangを選択しました。

github.com

脳死でgoroutineを書くとファイルディスクリプタを食い尽くして死ぬので、CPUのコア数あたりを見ていい感じにgoroutineの数を抑制しています。

   wg := &sync.WaitGroup{}
    cpus := runtime.NumCPU()
    semaphore := make(chan bool, cpus)
    ctx := context.Background()

基本的には先程のパスを与えてあげるとダウンロード出来るように仕込んでいます。

HTMLからcssや画像ファイル, javascriptを回収するのは2段階で行っています。

(なんでこんなにクソ面倒なことをしているかというと、生成するファイルパスをgolangURIから変更するのが面倒だったためです...)

#!/usr/bin/env perl
use strict;
use warnings;
use JSON::PP qw/encode_json/;
use utf8;

use File::Find qw/find/;

find({wanted => \&wanted, no_chdir => 1}, 'ie.u-ryukyu.ac.jp');

my $urls;
use DDP {deparse => 1};

sub wanted {
  my $filename = $_;
  if ($filename !~ /\.html/) {
    return;
  }

  open my $fh, '<:utf8', $filename;
  while (my $line = <$fh>) {
    if ($line =~ m|text/javascript.*src=(['"])(https.*?)\1|) {
        unless (exists $urls->{js}->{$2}) {
          $urls->{js}->{$2} =1;
        }
        next;
    }
    #  if ($line =~ /stylesheet.*href=(['"])(http.*?)\1/ ) {
    #    unless (exists $urls->{css}->{$2}) {
    #      $urls->{css}->{$2} =1;
    #    }
    #    next;
    #  }
    #  if ($line =~ /img.*src=(['"])(http.*?)\1/) {
    #    unless (exists $urls->{img}->{$2}) {
    #      $urls->{img}->{$2} =1;
    #    }
    #    next;
    #  }
    #  #<a href="https://ie.u-ryukyu.ac.jp/files/2015/04/%E5%B7%A5%E5%AD%A6%E9%83%A8%E5%AE%89%E5%85%A8%E8%A1%9B%E7%94%9F%E3%83%9E%E3%83%8B%E3%83%A5%E3%82%A2%E3%83%AB.pdf" target="_blank" rel="noopener">工学部 安全衛生マニュアル(PDF)</a>
    #  while ($line =~ m|href=(['"])(https?://.*/files/.*?)\1|g) {
    #    unless (exists $urls->{files}->{$2}) {
    #      $urls->{files}->{$2} =1;
    #    }
    #    next;
    #  }
  }
}


my $outputJsonData = {};

for my $key (qw/js/) {
  for my $url (sort keys %{$urls->{$key}}) {
    my $filePATH = $url;
    $filePATH =~ s|https?://||;

    my $outputDir = $filePATH;
    $outputDir =~ s|(.+)/(.+)$|$1|;
    push(@{$outputJsonData->{$key}}, {url => $url, filePATH => $filePATH, outputDir => $outputDir});
  }
}

#$outputJsonData->{css} = [ sort ];
#$outputJsonData->{img} = [ sort keys %{$urls->{img}}];
#
print encode_json($outputJsonData);

パスの相対パス

取得したこれらのコンテンツですが、WordPressは仕様でリンクをすべて絶対URLで作成するので、ローカルでCSSや画像のテストが出来ません。

今回はPerlスクリプトを用いてs|https://ie.u-ryukyu.ac.jp||gのようなことを行い、相対パスに変換しています。こうすると、変換したあとはlocalでwebサーバーを立てて確認する事が可能となります。

テスト用のwebサーバーは当初psgiで実装しましたが、他の人が使いそうなのでgolangで書き直しました。

use Plack::Builder;

builder {
    enable "Plack::Middleware::Static",
    path => sub {
        s!/([^\.]+)$!/$1/!;
        s!(.*/$)!$1/index.html! or return qr{^/.+};
    },
    content_type => sub {
        my $file = shift;
        my $content_type = Plack::MIME->mime_type($file) || 'text/plain';

        if ($content_type eq 'application/atom+xml') {
            $content_type .= "; charset=utf-8";
        }

        return $content_type;
    },
    root => './ie.u-ryukyu.ac.jp/';
};

github.com

ということで学科サイトはひとまず静的化が完了しました。

ie.u-ryukyu.ac.jp

静的化したため、学科のGitLabや、GitHubにgitリポジトリ化したサイトをcloneするだけでバックアップになるので、極めて便利です。さらにwebサーバーをapacheからnginxに乗り換えたため、スピードが大分向上しました。

早くなった学科サイトですが、いくつか課題があります

  • markdownから編集しやすくしたい
  • wordpress自体のCSSとかが一部あたっている
  • 更新フローの確立

このあたりもシステム管理チームで行っているので、webやってみたい人がいたらぜひシス管に入りましょう!!!!

明日はYoshiaki Sanoさんで「 ニューラルネットワークの仕組みと実装の解説」です。お楽しみに!!