Google広告を出稿してキーワードプランナーを使い倒す

売りたいモノが(自分自身以外)無くても広告を出稿しよう

 検索広告の存在は誰でも(嫌というほど)知っていますが、広告の出稿方法を実際に知っている人は「仕事で使ったことがある」方以外にはあまり居ないと思います。

 しかし、can.ne.jpでは敢えて皆さんにお勧めします。

誰でも広告を出稿しておこう

理由① お金はあまりかからない

 Web広告の基本であり王道である『キーワード広告』。CPC(Cost Per Click)と呼ばれ、クリック毎に課金されます。キーワードごとにクリック単価が設定されており、出稿出来る総量も決まっています(検索されないと表示されないので)。

 つまり、「出稿量が少なくクリック単価も低い広告であれば、出稿しても微課金で済む(もちろん流入もそれなり)」ということです。そこで、私も実際にGoogle広告を出稿しています☺

 わずかではありますが、実際に広告をクリックしてcan.ne.jpをご訪問頂いた方もいらっしゃいます!

理由② Google Analyticsの勉強になる

 Google Analyticsのキャンペーンメニューを見ると、『上位のキャンペーン』に『cpc』が表示されています。これがGoogle広告経由での流入となります。

 もちろん、この項目は広告を出稿しないと表示されません。実際に広告を出稿してみて初めて、GAが広告出稿をどのように捕捉するかが分かることになります。しかし、ふつうは仕事で広告に関わっていないと肌感覚で「広告出稿~効果測定」の流れを知ることが出来ません。運よく仕事(会社のカネ!)で広告出稿をする機会があれば自腹を切る必要はありませんが、そうでなければ自分で広告を出稿するしかありません。

 同じ発想で言えば、自分のショッピングカートや自分の商品もサイト上に置いておくべきです(can.ne.jpではまだ出来ていませんが)。「さすがにそこまでやるのは面倒すぎる」という方は、B2Bのお約束である『資料請求フォーム』を用意してリード獲得のコンバージョンを設定するのが良いでしょう。

 理由③ キーワードプランナーが使える

 これが私がGoogle広告を登録した最大の理由です。広告を出稿したことがない方でもGoogleトレンドなどでキーワードごとの検索動向をチェックしたことがあるかも知れません。

 しかし、Googleトレンドではグラフが出るだけで、実数として検索数を取得することは出来ません。実は、Google広告に付属の『キーワード プランナー』というツールを使えば、とてもざっくりとではありますが任意のキーワードに対する検索数を取得出来ます

  本来は出稿するキーワードやフレーズを検討するためのツールですが、とても自分では出稿出来ないようなビッグワードの検索推移なども把握できます。このツールを用いて、「どのような検索ワードの人気があるか」「どのようなテーマの人気が上がってきているか」が分かるので、

どんなテーマでブログを書けば人が来るか分かる

ということになります。私は実際に仕事でキーワードプランナーを用いてニーズが高まっているテーマを見つけて、人気ページに育てた経験があります。このような分野を『コンテンツマーケティング』と呼びます。

コンテンツマーケティングの光と陰

 ただし、アクセスがどんどん増えていったものの、取扱商品の売上はさっぱり伸びませんでした(苦笑)。アクセスさえ稼げばお金になるのはニュースサイトなどメディア系だけで、Eコマースでは「テーマへの関心」から「商品への関心」を引き出す『コンバージョン』というプロセスが欠かせません。

 コンテンツマーケティングは「ひたすら書くだけで、お金がかからない」という性質があるので、中小サイトではコンバージョンの有無に関わらず考え付く限りの関連ページを量産するのが普通です。しかし、人生もお金も有限なので、仕事である限りはコンバージョン率を常に念頭に置いてコンテンツマーケティングにも取り組むべきだと思います。

 Webは良くも悪しくも「無料が当たり前」の世界です。Webが好きな人はボランティア精神に溢れた方が多く、多くのアクセスや『いいね』をもらえれば、お金にならなくても熱心にコンテンツを作る方が少なくありません。

 しかし、私のように食うのにも困る状況になってしまっては元も子もありません。仕事では心を鬼にして、「いまのWeb作業はマネタイズにつながっているか」を常に意識する必要があります(自戒をこめて)。

Oracle Cloudの無料枠が太っ腹(ただし初心者向きではない)

AWS以外のクラウドには仮想マシンの無料枠がある

 AWSで構築したcan.ne.jpですが、維持費に毎月数ドルかかります。「AWSのメールマガジンで毎月バウチャーをもらえばホニャララ」という話もあるのですが、AWSでWebサイトを作ると初年無料を除けば基本的に有料です(2021年4月現在)。

 一方で、AWSに対して出遅れ感があるGCP(Google Compute Engine)やOCI(Oracle Cloud Infrastructure)は永年無料でWebサイトを構築出来る仮想マシン(Virtural Machine, VM)を立てられます。

 クラウドに慣れる目的もあり、GCPとOCIの双方で仮想マシンを立ててみましたが、特にオラクルのOCIが太っ腹だったのでご紹介します。

