読者です 読者をやめる 読者になる 読者になる

備忘録

なんとなく暇なときにでも....

2017年を迎えて

ひさしぶりの投稿です。

2017年ですね(もう2ヶ月経ちましたが…)

2016年はエンジニアとして働くために東京にでてきて1年かドタバタしていたので今年は少し落ち着くといいなと思っております。
初めた当初は毎週書いてたブログも少しづつ書かなくなっていまい、これに関してはあまりよろしくないなと……


正直にいうとブログに関してはフェードアウトしていくんだろうなと感じていましたがひさしぶりのはてブを開いたら

“なんと”

2件も私が書いた記事が引用されていました。

seishin55.hatenablog.com
pgsatooo.hatenablog.com
自分がやったことが誰かの役に立つのは嬉しいですよね。やっぱり
今までブログは読んで活用させていただく方だったので感慨深いです。
単純ですが、またなにかあれば書いていこうかなと思わせられましたね。


2017年ですが、エンジニアとしては2年目になる年ですので、いくつか目標を立てました。

  1. 言語を1個勉強する
     今のところはgolangかなと。
     なんとか会社が終わった後30分でもいいので tour of go やったり少しづつ勉強してます。
     エンジニア1年目は7, 8割はPHPだったのでだいぶ慣れてきた(とは言っても先輩エンジニアに毎日しごかれる日々ですが….)ので2年目の年もそんな感じでいければなと。

  2. コミュニティに積極的に参加する
     社内だと僕以外のエンジニアは全員最低でも5年以上経験のあるエンジニアの方なので、会社の外で切磋琢磨する相手を見つけられればなと。

  3. (ブログをもう少し書く)
     


最低でも最初の2つは意識してやっていきたいです。

ちなみにgolangは(少しづつですが)ちゃんと勉強してますよ。

github.com


本日は以上です

php-webdriverを使用してスクレイピングをした話

久しぶりの投稿です。
あっという間に今年も終わり。

以前phpのライブラリを使用せずにwebサイトをスクレイピングする記事を投稿しました。
hchckeeer.hatenadiary.jp hchckeeer.hatenadiary.jp

これまでは、このツールを少しづつ改良しスクレイピングを行っていたのですが
このツールではjsでレンダリングされた箇所についてはうまくスクレイピングできませんでした。

調べてみるとSelenium-webdriverのphp実装用にFacebookからphp-webdriverなるソースがあるらしくそちらを利用しました。(結局ライブラリを使うことになってしまいましたが...)
github.com

  • Seleniumの導入
    $ sudo wget http://selenium-release.storage.googleapis.com/2.48/selenium-server-standalone-2.48.2.jar

  • JDK, Xvfb, Firefoxのインストール
    $ sudo yum install -y java-1.8.0-openjdk $ sudo yum install -y xorg-x11-server-Xvfb firefox
    今回はブラウザとしてfirefoxを使用します。

  • 環境変数DISPLAYの設定

$ vi ~/.bash_profile
export DISPLAY=:3

$ source ~/.bash_profile
  • Xvfb起動
    $ Xvfb :3 -screen 0 1024x768x24 &
  • SeleniumServer起動
    $ java -jar selenium-server-standalone-2.48.2.jar

準備が整ったのでとりあえずはページタイトルを取得してみます。
実行してみると複数のエラーに直面したので1つずつ解消しました。

PHP Fatal error:  Uncaught exception 'Facebook\\WebDriver\\Exception\\WebDriverCurlException' with message 'Curl error thrown for http POST to /session with params: {"desiredCapabilities":{"browserName":"firefox","platform":"ANY","firefox_profile":"UEsDBBQAAAAIAByBnUnf9RXUNAAAADIAAAAHAAAAdXNlci5qcystTi2KLyhKTdNQKkpNTEkt0itILCpO1c3P083JT0zRS81LTMpJTVHSUUhLzClO1bTmAgBQSwECAAAUAAAACAAcgZ1J3\\/UV1DQAAAAyAAAABwAAAAAAAAAAAAAAAAAAAAAAdXNlci5qc1BLBQYAAAAAAQABADUAAABZAAAAAAA="}}\n\nOperation timed out after 30240 milliseconds with 0 out of -1 bytes received'

