hi9a's blo9

こんにちは

沖縄で働こう - PHPカンファレンス沖縄2019 その1

PHPカンファレンス沖縄2019 に参加してきました。

https://r.cacts.icu/s/t600

沖縄のITエンジニアの方々と交流して、沖縄の業界はどのくらい盛り上がっているのかを知る目的で行きました。 あわよくば、沖縄で働けるか確かめておきたいなー、と。

行く前の見立てとしては、

  • 業界は大和の子会社が中心
  • 業務はサーバ保守が中心 (災害対応で沖縄にデータセンタを置いている)
  • あまり給料よくない・上がらない

行って分かったことは、

  • 業界は大和の子会社が中心 → だいたいYES. 沖縄で起業してベンチャキャピタルから融資を受け、東京で営業・案件をもらうケースもある
  • 業務はサーバ保守が中心 (災害対応で沖縄にデータセンタを置いている) → NO. 業務システムの受託開発・ウェブサイト・ウェブサービススマートフォンアプリなど何でもござれ。つまり、業務内容の差は、大和と比べてそこまで大きくはない
  • あまり給料よくない・上がらない → 部分的にNO. 大和の都市と比べると少額にはなるっぽい。ただ、業務内容に差がないぶん、仕事を通じてキャリアアップできる (= 昇給できる) 様子。これは珍しいケースだろうけど、求人の中には「沖縄電力以上の待遇を約束します」などのアピールがあって驚いた
  • 現地に行かなくても調べれば分かるんじゃね? → YES. ただ、沖縄コミュニティの熱気や、技術者の人柄などは、現地に行って見たほうが手っ取り早いです。 じっさいに足を運んでみて、思った以上に先進的な企業と、技術者育成に励んでいる沖縄のIT業界を知ることができました。大きな収穫になりました。

ということで、実力があれば沖縄でも楽しく働けることが分かりました。

これまで、どなたかのブログで、 「子会社を興すとき、北海道と沖縄で迷いましたが、潜在的に人材いそうなのは北海道なので、そこにしましたー」 と書かれた記事をちょこちょこ読んできました。

そのたびに、悔しい! (でも、もっともだなー) と思ってきたのですが、今回のカンファレンスに参加してみて、沖縄の業界には希望があると感じました。 移住してきた技術者の方々の優秀さと、存在しながらも規模が小さかったコミュニティを繋いで大きなうねりにする行動力が、おおいに影響を与えているようです。

で、そんなメンバが企画・運営したのが、今回のカンファレンスのでした。沖縄の盛り上げている立役者の生の声を聞くことができ、僕にとってはとても有意義なカンファレンスになりました。

地元に思い入れのある人間として、みなさまの尽力に感謝します。

帰りのバスで興奮を抑えきれずツイートしたのですが、ろくな推敲もせずに書き殴ったせいで、主語が分かりにくい変な文章になりました。 今読み返すと恥ずかしい。 ただ、沖縄のみなさまには思うところがあったようで、いくつか反応いただきました。

セッションなど、他のことについてはまた書きます。

とりあえずは満足しました。

リリースしました - Cacts Redirection

リダイレクトサービスをリリースしました。

https://r.cacts.icu/

自主課題として作ったものを、一般にも公開しました。

短縮 URL 系サービスの割には

  • ドメインが短くない
  • まだ設定できる URL に制限がある

といった欠点があります。 この点は、他のサービス (bitly とか) が、ずっと優れていますね。

こちらが他と比べてよろしいのは、リダイレクト用の URL を作成したときに、かわいいサボテンの画像が出てくることくらいでしょうか。 絵はいい感じなので、ぜひ適当な URL で試してほしいです。

リリースできたので、満足しました。

CentOS7 で fail2ban を正常稼動させる

CentOS7 に fail2ban を入れてみました。

周りの人たちと同じような設定にできたと思うのですけど、 log に下のような NOTICE が出て上手く動作しませんでした。

Jail started without 'journalmatch' set. Jail regexs will be checked against all journal entries, which is not advised for performance reasons.

そこで、下記サイトの処置を行いました。

Configure fail2ban to secure your server? - DebYum
https://r.cacts.icu/s/6Tc0