無料枠なのにAWSのLightSailよりハイスペック

 OCIの無料枠『Always Freeリソース』にはデータベースやストレージもありますが、好きなデータベースをインストールして使えるのはAlways Freeコンピュート仮想マシン(VM)インスタンスです。スペックは

  • シェイプ: VM.Standard.E2.1.Micro
  • プロセッサ: 追加のCPUリソースを使用する機能を持つ1/8 OCPU
  • メモリー: 1 GB
  • ネットワーキング: 1つのパブリックIPアドレスと最大480 Mbpsネットワーク帯域幅を持つ1つのVNICが含まれます
  • オペレーティング・システム: 次のいずれかのAlways Free対応オペレーティング・システムを選択できます:
    • Oracle Linux (Oracle Autonomous Linuxを含む)
    • Canonical Ubuntu Linux
    • CentOS Linux

1/8 OCPUってなんぞや?

 「1/8 OCPUってなんぞや?」と調べてみたところ、AMD EPYC 7551 32-Core ProcessorというCPUの1コアが割り当てられていました。最近のCPUは6コア12スレッドくらいあるので、その1コアぶんくらいでしょう。

[opc@mysql ~]$ sudo cat /proc/cpuinfo
processor	: 0
vendor_id	: AuthenticAMD
cpu family	: 23
model		: 1
model name	: AMD EPYC 7551 32-Core Processor
stepping	: 2
microcode	: 0x1000065
cpu MHz		: 1996.249
cache size	: 512 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 1
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm rep_good nopl cpuid extd_apicid tsc_known_freq pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy svm cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw topoext perfctr_core ssbd ibpb vmmcall fsgsbase tsc_adjust bmi1 avx2 smep bmi2 rdseed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 nt_good virt_ssbd arat npt nrip_save
bugs		: fxsave_leak sysret_ss_attrs null_seg spectre_v1 spectre_v2 spec_store_bypass
bogomips	: 3992.49
TLB size	: 1024 4K pages
clflush size	: 64
cache_alignment	: 64
address sizes	: 40 bits physical, 48 bits virtual
power management:

processor	: 1
vendor_id	: AuthenticAMD
cpu family	: 23
model		: 1
model name	: AMD EPYC 7551 32-Core Processor
stepping	: 2
microcode	: 0x1000065
cpu MHz		: 1996.249
cache size	: 512 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 1
apicid		: 1
initial apicid	: 1
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm rep_good nopl cpuid extd_apicid tsc_known_freq pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy svm cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw topoext perfctr_core ssbd ibpb vmmcall fsgsbase tsc_adjust bmi1 avx2 smep bmi2 rdseed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 nt_good virt_ssbd arat npt nrip_save
bugs		: fxsave_leak sysret_ss_attrs null_seg spectre_v1 spectre_v2 spec_store_bypass
bogomips	: 3992.49
TLB size	: 1024 4K pages
clflush size	: 64
cache_alignment	: 64
address sizes	: 40 bits physical, 48 bits virtual

メモリー1GBは余裕がある

 can.ne.jpが動いているAWS LightSailは最安プランではメモリが512MBしかありません。

LightSailの最安プランはメモリが512MBしかない

 OCIの仮想マシンはメモリが2倍の1GBあるので、アクセス負荷への耐性がLightSailの最安プランよりずっと高いと思われます。今から自分がWordPressのブログサイトを作るなら、少し面倒でもOCIで自分で構築すると思います。

ストレージは100GBまでいけそう

 画像をたくさん置くブログでは気になるストレージ容量ですが、「コンピュート・インスタンスをプロビジョニングする場合、インスタンスはストレージ用に50 GBのブート・ボリュームを自動的に受け取ります」との記述があり基本50GBです。

 さらに「ブロック・ボリュームを作成してアタッチすると、コンピュート・インスタンスのストレージ容量を拡張できます」「すべてのテナンシは、合計100 GBのAlways Freeブロック・ボリューム・ストレージと、5つのボリューム・バックアップを受け取ります」とあるので、ブロックボリュームをアタッチすることで100GBまで拡張出来ると思われます。

 上のLightSailプランの5倍ですね……。

やっちまった…… Oracle Linuxってなに??

 深く考えずにポチポチして仮想マシンを作ったところ、Oracle Linux 7.9という独自Linuxが入ってしまいました。Red Hat Enterprise Linuxのクローンなのでコマンド周りはCentOSに近いのですが、正直

「特に理由が無いのに、よく知らないOSは使いたくない。面倒くさい」

 というのが本音です。UbuntuやCentOSも選べるらしいので、これからOCIを始める方はOS選びに気をつけて下さい。

セキュリティ周りが面倒くさい(間違ってはいないけど)

 OCIでインスタンスを作ると、他社とは異なりデフォルトでファイアウォールが有効になっています。ファイアウォールを設定してポート開放しないとWebサイトすら公開出来ないので、初心者向きではありません。

 とは言え、セキュリティの問題はサイトを公開する以上は避けて通れないので、敢えて苦しんで設定してみるのも良いかと思います。

 また、AWSやGCPには標準で備わっている『Webブラウザ版のSSHクライアント』が無い(ような)ので、RLoginなどのSSHクライアントをインストールするかシェル版のsshを長いコマンドを打って起動する必要があります。こういった「とっつきやすさ」では、OCIはまだAWSやGCPより一歩遅れているように思います。

 オラクルのビジネスモデルを考えると、そもそも初心者やライトユーザーは見込み顧客として想定していないと思いますが……💦