デフォルトのタイムアウトの時間を変更してエラーを解消。
stackoverflow.com

-タイムアウトエラーの解消後に、WebDriverからFirefoxを起動する際に発生したエラー

org.openqa.selenium.firefox.NotConnectedException: Unable to connect to host 127.0.0.1 on port 7055 after 45000 ms. Firefox console output:

WebDriverのバージョンがFirefoxのバージョンに対応していないらしい
qiita.com
firefoxのバージョンを調べてみると...

firefox -v
Mozilla Firefox 45.6.0

だったので一旦30.0.0まで引き下げました。

seopt.pw

最終的なソースコードはこんな感じに...

<?php

require_once 'vendor/autoload.php';

use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;

//web driver
$host = 'http://localhost:4444/wd/hub';
$web_driver = RemoteWebDriver::create(
                                    $host,
                                    DesiredCapabilities::firefox(),
                                    60 * 10000, // Connection timeout in miliseconds
                                    60 * 10000  // Request timeout in miliseconds
                                );
$url = 'http://hchckeeer.hatenadiary.jp/';
$web_driver->get($url);
var_dump( $web_driver->getTitle() );
                            

これでページのタイトルを取得することができました。

あとは本当に取得したかったjsでレンダリングされた後の要素を指定(webdriverでは class名, id名, tag名, xpathなどで欲しい要素の指定が可能です。)して、要素を取得するだけでした。

下記コードではtag名を指定して、そのタグに含まれるattributesを取得しています。

<?php

require_once 'vendor/autoload.php';

use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;

//web driver
$host = 'http://localhost:4444/wd/hub';
$web_driver = RemoteWebDriver::create(
                                    $host,
                                    DesiredCapabilities::firefox(),
                                    60 * 10000, // Connection timeout in miliseconds
                                    60 * 10000  // Request timeout in miliseconds
                                );
$url = 'http://hchckeeer.hatenadiary.jp/';
$web_driver->get($url);
$ele = $web_driver->findElement( WebDriverBy::tagName('iframe') )->getAttribute('src');
                                

導入に少し時間がかかってしまいましたが、
比較的簡単にスクレイピングができたと思います。

本日はここまで

参考 tmysz.hateblo.jp

stackoverflow.com

WebDriver API Document

Working with PHPUnit and Selenium Webdriver

CakePHPのHtmlヘルパを使用した<a>タグ内にimg要素やdiv要素追加するには...

最近週末はcakephp3を使用して、Webページを作成しているのですが、それに伴ってHtml部分をcakephpのHtmlヘルパやFormヘルパを使ったり(使わなかったり)してます。

今日はその中でもHtmlヘルパでaタグの生成に使用するlink()についてです。

link()は

$this->Htm->link(
    string $title, 
    mixed $url = null, 
    array $options = array(), 
    string $message = false
);

//$title : リンクされる文字
//$url : リンク先のURLを出力するパラメーター
//$options : オプションのパラメーター(なくてもよし)
//$message : 表示されるメッセージ(なくてもよし)です。

のような関数になっています。

以下のような画像付きリンクにしたいときは

<a href="/hoge/foo">
    <img src="img.png" alt="">
</a>
<?php 
echo $this->Html->link(
    $this->Html->image('img.png'),
    array(
        'controller'=>'hoge',
        'action'=>'foo'
    ),
    array(
        'escape'=>false
    )
);
?>

の書けば生成することができますよね。

ここでポイントなのは第3引数であるオプションの'escape'=>false
これを指定してないと第1引数の$this->Html->image('img.png')生成したimgタグがエスケープされてしまい、文字列として表示されてしまいます。