"fail2ban.filtersystemd Issue." の箇所を真似て、

$ cat /etc/fail2ban/jail.local
[filter名]
backend = auto
(略)

と設定。daemon を reload したところ、 NOTICE が出なくなりました。

自分で攻撃 () してみたところ、無事に ban されましたー。期待どおりの動きです。

以降、 fail2ban-client status コマンドで ban された方々を見るのが毎朝の楽しみになりました。

満足しました。

サブ・メイン・トップレベルドメインに分けてくれるライブラリを探す

一身上の都合により 、あるドメインを渡すと、

を返してくれる機能が欲しくなりました。

<例>

  • インプット ja.nantokapedia.example.example.com

  • アウトプット(サブドメインja.nantokapedia.example

  • アウトプット(メインドメインexample

Python で有用そうなライブラリがありまして、 tld でした。トップレベルドメインの略称です。 インストールは pip で簡単にできます。素晴らしいですね。

tld — tld 0.9.3 documentation

tld · PyPI

サブドメインと、メインドメイントップレベルドメインは省いた状態)のプロパティを持つインスタンスを作れるので、楽に機能を実装できました。また、その先の、最終的にやりたかった機能を楽に実現できました。

満足しました。

NASA APODから最新画像を取得する

Windows スポットライトのように、ロック画面の画像が自分で更新できたら楽しいな、と感じました。

目をつけたのはNASAのAPOD (Astro Picture Of the Day) 。 天文・宇宙科学の画像が毎日更新されます。 毎日、このサイトトップにある最新の画像をあるフォルダに保存する機能を作成し、ロック画面はそのフォルダから参照してもらうことにします。

肝心の画像取得機能ですが、スクラッチで作成する必要はなさそうです。 というのも、NASAのサイトは世界的に認知されていますし、英語サイトです。 天体の綺麗な画像が手に入るとなれば、いかにも誰かがウェブクローラを書いていそうではあります。

というわけで、GitHubで検索したところ、すぐにヒットしました。

GitHub - UtkarshGpta/nasa-apod-image-crawler: Python Script to download all the images released by NASA's APOD till date to a specified folder

もとのコードは過去画像をすべて取得するように書いてあります。 こちらはフォルダに新しい画像をためていきたいので、サイトのトップページにある最新画像を取得する処理に書き換えます。

コードはPython 3系です。

#/usr/bin/python
# -*- coding: utf-8 -*-

# https://github.com/UtkarshGpta/nasa-apod-image-crawler

import requests
import urllib.request
import os
from bs4 import BeautifulSoup


def spider():
    directory = r'C:/Users/apod/Pictures/'
    try:
        os.chdir(directory)
    except:
        print("ERR!: Unable to use the directory provided")
        os._exit(1)
    # アーカイブ から トップページ にアクセスするよう変更
    index_url = r'http://apod.nasa.gov/apod/astropix.html'
    url = 'http://apod.nasa.gov/apod'
    source_code = requests.get(index_url)
    plain_text = source_code.text
    soup = BeautifulSoup(plain_text, "html5lib")
    for link in soup.findAll('img'):
        src = link.get('src')
        url2 = url + '/' + src
        name = src[11:]
        local = directory + '/' + name
        if (os.path.exists(local)) :
            continue
        print(name)
        urllib.request.urlretrieve(url2,name)


spider()

あとはこのスクリプトが適切な条件(毎時でしたり、ログインのたびにでしたり)で起動するよう設定しておけば、フォルダがAPODの画像でほくほくしてくるという寸法です。

満足しました。

Tリーグ 琉球アスティーダの試合結果をCSVで出力する

2018年10月、日本で卓球のプロリーグが開幕しました。

公式サイトでは、試合結果のほか、個人成績も集計・ランキング化されていて、なかなか面白いです。サービスエースはあの選手が一番得点が多いんだなあ、ですとか。

公式サイトではAPIなど使っていないようで、試合のスコアや選手のランキングはJSON取得して、その値をページに書き込む、といった処理はしていません。 これだと個人でデータを集計したり加工したりできず、不便です。不便ですよね? そこで、HTMLソースの値をCSVとして出力することにしました。

使用したのはPHP 5.6 (JavaScriptより面倒そうだから。面倒だと勉強になりそうだから)。 今回、ファイル出力する対象は、贔屓チームの試合結果としました。

https://tleague.jp/match/?season=2018

↓用意したスクリプト

<?php

const SEASON_MONTH_SET = [
    ['2018', '201810'],
    ['2018', '201811'],
    ['2018', '201812'],
    ['2018', '201901'],
    ['2018', '201902'],
    ['2018', '201903'],
];
const MATCH_URL = 'https://tleague.jp/match/';
const TEAM = '琉球';
const CSV = '/tmp/output.csv';
const CSV_TITLE = ['date', 'team', 'team score', 'opponent score', 'opponent', 'home/away', 'arena'];

function csv_records($resource, $url) {
    $csv_records = [];

    curl_setopt($resource, CURLOPT_HEADER, false);
    // http://php.net/manual/en/function.curl-exec.php
    // if the CURLOPT_RETURNTRANSFER option is set, it will return the result on success, FALSE on failure. 
    curl_setopt($resource, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($resource, CURLOPT_BINARYTRANSFER, true);
    curl_setopt($resource, CURLOPT_URL, $url);
    curl_setopt($resource, CURLOPT_SSLVERSION,1); 
    $html = curl_exec($resource);
    $dom = new DOMDocument();

    // 取得htmlが適切でないらしく下記warningが出力される。
    // PHP Warning:  DOMDocument::loadHTML(): htmlParseEntityRef: no name in Entity
    // 出したくないなら
    // libxml_use_internal_errors(true);
    $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));

    $xpath = new DOMXPath($dom);
    $match_men = $xpath->query('.//li[@class="match-men"]');

    foreach ($match_men as $match) {
        $c_home = $xpath->query('.//div[@class="cell-home"] /a', $match);
        $c_away = $xpath->query('.//div[@class="cell-away"] /a', $match);

        $home = $c_home->item(0)->textContent;
        $away = $c_away->item(0)->textContent;
        if (!in_array(TEAM, [$home, $away])) {
            continue;
        }

        $c_score = $xpath->query('.//div[@class="cell-result"] /div /strong', $match);
        $score = $c_score->item(0)->textContent;
        $scores = explode(' ', $score); // [home score, '-'(constant string value), away score]

        $c_date = $xpath->query('.//div[@class="cell-date"]', $match);
        $date = $c_date->item(0)->textContent;

        $home_game = TEAM === $home;

        $team           = $home_game ? $home      : $away;
        $opponent       = $home_game ? $away      : $home;
        $team_score     = $home_game ? $scores[0] : $scores[2];
        $opponent_score = $home_game ? $scores[2] : $score[0];
        $homeaway       = $home_game ? 'home'     : 'away'; 

        $c_arena = $xpath->query('.//div[@class="cell-arena"] /a', $match);
        $arena = $c_arena->item(0)->textContent;

        $csv_records[] = [$date, $team, $team_score, $opponent_score, $opponent, $homeaway, $arena];
    }
    return $csv_records;
}

////////////////////////////////
// main
////////////////////////////////
$resource = curl_init();
$csv_records = [CSV_TITLE];
$csv_file = fopen(CSV, 'w');
$season_month_set = SEASON_MONTH_SET;

foreach ($season_month_set as $season_month) {
    $season = $season_month[0];
    $month = $season_month[1];
    // example:    $url = 'https://tleague.jp/match/?season=2018&month=201902'
    $url = MATCH_URL . '?season=' . $season . '&month=' . $month;

    $monthly_records = csv_records($resource, $url);
    $csv_records = array_merge($csv_records, $monthly_records);
}
foreach ($csv_records as $record) {
    fputcsv($csv_file, $record);
}
curl_close($resource);
fclose($csv_file);

↓実行結果。

date,team,"team score","opponent score",opponent,home/away,arena
2018/10/26(金),琉球,2,3,岡山,away,名古屋武田テバ
(中略)
2019/02/22(金),琉球,0,4,TT彩たま,home,宜野湾宜野湾市

満足しました。