- 投稿日:2020-09-24T22:16:20+09:00
Redisの概要とNode.jsでの使い方
Redisの概要
redisはインメモリのNoSQLのデータベースです。単純なKey-Valueストアのように文字列だけでなく、様々な種類の値をサポートしています。
Webアプリケーションのスケーリングに便利です。環境構築
dockerが利用できるなら、それで環境を作成するのが楽です。
docker run --name redis -p 6379:6379 -d -v /Users/hoge/dev/node/socketio/data:/data redis:latestdockerで作成したコンテナでredis-cliを使用するには以下のようにします。
hoge@macpro socketio % docker exec -it redis /bin/bash root@8f9888c8bb76:/data# redis-cli 127.0.0.1:6379>ホストとポートを指定したい場合は以下のようにします。デフォルトではlocal:6379に接続しています。
redis-cli -h localhost -p 6379Redisのデータについて
Redisは単純なキーと値だけでなくリストやハッシュなどが使えます。
この章は以下を元に記述されていますので英語読める人は以下読んだほうがいいと思います。
https://redis.io/topics/data-types-intro
https://redis.io/topics/data-typesまた、この章で記述されているコマンドはredis-cliで実行したものとなります。
キー
Redisのキーはバイナリデータを正しく扱うことができます。"foo"のような文字列からJPEGファイルの中身まで任意のバイナリをキーとして使えます。空の文字列も有効なキーです。
キーに関するルールは以下の通りです。
- 非常に長いキーはお勧めしません。例えば1024バイトのキーはメモリの点だけでなく、データセット内の検索にコストがかかります。
- 多くの場合、非常に短いキーはお勧めしません。"user:1000:followers"と記載する代わりに"u1000flw"とかく意味はほとんどありません。前者は読みやすく、使用されるスペースもわずかです。
- スキーマーに固執するようにしましょう。とえば、"user:1000"のように、 "object-type:id"のような形式にするのは良いアイディアです。 キーの最大は512MBです。
キーに関係するコマンド
コマンド名 概要 DEL キーを削除する UNLINK 別スレッドで非同期にキーを削除する。DELと同じだが非ブロッキング。 EXISTS キーが存在するか確認する TYPE キーに格納されている値のタイプを取得する KEYS 指定のパターンに一致するキーの一覧を取得する TOUCH キーの最終アクセス時刻を変更する RENAME キーの名前を変更する RENAMENX 新しいキーが存在しない場合のみキーの名前を変更する DUMP 指定されたキーに格納されている値のシリアル化されたバージョンを取得する OBJECT Redisオブジェクトの内部を検査する MOVE キーを別のデーターベースに移動する MIGRATE キーをRediusインスタンスから別のインスタンスに転送する RANDOMKEY キースペースからランダムなキーを返す RESTORE 以前にDUMPしたシリアル化された値を使用してキーを作成する SORT リスト、セット、ソート済セットの要素を並び替える WAIT 別の接続コンテキストで送信された全ての書き込みコマンドの同期複製を待つ SCAN キースペースを段階的に反復する 文字列
文字列は最も基本的な種類のRedis値です。Redis文字列はバイナリを正しく扱うことができ、単純な文字列から、JPEG画像など、あらゆる種類のデータを含めることができます。文字列値の最大は512MBです。
単純なサンプル
SETコマンドとGETコマンドを使用することで文字列の値を格納、取得することができます。
redis-cliを使用して簡単なキーと値を格納するサンプルを以下に示します。
使用例localhost:6379> set mykey somevalue OK localhost:6379> get mykey "somevalue" localhost:6379>規定ではSETコマンドではキーがすでに存在している場合は、既存のキーに格納されている値を新しい文字列に置き換えることができます。
すでに存在するキーの場合は失敗させる、または存在するキーの場合のみ更新させる場合にはnxオプション、xxオプションを以下のようにして使用します。使用例すでに存在するキーの場合は失敗させる localhost:6379> set mykey newval nx (nil) すでに存在するキーがある場合のみ成功させる localhost:6379> set mykey newval xx OKGETコマンドではキーが存在しない場合はnilを返します。また、格納されている値が文字列ではない場合、エラーとなります。
文字列に関係するコマンド
文字列に関係するコマンドは以下の通りです。
https://redis.io/commands/#string
コマンド名 概要 APPEND 指定のキーの値に指定の文字列を追記する GET 指定のキーの文字列値を取得する SET キーの文字列値を設定する SETEX 有効期限を秒で指定してキーの文字列値を設定する SETNX キーが存在しない場合にキーの文字列値を設定する PSETEX 有効期限をミリ秒で指定してキーの文字列値を設定する GETSET キーの文字列値を設定し、古い値を取得する MGET 指定された複数のキーの値を取得する MSET 指定された複数のキーと値を設定する MSETNX キーが存在しない複数のキーと値を設定する GETRANGE 指定のキーの値の部分文字列を取得する SETRANGE 指定のキーの値の部分文字列を設定する STRLEN キーの値の文字列の長さを取得する APPENDコマンド
指定のキーの値に指定の文字列を追記します。
使用例127.0.0.1:6379> set mykey hello OK 127.0.0.1:6379> append mykey world (integer) 10 127.0.0.1:6379> get mykey "helloworld"GETSETコマンド
GETSETコマンドを使用することで新しい値を設定して、古い値を取得することが可能です。
使用例localhost:6379> set mykey test OK localhost:6379> getset mykey testnew "test" localhost:6379> get mykey "testnew"MSETとMGETコマンド
複数のキーの値を同時に設定、取得するにはMSET,MGETコマンドを使用します。
使用例localhost:6379> mset a 10 b 30 c 30 OK localhost:6379> mget a b c 1) "10" 2) "30" 3) "30" localhost:6379> get a "10" localhost:6379> get b "30" localhost:6379> get c "30"数値として取り扱うコマンド
rediusの文字列値が整数または浮動小数として変換できる場合は以下のコマンドが使用できます。
コマンド名 概要 INCR キーの整数値を1増やす INCRBY キーの整数値を指定された数増やす DECR キーの整数値を1減らす DECRBY キーの整数値を指定された数減らす INCRBYFLOAT キーのfloat値を指定された数増やす これらのコマンドはアトミックな操作となっており、同じキーに対して複数のクライアントが同時に操作しても競合状態にはなりません。例えばINCRコマンドを使用してクライアントAが「10」を読み取り、同時にクライアントBが「10」を読み込んで、両方とも「11」に設定することはなく、最終値は常に「12」になります。
使用例localhost:6379> set counter 100 OK localhost:6379> incr counter (integer) 101 localhost:6379> incr counter (integer) 102 localhost:6379> incr counter (integer) 103 localhost:6379> set counter 100 OK localhost:6379> incrby counter 5 (integer) 105 localhost:6379> decr counter (integer) 104 localhost:6379> decrby counter 5 (integer) 99リスト
Redisリストは、挿入順で並べ替えられた、文字列のリストです。リストに対して先頭(左)または後方(右)に値を追加できます。
リストの最大長は4294967295です。単純なサンプル
RPUSHコマンドでリストの後方に文字列を追加し、LPUSHコマンドでリストの先頭に文字列を取得します。リストの内容を取得するにはLRANGEコマンドで範囲を指定して取得します。
使用例127.0.0.1:6379> rpush mylist AA (integer) 1 127.0.0.1:6379> rpush mylist BB (integer) 2 127.0.0.1:6379> lpush mylist first (integer) 3 127.0.0.1:6379> lrange mylist 0 -1 1) "first" 2) "AA" 3) "BB"リストに関係するコマンド
https://redis.io/commands#list
コマンド名 概要 BLPOP リストの最初の要素を削除して取得するまたは、要素が使用可能になるまでブロックする BRPOP リストの最後の要素を削除して取得するまたは、要素が使用可能になるまでブロックする BRPOPLPUSH リストから要素をポップして、それを別のリストにプッシュして返す。またはいずれかが利用可能になるまでブロックする LINDEX インデックスでリストから要素を取得する LINSERT リスト内の別の要素の前後に要素を挿入する LLEN リストの長さを取得する LPOP リストの最初の要素を削除して取得する LPOS リストの一致する要素のインデックスを取得する LPUSH リストに1つまたは複数の要素をリストの先頭に追加する LPUSHX リストが存在する場合のみ、リストの先頭に要素を追加する LRANGE リストから指定の範囲の要素を取得する LREM リストから要素を削除する LSET リスト内の要素の値をインデックスで指定する LTRIM リストを指定された範囲でトリムします。(指定した範囲外の要素を削除する) RPOP リストの最後の要素を削除して取得する RPOPLPUSH リストの最後の要素を削除して別のリストの前に追加して返す RPUSH リストの末尾に1つまたは複数の要素を追加する RPUSHX リストが存在する場合にのみ、リストに要素を追加する セット
文字列の順序付けられていないコレクションです。
コレクション内のメンバーは同じメンバーを許可しないという性質を持っています。
セット内のメンバーの数は最大4294967295になります。簡単なサンプル
セットを使用するにはSADDコマンドで新しい要素を追加して、SMEMBERSコマンドでセットの要素を取得します。
127.0.0.1:6379> sadd myset 3 1 2 3 (integer) 3 127.0.0.1:6379> smembers myset 1) "1" 2) "2" 3) "3"この例では重複する要素が登録されていないことをが確認できます。
Redisのセットでは要素の順番については保証がないため、SMEMBERSコマンドは呼び出しのたびに任意の順番で返却されます。セット用コマンド
コマンド名 概要 SADD セットにメンバーを追加する SCARD セットのメンバー数を取得する SDIFF 複数のセットの差を取得する SDIFFSTORE 複数のセットの差を取得して結果セットをキーに格納する SINTER 複数のセットを交差させる SINTERSTORE 複数のセットを交差させ、その結果セットをキーに格納する SISMEMBER 指定された値がセットのメンバーであるか判定する SMEMBERS セット内の全てのメンバーを取得する SMOVE メンバーをあるセットから別のセットに移動する SPOP セットから1つまたは複数のメンバーをランダムに削除して返す SRANDMEMBER セットから1つまたは複数のメンバーをランダムに取得する SREM セットから1つ以上のメンバーを削除する SUNION 複数のセットを追加する SUIONSCTORE 複数のセットを追加して、結果のセットをキーに格納する SDIFFとSINTERコマンドのサンプル
SINTERコマンドは最初に指定したセットと後に指定したセットと同じメンバーを取得します。
SDIFFコマンドでは最初に指定したセットから、共有するメンバーを除いたメンバーを返します。127.0.0.1:6379> sadd key1 a b c (integer) 3 127.0.0.1:6379> sadd key2 c d e (integer) 3 127.0.0.1:6379> sdiff key1 key2 1) "a" 2) "b" 127.0.0.1:6379> SINTER key1 key2 1) "c"ソート済セット
Redisセットと同様に、文字列の繰り返しのないコレクションです。違いは、並べ替えられたセットのすべてのメンバーがスコアに関連付けられていることです。これは、並べ替えられたセットを最小から最大のスコアまで順番に並べるために使用されます。メンバーは一意ですが、スコアは繰り返される場合があります。
簡単なサンプル
ZADDコマンドを使用してスコアと値を指定してソート済セットに追加します。
ソート済セットの内容はZRANGEコマンドを使用してスコアの小さい順で取得することができます。この際、withscoresオプションを使用するとスコアも同時に取得することができます。
スコアの大きいの順番で取得するにはZREVRANGEコマンドを取得できます。127.0.0.1:6379> zadd hackers 1940 "Alan Kay" (integer) 1 127.0.0.1:6379> zadd hackers 1957 "Sophie Wilson" (integer) 1 127.0.0.1:6379> zadd hackers 1953 "Richard Stallman" (integer) 1 127.0.0.1:6379> zadd hackers 1949 "Anita Borg" (integer) 1 127.0.0.1:6379> zadd hackers 1965 "Yukihiro Matsumoto" (integer) 1 127.0.0.1:6379> zadd hackers 1914 "Hedy Lamarr" (integer) 1 127.0.0.1:6379> zadd hackers 1916 "Claude Shannon" (integer) 1 127.0.0.1:6379> zadd hackers 1969 "Linus Torvalds" (integer) 1 127.0.0.1:6379> zadd hackers 1912 "Alan Turing" (integer) 1 127.0.0.1:6379> zrange hackers 0 -1 1) "Alan Turing" 2) "Hedy Lamarr" 3) "Claude Shannon" 4) "Alan Kay" 5) "Anita Borg" 6) "Richard Stallman" 7) "Sophie Wilson" 8) "Yukihiro Matsumoto" 9) "Linus Torvalds" 127.0.0.1:6379> zrange hackers 0 -1 withscores 1) "Alan Turing" 2) "1912" 3) "Hedy Lamarr" 4) "1914" 5) "Claude Shannon" 6) "1916" 7) "Alan Kay" 8) "1940" 9) "Anita Borg" 10) "1949" 11) "Richard Stallman" 12) "1953" 13) "Sophie Wilson" 14) "1957" 15) "Yukihiro Matsumoto" 16) "1965" 17) "Linus Torvalds" 18) "1969" 127.0.0.1:6379> zrevrange hackers 0 -1 1) "Linus Torvalds" 2) "Yukihiro Matsumoto" 3) "Sophie Wilson" 4) "Richard Stallman" 5) "Anita Borg" 6) "Alan Kay" 7) "Claude Shannon" 8) "Hedy Lamarr" 9) "Alan Turing"ソート済セットで使用するコマンド
コマンド名 概要 BZPOPMIN 1つ以上のソート済セットからスコアが最も低いメンバーを削除して取得するまたは、使用可能になるまでブロックする BZPOPMAX 1つ以上のソート済セットからスコアが最も高いメンバーを削除して取得するまたは、使用可能になるまでブロックする ZADD ソート済セットに1つ以上のメンバーを追加するか、すでに存在する場合はそのスコアを更新する ZCARD ソート済セットのメンバー数を取得する ZCOUNT 指定されたスコアの範囲でソート済セットのメンバー数を取得する ZINCRBY ソート済のメンバーのスコアをインクリメントする ZINTERSTORE 複数のソート済セットを交差させ、結果のソート済セットに新しい値を格納する ZLEXCOUNT 与えられた辞書識範囲内でソート済メンバー数を取得する ZPOPMAX ソート済セットで最高スコアを持つメンバーを削除して取得する ZPOPMIN ソート済セットで最低スコアを持つメンバーを削除して取得する ZRANGE インデックスでソート済セットを取得する ZRANGEBYLEX 辞書式の範囲でソート済セットのメンバーを取得する ZREVRANGEBYLEX 辞書式の範囲でソート済セットのメンバーを上位から下位の文字列の順に取得する ZRANGEBYSCORE スコアの範囲でソート済のセットのメンバーを取得する ZRANK ソート済セットのインデックスを決定する ZREM ソート済セットから1つ以上のメンバーを削除する ZREMRANGEBYLEX 指定された辞書式範囲でソート済セットの全てのメンバーを削除する ZREMRANGEBYRANK 指定されたインデックス内のソート済セットの全てのメンバーを削除する ZREMRANGEBYSCORE 指定されたスコア内のソート済セットの全てのメンバーを削除する ZREVRANGE ソート済セットをスコアの高い順に取得する ZREVRANGEBYSCORE スコアの範囲内でソート済セットをスコアの高い順に取得する ZREVRANK ソート済セットのインデックスを決定する。スコアは高い順に並べられる ZSCORE ソート済セット中のメンバーに関連づけられたスコアを取得する ZUNIONSTORE 複数のソート済セットを追加して結果のソート済セットに新しいキーを追加する ZSCAN ソートされたセット要素と関連するスコアを段階的に反復する 辞書式スコア
ZRANGEBYLEXコマンドではソート済セットの要素が全て同じスコアで挿入されたと仮定して、範囲を辞書式で取得することができます。
辞書式の範囲の開始と終了を指定する際、「[」または「(」で始まる必要があります。
「[」の場合、後続の文字列を含んだ値を認めますが、「(」の場合は認めません。
また、「-」を指定すること負の方向で無限である文字列を現し、「+」を指定することで正に無限の文字列を現します。すなわち「ZRANGEBYLEX myzset - +」を指定した場合、全ての要素が同じスコアだとして、全てのメンバーを返します。127.0.0.1:6379> ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f 0 g (integer) 0 127.0.0.1:6379> ZRANGEBYLEX myzset - + 1) "a" 2) "b" 3) "c" 4) "d" 5) "e" 6) "f" 7) "g" 127.0.0.1:6379> ZRANGEBYLEX myzset - [f 1) "a" 2) "b" 3) "c" 4) "d" 5) "e" 6) "f" 127.0.0.1:6379> ZRANGEBYLEX myzset - (f 1) "a" 2) "b" 3) "c" 4) "d" 5) "e" 127.0.0.1:6379> ZRANGEBYLEX myzset [c (f 1) "c" 2) "d" 3) "e" 127.0.0.1:6379> ZRANGEBYLEX myzset (c (f 1) "d" 2) "e"ハッシュ
ハッシュは、フィールドと値のペアを使用してオブジェクトを表すことができます。
簡単な例
HSETコマンドを使用してキーに紐づくハッシュ値にフィールドを設定します。
HGETALLコマンドを使用して指定のキーのハッシュの全てのフィールドとその値を取得します。
HGETコマンドを使用することでキーとハッシュのフィールドを指定して、その文字列値を取得します。127.0.0.1:6379> hset user:1000 username "Joe" (integer) 1 127.0.0.1:6379> hset user:1000 birthyear 1945 (integer) 1 127.0.0.1:6379> hset user:1000 verified 1 (integer) 1 127.0.0.1:6379> hgetall user:1000 1) "username" 2) "Joe" 3) "birthyear" 4) "1945" 5) "verified" 6) "1" 127.0.0.1:6379> hget user:1000 username "Joe" 127.0.0.1:6379> hget user:1000 birthyear "1945" 127.0.0.1:6379> hget user:1000 verified "1"ハッシュに関係するコマンド
コマンド名 概要 HDEL 1つい上のハッシュフィールドを削除する HEXISTS ハッシュフィールドが存在するか確認する HGET ハッシュフィールドの値を取得する HGETALL ハッシュの全てのフィールドと値を取得する HINCRBY ハッシュフィールドの整数値を指定された数だけインクリメントする HINCRBYFLOAT ハッシュフィールドのfloat値を指定された数だけインクリメントする HKEYS ハッシュ内の全てのフィールドを取得する HLEN ハッシュのフィールド数を取得する HMGET 指定された全てのハッシュフィールドの値を取得する HMSET 複数のハッシュフィールドを複数の値に設定する HSET ハッシュフィールドの文字列値を設定する HSETNX フィールドが存在しない場合のみ、ハッシュフィールドの値を設定する。 HSTRLEN ハッシュフィールドの値の長さを取得する HVALS ハッシュの全ての値を取得する HSCAN ハッシュフィールドと関連する値を段階的に反復する ビットマップ
ビット操作を行うコマンド
redisの文字列値はバイナリデータを取り扱うことができます。いくつかのコマンドはそのバイナリのビットを操作することが可能です。
コマンド名 概要 BITCOUNT 文字列中の1に設定されたビット数を数える BITFIELD 文字列に対して任意のビットフィールド整数演算を行う BITOP 文字列間でビット演算を行う BITPOS 文字列の最初のビットセットまたはビットクリアを見つける GETBIT キーの文字列における指定のオフセットでのビットを取得する SETBIT キーの文字列における指定のオフセットでのビットを設定する Redisキーの有効期限
Redisのキーには有効期限を設定することができます。特定の時間が経過するとキーはDELコマンドを実行した時のように自動的に破棄されます。
コマンド名 概要 EXPIRE キーの存在時間を秒で設定する EXPIREAT キーの有効期限をUNIXタイムスタンプとして設定する PERSIST キーから有効期限を削除する PEXPIRE キーの有効期間をms単位で設定する PEXPIREAT キーの有効期限をmsで指定されたUNIXタイムスタンプとして設定する TTL キーの生存時間を秒単位で取得する PTTL キーの存在時間をms単位で取得する 単純なサンプル
有効期限の設定には以下のようにEXPIREコマンドを使用します。
localhost:6379> set mykey test OK localhost:6379> expire mykey 5 (integer) 1 5秒以内・・・ localhost:6379> get mykey "test" 5秒経過・・・ localhost:6379> get mykey (nil)また同じことはSETコマンドのexオプションで指定することもできます。
localhost:6379> set mykey sample ex 5 OK localhost:6379> get mykey "sample" localhost:6379> get mykey (nil)有効期限の残り秒数またはミリ秒を取得するにはTTLコマンドまたはPTTLコマンドを使用します。
localhost:6379> set mykey sample ex 10 OK localhost:6379> pttl mykey (integer) 7982 localhost:6379> ttl mykey (integer) 4Redis-cli
Redis-cliはRedisにコマンドを送信し、サーバーから送信された応答をターミナルから直接読み取ることができるシンプルなプログラムであるRedisコマンドラインインターフェイスです。
https://redis.io/topics/rediscli
redis-cli 6.0.8ではコマンドラインの引数として以下を設定できます。
引数 説明 -h サーバーのホスト名 (default: 127.0.0.1). -p サーバーのポート (default: 6379). -s サーバーのソケット(hostnameとportを上書きする) -a サーバーに接続するときに使用するパスワード。 REDISCLI_AUTH環境変数を使用して、このパスワードをより安全に渡すこともできる。(両方が使用されている場合、この引数が優先される) --user ACLスタイル'AUTH username pass'を送信するために使用される.-aが必要. --pass 新しい--userオプションと一貫性を保つために使用される-aのエイリアス --askpass STDINからマスク付きのパスワードを入力するようユーザーに強制する。もしこの引数が使われた場合, '-a' と REDISCLI_AUTH環境変数は無視される -u サーバーURI. -r 特定のコマンドをN回繰り返す -i -rオプションを使用した場合にコマンドごとに 秒待機する。次のように1秒未満の時間を指定することが可能。例: 0.1. -n データベースナンバー -3 RESP3 protocol modeでセッションを開始する -x 最後の引数をSTDINから読み込む -d Multi-bulk delimiter in for raw formatting (default: \n). -c クラスターモードを有効にする(follow -ASK and -MOVED redirections). --tls 安全なTLS接続を確立します。 --sni TLSサーバー名を表示する --cacert 検証するCA証明書ファイル。 --cacertdir 信頼できるCA証明書が保存されているディレクトリ。cacertとcacertdirのどちらも指定されていない場合、デフォルトのシステム全体の信頼されたルート証明書の構成が適用される。 --cert 認証に使用するクライアント証明書 --key 認証に使用する秘密鍵ファイル --raw 返信に未加工のフォーマットを使用します(STDOUTがttyでない場合のデフォルト)。 --no-raw STDOUTがttyでない場合でもフォーマットされた出力を強制します。 --csv CSV形式で出力する --stat server: mem, clientsなどの統計情報を出力し続ける --latency Enter a special mode continuously sampling latency.If you use this mode in an interactive session it runs forever displaying real-time stats. Otherwise if --raw or --csv is specified, or if you redirect the output to a non TTY, it samples the latency for 1 second (you can use -i to change the interval), then produces a single output and exits. --latency-history --latencyと似ていますが、追跡の遅延は時間とともに変化する。デフォルトの時間間隔は15秒。 -iを使用して変更します。 --latency-dist Shows latency as a spectrum, requires xterm 256 colors.Default time interval is 1 sec. Change it using -i. --lru-test Simulate a cache workload with an 80-20 distribution. --replica マスターから受信したコマンドを示すレプリカをシミュレートする --rdb RDBダンプをリモートサーバーからローカルファイルに転送する。 --pipe raw Redisプロトコルをstdinからサーバーに転送します。 --pipe-timeout --pipeモードでは、すべてのデータの送信後、秒以内に応答が受信されない場合はエラーで中止する。デフォルトのタイムアウト:30。永久に待機するには0を使用する。 --bigkeys 多くの要素(複雑さ)を持つキーを探すRedisキーのサンプル。 --memkeys 多くのメモリを消費するキーを探すRedisキーのサンプル。 --memkeys-samples 大量のメモリを消費するキーを探すRedisキーのサンプル。サンプリングするキー要素の数を定義する --hotkeys ホットキーを探すサンプルRedisキー。maxmemory-policyが* lfuの場合にのみ機能します。 --scan SCANコマンドを使用した全てのキーのリスト. --pattern --scan, --bigkeys or --hotkeys オプションを使用した場合のキーのパターン (default: *). --intrinsic-latency 固有のシステム遅延を測定するテストを実行する。テストは指定された秒数実行される --eval でLuaスクリプトを使用してEVALコマンドを送信する --ldb --evalと共に使用すると、Redis Luaデバッガーが有効になる --ldb-sync-mode --ldbと同様ですが、同期Luaデバッガーを使用します。このモードでは、サーバーはブロックされ、スクリプトの変更はサーバーのメモリからロールバックされない --cluster [args...] [opts...] Cluster Managerコマンドと引数 --verbose Verboseモード. --no-auth-warning Don't show warning message when using password on command line interface. --help ヘルプの出力 --version バージョンの出力 実行例
コマンドを指定してredis-cliを実行する例
root@8f9888c8bb76:/data# redis-cli set mykey test OK root@8f9888c8bb76:/data# redis-cli get mykey "test"コマンドを複数回繰り返す例
root@8f9888c8bb76:/data# redis-cli -r 100 lpush mylist xコマンドのモニターを行う例
monitorコマンドを実行することでRedisの操作を監視できます。root@8f9888c8bb76:/data# redis-cli monitor OK 1600950437.950595 [0 172.17.0.1:48396] "info" 1600950437.955578 [0 172.17.0.1:48396] "set" "testkey2" "test" 1600950437.961197 [0 172.17.0.1:48396] "get" "testkey2"node.jsでの使用例
Node Redis
Node Redisを使用することでnode.jsからredisの操作が行えます。
https://github.com/NodeRedis/node-redisインストール方法
npm install --save redisサンプルコード
以下のサンプルは文字列値を追加して、それを取得するサンプルです。また、redisの操作についてモニタリングをしています。test1.jsconst redis = require('redis') const client = redis.createClient(6379, 'localhost') client.monitor(function(err, res) { console.log('client.monitor', err, res); }); client.on('monitor', function(time, args, rawReply) { console.log('on monitor', time, args, rawReply); }); client.set('testkey2', "test", (err, res)=> { console.log(err, res) client.get('testkey2', (error, result)=> { console.log(error, result); client.end(true); }) })IOREDIS
ioredisはredisの機能をフルサポートしており、 Cluster, Sentinel, Streams, Pipeliningなどが使用できます。
またNode RedisではサポートしていないPromisでの記述をサポートしています。https://github.com/luin/ioredis
2020年9月時点のIOREDISとNode RedisのNPMでのトレンドの比較は以下の通りです。
https://www.npmtrends.com/ioredis-vs-redisインストール方法
npm install --save ioredisサンプルコード
test2.jsconst Redis = require("ioredis"); const redis = new Redis(); let monitor = null; redis.monitor((err, result)=> { monitor = result monitor.on('monitor', (time, args)=>{ console.log('on monitor', time, args) }) }) redis.pipeline() .set('mykey', 'hogehoge') .get('mykey', (err, result)=>{ console.log('get:', err, result); }) .exec((err, result)=> { console.log('exec:', err, result) monitor.disconnect() redis.disconnect() })redisを使用したsocket.ioのスケールアウトの例
Socket.ioサーバをスケールアウトする(Redisを使った複数プロセス間でのブロードキャスト)
https://qiita.com/takehilo/items/8c773d18ec6cfccd6679下記のモジュールを使用することでredisを使用したsocket.ioのスケールアウトが可能です。
- socket.io-redis
- socket.io-adapter
- sticky-session以下のコードは複数プロセスで動作しているsocket.ioを使用したチャットプログラムのサンプルになります。このコードはSocket.ioサーバをスケールアウトする(Redisを使った複数プロセス間でのブロードキャスト)を元に作成しています。
package.json{ "name": "socketio", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "cluster": "^0.7.7", "express": "^4.17.1", "socket.io": "^2.3.0", "socket.io-redis": "^5.4.0", "sticky-session": "^1.1.2" } }server.jsconst cluster = require('cluster'); const io = require('socket.io')(); const sticky = require('sticky-session'); const http = require('http'); const redis = require('socket.io-redis'); const express = require("express"); const app = express(); app.use(express.static(__dirname + "/public")); const server = http.createServer(app); io.adapter(redis({host: '127.0.0.1', port: 6379})); io.attach(server); isWorker = sticky.listen(server, 3000); if (isWorker) { io.on('connection', (socket) => { console.log(`worker: ${cluster.worker.id}, connected, id: ${socket.id}`); socket.on('message', (user, message) => { data = `${message} from ${user}`; console.log(data); socket.broadcast.emit('message', user, message); }); socket.on('disconnect', () => { console.log(`disconnected, id: ${socket.id}`); }); }); }public/index.html<!DOCTYPE html> <html> <head> <title>Sample</title> <meta charset="UTF-8" /> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <input v-model:value="user"></input> <input v-model:value="message"></input> <button v-on:click="send">送信</button> </div> <script src="/socket.io/socket.io.js"></script> <script src="/app.js"></script> </body> </html>app.jsconst app = new Vue({ el: '#app', data: { socket : {}, user: 'hoge', message : 'message' }, created : function() { console.log(window.location.origin); this.socket = io.connect(window.location.origin); console.log(this.socket); this.socket.on("message", (user, message) => { console.log(user, message); }) }, methods: { send: function() { console.log(this.socket); console.log(this.user, this.message); this.socket.emit("message", this.user, this.message); } } })
- 投稿日:2020-09-24T20:26:37+09:00
Amazon Product Advertising API 5.0(PA-API v5)をexpressでREST API化する
概要
Amazon Product Advertising API 5.0 というAmazonが提供している商品情報検索APIがあります。
SDKが公式から提供されているのですが、サンプルコードのままだとコード内でASINやキーワードを指定するためAPIを単発でしかコールできないため、expressでREST APIとして動作するようにしました。リポジトリ:https://github.com/sakatech-jp/paapi5-express-sample
【参考】
・Product Advertising API 5.0 Documentation
・Product Advertising API 5.0 SDK for NodeJS前提条件
- Amazonアソシエイト・プログラムに承認されたアカウントを所持している
- 認証キーを発行している
- Node.jsが実行できる
使用方法
実行環境
$ sw_vers ProductName: Mac OS X ProductVersion: 10.15.6 BuildVersion: 19G2021 $ node -v v14.1.0 $ docker -v Docker version 19.03.12, build 48a66213feセットアップ
クローンまたはフォークして依存パッケージをインストールします。
$ git clone https://github.com/sakatech-jp/paapi5-express-sample.git $ cd paapi5-express-sample $ npm installconfig.js内に認証キーとアソシエイトタグを設定します。
module.exports = { ACCESS_KEY: '<YOUR ACCESS KEY>', // アクセスキーをセット SECRET_KEY: '<YOUR SECRET KEY>', // シークレットキーをセット API_HOST: 'webservices.amazon.co.jp', REGION: 'us-west-2', PARTNER_TAG: '<YOUR PARTNER TAG>', // アソシエイトタグをセット PARTNER_TYPE: 'Associates' }app.jsを実行することでAPIを立ち上げることができます。
$ node app.js Listening to PORT: 3000
エンドポイント
以下の4つのエンドポイントがあります。
/searchItems
検索条件から商品データを取得することができます。
/getItems
指定したASINを持つ商品の情報を取得することができます。
/getVariations
指定したASINを持つ商品のバリエーション(サイズや色など)を取得することができます。
/getBrowseNodes
指定した商品カテゴリの情報を取得することができます。
リクエストとレスポンスのサンプル
各エンドポイントへのリクエストのサンプルとそれに対するレスポンスです。
/searchItems
「Node.js」でキーワード検索をしてみます。
$ curl -X GET localhost:3000/searchItems \ -d '{ "Keywords": "Node.js", "Resources": ["Images.Primary.Medium", "ItemInfo.Title", "Offers.Listings.Price"], "ItemCount": 1 }'「Node.js超入門[第3版]」という書籍の情報が返ってきます。
{ "SearchResult": { "TotalResultCount": 146, "SearchURL": "https://www.amazon.co.jp/s?k=Node.js&rh=p_n_availability%3A-1&tag=***&linkCode=osi", "Items": [ { "ASIN": "B08HRMTXHB", "DetailPageURL": "https://www.amazon.co.jp/dp/B08HRMTXHB?tag=***&linkCode=osi&th=1&psc=1", "Images": { "Primary": { "Medium": { "URL": "https://m.media-amazon.com/images/I/51SoAyWCBdL._SL160_.jpg", "Height": 160, "Width": 124 } } }, "ItemInfo": { "Title": { "DisplayValue": "Node.js超入門[第3版]", "Label": "Title", "Locale": "ja_JP" } }, "Offers": { "Listings": [ { "Id": "rnBB%2BZKboyhEDyET8hzwlpvGBT%2FHZ%2BfRAoEtE4FR6i7%2Bv%2B5YeQ1ap7PjpdIUPxEugDBSHdeZavd6BbESDvgw6Q6zLIAPCpYKOoboMSZgkd6bu1zutQ5byVmg55svmhuJlopqxFxF3WQs4aCICTPDPUWEokDz1%2Fj%2FJgC3VYcQoodSEWsCS1Zu0A%3D%3D", "Price": { "Amount": 3168, "Currency": "JPY", "DisplayAmount": "¥3,168" }, "ViolatesMAP": false } ] } } ] } }/getItems
「B08HRMTXHB」というASINを持つ商品の情報を取得してみます。
$ curl -X GET localhost:3000/getItems \ -d '{ "ItemIds": ["B08HRMTXHB"], "Resources": ["Images.Primary.Medium", "ItemInfo.Title", "Offers.Listings.Price"] }'Node.js超入門[第3版]」という書籍の情報が返ってきます。
{ "ItemsResult": { "Items": [ { "ASIN": "B08HRMTXHB", "DetailPageURL": "https://www.amazon.co.jp/dp/B08HRMTXHB?tag=***&linkCode=ogi&th=1&psc=1", "Images": { "Primary": { "Medium": { "URL": "https://m.media-amazon.com/images/I/51SoAyWCBdL._SL160_.jpg", "Height": 160, "Width": 124 } } }, "ItemInfo": { "Title": { "DisplayValue": "Node.js超入門[第3版]", "Label": "Title", "Locale": "ja_JP" } }, "Offers": { "Listings": [ { "Id": "2QzP8U6GS%2BOE3G8ybPwm1FbqJ8V%2Fh6jc24%2Bmw%2FeCFpItFscuzkMg8wE5rhC%2BVJ4bttattXONPnhLtfj%2BI2UQiM3rvFpB7SIcX3YozDc9Gb1UjeyINE%2Btn0Vl%2BdI8hjNwvDExYbcO5QCSnqCcxs%2BwuKUam5dl5tXoc6syYJ3Blv0xxDt6RyoiaA%3D%3D", "Price": { "Amount": 3168, "Currency": "JPY", "DisplayAmount": "¥3,168" }, "ViolatesMAP": false } ] } } ] } }/getVariations
いろはすのペットボトルのASINである「B0026IAWMU」で情報を取得してみます。
$ curl -X GET localhost:3000/getVariations \ -d '{ "ASIN" : "B0026IAWMU" }'24本と48本の2種類が返ってきます。
{ "VariationsResult": { "Items": [ { "ASIN": "B0026IAWMU", "DetailPageURL": "https://www.amazon.co.jp/dp/B0026IAWMU?tag=***&linkCode=ogv&th=1&psc=1", "ItemInfo": { "Title": { "DisplayValue": "コカ・コーラ い・ろ・は・す 天然水 555mlPET×24本", "Label": "Title", "Locale": "ja_JP" } }, "VariationAttributes": [ { "Name": "size_name", "Value": "1) 555ml×24本" } ] }, { "ASIN": "B007B9T4UK", "DetailPageURL": "https://www.amazon.co.jp/dp/B007B9T4UK?tag=***&linkCode=ogv&th=1&psc=1", "ItemInfo": { "Title": { "DisplayValue": "I LOHAS(い・ろ・は・す) いろはす 555ml×24本×2ケース", "Label": "Title", "Locale": "ja_JP" } }, "VariationAttributes": [ { "Name": "size_name", "Value": "555mlx48本" } ] } ], "VariationSummary": { "PageCount": 1, "VariationCount": 2 } } }/getBrowseNodes
商品カテゴリID「2275256051」の情報を取得してみます。
$ curl -X GET localhost:3000/getBrowseNodes \ -d '{ "BrowseNodeIds": ["2275256051"] }'「Kindle本」というカテゴリ情報が返ってきます。
{ "BrowseNodesResult": { "BrowseNodes": [ { "ContextFreeName": "Kindle本", "DisplayName": "Kindle本", "Id": "2275256051", "IsRoot": false } ] } }指定できるパラメータについて
上記のサンプルで指定しているもの以外にもパラメータは多く定義されています。
詳しくは公式ドキュメントをご覧ください。searchItems
https://webservices.amazon.com/paapi5/documentation/search-items.html
getItems
https://webservices.amazon.com/paapi5/documentation/get-items.html
getVariations
https://webservices.amazon.com/paapi5/documentation/get-variations.html
getBrowseNodes
https://webservices.amazon.com/paapi5/documentation/getbrowsenodes.html
- 投稿日:2020-09-24T18:49:00+09:00
【NestJS】ヘルスチェック(v7版)
【NestJS】ヘルスチェック(v6版) を NestJS 7 へのバージョンアップに伴い、変更した内容になります。
環境
NestJS 6 時代は、
godaddy/terminus
が必要でしたが、7 から依存が排除されています。インストール
yarn add @nestjs/terminus --no-optional // or npm install --save @nestjs/terminus --no-optional実装
- ほとんど、↓のページに書いている通りです。
const dbConfig = config.get('database')
とそれに関連する箇所は、各環境に応じて変更してください。src/health/health.module.ts
に置いていますが、場所は任意です。src/health/health.module.tsimport config from '@config' import { Controller, Get, Module } from '@nestjs/common' import { HealthCheck, HealthCheckResult, HealthCheckService, HealthIndicatorResult, TerminusModule, TypeOrmHealthIndicator, } from '@nestjs/terminus' import { TypeOrmModule } from '@nestjs/typeorm' @Controller('healthz') export class HealthController { constructor( private readonly health: HealthCheckService, private readonly db: TypeOrmHealthIndicator ) {} @Get() @HealthCheck() healthCheck(): Promise<HealthCheckResult> { return this.health.check([ // Set the timeout for a response to 300ms (): Promise<HealthIndicatorResult> => this.db.pingCheck('database', { timeout: 300 }), ]) } } const dbConfig = config.get('database') @Module({ imports: [TerminusModule, TypeOrmModule.forRoot(dbConfig)], providers: [], controllers: [HealthController], }) export class HealthRestModule {}上記以外の設定
【NestJS】ヘルスチェック(v6版) を参考にしてください。
- 投稿日:2020-09-24T16:21:28+09:00
Twitter広告APIを利用してキャンペーンを作ってみる その5~Twitter広告APIでキャンペーン作成編~
経緯
私が所属している会社では待ラノという小説投稿サイトを運営しています。
待ラノではオススメ小説のランキング上位5作を定期的にTwitterの公式アカウントで紹介しています。
紹介された小説をTwitter広告のキャンペーンを利用してプロモーションをしようってなりました。そもそもTwitter広告のキャンペーンって何?
Twitter広告のキャンペーンですが、簡単いうと1日にかける予算や期間内にかける総予算を指定して、Twitterに広告を出す機能です。
Twitter広告APIでキャンペーンを作る理由
1つのキャンペーンで複数のツイートをプロモーションする場合、1日にかける予算を一気に消化されてしまいます。
しかもどのツイートにどれだけ予算が消化されているかがわかりません。
そのため、1つのツイートに1キャンペーンを紐付けることで消化される予算の見える化を行うことになりました。ただ手動でTwitterの広告コンソールから、1ツイートに1キャンペーンを毎回作ることになると結構手間です。
というわけで、Twitter広告APIを利用して動的にキャンペーンを作成することになりました。Twitter広告APIでキャンペーンを作成するためには
以下の手順が必要です。
- Tiwtterアカウントを作成する(省略)
- Tiwtterアカウントにメールアドレスと電話番号を設定する(省略)
- TiwtterAPIの利用申請をする
- TiwtterAPIのAPIキーとトークンを取得する
- TiwtterAPIを利用してツイートをする
- Tiwtter広告APIの利用申請をする
- Tiwtter広告APIでを利用してツイートを使ったキャンペーンを作る ←イマココ
今回は5の【Tiwtter広告APIでを利用してツイートを使ったキャンペーンを作る】について説明していきます。
動作環境
- 使用端末:Mac
- Node.js:v12.18.2
- yarn:v1.22.4
- gem:v3.0.3
使用するライブラリ
仕様
サーバーのURLにアクセスすると【その5】で作成したツイートも利用したキャンペーンを作成する。
1.Twitterの連携アプリを認証する
1-1. Twurlをインストールする
$ sudo gem install twurl1-2. Twurlコマンドで認証URLを取得する
【その2】で取得した「APIキー」と「APIシークレットキー」を使って以下のコマンドを叩きます。
すると、認証URLが出力されます。
さらにPINコードの入力も求められていますが、下記の【1-4】までそのままにしてください。$ twurl authorize --consumer-key [APIキー] --consumer-secret [APIシークレットキー] Go to https://api.twitter.com/oauth/authorize?oauth_consumer_key=xxx&oauth_nonce=yyy&oauth_signature=zzz&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1600840260&oauth_token=fff&oauth_version=1.0 and paste in the supplied PIN1-3. ブラウザで認証URLにアクセスしてPINコードを取得する
【1-2】で取得した認証URLにブラウザでアクセスします。
以下のようにページが開かれるので「連携アプリを認証」ボタンをクリックします。
するとPINコードが表示されます。
1-4. 取得したPINコードをコマンドライン上にペーストして実行する
成功すると「Authorization successful」と表示されます。
$ twurl authorize --consumer-key [APIキー] --consumer-secret [APIシークレットキー] Go to https://api.twitter.com/oauth/authorize?oauth_consumer_key=xxx&oauth_nonce=yyy&oauth_signature=zzz&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1600840260&oauth_token=fff&oauth_version=1.0 and paste in the supplied PIN 111111 ←PINコードを入力してEnterを押す Authorization successful1-5. アクセストークンを再生成する
「開発ポータル」にアクセスして、対象のアプリの「鍵」ボタンをクリックしてください。
次にAccess Token & Secretの「Regenerate」ボタンをクリックします。
確認画面が表示されますので「Yes, regenerate」ボタンをクリックします。
再生成された「Access token」と「Access token secret」をどこかにメモしておきます。
2.Twitter広告APIのバージョンを確認する
Twitter広告APIのバージョンが掲載されているページにアクセスします。
有効期限が細く設定されているので、確認してください。
※2020年9月23日現在ではバージョンは「8」となってます。
3.アプリIDを取得する
「開発ポータル」にアクセスして、対象のアプリの「設定(歯車)」ボタンをクリックしてください。
4.package.jsonを作成する
以下のコマンドでpackage.jsonを作成します。
対話式で質問を聞かれるので基本的に全てEnterで問題ありません。$ yarn init question name (twitter-api-post-tweet): question version (1.0.0): question description: question entry point (index.js): question repository url: question author: question license (MIT): question private:2.ライブラリをインストールする
以下のコマンドでライブラリをインストールする
$ yarn add express twitter-ads3.ソースファイルを作成する
$ touch index.js4.ソースコードを書く
今回使用しているTwitter広告APIに関しては、「キャンペーンの作成 - 詳細な手順」を参考にしております。
※ちなみに今回使用してる「twitter-ads」はasync/awaitに対応していないため、コールバック地獄になってるのであしからず
index.js// Server settings const express = require('express') const server = express() const port = 3000 // Twitter settings const TwitterAdsAPI = require('twitter-ads') // 【その2】で取得したAPIキー const API_KEY = 'AAAAAAAAAA' const API_SECRET_KEY = 'BBBBBBBBBB' // 【1-5】で取得したトークン const ACCESS_TOKEN = 'CCCCCCCCCC' const ACCESS_TOKEN_SECRET = 'DDDDDDDDDD' // 【2】で取得したトークン const VERSION = '8' // 【3】で取得したアプリID const APP_ID = 'XXXXXXX' // 【その3】で取得したツイートID const TWEET_ID = '1309021433616633862' const twitterAdsClient = new TwitterAdsAPI({ consumer_key: API_KEY, consumer_secret: API_SECRET_KEY, access_token: ACCESS_TOKEN, access_token_secret: ACCESS_TOKEN_SECRET, sandbox: false, api_version: VERSION }) // Routes server.get('/', (req, res, next) => { res.send('server is up') }) // http://localhost:3000/campaign にアクセスすると自動でキャンペーンを作成する server.get('/campaign', (req, res, next) => { // 1.アカウントIDを取得します // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/accounts#get-accounts」を確認してください twitterAdsClient.get('accounts', null, (accountError, accountResponse, accountBody) => { if (accountError) { console.log(accountError) res.send(accountError) return } if (accountBody.data.length > 0) { const accountId = accountBody.data[0].id // 2.お支払いIDを取得します // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/funding-instruments#get-accounts-account-id-funding-instruments」を確認してください const fundingInstrumentsUrl = 'accounts/' + accountId + '/funding_instruments' twitterAdsClient.get(fundingInstrumentsUrl, null, (fundingInstrumentError, fundingInstrumentResponse, fundingInstrumentBody) => { if (fundingInstrumentError) { console.log(fundingInstrumentError) res.send(fundingInstrumentError) return } if (fundingInstrumentBody.data.length > 0) { const fundingInstrumentId = fundingInstrumentBody.data[0].id // 3.キャンペーンを作成し、お支払い方法に関連付けます // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/campaigns#post-accounts-account-id-campaigns」を確認してください const createCampaignUrl = 'accounts/' + accountId + '/campaigns' const createCampaignParams = { funding_instrument_id: fundingInstrumentId, // お支払いID name: 'キャンペーン01', // キャンペーン名 start_time: '2020-10-01T00:00:00Z', // キャンペーン開始時間(ISO 8601表記) end_time: '2020-10-07T23:59:59Z', // (ISO 8601表記) total_budget_amount_local_micro: 1000000, // キャンペーンに割り当てる合計予算額(1000000で1円扱い) daily_budget_amount_local_micro: 1000000, // キャンペーンに割り当てる日別予算額(1000000で1円扱い) entity_status: 'PAUSED' // ステータス } twitterAdsClient.post(createCampaignUrl, createCampaignParams, (createCampaignError, createCampaignResponse, createCampaignBody) => { if (createCampaignError) { console.log(createCampaignError) res.send(createCampaignError) return } const campaignId = createCampaignBody.data.id // 4.キャンペーンに関連付けた行項目(広告グループ)を作成します // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/line-items#post-accounts-account-id-line-items」を確認してください const createLineItemUrl = 'accounts/' + accountId + '/line_items' const createLineItemParams = { campaign_id: campaignId, // キャンペーンID, name: '広告グループ01', // 行項目(広告グループ)名 objective: 'WEBSITE_CLICKS', // キャンペーン目的 placements: 'ALL_ON_TWITTER', // 配置場所 product_type: 'PROMOTED_TWEETS', // プロモ商品のタイプ automatically_select_bid: true, // 自動入札最適化フラグ entity_status: 'PAUSED' // ステータス } twitterAdsClient.post(createLineItemUrl, createLineItemParams, (createLineItemError, createLineItemResponse, createLineItemBody) => { if (createLineItemError) { console.log(createLineItemError) res.send(createLineItemError) return } const lineItemId = createLineItemBody.data.id // 5.行項目にツイートを関連付けます // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/promoted-tweets#post-accounts-account-id-promoted-tweets」を確認してください const relatePromotedTweetUrl = 'accounts/' + accountId + '/promoted_tweets' const relatePromotedTweetParams = { line_item_id: lineItemId, // 行項目ID, tweet_ids: TWEET_ID // ツイートID } twitterAdsClient.post(relatePromotedTweetUrl, relatePromotedTweetParams, (relatePromotedTweetError, relatePromotedTweetResponse, relatePromotedTweetBody) => { if (relatePromotedTweetError) { console.log(relatePromotedTweetError) res.send(relatePromotedTweetError) return } /** * 6.行項目に関連付けるターゲティングプロフィールを作成します */ // 6-1.行項目に関連付けるターゲティング(地域)を取得します // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/targeting-options#get-targeting-criteria-locations」を確認してください const getTargetLocationsUrl = 'targeting_criteria/locations?location_type=COUNTRIES&q=Japan' twitterAdsClient.get(getTargetLocationsUrl, null, (getTargetLocationsError, getTargetLocationsResponse, getTargetLocationsBody) => { if (getTargetLocationsError) { console.log(getTargetLocationsError) res.send(getTargetLocationsError) return } if (getTargetLocationsBody.data.length > 0) { const locationTargetValue = getTargetLocationsBody.data[0].targeting_value // 6-2.行項目にターゲティング(地域)を関連付けます // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/targeting-criteria#post-accounts-account-id-targeting-criteria」を確認してください const relateTargetLocationUrl = 'accounts/' + accountId + '/targeting_criteria' const relateTargetLocationParams = { line_item_id: lineItemId, // 行項目ID, operator_type: 'EQ', // 比較演算子(EQ/NE/GTE/LTE) targeting_type: 'LOCATION', // ターゲティング種別 targeting_value: locationTargetValue // ターゲティング値 } twitterAdsClient.post(relateTargetLocationUrl, relateTargetLocationParams, (relateTargetLocationError, relateTargetLocationResponse, relateTargetLocationBody) => { if (relateTargetLocationError) { console.log(relateTargetLocationError) res.send(relateTargetLocationError) return } // 6-3.行項目に関連付けるターゲティング(言語)を取得します // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/targeting-options#get-targeting-criteria-languages」を確認してください const getTargetLanguagesUrl = 'targeting_criteria/languages?q=Japanese' twitterAdsClient.get(getTargetLanguagesUrl, null, (getTargetLanguagesError, getTargetLanguagesResponse, getTargetLanguagesBody) => { if (getTargetLanguagesError) { console.log(getTargetLanguagesError) res.send(getTargetLanguagesError) return } if (getTargetLanguagesBody.data.length > 0) { const languageTargetValue = getTargetLanguagesBody.data[0].targeting_value // 6-4.行項目にターゲティング(言語)を関連付けます // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/targeting-criteria#post-accounts-account-id-targeting-criteria」を確認してください const relateTargetLanguageUrl = 'accounts/' + accountId + '/targeting_criteria' const relateTargetLanguageParams = { line_item_id: lineItemId, // 行項目ID, operator_type: 'EQ', // 比較演算子(EQ/NE/GTE/LTE) targeting_type: 'LANGUAGE', // ターゲティング種別 targeting_value: languageTargetValue // ターゲティング値 } twitterAdsClient.post(relateTargetLanguageUrl, relateTargetLanguageParams, (relateTargetLanguageError, relateTargetLanguageResponse, relateTargetLanguageBody) => { if (relateTargetLanguageError) { console.log(relateTargetLanguageError) res.send(relateTargetLanguageError) return } // 6-5.行項目にターゲティング(年齢)を関連付けます // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/targeting-criteria#post-accounts-account-id-targeting-criteria」を確認してください const relateTargetAgeUrl = 'accounts/' + accountId + '/targeting_criteria' const relateTargetAgeParams = { line_item_id: lineItemId, // 行項目ID, operator_type: 'EQ', // 比較演算子(EQ/NE/GTE/LTE) targeting_type: 'AGE', // ターゲティング種別 targeting_value: 'AGE_20_TO_49' // ターゲティング値(詳細は「https://developer.twitter.com/ja/docs/ads/general/overview/enums#targeting-age」を参照) } twitterAdsClient.post(relateTargetAgeUrl, relateTargetAgeParams, (relateTargetAgeError, relateTargetAgeResponse, relateTargetAgeBody) => { if (relateTargetAgeError) { console.log(relateTargetAgeError) res.send(relateTargetAgeError) return } // 6-6.行項目にターゲティング(キーワード)を関連付けます // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/targeting-criteria#post-accounts-account-id-targeting-criteria」を確認してください const relateTargetKeywordUrl = 'accounts/' + accountId + '/targeting_criteria' const relateTargetKeywordParams = { line_item_id: lineItemId, // 行項目ID, operator_type: 'EQ', // 比較演算子(EQ/NE/GTE/LTE) targeting_type: 'PHRASE_KEYWORD', // ターゲティング種別 targeting_value: 'キーワード' // ターゲティング値 } twitterAdsClient.post(relateTargetKeywordUrl, relateTargetKeywordParams, (relateTargetKeywordError, relateTargetKeywordResponse, relateTargetKeywordBody) => { if (relateTargetKeywordError) { console.log(relateTargetKeywordError) res.send(relateTargetKeywordError) return } // 7.行項目の一時停止を解除します // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/line-items#put-accounts-account-id-line-items-line-item-id」を確認してください const updateLineItemUrl = 'accounts/' + accountId + '/line_items/' + lineItemId const updateLineItemParams = { entity_status: 'ACTIVE' // ステータス } twitterAdsClient.put(updateLineItemUrl, updateLineItemParams, (updateLineItemError, updateLineItemResponse, updateLineItemBody) => { if (updateLineItemError) { console.log(updateLineItemError) res.send(updateLineItemError) return } // 8.最後に、キャンペーンの一時停止を解除します // APIの詳細は「https://developer.twitter.com/en/docs/ads/campaign-management/api-reference/line-items#put-accounts-account-id-line-items-line-item-id」を確認してください const updateCampaignUrl = 'accounts/' + accountId + '/campaigns/' + campaignId const updateCampaignParams = { entity_status: 'ACTIVE' // ステータス } twitterAdsClient.put(updateCampaignUrl, updateCampaignParams, (updateCampaignError, updateCampaignResponse, updateCampaignBody) => { if (updateCampaignError) { console.log(updateCampaignError) res.send(updateCampaignError) return } // TwitterAdsAPIの結果をそのまま返す res.send(updateCampaignBody) }) }) }) }) }) } else { res.send(new Error('Not found targeting languages.')) } }) }) } else { res.send(new Error('Not found targeting locations.')) } }) }) }) }) } else { res.send(new Error('Not found funding instrument.')) } }) } else { res.send(new Error('Not found account.')) } }) }) server.listen(port, function () { console.log('Listening on port ' + port) })5.サーバーを立ち上げる
以下のコマンドを叩いてサーバーを起動させる。
$ node index.jp
6.自動ツイートするURLにアクセスする。
ブラウザで「http://localhost:3000/campaign」にアクセスする。
アクセスするとキャンペーンが作成されます。
差育成されているかを確認する場合はTwitterの広告コンソールをご確認ください。
■「Twitter広告APIを利用してキャンペーンを作ってみる 」シリーズ
- その1~TwitterAPI申請編~
- その2~TiwtterAPIのAPIキーとトークンの取得編~
- その3~TwitterAPIでツイート編~
- その4~Tiwtter広告APIのAPI申請編~
- その5~Twitter広告APIでキャンペーン作成編~ ←イマココ
- 投稿日:2020-09-24T15:33:57+09:00
『このExcelのA列のファイルのB列の文字列をC列の文字列にしてクレメンス』
「ちな、7,000件な!」
さすがに手入力はやってられん。という事で、Node.js で自動化してみました※ 完成品はこちら
テスト環境の下準備
Node.jsはインストール済みで
最低限のjs知識がある方を前提としていますプロジェクトの作成
任意のディレクトリを作成して
npm init
しましょう
入力項目は任意で決めて大丈夫ですbashmkdir excel-replaceer cd excel-replaceer npm initモジュールのインストール
今回使用するモジュールは
fs
とxlsx
の2つですfs : https://www.npmjs.com/package/fs
xlsx : https://www.npmjs.com/package/xlsxbashnpm install fs npm install xlsxテスト用のExcelファイルの作成
読み込んでくる
test.xlsx
ファイルを作成します
今回はプロジェクト直下に/test
というディレクトリを作成し
そこに読み込んでくるExcelファイルと置換するhtmlファイルを設置します今回、Excelのデータ内容は下記の様な感じで作成します
A B C 1 test01.html dummy test01 2 test02.html dummy test02 3 test03.html dummy test03 テスト用のhtmlファイルの作成
今回のテストで置換するhtmlファイルを作成します
『A列』に記載がある通りの名前でhtmlを3つ作成してください
ディレクトリはExcelファイルと同様/test
配下に保存しますhtmlファイルの中身は任意で決めてもらえばよいですが、
今回は『B列』の「dummy」を置換するのでどこかしらにdummyを記述してくださいtest01~3.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>dummy</title> </head> <body> </body> </html>これでテストを行う為の下準備は完了です
モジュール作成
次は実際にExcelから情報を取得して、置換処理を行うまでの解説です
index.js
の作成プロジェクト直下に
index.js
を作成してください
ここに実際の置換処理を書いていきます
node module
の読み込み下準備の段階でインストールを行った
fs
とxlsx
を使用するので
この2つを呼び出して、いつでも使えるように変数に格納しますindex.jsconst xlsx = require('xlsx'); const utils = xlsx.utils; const fs = require('fs');
utils
はxlsx
の機能の一つで、こちらも毎回呼び出す手間を省く為に
変数に格納しています
これでモジュールを使う準備ができましたExcelファイルの読み込み
Excelファイルの操作は
xlsx
モジュールを使用して行いますindex.js// 上部 略 const testX = xlsx.readFile('./test/test.xlsx'); const sheet = testX.Sheets['Sheet1'];
readFile
を使用することによってExcelファイルの読み込みができます
読み込んだExcelの内容をtextX
に格納し、その中のSheet1
の情報を
sheet
変数に格納していますセルの値を取得する為には
sheet['A1']
というように
シート情報からセル名を指定して取得してきますが、
今回は入力されている範囲を取得して、ループを回してデータを取得します範囲の取得
index.js// 上部 略 const range = sheet['!ref']; const rangeN = utils.decode_range(range);次にシートの記載がある範囲を取得します
先ほどシートの情報を格納したsheet
変数から['!ref]
を指定すると
範囲が取得できるので、こちらをrenge
の変数に格納します
今回の場合、range
には『A1:C2』が入っています範囲を取得することはできましたが、今取得してきた範囲は
テキストデータになっている為、A列からC列までループ処理は回せません
これを解決するためにutils
を使用していきます
utils
のdecode_range()
を使用して、先ほどとってきたテキストデータの
範囲を渡してあげる事で、詳細な範囲データを返してくれます
これをrangeN
に格納しています 中身は下記の様になっています{ s: { c: 0, r: 0 }, e: { c: 2, r: 1 } }それぞれ情報内容はこんな感じ
key 情報 s スタートのセル情報 e エンドのセル情報 c 列 (A, B, C ~~) の情報 r 行 (0, 1, 2 ~~) の情報 この情報を利用すれば、ループ分を回して行数分実行したり
列分実行したり、全レコード分実行したりできます今回は『B列』に元データ『C列』に変更後データがある状態を想定して作るので
行数分、置換が実行できれば良いことになりますループ・置換処理
今回は『A列』に置換したいファイル名が記載されており
『B列』に変更前の文字列、『C列』に変更後の文字列が記載されているので
行数分、置換処理を実行すれば良いことになります
for文
で行数分ループするようにしましょうindex.js// 上部 略 for (let r = rangeN.s.r; r <= rangeN.e.r; r++) { let address = utils.encode_cell({c:0, r:r}); let cell = sheet[address]; }for (let r = rangeN.s.r; r <= rangeN.e.r; r++)で
rangeN.s.r
のスタートの行番号から、
rangeN.e.r
のエンドの行番号までループする処理を書きます次にセルの情報を取得してきます
『A列』に置換したいファイル名が記載されているので
列のインデックス番号であるc
をc:0
で固定して編集するファイルを取得しましょうlet address = utils.encode_cell({c:0, r:r}); let cell = sheet[address];
utils
のencode_cell()
を使用することでc
とr
のプロパティで
指定されたセルを、テキストベースに変換してくれます
let address = utils.encode_cell({c:0, r:0});
の場合は、addressに「A1」が入りますこれで、
cell
に『A列』のデータ(ファイル名)が入るように設定できました
指定のファイルを読み込んでみましょう// fs.readFile([読み込むファイルのパス], [読み込む文字コード], [コールバック関数]); fs.readFile('./test/' + cell.v, 'utf-8', (err, data) => { // エラー処理 if (err) { console.log(`【 ${cell.v} 】ファイル読み込みエラー`); throw err; } });ファイルの読み書きには
fs
モジュールを使用します
ファイルの読み込みではreadFile()
を使用します
上記のように、簡単なエラー処理も書いておきましょう
このコールバック関数内で、置換の処理を行っていきます
コールバック関数で渡しているdata
には取得してきた内容が入っています// 変更前の内容を取得 let B_address = utils.encode_cell({c:1, r:r}); let B_cell = sheet[B_address]; // 変更後の内容を取得 let A_address = utils.encode_cell({c:2, r:r}); let A_cell = sheet[A_address]; // 置換 const beforeTxt = data; const afterTxt = beforeTxt.replace(new RegExp(B_cell.v,"g"), A_cell.v);今回は『B列』に変更前の文字列、『C列』に変更後の文字列が
入っていることがわかっているので、それぞれ『A列』の情報を
とってきた時と同様に、c
プロパティを固定して情報を取ってきますあとはおなじみの
replace()
を使用して置換をおこない
afterTxt
に格納しておきます// fs.writeFile([書き込むファイルパス], [書き込む内容], コールバック関数) fs.writeFile('./test/' + cell.v, afterTxt, (err) => { if (err) { console.log(`【 ${cell.v} 】ファイル置換エラー`); throw err; } console.log(`【 ${cell.v} 】success !`); });最後にファイルの上書きを行っていきます
ファイルの書き込みにはwriteFile()
を使用します
こちらにも簡単なエラー処理を書いてあげましょう
index.js
の完成形はこんな感じindex.js// モジュールのインストール const xlsx = require('xlsx'); const utils = xlsx.utils; const fs = require('fs'); // エクセルファイルの読み込み const testX = xlsx.readFile('./test/test.xlsx'); // シートの読み込み const sheet = testX.Sheets['Sheet1']; // セルの範囲の取得 const range = sheet['!ref']; // console.log(range); // セルの範囲を数値化 const rangeN = utils.decode_range(range); // ループ処理 for (let r = rangeN.s.r; r <= rangeN.e.r; r++) { // ファイル名取得 let address = utils.encode_cell({c:0, r:r}); let cell = sheet[address]; // htmlの読み込み fs.readFile('./test/' + cell.v, 'utf-8', (err, data) => { // エラー処理 if (err) { console.log(`【 ${cell.v} 】ファイル読み込みエラー`); throw err; } // 置換処理 // 変更前の内容を取得 let B_address = utils.encode_cell({c:1, r:r}); let B_cell = sheet[B_address]; // 変更後の内容を取得 let A_address = utils.encode_cell({c:2, r:r}); let A_cell = sheet[A_address]; // 置換 const beforeTxt = data; const afterTxt = beforeTxt.replace(new RegExp(B_cell.v,"g"), A_cell.v); // ファイルの上書き fs.writeFile('./test/' + cell.v, afterTxt, (err) => { if (err) { console.log(`【 ${cell.v} 】ファイル置換エラー`); throw err; } console.log(`【 ${cell.v} 】success !`); }); }); }これで処理は完成しました!
処理の実行
最後に下記で実行して置換が行われるか試してみてください
node index.js
- 投稿日:2020-09-24T01:00:45+09:00
WebSocket のサーバー・クライアントをコマンドラインで簡単に実行する(npx と wscat)
はじめに
こちらのツイートをきっかけに、ふと思いついて試した内容の第二弾のメモです。
npxで実行できるMQTTクライアントってないのかなぁ #iotlt
— 菅原のびすけ (@n0bisuke) September 23, 2020上記を見て「MQTT.js で実現できそうかも」という話をコメントして、その際に試した内容を以下の記事に書きました。
●MQTTクライアントをコマンドラインで簡単に実行する(npx と MQTT.js) - Qiita
https://qiita.com/youtoy/items/edeaa9cc316d5f47c0e8そして、このときもう 1つ思いついていたのが「wscat も似たような、便利な使い方ができるのでは?」ということでした。
npx で wscat を実行する
IoTな作品を作ろうとしたときに、デバイス間の通信などで MQTT の他に使っていたものの 1つに WebSocket がありました。そして、その検証時に活用できそうなツールを調べ、見つけて実際に利用していたものの 1つに wscat があります。
wscat を知ったのは、情報を調べていて以下の記事にたどり着いたことがきっかけでした。
●WebSocket の検証では wscat が便利だった - Qiita
https://qiita.com/toshihirock/items/4dfaca29058c9e744529以下のような手順で利用でき、自分はあまり利用してないですが様々なオプションもあるようです。
- インストール:
npm install -g wscat
- サーバーとして実行(ポート番号は 8000 を指定):
wscat -l 8000
- クライアントとして実行:
wscat -c ws://【サーバーのURL】:【ポート番号】
通常の利用方法は上記のとおり、グローバルインストール後にコマンドを実行する形ですが、npx を利用することで、インストールのステップを踏むことなく wscatコマンドでのサーバー・クライアントとしての実行を行うことができました。
- サーバーとして実行:
npx wscat -l 【ポート番号】
- クライアントとして実行:
npx wscat -c ws://【サーバーのURL】:【ポート番号】
Mac のターミナル上で実行すると、以下のようになります。
あとは、サーバー側でもクライアント側でも、どちらかで文字を入力してみると、他方にその入力された文字が表示される、という挙動が確認できます。
おわりに
WebSocket を使った開発を行う際、簡単に環境を準備できて便利に使えそうです!
- 投稿日:2020-09-24T00:05:49+09:00
グローバルインストールせずにMQTTをCLIで試す
JavaScriptやNode.jsを書いてる人はnpmコマンドなどは通常装備だと思うのでnpxでmqttを利用する方法のメモです。
JSerやNoder向けIoTって感じですね。とはいえMQTTをとりあえず試すってときの話です。npmが入ってない人は素直にmosquittoをインストールでも良いかも。
MQTTクライアントライブラリ(MQTT.js)を利用
mqttをcliで利用できるかつnpmにレジストリされてるツール探してました。
Sub のほうなら、MQTT.js で「npx mqtt sub -t 'トピック' -h 'ホスト名'」みたいなの感じでどうでしょう?
— you (@youtoy) September 23, 2020
(意図と異なるかもですが)
Pubのほう、「npx mqtt pub -t 'トピック' -h 'ホスト名' -m 'from MQTT.js'」とかやったら、メッセージを送るたびにインストールがされて微妙でしたがw探しても見つからないなぁと思ってたらmqtt.jsのCLI利用のドキュメントにありました。 @youtoyさんありがとうございます :)
subscribe側
$ npx mqtt sub -h ホスト名 \ -t トピック名 \これで待ち受けてくれます。npxは一回だけインストールして実行してくれるのでグローバルインストール(npm i -g ~~)したく無い時に使えます。
publish側
$ npx mqtt pub -h ホスト名 \ -t トピック名 \ -m '{"content":"Hello!!! from MQTT!"}'PubNubの場合
PubNubで5分でリアルタイムWebこと初め + MQTTでデバイス連携も など最近PubNub触ってるのでこちらもメモ。mosquittoのCLIとオプションが一緒なのでコピペで大丈夫でした。
subscribe側
$ npx mqtt sub -h mqtt.pndsn.com \ -t pub-c-xxxxxxxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxxxxxx/トピック名 \ -i pub-c-xxxxxxxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxxxxxx/クライアントIDpublish側
$ npx mqtt pub -h mqtt.pndsn.com \ -t pub-c-xxxxxxxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxxxxxx/トピック名 \ -i pub-c-xxxxxxxxxxxxxxxxxxxx/sub-c-xxxxxxxxxxxxxxxxxxxx/クライアントID \ -m '{"content":"Hello!!! from MQTT!"}'所感
subscribe側は一度実行したら実行しっぱなしというのが多いと思うので、npxでも良さそうだけど、publish側は確認で何回も実行すると思うのでnpxでの実行だと毎回インストールされてだるいかもしれませんね。
npm i -g mqtt
でグルーバルインストールしてしまってもよいかもしれません。追記: 同じタイミングで記事書いてくれてたという笑
@youtoyさんも記事を書かれていたので、リンクを掲載します。
やばい、うける / グローバルインストールせずにMQTTをCLIで試す https://t.co/DG5KSgqpXD
— 菅原のびすけ (@n0bisuke) September 23, 2020同じタイミングで......わろ