と、ここまではググったらすぐにわかることなんですが、私が詰まってしまったのは、

divタグをaタグの中に入れたいときはどうすればよいのか...



ググってもあんまりパッとする日本語のソースを見つけることができませんでした。
諦めて普通のhtmlで書こうと思ったのですが、とりあえず英語でググってみるとそれっぽいソースをstack overflowで発見しました。

結論から言いますと、第1引数に含めればよいとのことでした。

<?php
echo $this->Html->link(
    $this->Html->image('img.png') . '<div id="hoge-class">hogehgoe</div>' ,
    array(
        'controller'=>'hoge',
        'action'=>'foo'
    ),
    array(
        'escape'=>false
    )
);
?>

のように第1引数にまとめて書いてしまえばOK。(ちなみにdivタグを生成するヘルパ関数もあります。)

<a href="/hoge/foo">
    <img src="img.png" alt="">
 <div class="hoge-class">hogehoge</div>
</a>

のように出力されるはずです。

link()についてある程度理解していれば、ちゃちゃっと解決できてしまう問題でした。



本日はここまで。

参考 qiita.com

stackoverflow.com

楽天の商品検索APIで詰まってしまった話

楽天の商品検索APIを使って、指定したキーワードを用いて楽天市場に出回っている商品を検索するツールを受託で開発しました。
その際に、ある問題で1時間ほどハマってしまいましたので、共有できればと思います。

まず楽天の商品検索APIについてですが、導入や使い方については割愛させていただきます。
(実際に使用したAPIはこちらです。↓)
webservice.rakuten.co.jp
一旦は開発しまして、クライアントさんに納品したのですが、あるキーワードを入力するとバグが出るとのこと。
そのワードとは........

「半角1文字」


再度上記のAPIの仕様書を読んでみますと、

各検索キーワードは半角2文字 もしくは 全角1文字 以上で指定する必要があります。

との記述が...
完全に見落としてました。

じゃあいったい半角英数字を入力したい場合はどうすればよいのか...
「そもそも、半角1文字で検索するシチュエーションなんてほとんどないわけだし、気にしなくていいのでは?」とも思いましたが、
どうやら半角1文字だけでなく、「i can fly」のようにスペースを空けて半角1文字を使用した場合でも、駄目だという。

f:id:tomAn:20161008182900j:plain

( i , can , fly の3つワードのアンド検索になっているのでしょう。 )

どうしたものかと考えていましたが、ふと実際に楽天で同様の条件で検索したらどうなるのだろうと思いまして、「i can fly」で検索してみたところ、「i+can+fly」のように検索を行っているようです。

f:id:tomAn:20161008183120p:plain

なので、これを踏襲することとし、
目標としては、単語の羅列の中から1文字の単語が来たら、その後ろのスペースを"+"に変換することにしました。

手順としては、

1. 単語の羅列の中に含まれる全角スペースを半角スペースに変換(preg_replace)

2. 半角スペースをデリミタにして、各単語を配列の格納(preg_split)

3. 各単語の文字数をカウントし、もし1文字だったら、後ろに"+"を付ける。(念のため、ひらがな1文字の場合も1文字とカウントするためmb_strlenを使用しました。)

4. 配列の最後までチェックが終わったら、配列の要素(単語)同士を結合して、元の文字列に戻す。

こんな感じでなんとか「i+can fly」というキーワードに変換することができました。


本日はここまで。

参考サイト

web-dou.com

unskilled.site

[ PHP ] 正規表現サンプル12選 ( preg_match / preg_match_all) – 行け!偏差値40プログラマー

ysklog.net

mb_strlen:文字列の文字数をカウントする

追記

よくよく考えてたら、スペースをpreg_replaceで全て"+"にすればよかっただけの話でしたな。

CakePHP3をインストールする際にハマった...

