20
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

シュッとHTMLをJSON化できるライブラリを作った

Last updated at Posted at 2017-03-03

まとめ

追記 コマンドラインツール作りました

下記のようにhtmlとCSSSelectorを値にしたオブジェクトを渡すと、
値の部分に抽出結果が出力されるライブラリを作りました。

https://github.com/rike422/kirinuki-core
Demo

readmeから抜き出したもの
const kirinuki = require('kirinuki');
const html = `
<html>
  <head>
    <title>Hero News!</title>
  </head>
  <body>
    <div class="main">
        <h3 class="topic">Amalgam</h3>
        <ul class="news-list">
            <li>
              <span class="content">Batman come back in Gossam City!</span>
              <img class="thumbnail" src="https://exmaple.com/batman.png"></img>
            </li>
            <li>
              <span class="content">Dr. Strange got into a traffic accident.</span>
              <img  class="thumbnail" src="https://exmaple.com/strange.png"></img>
            </li>
        </ul>
    </div>
  </body>
</html>
`;
const schema = {
  topic: {
    content: ".content",
    contents: ".content"
  }
}

kirinuki(schema, html)
// > { topic: { 
// キーが複数形でない場合は、先頭のデータが入る。
// content: 'Batman come back in Gossam City!'
// 複数系の場合は配列で帰ってくる。 
// contents: [
//  'Batman come back in Gossam City!',
//  'Dr. Strange got into a traffic accident.',
// ]
// } }

経緯

jam-apiというアプリケーションがあり、
こちらのパーサをapigateway + aws-lambda上で利用したりしていたのですが、
下記のような抽出に対応していなかったため、自作しました。

抽出したいhtml
<ul>
  <li class="item">
    <span class="topic">topic1</span>
    <img src="https://example.com/imgs/img1.png"/>
  </li>
  <li  class="item">
    <span class="topic">topic2</span>
    <img src="https://example.com/imgs/img2.png"/ >
  </li>
  <li  class="item">
    <span class="topic">topic3</span>
    <img src="https://example.com/imgs/img3.png"/ >
  </li>
</ul>

このようなリスト構造になっているhtmlに対して、下記のようなオブジェクトの配列で返してほしいと思っていました。
しかし、現在のjam-apiの仕様では配列として受け取ることしか出来ません。

結果として期待しているjson
{
    "items": [
     
        "topic": "topic1",
        "thumbnail": "https://example.com/imgs/img1.png"
     },
     
        "topic": "topic2",
        "thumbnail": "https://example.com/imgs/img2.png"
     },
     
        "topic": "topic3",
        "thumbnail": "https://example.com/imgs/img3.png"
     }
   ]
}

jam-apiが返すjson
{
    "items": [
     
        "topic": ["topic1","topic2","topic3",]
        "thumbnail": [
           "https://example.com/imgs/img1.png",
           "https://example.com/imgs/img2.png",
           "thumbnail": "https://example.com/imgs/img3.png"
         ]
     }
   ]
}

使い方

基本的にはrequire|importしたfunctionに対して、
欲しい形式のオブジェクトとHTML文字列を渡すことで戻り値でパース結果が帰ってきます。
Demoページでなんとなくの使用感がわかるかと思います。

jam-apiと同様、CSS Selectorで取得した要素からは自動的に値が抽出されます。

  • imgタグの場合はsrc属性
  • a,linkタグの場合はhref属性
  • その他のタグは子要素内ののtext

命名規則によるデータの変換

ざっくりとしたselectorで取得すると要素が複数取得されるケースで抽出が面倒だったので、
名前による変換を実装しました。
キーの名前が単数形(ex:item)の場合、自動で先頭の値を抽出して値にします。
複数形(ex:items)の場合は配列で返します

<ul>
  <li class="item">item1</li>
  <li class="item">item2</li>
  <li class="item">item3</li>
</ul>

kirinukiに渡す形式
const schema = {
  item: ".item",
  items: ".item"
}
結果
{
  item: "item1",
  items: ["item1", "item2", "item3"]
}

