masalibの日記

システム開発、運用と猫の写真ブログです

node.jsで「はてなフォトAPI」を使ってみた

はてなブログで画像を投稿されると
はてなフォト」というサービスに画像が登録されます
そちらをブログで使っています
そちらのAPIをnode.jsで使ってみたいと思う



画像は公開しているので簡単に取得できます

var fs = require('fs');
var client = require('cheerio-httpcli');

// ①ダウンロードマネージャーの設定(全ダウンロードイベントがここで処理される)
// dlimageというフォルダはある前提
client.download
.on('ready', function (stream) {
    var filename_ex = stream.url.href.match(".+/(.+?)([\?#;].*)?$")[1];
    console.log('./dlimage/' + filename_ex);
    stream.pipe(fs.createWriteStream('./dlimage/' + filename_ex ));
    console.log(stream.url.href + 'をダウンロードしました');
})
.on('error', function (err) {
    console.error(err.url + 'をダウンロードできませんでした: ' + err.message);
})
.on('end', function () {
    console.log('ダウンロードが完了しました');
});

// ④並列ダウンロード制限の設定
client.download.parallel = 4;

// ②スクレイピング開始
client.fetch('http://f.hatena.ne.jp/masalib/20170924153055', function (err, $, res, body) {
    // ③class="thumbnail"の画像を全部ダウンロード
    $('img.foto').download();
    console.log('OK!');
});

画像一覧のページや画像アップしたりするには認証がかかるようです
認証には2つあるようで「wsse」と「oauth」です

oauthは設定登録とかしないといけない
自分がやるならoauthでいいけど
それ以外の人がやる場合は手順が複雑になるので候補からはずした
wsseはベーシック認証に近い認証です

詳細は以下参照
qiita.com

実際のデータは以下のような感じ

UsernameToken Username="masalib", PasswordDigest="XX121XX121o+wyv4l5W12ePH1pFYU=", Nonce="be6e7e215effc4c34", Created="2017-09-24T09:22:41.719Z"

セキュリティ的には弱いのでどうかなと思うのですが
通信している内容はたいしたことのないデータなのでいいかなと思いました


node.jsにパッケージがあったのでinstall

npm install wsse -save


リストをダウンロードするプログラムです

var wsse = require('wsse');
//いつものはてなのAPIのパスワードです
var token = wsse({ username: 'masalib', password: 'password' });

console.log("userid:" + token.getUsername());
console.log("password:" + token.getPassword());
console.log("created:" + token.getCreated());
console.log("getNonce:" + token.getNonce());
console.log(token.getNonceBase64());
console.log(token.getPasswordDigest());
console.log(token.getWSSEHeader());
console.log(token.toString());

// スクレイピング用のモジュール
var client = require('cheerio-httpcli');

//client.set('headers', "X-WSSE:" + token.getWSSEHeader()); //base64じゃないと403エラーが起きる
client.headers['X-WSSE'] = token.getWSSEHeader({ nonceBase64: true });

client.fetch('http://f.hatena.ne.jp/atom/feed',  function (err, $, res) {
  // レスポンスヘッダを参照
  console.log(res.headers);
  console.log($.html());
});

結果の一部はいかのとおりです

 <entry>
    <title/>
    <link rel="alternate" type="text/html" href="http://f.hatena.ne.jp/masalib/20140419154014"/>
    <link rel="service.edit" type="application/x.atom+xml" href="http://f.hatena.ne.jp/atom/edit/20140419154014" title=""/>
    <issued>2014-04-19T15:40:14+09:00</issued>
    <author>
      <name>masalib</name>
    </author>
    <id>tag:hatena.ne.jp,2005:fotolife-masalib-20140419154014</id>
    <hatena:imageurl>http://cdn-ak.f.st-hatena.com/images/fotolife/m/masalib/20140419/20140419154014.jpg</hatena:imageurl>
    <hatena:imageurlsmall>http://cdn-ak.f.st-hatena.com/images/fotolife/m/masalib/20140419/20140419154014_m.jpg</hatena:imageurlsmall>
    <hatena:syntax>f:id:masalib:20140419154014j:image</hatena:syntax>
    <content type="text/html" mode="escaped" xml:lang="ja" xml:base="http://f.hatena.ne.jp/masalib/">
      <![CDATA[<a href="http://f.hatena.ne.jp/masalib/20140419154014"><img src="http://cdn-ak.f.st-hatena.com/images/fotolife/m/masalib/20140419/20140419154014.jpg" alt=""></a>]]>
    </content>
  </entry>


一覧ができたので新規投稿をやってみました

var wsse = require('wsse');
var fs = require('fs');
var webclient = require("request");

var token = wsse({ username: 'masalib', password: 'password' });

var  postdata = '<entry xmlns="http://purl.org/atom/ns#"><title>Sample</title><content mode="base64" type="image/jpeg">';
var fs = require('fs');

fs.readFile( './dlimage/icon.jpg', function( err, content ) {
	if( err ) {
		console.error(err);
		}
	else {
		/* Base64変換 */
		var base64_data =  content.toString( 'base64' );
		postdata = postdata + base64_data + '</content></entry>';
		webclient.post({
		  url: "http://f.hatena.ne.jp/atom/post",
		  headers: {
		    "content-type": "application/x.atom+xml",
		    "X-WSSE": token.getWSSEHeader({ nonceBase64: true }),
		  },
		  body: postdata
		}, function (error, response, body){
			console.log('statusCode:'+ response ); 
		});

	}
});


結果は小さい画像の場合はうまくいくけど
大きい画像の場合は失敗しました
現在調査中・・・

f:id:masalib:20170925200334p:plain


たぶんNode.js(JavaScript)のrequstモジュールでは
非同期となるため、コールバック関数なしで
同期ができるsrnc-requestモジュールでやればいけると思う
http://designetwork.hatenablog.com/entry/2016/11/19/sync-request-post-node-js

defaultが非同期なのね・・・

同期処理を作ったけど結果は同じだった
色々な画像を試してみるとこの画像だけだめだった
(CDN上はOKだけどオリジナルの場合はNGだった。意味わからん)
1Mぐらいのファイルでもアップできた

同期処理でのアップは以下のような形

//画像をアップのテスト
var wsse = require('wsse');
var fs = require('fs');
var webclient = require("sync-request");

var token = wsse({ username: 'masalib', password: 'password' });

var  postdata = '<entry xmlns="http://purl.org/atom/ns#"><title>Sample</title><content mode="base64" type="image/jpeg">';
var fs = require('fs');

fs.readFile( './dlimage/IMG_0888.JPG', function( err, content ) {
	if( err ) {
		console.error(err);
		}
	else {
		/* Base64変換 */
		var base64_data =  content.toString( 'base64' );
		postdata = postdata + base64_data + '</content></entry>';
		var res = webclient( 'POST' , 'http://f.hatena.ne.jp/atom/post' , {
		  headers: {
		    "content-type": "application/x.atom+xml",
		    "X-WSSE": token.getWSSEHeader({ nonceBase64: true }),
		  },
		  body: postdata
		});
		console.log(res);
	}
});

非同期の場合はresがとれなかったけど
同期処理ならとれた!!


あとhatenaのブログのSSL化が発表されたのですが
APIのURLとかの仕様変更は早めに発表してほしい
wsseとか使えなくなるのかな
oauthとか知らない人には無理ゲーだな