約1年ぶりにCakePHP3をインストールしました。
一年前はVagrantを使用した仮想環境上(たしかCentOS 7)にCakeを導入したのですが、
その時は、参考サイトに言われるがままにPHPの導入からやっていたのですが、
今回はdocker上に導入するにあたって、ローカルのマシンにCakePHP3をインストールしました。

公式サイトにしたがって
composerを使ったCakePHP3を導入しました。

$ curl -s https://getcomposer.org/installer | php

$ composer create-project --prefer-dist cakephp/app [プロジェクト名]

プロジェクト名は適宜自分で。

ここでまさかのエラーが...

Your requirements could not be resolved to an installable set of packages.

Problem 1
 - cakephp/cakephp 3.0.x-dev reqyures ext-intl * -> the requested PHP extention intl is missing from your system.
 - ...
 - ..
 - .

調べてみるとintlがないとのこと。
さっそくphp.initを調べてみる。

$ php -i | grep intl

なにも表示されない....

まず必要なpeclをインストールします。

$ sudo php install-pear-nozlib.phar
   .
   . 
   . 
Wrote PEAR system config file at: /private/etc/pear.conf
You may want to add: /usr/lib/php/pear to your php.ini include_path

これでpeclを使う準備できました。

インストールしたpeclを使って、intlをインストールします。

$ sudo pecl update-channels

$ sudo pecl install intl


インストール成功したら、php.iniにextension=intl.soを追加しろとのこと。

$ php -m | grep intl
intl

表示されました。
ようやくこれでインストールできるようになりました。

$ composer create-project --prefer-dist cakephp/app [プロジェクト名]



無駄に時間取られました。


本日はここまで

初のAPI実装を行っての反省点

会社で初めて規模が比較的大きめのAPI実装を行いました。

大規模なAPI実装自体は、個人でアプリ開発を行った際は、すべて1人で行ったのですが、
そのときは、アプリ側からのリクエストに対して、正しいレスポンスをすることだけしか考えてませんでした。
それに加えて、その際は、自分の隣にアプリエンジニアがいて、意思疎通を取りながらできていたので、認識のズレが少なくて済んでました。

今回ははじめてアプリエンジニアと新機能企画者とAPIエンジニアのように役割が分担されたケース(1番よくありそうなケースですが...)で、 私にとっては初仕事でした。


今回の実装で大きな反省点は2つです。

  • レスポンスの型を意識する

  • アプリ側で実装された完成型もイメージする

レスポンスの型を意識する

まず1つ目は、アプリ側へのレスポンスするときの型についてです。
私は、個人でも会社でもPHPをメインにAPIの実装を行っています。
ここで問題となるのが、"型"についてです。
正直に言いますと、動的型付き言語であるPHPが9割5分を占めている私のエンジニア人生において、型というものを意識する機会は皆無でした。
$a = 1$b = "1"も私にとっては大きく違いがあるものというイメージがありませんでしたが、開発現場ではそうもいかないようです。
アプリ側からのリクエストに対しても、API側で型のチェックを行っています。これは、誤った型でリクエストが渡ってきた場合に一見動いているように見えるという状況を防ぐためでもあります。
たとえば、

$get = $_GET;

$use_id = $get['user_id'];

とした場合。
$user_idは、API側ではinteger型を期待してるのに、もしかしたら、string型でリクエストが来てるかもしれないですよね。

もしものためにバリデーションしておきますが...

$get = $_GET;

$use_id = intval($get['user_id']);

アプリ→APIのようなリクエストの際に、ある特定の型を期待しているのと同様に、
API→アプリのレスポンスの際にも、アプリ側は、(バリデーションをかけているとはいえ)ある特定の型を期待していることでしょう。

ということで、レスポンスの内容が、正しいからといって、そこで終わりではなく、アプリエンジニアとのきちんと型についても意識を共有しておくことが大事だという反省点でした。

もう1つが、レスポンスの際の内容がなかった場合(たとえば、ユーザーのフォロワーを返すようなAPIに対して、user_id = 1のフォロワーが1人もいなかった場合)
この場合レスポンスをどうするべきかということ。

