masalibの日記

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

node.jsで「はてなブログ」の記事から画像の引っこ抜く

node.jsを使って「はてなブログ」の記事から画像を引っこ抜く事ができました

基軸になるXMLは以下で簡単にダウンロードできます
masalib.hatenablog.com

概要

はてな記事のXMLからcontens部分を抽出してその中に記載している画像のみをダウンロードする
単純に全画像を引っこ抜くなら超ー簡単なのですが
いらない画像はダウンロードしたくないので選別しています



ソース

var fs = require('fs'),
    che = require('cheerio');
var client = require('cheerio-httpcli');
const {HATENA_SITE_ID, HATENA_SITE_PW} = require('./config.json');


//markdownの場合
//var xml_data = fs.readFileSync("./tempxml/10328537792363751905.xml", "utf-8");
//htmlの場合
var xml_data = fs.readFileSync("./tempxml/10328749687197681522.xml", "utf-8");

//解説1
$ = che.load(xml_data, { xmlMode: true });
//リンクされているページを取得
 $("link").each(function(idx){
	    if ($(this).attr('rel') == 'edit'){
	      edit_url= ($(this).attr('href'));	//ブログを更新時に使用する
	    } else {
	      alternate_url= ($(this).attr('href'));//画像を引っこ抜く時に使う
	    }
    });

var content_type = $("content").attr('type');
var content = $("content").html();
var image_id = [];
var image_id_replace = [];  //置き換えるようの配列
var image_id_index = [];   //検索用の配列

//解説2
if (content_type == "text/x-hatena-syntax" || content_type == "text/x-markdown" ) {
	//はてな表記とmarkdownの場合
	//[f:id:masalib:20161026224357p:plain]みたいな形になっている。それを引っこ抜く
	var s = content;
	var r = /\[([^\]]*)\]/g;
	while ((m = r.exec(s)) != null) {

	  if ( m[1].indexOf('f:id:') != -1) {
	    image_id.push(m[1]);	//変換前(この時点では変換前も変換後も同じ)
	    image_id_replace.push(m[1]);//変換後(この時点では変換前も変換後も同じ)
	    image_id_index.push(m[1]);	//ダウンロード判定の配列
	  }
	}

} else {

	console.log("htmlの場合");
	$xml_content = che.load(content);
	$xml_content("img").each(function(i, el) {
		//console.log($xml_content(this).toString()  );

		image_id.push($xml_content(this).toString());//変換前(この時点では変換前も変換後も同じ)
		image_id_replace.push($xml_content(this).toString() );//変換後(この時点では変換前も変換後も同じ)
		image_id_index.push($xml_content(this).attr('src'));//ダウンロード判定の配列
	});

}


//解説3
//記事のURLから画像引っこ抜く alternate_url
//実行フォルダの配下にdlimageフォルダはあるものとする
// ①ダウンロードマネージャーの設定(全ダウンロードイベントがここで処理される)
// dlimageというフォルダはある前提
client.download
.on('ready', function (stream) {
    var filename_ex = stream.url.href.match(".+/(.+?)([\?#;].*)?$")[1];
    var filename    = stream.url.href.match(".+/(.+?)\.[a-z]+([\?#;].*)?$")[1];

    var i = 0;
    image_id_index.forEach(function(temp_fileid){
	    if ( temp_fileid.indexOf(filename) != -1) {
		    console.log('./dlimage/' + filename_ex);
		    stream.pipe(fs.createWriteStream('./dlimage/' + filename_ex ));
		    console.log(stream.url.href + 'をダウンロードしました');
		    image_id[i] = filename; 	
	     } else {
		    console.log(stream.url.href + 'をダウンロードしません');
	     }
	     console.log(temp_fileid);
	     i += 1;
    });


})
.on('error', function (err) {
    console.error(err.url + 'をダウンロードできませんでした: ' + err.message);
})
.on('end', function () {
    console.log('ダウンロードが完了しました');
});

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

// ②スクレイピング開始
client.fetch(alternate_url, function (err, $, res, body) {
    // ③imgタグをダウンロード(imageがない時の処理が足りない・・・)
    $('img').download();
    console.log('OK!');
});


解説

解説1

if ($(this).attr('rel') == 'edit'){
	edit_url= ($(this).attr('href'));	//ブログを更新時に使用する
} else {
	alternate_url= ($(this).attr('href'));//画像を引っこ抜く時に使う
}

はてなブログの記事はかならず
記事の更新用URLと表示用のURLがあります
どちらも後続処理で使うので取得します

解説2

if (content_type == "text/x-hatena-syntax" || content_type == "text/x-markdown" ) {

はてなブログは、「markdown」と「はてな表記」と「HTML」の形式があります
markdown」と「はてな表記」のimageの記載方法は、同じっぽい・・・
どちらとも正規表現で抽出する事で対応

var s = content;
var r = /\[([^\]]*)\]/g;
while ((m = r.exec(s)) != null) {

  if ( m[1].indexOf('f:id:') != -1) {


「HTML」は普通のimgタグなので、そちらを配列にいれます
後続の更新処理で使う予定です

	$xml_content = che.load(content);
	$xml_content("img").each(function(i, el) {
		//console.log($xml_content(this).toString()  );
		image_id.push($xml_content(this).toString());//変換前(この時点では変換前も変換後も同じ)
		//例
		//<img class="hatena-fotolife" title="f:id:masalib:20161207001505j:image" src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/masalib/20161207/20161207001505.jpg" alt="f:id:masalib:20161207001505j:image">

cheerioの公式サイトに「toString()」のメソッドが書いておらず、困った
ブログレベルだと、表示されないものもあるらしい
テストしながら判別していきます


解説3

client.download
.on('ready', function (stream) {

ダウンロードはcheerio-httpcliの本家のサンプルから作りました

qiita.com


テストデータが少なくて気が付かなかったけど
はてなフォト以外の画像は引っこ抜く必要がないかも
選択できるようにするべきかも



はてなブログをnode.jsで更新シリーズの進捗

・記事データをXMLとしてダウンロード
 完了

・記事データXMLから画像をダウンロード
 はてな表記:完了 ←★NOW
 HTML:完了 ←★NOW
 MARKDOWN:完了 ←★NOW

・画像をクオリティーを下げずに圧縮 完了
 あとは組み込むだけ

・画像をはてなフォトにアップ
  画像アップ 完了
  アップした内容からXMLを更新:未完

・記事データのSSL
 はてな表記:現在進行中
 HTML:未完
 MARKDOWN:未完
 変更箇所を洗い出し中

・記事データXMLから記事を更新
 はてな表記:完了(組み込むだけ)
 HTML:未完
 MARKDOWN:未完

XMLをもとに検証用ブログにアップする(追加:2017/09/27)
・手順書作成(追加:2017/09/27)