初歩からの無職

ContentfulのContent Management APIでエントリーを一括で修正する

  • Contentful
  • GatsbyJS

経緯

iframelyの無料枠オーバーで代替手段が必要になった

WordPressからJamstack構成にブログを移す際に悩むのがTwitter等の埋め込みコードをどう処理するかです。このブログではgatsby-remark-embedderを利用することにしています。採用を決めた基準は次のようなものです。

  1. ダウンロード数が多い(大事)
  2. Markdown側で特別な表記が必要なく、単純にURLだけを記載すればよいこと
  3. 主要なサービスに一括対応していること
  4. 対応外のサービスでも自分で定義すれば拡張できること

ほとんどの埋め込みはこれ一つで対応できましたが、卑しい小銭稼ぎ用のAmazonアソシエイトのリンクには標準では対応していません。GatsbyでAmazonLinkをtransformするプラグインもないようで、Webで転がってる埋め込みコード生成系サービスもPA-API5.0へのアップデートで冬の時代を迎えているようでした。以上のような経緯があり、Amazonのリンクに関してはさしあたってiframelyを利用していました。綺麗に表示してくれます。

iframely-sample

しかし、早速iframelyから「API Limit越えたので明日止めるで」というアラートが届き、ほどなく非表示となりました。弱小ブログだし滅多なことじゃ越えないだろうと思っていたのですが、初月から頓挫です。課金するにも少し手が出づらいPricingだったので、別の手段でAmazonのリンクをいい感じに変換する方法を探すことにしました。

gatsby-remark-embedderのcustomTransformersで実装する

結論から言うと、今回はTwitterその他の埋め込みコード変換に使っているgatsby-remark-embedderのcustomTransformersを利用することにしました。1からremark pluginを書くというのもとても勉強になるとは思うのですが、今はスピード重視です。だって今この瞬間に表示されてない(なかった)んだもん。

置き換え用のコードの取得にあたって、他のサービスが使っているようなURLから埋め込み用の情報を返してくれるような便利なoEmbed APIはAmazonにはないので、直接WebページからスクレイピングするかProduct Advertising API(PA-API)を利用するという手段が考えられます。今回は後者を利用しました。

なお、gatsby-remark-componentgatsby-plugin-mdxなどを用いてMarkdown中にコンポーネントを書いたりmdxで記事を管理するという方法も考えましたが、できるだけピュアなMarkdownにしておきたいという青臭い考えが捨てきれずに今回は見送りました。

ContentfulのBlog Postデータからiframely埋め込みコードをAmazonの素のURLに変換する

上のような経緯のために、まずは前段階としてContentfulのすべてのiframelyコードを素のURLに変換します。

挿入箇所がそれなりの量になるので、本当はWordPressからのmigrationのときに触りたかったContent Management APIを利用します。

記事取得は以下を参考にしました。

置き換えるiframelyの埋め込みコードの例です。

<div class="iframely-embed"><div class="iframely-responsive" style="height: 140px; padding-bottom: 0;"><a href="https://www.amazon.co.jp/-/en/dp/B00X9CDPE4" data-iframely-url="//cdn.iframe.ly/KjRzTXD?iframe=card-small"></a></div></div>

全記事に対してぶん回すよりもContent Management APIでエントリーを取得する際に本文中にiframelyを含む記事だけに絞るようにクエリを設定してあげたほうが何となく上品っぽい気がします。以下のようにすれば引っかかるはずです。

client.getEntries({
  content_type: "blogPost",
  "fields.body.ja[match]": "iframely"
}).then(entries => {
  console.log(entries.items);
});

ところがどうもうまく引っかかりません。同様の仕組みで検索をかけていると思われるContentfulの管理画面でもやはり同様です。おそらくHTMLタグ等などの記号が何らかの悪さをしてるっぽいですが、エスケープする方法もわからなかったので、結局全エントリ取得することにしました。

正規表現でASINだけ抜き取って有効なAmazonアソシエイトリンクに置き換えればOKです。

/contentful/iframely-to-amazon-url.js
require("dotenv").config({ path: `.env.development` });
const env = process.env;

process.on("unhandledRejection", console.dir);

const contentful = require("contentful-management");
const client = contentful.createClient({
  accessToken: env.CONTENTFUL_PERSONAL_ACCESS_TOKEN,
});

(async () => {
  const space = await client.getSpace(env.CONTENTFUL_SPACE_ID);
  const environment = await space.getEnvironment("master");
  const entries = await environment.getEntries({
    content_type: "blogPost",
    order: "-sys.createdAt",
  });
  for (let item of entries.items) {
    if (item.fields.body.ja.match(/iframely/g)) {
      // iframelyを素のリンクに変換
      const re = /<div class="iframely-embed">.*?\/dp\/(.*?)".*?<\/div><\/div>/g;
      item.fields.body.ja = item.fields.body.ja.replace(
        re,
        "https://www.amazon.co.jp/exec/obidos/ASIN/$1/mtane0412-22/"
      );
      const updatedEntry = await item.update();
      await updatedEntry.publish();
      console.log(`DONE: ${item.fields.title.ja}`);
    }
  }
})();

実行して無事変換が終了しました。便利です。今後、エントリーに対して様々な一括処理しやすくなるだろうし、WordPress等からのmigrationのやり方も何となく当たりがつけられるようになって勉強になりました。