$get = $_GET;

$use_id = intval($get['user_id']);

$follower_data_array = $this->get_follwer($user_id)

return $follower_data;

この例で言うならば、フォロワーがいる場合、フォロワーを返すAPIは、複数の各フォロワー情報を含んだ配列を返すはずです。

$follower_data = array(
    array( 
        user_id  = 2,
        name     = 'AAA',
    ),
    array( 
        user_id  = 5,
        name     = 'BBB',
    ),
    ........
);

しかし、最初に述べたように、user_id = 1のフォロワーが誰もいなかった場合、どのようなレスポンスを返すべきなのでしょうか。
考えられるパターンとしましては、

  1. $follower_data=NULL; で返す

  2. $follower_data=array();(空配列) で返す

  3. 1と2のどちらも許容する

のようなパターンが考えられるかと思われます。
ここでもアプリ側が期待しているものに絞るべきです。
でないとアプリ側の実装の際に、不必要なバリデーションが増えてしまい、冗長ですよね。
1か2が妥当だと思います。(NULLを許容しない2がいいらしい。)

アプリ側で実装された完成型もイメージする

2つ目は、完成型をイメージするということですが、たとえば、あるリクエストに対して、所定のテーブルから、指定のIDの画像のURLを取ってきて返す。といったAPIがあるとします。
しかし、まだ指定された画像が準備おらず、そのレコードの画像URLののカラムがNULLになっています。

このようなケースですが、私は特に意識しておらず、画像のURLもいつか準備されてるだろうから、とりあえずはNULLを返しておけばいいかと思っていました。
しかし、アプリエンジニアからすると、NULLが返ってくるので、実際の画像が表示できず困ってしまうようです。
もう少し、アプリでの実装過程もイメージして、仮の画像を準備するなどして、対処するべきでした。


以上2点が今回の反省点です。
新人エンジニアの人はそこらへんも注意して、API実装を行ってみてはどうでしょうか。


本日はここまで。


参考

qiita.com



Twitterもフォローしてください

twitter.com

"リーダブルコード -より良いコードを書くためのシンプルで実践的なテクニック-"を読み終えました。

かの有名な書籍”リーダブルコード”を読み終えました。


やっぱり有名なだけあって非常に為になる本でした。そしてなによりその読みやすさ。
読書が苦手な私でも毎日二章ずつくらい読みすすめることができて、結局1週間ほどで完読しました。

日々の業務でも、読んだことを実践してます。
4章の"美しさ"なんて即実践に移せましたね。

私は関数や変数への名前付けが苦手なので、2章や3章はとても興味深い内容でした。

  • 2章 名前に情報を詰め込む

    • 名前に情報を詰め込む
    • 類語辞典でもっとカラフルな名前を探す
    単語 代替案
    send deliver,dispatch,announce,distribute,route
    find search,etract,locate,recover
    start launch,create,begin,open
    make create,set up,build,generate,compose,add,new
    • tmpやretvalなどの汎用的すぎる名前は避ける
    • i, j, k などはループの変数として理解できるため使ってもよい
    • スコープが小さければ短い名前でもいい
    • エディタの補完機能などがある現在では、長い名前を入力するのは問題じゃない
    • stringをstr、documentをdocと省略するのは問題ないが、チーム独自の省略規則はやめよう(新しい人がジョインしてきたときに、その人の理解を邪魔するため。)
  • 3章 誤解されない名前

    • 名前が「他の意味と間違えられることはないだろうか?」と何度も自問自答する
    • 限界値を示すときは min_hogehoge , max_hogehoge を使う
    • 範囲を示すときは first_hogehoge , last_hogehoge を使う
    • 包括的範囲には begin_hogehoge , end_hogehoge を使う
    • get_hogehogeは変数へのアクセッサの意味として認知されているので、そのような処理以外でgetを使用しないこと


とこんな感じでした。



早足ですが、本日はここまで。