【追記】ufwでポートが開かない問題、私も遭遇しました😭

 OCIでのポート開放で、「ufwを用いてポートを開放できない問題に遭遇」する問題に私も遭遇しました。

 1つ目のインスタンスにLogstashを入れたところ凄いメモリ食いでメモリ1GBでもフリーズが頻発。泣く泣く他のサーバーを落としてOCIのサイトを見ていたところ「2つのAMDコンピュートVM」との記述を発見。「Oracleどんだけ太っ腹なんだ」と思いつつ2個めを立ち上げたところ、SSLが開きません。

 セキュリティーグループを作りイングレスを設定しても無反応。「これはインスタンス内部の問題か?」と思いufwを設定するも無反応。かなり絶望的な気分で1時間くらい検索しまくったところ

結論だけ先に言っておくと、Oracle Cloud上だとufwはバグっているのでiptablesをいじってポート開放すればいいです。

Oracle Cloudでポートを開放されない問題の解決策 – viasnake.com

との超ありがたい記述が。こんなんわかんねえよ先に言っといてくれよOCIさん😭😭😭

 ……というわけでサイトの記述に従ってiptablesを直接書き換えて無事解決しました。ufwは幅広く使われているのでポート開放でドハマりしたUbuntuユーザーは少なくないはず。不具合に対処するか、せめて「ufw使うな」と公式に書いておいてほしかったです。

CloudFront+LightSailでスケーラビリティが(それなりに)あるサイトを安く作る

敢えてサイト丸ごとCDNにしない

 can.ne.jpではサイトを丸ごとCDN(配信サービス)で配信しないようにしています。CloudFrontなどのCDNはそれなりにお高い上に、フォームを置いたインタラクティブなサイトでトラブルが生じる可能性を否定出来ないからです。

動画や画像だけCDNに置く

 can.ne.jpが使っているWordPressというCMSは基本的にはローカルでファイルを管理します。ただし、『HTMLブロック』機能でリンクを手書きすれば外部の画像を表示出来ます。

 また、WordPressプラグインの動画プレイヤー『FV Flowplayer Video Player』や音声プレイヤー『MP3 Music Player by Sonaar』はURL指定で外部ファイルを再生できます。

 これらを使うことで、ファイルサイズが大きい動画や画像だけをCDNに置くことが出来ます。

2サーバー+CloudFront構成で大きなファイルだけCDN配信する

 これらを念頭に置いて構築したcan.ne.jpの構成図は下記となります。

can.ne.jpのCDN構成図

 まず、WordPress MultiSiteを設置したブログサーバー(can.ne.jp)とは別に配信サーバー(cloudfront.can.ne.jp)をLightSailで作ります。ただのファイル置き場なのでCMSなどは動かさず、Webサーバーも高速と言われるNginxを選びました。このようなサーバーを『オリジンサーバー』と呼びます。オリジンサーバーは外部からのアクセスを想定しないため、SSL証明書は不要です。

 次に、CloudFrontのインスタンス(cf.can.ne.jp)を作成してSSL証明書を取得します。can.ne.jpのドメインはAWSのDNSサーバー『Route 53』に置いているので、cf.can.ne.jpへのアクセスをCloudFrontに流し込む設定を簡単に行えます。

 なお、CloudFrontのようにサイト訪問者に最寄りから高速にデータを配信するサーバーを『エッジサーバー』と呼びます。各種設定の際は、オリジンサーバーとエッジサーバーが別のサーバー名(FQDN)になる点に気を付けて下さい。

Amazonの回し者のような図をついに描いてしまいましたが

 AWSがAmazonの利益率が高い事業であることは知っているので、AWSが決して『最安』ではないとの認識です(Amazonの商品が必ずしも最安ではないように)。

 CDNの導入にあたっては、無料枠があるCloudflareも検討しました。しかしDNSをRoute 53からCloudflareに移さなければならないことが分かったため断念しました。とにかく安くサイトを運営したいなら、DNSを設置する段階からCloudflareを選んでおくのが良いと思います。

 また、CDNを選ぶ際はエッジサーバーが日本国内でどの程度配備されているかが重要なポイントとなります。個人サイトなら落ちさえしなければ遅くても構いません(笑)。しかしビジネス目的なら国内に多数のエッジロケーションがあるかどうかが配信速度を左右しますので、無難にCloudFrontや、お高いことで有名なAxxxxxなど有名なサービスを選ぶことになると思います。

CDNは「逃げられるようにしておく」のが大事

 ロートルWeb担当者なら身に覚えがあるかも知れませんが、昔はCDNと言えばAxxxxx社だったので、CDNを入れるイコール「青天井の予算を組む」という恐ろしいタスクでした。

 特に企業サイトでは新製品発表など訪問者がスパイクするタイミングで波の大きさがどれくらいになるか、なかなか読めないものです。発表日にはCDNのコンソール画面に貼りついて、アクセスが殺到して予算超過にならないか見張らなければなりません。

 「いよいよ予算的にヤバイ」となれば、泣きながら手動でリンクをオリジンサーバーに付け替えるわけです。これでサーバーが重くなったり最悪落ちるかも知れませんが、CDNの青天井課金から逃げることだけは出来ます。

 「そもそもアクセスが殺到しても耐えられるくらい予算を確保(できる会社に就職)しておけよ」という正論が聞こえてきますが、悲しきかな日本の社畜はそうも行かないのが常です。保身の手段として覚えておくと良い、かも知れません。

Next.jsで苦しんでいます