配列への展開

リスト上になっているデータをオブジェクトの配列で欲しい場合、_unfold:trueを定義に付与すると、展開が行われます。

<ul>
  <li class="item">
    <span class="topic">topic1</span>
    <img src="https://example.com/imgs/img1.png"/>
  </li>
  <li  class="item">
    <span class="topic">topic2</span>
    <img src="https://example.com/imgs/img2.png"/ >
  </li>
  <li  class="item">
    <span class="topic">topic3</span>
    <img src="https://example.com/imgs/img3.png"/ >
  </li>
</ul>
kirinukiに渡す形式
const = {
  unfoldItems: {
    _unfold: true,
    topic: ".item .topic",
    thumbnail: ".item img"
  }
}
結果
{
  "unfoldItems": [
    
      "topic": "topic1",
      "thumbnail": "https://example.com/imgs/img1.png"
    },
    
      "topic": "topic2",
      "thumbnail": "https://example.com/imgs/img2.png"
    },
    
      "topic": "topic3",
      "thumbnail": "https://example.com/imgs/img3.png"
    }
  ]
}

探索パスの指定

クエリでの探索範囲を絞りたい場合は、_rootをキーにしたCSSSelectorを定義すると、
_rootで予め絞り込んだ要素内での探索が行われます。

root.html

<div>
  <ul class="main-items">
    <li class="item">
      <span class="topic">topic1</span>
      <img src="https://example.com/imgs/img1.png"/>
    </li>
    <li  class="item">
      <span class="topic">topic2</span>
      <img src="https://example.com/imgs/img2.png"/ >
    </li>
    <li  class="item">
      <span class="topic">topic3</span>
      <img src="https://example.com/imgs/img3.png"/ >
    </li>
  </ul>
  <ul  class="sub-items">
    <li class="item">
      <span class="topic">sub-topic1</span>
      <img src="https://example.com/imgs/sub-img1.png"/>
    </li>
    <li  class="item">
      <span class="topic">sub-topic2</span>
      <img src="https://example.com/imgs/sub-img2.png"/ >
    </li>
    <li  class="item">
      <span class="topic">sub-topic3</span>
      <img src="https://example.com/imgs/sub-img3.png"/ >
    </li>
  </ul>
</div>

kirinukiに渡す形式
{
    "mainItems": {
        "_root": ".main-items",
        "_unfold": true,
        "content": ".topic",
        "image": "img"
    },
    "subItems": {
        "_root": ".sub-items",
        "_unfold": true,
        "content": ".topic",
        "image": "img"
    }
}
結果

{
  "mainItems": [
    
      "topic": "topic1",
      "thumbnail": "https://example.com/imgs/img1.png"
    },
    
      "topic": "topic2",
      "thumbnail": "https://example.com/imgs/img2.png"
    },
    
      "topic": "topic3",
      "thumbnail": "https://example.com/imgs/img3.png"
    }
  ],
  "subItems": [
    
      "topic": "sub-topic1",
      "thumbnail": "https://example.com/imgs/sub-img1.png"
    },
    
      "topic": "sub-topic2",
      "thumbnail": "https://example.com/imgs/sub-img2.png"
    },
    
      "topic": "sub-topic3",
      "thumbnail": "https://example.com/imgs/sub-img3.png"
    }
  ]
}

属性の取得

課題・感想

  • 書いてる途中に気づきましたが、<a>タグのhrefとテキストを取得することが出来ません。
    • ["img","text"]のように、取得したい属性:値を配列で渡してもらうか、keyに_textなどつけると該当する属性を取りに行くなどの機能の実装すると思います。
    • 実装しました
  • デモページの構築にkatatema.jsを利用しました。めちゃくちゃ便利でした。
    • 一点ハマったところとして、cheerioをwebpackでビルドする際、json-loaderが必要で、カスタムのwebpack.configを定義する方法がない?ため、browserfiyで一度ビルドしたものを更にwebpackで読み込む必要がありました。
  • jam-apiっぽいAPIアプリ作ると思います。
  • 英語
20
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?