Node.js以来、Web周辺の技術はすっかり変わってしまった

 WordPressによるサイト構築で苦しむことはなかった私ですが、Node.js以降時代が変わりSPA(Single Page Application)やJamstack(JavaScript/APIs/Markup)など訳の分からない技術であふれています。

 「PHPのままでいいじゃん」と正直思っているのですが、時代の変化から目を背けるとどんどん「昔の人」になり、「仕事では使えねーやつ」と若い人から後ろ指を差されてしまいます。

 基本的にはJavaScriptを用いたWebサイトの高速化とアプリ化、API連携あたりがトレンドなのだろうとは理解していますが、あまりにも複雑過ぎて手を出せないまま今日まで来てしまいました。さらにTypeScriptなる派生言語まで出てきて、おじさんはどこから始めたら良いかすら分からず頭を抱えています。

またインストールおじさんに

 取り敢えず環境を構築してみないと分かるか分からないかも分からないので、泣きながら環境構築を始めました。

 しかしフロントエンド周りの環境は複雑を極めており、エラーと試行錯誤とフォルダごと削除を何時間も繰り返すような地獄の様相です。素人なので確たることは言えませんが、フレームワークが林立して「混乱している」ように感じます。npm?npx?yarn?と首をかしげているうちに環境がぶっ壊れてしまいます。

 一番きつかったのが、またもやAnacondaとの競合です。Anacondaが起因と見られるエラーでインストールが止まってしまいました。仮想環境を切り替えても解決しなかったので、止むなくUbuntu LinuxにWeb専用のアカウントを追加し、そちらで作業することで何とかHello Worldまではこぎつけることが出来ました。

データ分析用とWeb開発用はアカウントを分けた方が良い

 完璧な設定をすればデータ分析用とWeb開発用で共通のアカウントを使うことも出来るのでしょう。しかし複数のフレームワークが混ざることに伴うトラブルシューティングの手間を考えれば、最初からWeb開発用のアカウントを別途作成した方が苦しまずに済むのではないかと思います。

 半日環境構築で格闘して皆さまのお役に立てそうなTipsはこれだけでした……。

今のフロントエンドは『プログラマー』になる覚悟が必要

 HTML/CSS、PHPあたりを軽くかじってWordPressなどのCMSをポチポチしてきただけの人には、相当厳しいと思います。今のフロントエンドは、Pythonなどと同様『プログラマー』になる覚悟が必要です。

 WordPressですべての用が足りている人は、無理に次のトレンドに乗る必要はないと思います。しかし、私のようにWeb担当の経験くらいしか売りモノがないおじさんは、この塹壕戦から逃れることが出来ません。

 正直、なぜフロントエンドをここまで複雑にしなければならないのか、未だに腑に落ちないところがあります。しかし時代の変化を嘆いていても仕方ないので、可能な限りキャッチアップしていきたいと思います。

 そんなわけで、更新が滞り気味な私の近況報告でした。

Cloudfront+LightSailでコンテンツ配信サービス(CDN)を作る

自前主義なら動画まで

 「自前主義で行こう」と独自ドメインでサイトを再開した私ですが、動画はYouTube先生に依存しています。動画はファイルが大きいので自分のサイトに置くのは正直怖いのですが、自前主義をテーマにしてみたので動画もサイトに置いてみることにしました。

LightSailは安いがしょぼい

 LightSailはAmazonの格安ホスティングサービスで「月額 3.50 USD から」が売りです。お財布に余裕が無い私は迷わず3.5ドルコースを選びましたが、スペックは下記のようなものです。

 うちのパソコンよりしょぼいです💦 無名サイトなので余裕ではあるのですが、動画を置くのは怖い。というわけで、Amazonのコンテンツ配信サービス(CDN) 『CloudFront』を入れてみることにしました。

 CloudFrontはWebサーバーのファイルをキャッシュして配信を代行してくれるサービスです。CodeZineによれば「東京における16のCloudFrontエッジロケーションおよび1つのCloudFrontリージョナルエッジキャッシュ、2つのAWS Direct Connectロケーション、大阪における1つのCloudFrontエッジロケーション、および1つのDirect Connectロケーション」とあり、2021年3月時点で日本国内に17か所の配信サーバーがあります。

 これならWebサーバーがしょぼいままでも、動画をCloudFrontに置けば、万が一アクセスが増えてもサーバーが落ちる事態は防げそうです。

で、CloudFrontはナンボなの?

 「でも、お高いんでしょう?」

 特に仕事でアカマイなどのCDNを入れたことがある方は、とても、とても心配になると思います。実際、企業向けのサービスなので多少の出費は覚悟する必要があります。具体的には、

 日本向けの料金が1GBあたり0.114ドルです。20MBの動画が月間1万再生されると約200GBになりますので、月22.8ドルになります。

「やはり、お高い……」

 しかしここはぐっと歯を食いしばって「1万再生もあれば22.8ドルくらいの収入は期待出来るだろう」と前向きに考えます。

CloudFront、なんとか動きました

 その後試行錯誤した結果、なんとかCloudFrontを動かすことが出来ました。

$ curl -svo /dev/null https://cf.can.ne.jp/v/my-domain-in-2020s.mp
4
*   Trying 54.239.169.55:443...
* Connected to cf.can.ne.jp (54.239.169.55) port 443 (#0)
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /opt/bitnami/common/openssl/certs/curl-ca-bundle.crt
*  CApath: none
...
< HTTP/1.1 200 OK
< Content-Type: video/mp4
< Content-Length: 13960998
< Connection: keep-alive
< Server: nginx
< Date: Mon, 12 Apr 2021 09:15:50 GMT
< Last-Modified: Mon, 12 Apr 2021 08:33:00 GMT
< ETag: "607405bc-d50726"
< X-Frame-Options: SAMEORIGIN
< X-Cache: Hit from cloudfront

X-Cache: Hit from cloudfront

 次回以降、どのような設定を行ったかをゆっくり書いていきたいと思います。

2020年代に独自ドメインは必要なのか?

 こんにちは、まさるです。

 このブログはcan.ne.jpというドメインに設置しています。
 whoisというサービスでドメインの登録情報を確認すると、このドメインを登録したのは1998年3月26日、と分かりました。
実に、23年1ヶ月前くらいです。なんと長い年月が過ぎてしまったのでしょうか……。

 この化石のようなドメイン、実は維持にけっこうお金がかかっています。
can.ne.jpは属性型JPドメインというもので、1年分の維持費が7700円もかかります。
 これとは別に、ドメイン預かりというサービスが年間3960円かかります。
合計で毎年11660円も払ってます。お財布に厳しいですね……。

 それでもドメインを維持しているのは、can.ne.jpという3文字のドメインが珍しいからです。しかし、今となっては、単なる自己満足でしかないような気もします。

 初めてドメインを取った1998年ごろは検索エンジンがまだ発達しておらず、ドメイン名を覚えてもらったりブックマークしてもらうことに価値がありました。しかし今は検索が全盛の時代です。わざわざURLを入力してサイトを訪問してくれる人はほとんどいません。

 今では昔ほど独自ドメインを取る価値が無いのは間違いありません。では、どのような時に独自ドメインを使う価値があるのでしょうか。

 独自ドメインを選ぶ最大の理由は、「コンテンツを置く先のサービスに振り回されたくない」ということです。外部サービスは終了してしまうことがありますし、ツイッターやフェイスブックのようにアカウントが特に理由もなく凍結されてしまうことがあります。そうした時に独自ドメインという『本拠地』があれば、心配した人がGoogleで検索して自分のサイトを確認してもらえる可能性があります。

 もうひとつの理由が、自由なソフトでコンテンツを公開出来ることです。このサイトもワードプレスというCMSで運営しているので、自分が好きなようにサイトをカスタマイズできます。また、サブドメインを作ることで自分のサイトであることを証明しながら複数のサイトを運営することも出来ます

 独自ドメインは、集客力では、どうしても大手サービスにはかないません。技術ブログであればキータなどで記事を書いた方が、たくさんの人の目にふれると思います。大手サービスのアドバンテージを理解した上で、よそに振り回されずにサイトを運営したい方は独自ドメインを検討する余地があると思います。

 このブログで紹介しているギットハブ・ページーズは無料で独自ドメインのサイトを作れます。もちろんドメイン自体の取得・維持にはお金がかかりますが、興味がある人はまずギットハブで試してみるのも悪くないと思います。

WordPressサイトを静的に出力してGitHub Pagesを作る

セキュリティが心配されるWordPress

 前にいた会社でキャラクター商品宣伝用のスペシャルサイトを作ることになりました。独自CMSを使っておりセキュリティ上の理由で制作会社さんからのCMS接続が難しかったため、系列会社のホスティングでサブドメインを作ることにしました。

 運営もコンテンツ修正が出来るようにしたかった(ベタHTMLで作ると都度制作会社さんに修正料金を払う必要がある)のでWordPressの導入を検討。しかし情シス担当者に「WordPressはセキュリティが弱いから止めてくれ」と言われ、制作会社さんにも「ベタHTMLにしてくれ(ないと面倒だし儲からない)」と言われ泣く泣くベタHTMLにしました。

WordPressのセキュリティが弱いのは動的CMSだから

  WordPressのセキュリティが多方面から懸念されてしまうのは、PHPという動的なサイト生成基盤を使っていることと単に「有名だから狙われやすい」のが理由です。「狙われたくないから情シスが勧めるマイナーなCMSを選ぶ」のは消極的であって根本的な問題解決になっていません。

 WordPressで作成した動的サイトを静的サイト(HTML+CSS+JavaScript)に変換して公開すれば、サイトが動的に生成されることによるセキュリティの問題を回避出来ます。

無料のGitHub Pagesで試してみる

 当サイトを無料のGitHub Pagesに静的サイトとしてミラーしてみましたので、やり方をメモとして残します。

https://masaru-kmt.github.io/

 もちろん、WordPressで静的サイトを作成したからといって大元のWordPressが安全になったわけではありません。セキュリティ上の観点からは、先にご紹介したLocalなど非公開のWordPressでサイトを作成し、GitHub Pagesなど公開環境に静的サイトをコミットするのが適切です。

 まず、GitHubでサイト名のレポジトリを作成します。

GitHubでGitHub Pages用のレポジトリを作成する

 次に、GitHubをまだ本格的に使っていない人はGitHub Desktopをダウンロードします(Windows/Mac)。私はUbuntuにはGitを入れていますが、Webサイトでは一度にたくさんのファイルをコミットするので今回はWindowsで試してみました。

GitHub DesktopならCLIが苦手な初心者でも安心()

WordPressから静的サイトを生成する『Simply Static』

Simply Staticは2か月前に更新されており無償なので安心

 WordPressから静的サイトを生成するプラグインは複数ありますが、検索上位のSEOサイト()で紹介されているプラグインは数年間更新が無かったり有料化されてしまったダメダメなものばかりです。結局WordPress内で検索して見つけた『Simply Static』を使うことにしました。

 Simple Staticの設定はパブリッシュ先のドメイン名くらいで簡単です。HTML化したサイトデータをzipファイルとしてダウンロードし、解凍してGitHub Desktopのレポジトリにドラッグ&ドロップし、GitHubにコミットします。

しばらく待たないと404 Not Foundのまま……

 コミットしましたがページが表示されません。「なぜ?」と焦って検索したところ、GibHubにコミットしたHTMLがWebページとして反映されるまでに時間がかかるのが理由、のようです。

HTMLを表示するには、1つずつGitHub上でコミットし直すか数10分待つ必要がある

 即時に更新を反映したいサイトには、GitHub Pagesは向かないようです。とは言え、無償でサイトを公開出来る(独自ドメインもOK)のは魅力的です。「少なくともGit (Hub)の存在くらいは知っている」という証明にはなるので検討の余地はあると思います。

 ちなみに、コンタクトフォームやコメントなどWordPressの動的な機能はミラー先の静的サイトでは無効になります。静的サイトにする際は「コンタクトフォームだけ他サイトのサービスを利用する」といった対策が必要となります。

Jupyter NotebookのセルをWordPressに貼り付ける

手作業でJupyter NotebookをWordPressに転記するのはツライ……

 ということでコピペする方法を探してみたところ、nbconvertを使う方法が良さそうです。

$ jupyter nbconvert --to html --template basic gstore-cust-revenue-prediction.ipynb

 生成されたHTMLから該当セルをWebブラウザーのインスペクター(F12キーを押下)でコピーし、WordPressのカスタムHTMLブロックに貼り込みます。

<div class="input">
<div class="prompt input_prompt">In [7]:</div>
<div class="inner_cell">
    <div class="input_area">
<div class=" highlight hl-ipython3"><pre><span></span><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span><span class="o">,</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span><span class="o">,</span> <span class="nn">os</span><span class="o">,</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="nn">plt</span><span class="o">,</span> <span class="nn">seaborn</span> <span class="k">as</span> <span class="nn">sns</span>
<span class="kn">import</span> <span class="nn">json</span><span class="o">,</span> <span class="nn">re</span><span class="o">,</span> <span class="nn">gc</span>                              <span class="c1">#garbage collector</span>
<span class="kn">from</span> <span class="nn">sklearn.preprocessing</span> <span class="kn">import</span> <span class="n">LabelEncoder</span>
<span class="kn">from</span> <span class="nn">ast</span> <span class="kn">import</span> <span class="n">literal_eval</span>
<span class="kn">from</span> <span class="nn">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">KFold</span>
<span class="kn">from</span> <span class="nn">sklearn.metrics</span> <span class="kn">import</span> <span class="n">mean_squared_error</span>
<span class="kn">from</span> <span class="nn">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">GridSearchCV</span> <span class="c1">#Experimented hyperparams a bit with this</span>

<span class="kn">from</span> <span class="nn">catboost</span> <span class="kn">import</span> <span class="n">CatBoostRegressor</span>
<span class="kn">from</span> <span class="nn">xgboost</span> <span class="kn">import</span> <span class="n">XGBRegressor</span>
<span class="kn">import</span> <span class="nn">lightgbm</span> <span class="k">as</span> <span class="nn">lgb</span>

<span class="k">for</span> <span class="n">dirname</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">filenames</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="s1">'/home/masaru/data/kaggle_google_analytics'</span><span class="p">):</span>
    <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">filenames</span><span class="p">:</span>
        <span class="nb">print</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dirname</span><span class="p">,</span> <span class="n">filename</span><span class="p">))</span>
        <span class="k">pass</span>
<span class="n">gc</span><span class="o">.</span><span class="n">enable</span><span class="p">()</span>
<span class="n">sns</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="n">style</span><span class="o">=</span><span class="s1">'whitegrid'</span><span class="p">,</span><span class="n">palette</span><span class="o">=</span><span class="s1">'deep'</span><span class="p">,</span><span class="n">font_scale</span><span class="o">=</span><span class="mf">1.1</span><span class="p">,</span><span class="n">rc</span><span class="o">=</span><span class="p">{</span><span class="s1">'figure.figsize'</span><span class="p">:[</span><span class="mi">8</span><span class="p">,</span><span class="mi">6</span><span class="p">]})</span>
<span class="n">pd</span><span class="o">.</span><span class="n">set_option</span><span class="p">(</span><span class="s1">'float_format'</span><span class="p">,</span> <span class="s1">'</span><span class="si">{:f}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">)</span>     <span class="c1">#to display full numbers in dataframe and not just exponentiated form </span>
</pre></div>

    </div>
</div>
</div>

<div class="output_wrapper">
<div class="output">


<div class="output_area">

    <div class="prompt"></div>


<div class="output_subarea output_stream output_stdout output_text">
<pre>/home/masaru/data/kaggle_google_analytics/test_v2.csv
/home/masaru/data/kaggle_google_analytics/submission.csv
/home/masaru/data/kaggle_google_analytics/deep-learning-keras-ga-revenue-prediction.ipynb
/home/masaru/data/kaggle_google_analytics/gstore-cust-revenue-prediction.ipynb
/home/masaru/data/kaggle_google_analytics/ga-customer-revenue-prediction.zip
/home/masaru/data/kaggle_google_analytics/test.csv
/home/masaru/data/kaggle_google_analytics/sample_submission_v2.csv
/home/masaru/data/kaggle_google_analytics/GoogleAnalytics_Customer_Revenue_EDA_and_Prediction.ipynb
/home/masaru/data/kaggle_google_analytics/sample_submission.csv
/home/masaru/data/kaggle_google_analytics/train_v2.csv
/home/masaru/data/kaggle_google_analytics/train.csv
/home/masaru/data/kaggle_google_analytics/.ipynb_checkpoints/gstore-cust-revenue-prediction-checkpoint.ipynb
/home/masaru/data/kaggle_google_analytics/.ipynb_checkpoints/GoogleAnalytics_Customer_Revenue_EDA_and_Prediction-checkpoint.ipynb
</pre>
</div>
</div>

</div>
</div>

WordPressのテーマにNotebook用のCSSを追加する

 続いて、WordPressの『外観⇒カスタマイズ⇒追加CSS』でJupyter Notebookセル用のCSSを追加します(リンク先ページのソースを参照のこと)。

In [7]:
import numpy as np, pandas as pd, os, matplotlib.pyplot as plt, seaborn as sns
import json, re, gc                              #garbage collector
from sklearn.preprocessing import LabelEncoder
from ast import literal_eval
from sklearn.model_selection import KFold
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import GridSearchCV #Experimented hyperparams a bit with this

from catboost import CatBoostRegressor
from xgboost import XGBRegressor
import lightgbm as lgb

for dirname, _, filenames in os.walk('/home/masaru/data/kaggle_google_analytics'):
    for filename in filenames:
        print(os.path.join(dirname, filename))
        pass
gc.enable()
sns.set(style='whitegrid',palette='deep',font_scale=1.1,rc={'figure.figsize':[8,6]})
pd.set_option('float_format', '{:f}'.format)     #to display full numbers in dataframe and not just exponentiated form 
/home/masaru/data/kaggle_google_analytics/test_v2.csv
/home/masaru/data/kaggle_google_analytics/submission.csv
/home/masaru/data/kaggle_google_analytics/deep-learning-keras-ga-revenue-prediction.ipynb
/home/masaru/data/kaggle_google_analytics/gstore-cust-revenue-prediction.ipynb
/home/masaru/data/kaggle_google_analytics/ga-customer-revenue-prediction.zip
/home/masaru/data/kaggle_google_analytics/test.csv
/home/masaru/data/kaggle_google_analytics/sample_submission_v2.csv
/home/masaru/data/kaggle_google_analytics/GoogleAnalytics_Customer_Revenue_EDA_and_Prediction.ipynb
/home/masaru/data/kaggle_google_analytics/sample_submission.csv
/home/masaru/data/kaggle_google_analytics/train_v2.csv
/home/masaru/data/kaggle_google_analytics/train.csv
/home/masaru/data/kaggle_google_analytics/.ipynb_checkpoints/gstore-cust-revenue-prediction-checkpoint.ipynb
/home/masaru/data/kaggle_google_analytics/.ipynb_checkpoints/GoogleAnalytics_Customer_Revenue_EDA_and_Prediction-checkpoint.ipynb

 無事、表示出来ました。

Ubuntu 21.04でTensorflow-GPUが動いた(ことだけ)

既存のNVIDIA CUDAドライバで大丈夫でした

 まず最初にお断りですが、私はTensorflowの技術的な詳細やディープラーニングの技術については知識が全くありません。単なるインストールログですので、技術的なご期待にはお答え出来ません。予めご了承ください。

 Ubuntu 21.04は正式リリース前のためTensorflow GPUは動かないのではないかと思っていましたが、実際はcuda_11.2.2_460.32.03_linux.runがすんなり動きました。

sudo wget -O /etc/apt/preferences.d/cuda-repository-pin-600 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/7fa2af80.pub
sudo add-apt-repository "deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/ /"


wget https://developer.download.nvidia.com/compute/cuda/11.2.2/local_installers/cuda_11.2.2_460.32.03_linux.run
sudo sh cuda_11.2.2_460.32.03_linux.run

nvidia-smi
Thu Apr  8 16:29:59 2021       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.67       Driver Version: 460.67       CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GTX 1650    Off  | 00000000:01:00.0 Off |                  N/A |
| N/A   49C    P0    15W /  N/A |   3764MiB /  3911MiB |      4%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|    0   N/A  N/A      1741      G   /usr/lib/xorg/Xorg                214MiB |
|    0   N/A  N/A      1949      G   /usr/bin/gnome-shell               72MiB |
|    0   N/A  N/A      3535      G   ...AAAAAAAAA= --shared-files       81MiB |
|    0   N/A  N/A      7116      G   ...AAAAAAAAA= --shared-files        5MiB |
|    0   N/A  N/A    312502      C   ...saru/anaconda3/bin/python     3333MiB |
+-----------------------------------------------------------------------------+
sudo apt -y install libcudnn8 libcudnn8-dev
conda install tensorflow-gpu

jupyter-notebook &

 以下はJupyter Notebookでの作業となります。

import tensorflow as tf
print(tf.__version__)
2.4.1
gpu_num = len(tf.config.list_physical_devices('GPU'))
print(gpu_num)
1

 勉強会でみんな大好きiris、titanic、mnistを今さらやっても得られるものは無さそうなので、文系の私でも興味を持てそうなサンプルコードを見つけて実行してみようと思います。

Analyzing Google Analytics data using TensorFlow GPU

Jupyter NotebookからPythonでPostgreSQL 13を操作する

まずは堅実に定番RDBから

 NoSQLブームで勉強会界隈では影が薄れた感があるRDBMSですが、当たり前の存在になっただけで健在です。当サイトが使っているCMS『WordPress』にはMySQLが組み込まれていますし、当サイトを置いているAWSでもPostgreSQLがRedShiftやAuroraなど基幹サービスで使われています

 統計モデリングを知らないのにディープラーニングでイキる人が信用出来ないのと同様、RDBMSを知らないのにNoSQLを語るのもイタい人です。私も昨年はElasticsearchなどのNoSQLやApache Igniteなどの分散データベースにハマっていましたが、自分のイタさに気づいたためPostgreSQLを学び直しています。

 NoSQLや分散データベースを業務で使いこなしている方々の講演は勉強会で聞くことが出来ますが、彼らは「RDBMSを経験した上でNoSQLに進んでいる」のです。未経験者が講演を聞いていきなりNoSQLに行くのは『悪手』です。

初心者がNoSQLや分散RDBでやりたいことはPostgreSQLでも出来ている

 NoSQLが主に扱うJSONはPosgreSQLでも『JSON型』として扱えます。IMDBで話題となったインメモリ処理はPostgreSQLでも駆使されています。分散データベースの特徴であるクエリの並列処理はPostgreSQLでもパラレルクエリとして実装されている上、特別の設定無しで自動的に使ってくれます。

 昨年、実際にApage IgniteでPCサーバ5台のクラスタを構築しましたが、数10GB程度のデータではクエリに要する時間がシングルノードのPostgreSQLの方が速かったです。2.5GbEでもLANの遅延で並列処理のメリットが相殺されてしまいます。この規模のデータでクエリを高速化するなら、32コア64スレッドのパソコンでも買えば良いと思います。

 こういう事実は、基本的すぎて勉強会ではなかなか教えてもらえないですね。エンジニアの方にとっては常識なのでしょうが……💦

PythonでPostgreSQLを扱うパッケージ『psycopg2』

 PythonでPostgreSQLを扱う際は『psycopg2』というパッケージを使うのが一般的なようです。視覚化のためのパッケージ『Plotly』と併せてインストールします。

(base) masaru@ASUS-TUF-Gaming:~$ conda install psycopg2
(base) masaru@ASUS-TUF-Gaming:~$ conda install plotly

Jupyter Notebookでの実装

 必要なパッケージの読み込みなどの初期設定を行います。

# -*- coding: utf-8 -*-
import psycopg2
import pandas as pd
import plotly as py
import plotly.graph_objs as go
from plotly.offline import iplot, init_notebook_mode
init_notebook_mode()

 psychopg2でデータベース接続を定義します。

def connect():
    con = psycopg2.connect("host=" + "localhost" +
                           " port=" + "5432" +
                           " dbname=" + "google_mobility" +
                           " user=" + "masaru" +
                           " password=" + "xxxxxxxxxxxx")
    return con

 続いて、クエリを定義します。

def select_execute(con, sql):
    with con.cursor() as cur:
        cur.execute(sql)
        rows = cur.fetchall()

    return rows

 定義したクエリを実行します。

con = connect()
sql =  "select * from google_mobility where SUB_REGION_1 = 'Tokyo'"
result = select_execute(con, sql)

 クエリの結果をPandasデータフレームに代入します。

df = pd.DataFrame(result)
df.head()

 とりあえず全部カラム名をつけてあげます。

columns = ["id","country_region_code","country_region","sub_region_1","sub_region_2","metro_area","iso_3166_2_code","census_fips_code","date","retail","grocery","parks","transit","workplaces","residental","place_id"]
df.columns = columns
df.head()

 Plotlyで表示するデータを設定します。

trace1 = go.Scatter(
        x = list(df.date),
        y = list(df.retail),
        mode = 'lines+markers',
        name = 'retail',
        marker = dict(
                color = 'blue'
                )
        )

trace2 = go.Scatter(
        x = list(df.date),
        y = list(df.grocery),
        mode = 'lines+markers',
        name = 'grocery',
        marker = dict(
                color = 'orange'
                )
        )

data = [trace1, trace2]

 続いて、Plotlyの特長であるスライダーを設定します。

layout = dict(
    title='Time series with range slider and selectors',
    xaxis=dict(
        rangeselector=dict(
            buttons=list([
                dict(count=1,
                     label='1m',
                     step='month',
                     stepmode='backward'),
                dict(count=6,
                     label='6m',
                     step='month',
                     stepmode='backward'),
                dict(count=1,
                    label='YTD',
                    step='year',
                    stepmode='todate'),
                dict(count=1,
                    label='1y',
                    step='year',
                    stepmode='backward'),
                dict(step='all')
            ])
        ),
        rangeslider=dict(
            visible = True
        ),
        type='date'
    )
)

 グラフを描画します。

fig = dict(data=data, layout=layout)
iplot(fig)

 スライダーを動かせるグラフの出来上がりです。