poyopoyo0のブログ

poyopoyoのブログ

Web 関連のメモ書きブログです。

【Node.js】UTF8以外のページをlibxmljsでスクレイピングするには

【追記】metaタグのcharsetの置換について、当初はreplaceメソッドを使用していましたが、replaceは最初に一致した文字列だけを置換するので、metaタグのcharsetの置換が失敗することがあることが分かりました。そのため、置換の方法をsplitとjoinメソッドを用いる全置換に変更しました。

libxmljsスクレイピングする際、対象ページの文字コードがUTF8以外だとエラーが発生してしまう。そこで、取得したページをUTF8に変換するために

trycatchand.blogspot.com

を参考に

const request = require('request-promise');
const encoding = require('encoding-japanese');
const libxmljs = require('libxmljs');

const body = await (request.defaults({
  encoding: null
})).get(url);

const unicodeArr = encoding.convert(body, {
  to: 'UNICODE'
});

const htmlDoc = libxmljs.parseHtml(body);

としてみた。ところが、このコードの直後に

console.log(htmlDoc);

とすると

Error: encoding not supported EUC-JP

というエラーが発生してしまう。どうやらlibxmljsでは、実際の文字コードではなく、metaタグのcharsetを読み取って文字コードを判断してしまっているみたい。そこで encoding-japanese (=encoding.js)

github.com

文字コード判別機能 encoding.detect() を用いて

const request = require('request-promise');
const encoding = require('encoding-japanese');
const libxmljs = require('libxmljs');

const body = await (request.defaults({
  encoding: null
})).get(url);

const detected = encoding.detect(body);
const unicodeArr = encoding.convert(body, {
  to: 'UNICODE'
});

let strToUtf8 = '';
if (detected === 'UTF8') {
  strToUtf8 = encoding.codeToString(unicodeArr);
} else if (detected === 'EUCJP') {
  strToUtf8 = (encoding.codeToString(unicodeArr)).split('EUC-JP').join('UTF-8');
} else if (detected === 'SJIS') {
  strToUtf8 = (encoding.codeToString(unicodeArr)).split('Shift_JIS').join('UTF-8');
};

const htmlDoc = libxmljs.parseHtml(strToUtf8);

としたところ、 htmlDoc.get('/html/head/title') のようにXPathで要素を取得できるようになった。

本当はmetaタグのcharsetに絞って置換してやらないと、charset以外の文字列を置換してしまう可能性があるのだけど、手軽にcharsetのみを置換する方法が分からないので、取り敢えず全置換してしまってます。どなたか、charsetをピンポイントで置換する方法をご存知でしたらお教え下さい💦

ちなみに

const unicodeArr = encoding.convert(body, {
  to: 'UNICODE'
});

のところは

const unicodeArr = encoding.convert(body, {
  to: 'UTF8'
});

としてしまうと文字化けしてしまい、スクレイピングができなくなるので注意が必要。

encoding-japanese (=encoding.js) は自動で文字コードを判断したり、目的の文字コードに変換してくれるので、ほんと手軽で便利。贅沢を言うと、日本語以外の文字コードにも対応してくれるライブラリが存在すると嬉しいのだけど、今どきUTF8以外のWebページは滅多に存在しないから、あんまり需要が無いかなあ…。

【Contentful】 Content Management API で未公開entryを取得するには

最近はContentfulを簡易DBとして使いこなせないか試すため、個人プロジェクトのパラメータ保存先を少しずつContentfulに移行してみているのだけど、createdAtやupdatedAtなど、Railsで見覚えのあるパラメータだけでなく、publishedAtやfirstPublishedAt、createdByやpublishedByなど、至れり尽くせりのパラメータがデフォルトで用意されていて、なかなか便利。

Content Management API を使うか Content Delivery API を使うかで取得できるパラメータは違うのだけど、公開済みのentryを Content Management API で取得した場合、sysパラメータを覗くと画像のようなパラメータが存在している。

f:id:poyopoyo0:20190504233912p:plain

このsysパラメータを使って、ある条件に基づいたentryを取得したい場合は、公式ドキュメント

www.contentful.com

にあるように、

getEntries({
  content_type: 'hoge',
  'sys.updatedAt[gte]': '2013-01-01T00:00:00Z',
})

などと書いてやれば、簡単に取得できてしまう。

sysのパラメータではなく、自分で入力したfieldsの値に基づいてentryを取得したい場合、Content Management API では

getEntries({
  content_type: 'hoge',
  'fields.fuga[ja-JP]': 'aaaaaaa',
})

Content Delivery API では

getEntries({
  content_type: 'hoge',
  'fields.fuga': 'aaaaaaa',
})

と書くことで、fieldsの値に基づいたentryを取得することができる。

ちなみに、 Full-text search も用意されていて、

getEntries({
  'query': 'design'
})

と書けば、content_typeを跨いでentryを検索することもできてしまう。

で、今回の本題は、 Content Management API で未公開entryを取得する方法について。 Contentfulでは、公式ドキュメント

www.contentful.com

で説明されているように

  • Content Delivery API : 公開済みentryにアクセスできる読み込み専用のAPI
  • Preview API : 未公開entryにアクセスできる読み込み専用のAPI
  • Content Management API : 公開済み/未公開を問わず全てのentryにアクセスできて、CRUD操作可能なAPI

などが用意されていて、entryのCRUD操作をしたいときには Content Management API を使う必要がある。 このとき getEntries({}) 内で何も指定しないと、公開済み/未公開を問わず全てのentryが取得されてしまう。

公開済みのentryのみ、未公開のentryのみを取得したいときは、それぞれ Content Delivery APIPreview API が用意されているのだけど、 Content Management API だけで未公開entryのみを取得したいときもあるので、sysパラメータを指定することで選り分けられないかと、公式ドキュメントやコミュニティ/Slackを検索しまくった。

ても、それらしき方法が自分には見つからなくて、なぜContentfulではpublishedという名前のbooleanパラメータが用意されていないのだろうか…、とボヤいていたところ、 @_slont さんに

と教えて頂いた。試しに

getEntries({
  content_type: 'hoge',
  'sys.publishedAt[exists]': false,
})

と書いてみたところ、見事、未公開entryだけを取得することに成功…!

一度公開したentryは、未公開に戻してもpublishedAtが存在し続けると勝手に思い込んでいたのだけど、どうやら、entryを未公開に戻すとsys.publishedAtが消える仕様だったみたい。

念のため、公開から未公開に戻したentryのsysパラメータを覗いてみたところ、画像のように、publishedAtやpublishedByが消えていることも確認できた。

f:id:poyopoyo0:20190505004653p:plain

こういうtipsってサービスのドキュメントとかを調べているだけじゃ見つからないことが多いので、ほんとありがたいです。@_slontさん、ありがとうございました!

【Vue.js / Nuxt.js / Firebase】 NuxtでgenerateしたSPAをFirebase上でリダイレクト処理させるには

※middlewareについての記述が間違っていたので、訂正しました。

NuxtでgenerateしたSPAをFirebase上でリダイレクト処理させるのに、えらいこと手こずってしまったので、覚え書き。 以下は、ロケーションバーに直打ちして https://(app_url)/hoge にアクセスしたとき https://(app_url)/login にリダイレクトさせたいときの実装方法。

最初は、 pages/hoge.vue<script> タグの中にbeforeCreateを書いて、その中で条件分岐して this.$router.push(/login) させてみる、という方法を試みた。だけど、この方法だと、リダイレクトする直前に pages/hoge.vue が一瞬だけ表示されてしまい、印象が良くない。 なので、以下の2つの方法を試してみた。

middlewareを用いる方法

middlewareへの記述は、こちらの記事 su-kun1899.hatenablog.com が参考になった。 この方法で、ローカルの開発環境では問題無くリダイレクトできるようになった。

ところが、その後、generateしてからFirebaseにデプロイしたところ、Firebase上ではリダイレクトしてくれないという状況が発生。 Nuxtの GitHub Issue github.com でも報告されているのだけど、どうやら、generateしたSPAファイルにはmiddlewareの処理が反映されない、という仕様になっているみたい。 つまり、Firebase / Netlify / GitHub Pages / GitLab Pages などにデプロイしたアプリでリダイレクト処理をさせるには、別の方法が必要らしい。 (※こちらは古い情報でした)

【追記】 generateしたSPAファイルにはmiddlewareの処理が反映されないというのがNuxtの仕様、と思い込んでいたのだけど、 @nishiokaex さんから

こんにちは。ブログ楽しく拝見させて頂いたのですが、SPAモードでビルドするとミドルウェア使えないでしょうか。私の環境(nuxt.js v2)では、nuxt.config.jsに「mode: 'spa'」と書いて、「nuxt build」すると、ミドルウェアが動作しているように見えます。

というご指摘を受けたので、middlewareに書いた処理が別の理由で動かなくなっているのではないかと思い、もう一度見直してみた。

当初、middlewareには

export default function ({ route, redirect }) {
  if (!route.params.hoge && route.path == '/hoge') {
    return redirect('/login')
  }
}

という風にリダイレクト処理を書いていたのだけど、Firebase上でmiddlewareが動かないように見えていたのはこの記述が悪かったようで、これを

export default function ({ route, redirect }) {
  if (!route.params.hoge && (route.path == '/hoge' || route.path == '/hoge/')) {
    return redirect('/login')
  }
}

のように書き換えることで、Firebase上でもmiddlewareでリダイレクト処理できるようになった。以前のNuxtではgenerateしたSPAでmiddlewareが動かなかったようだが、今は動くように実装されているみたい。 ( @nishiokaex さん、ご指摘ありがとうございました!)

コンポーネントを切り替える方法

次に試みた方法は、読み込むコンポーネントを条件分岐で切り替えることで、疑似的なリダイレクトを実装する、という方法。

pages/hoge.vue

<template>
  <section>
    <div v-if="$route.params.hoge">
      <hoge/>
    </div>
  </section>
</template>

<script>
import hoge from '@/components/hoge.vue'

export default {
  components: {
    hoge
  },
  beforeCreate() {
    if (!this.$route.params.hoge && this.$route.path == '/hoge') {
      this.$router.push('/login')
    }
  }
}
</script>

のように記述したところ、ローカルの開発環境では狙い通りにリダイレクトするようになった。

ところが、generateしたファイルをFirebaseにデプロイさせたところ、リダイレクトしてくれない。 前回の記事 で書いた方法を使って、デプロイ先のページの this.$nuxt.$route.path をコンソールで表示させてみたところ、画面遷移して https://(app_url)/hoge にアクセスしたときは /hoge と表示されるのに、ロケーションバーに直打ちして https://(app_url)/hoge にアクセスすると、 /hoge/ のように、末尾にスラッシュが入ってしまっている。

仕方が無いので、beforeCreateの記述を

  beforeCreate() {
    if (!this.$route.params.hoge && this.$route.path == '/hoge/') {
      this.$router.push('/login')
    }
  }

のように書き換えたところ、デプロイ先のページでは問題無くリダイレクトするようになったが、今度はローカルの開発環境でリダイレクトしてくれなくなってしまった。 デプロイ先のページで動いているのなら問題無い気もするが、ローカルの開発環境できちんと動いてくれないのは気持ちが悪いので、最終的にbeforeCreateの記述を

  beforeCreate() {
    if (
      !this.$route.params.hoge &&
      (this.$route.path == '/hoge' || this.$route.path == '/hoge/')
    ) {
      this.$router.push('/login/')
    }
  }

と記述することで、ローカルでもデプロイ先でも、問題無くリダイレクトしてくれるようになった。

ちなみに、URLを直打ちしたとき、URLの末尾に自動でスラッシュ / が入ってしまうのはWebの仕様がそうなっているみたいで、Firebaseが悪いわけではなさそう。

今回手間取ったのは、ローカルの開発環境と、generateさせたSPA、および、デプロイ先で、微妙に挙動が違うことが原因だったのだけど、 前回見つけたやり方 は、デバッグに多大なる力を発揮してくれた。

補足

今回レベルの単純なリダイレクトであれば、Firebaseの公式ドキュメント Configure hosting behavior  |  Firebase にある通り、 firebase.json ファイルに

    "redirects": [
      {
        "source": "/hoge",
        "destination": "/login",
        "type": 301
      }
    ],

と記述してやれば、すぐ実装できるのだけど、CookieやLocalStorageなどで条件分岐してリダイレクトさせたいときには使えなさそう。なので、generateしたSPAでリダイレクト処理を書くときは、基本的にはコンポーネントの切り替えで対処するしかないかな…。

【追記】 上の追記でも触れていますが、現在のNuxtでは、generateしたSPAでもmiddlewareが動くので、middlewareでリダイレクト処理させる方が望ましいようです。

【Nuxt.js】 ブラウザのコンソールでNuxtオブジェクトを表示させるには

前回 、ブラウザのコンソールでVueオブジェクトを表示させる方法を記述したが、Nuxt.jsに限定すれば、もっと簡単なやり方があったので、覚え書き。

やり方は、ブラウザのデベロッパーツールのコンソールで this.$nuxt と入力するだけ。すると、画像のように、コンソールにNuxtオブジェクトが表示されるようになる。 f:id:poyopoyo0:20181224094542p:plain this.$nuxt. と入力すると、Nuxtオブジェクトがサジェストされることが確認できる。 f:id:poyopoyo0:20181224094715p:plain 試しに this.$nuxt.$route と入力すると、下記の画像のように $route オブジェクトの中身が表示される。 f:id:poyopoyo0:20181224095045p:plain

前回 の方法では、ローカルの開発環境のときしかVueオブジェクトを表示させることができなかったが、今回のやり方であれば、generateしたSPAのホスティング先でもNuxtオブジェクトを表示させることができる。

試しに、generateしたSPAをFirebaseにデプロイして、ホスティング先のページで this.$nuxt と入力すると、画像のように、コンソールにNuxtオブジェクトが表示されるようになった。 f:id:poyopoyo0:20181224113228p:plain f:id:poyopoyo0:20181224113327p:plain f:id:poyopoyo0:20181224113348p:plain

ローカルの開発環境とデプロイ先とで微妙に挙動が違っていることがあるので、デプロイ先のページでNuxtオブジェクトを表示できるというのは、かなり嬉しい。

残念ながら、NuxtではなくVueでデプロイ先のオブジェクトを表示させるやり方は分からないので、VueではなくNuxtを使い続ける理由がまた一つ増えてしまった😃

【追記(2020.1.19)】デプロイ先でもVueオブジェクトを表示させる方法があったようなので、 前回 の記事に追記をしました。

【Vue.js / Nuxt.js】 ブラウザのコンソールでVueオブジェクトを表示させるには

【追記】Nuxt.jsに限定すれば、もっと簡単なやり方があったので、こちらに書きました

知ってる人からすると、ほんとに今さらの知識なんだろうけど、ブラウザのコンソールでVueオブジェクトを表示させる方法が分かったので、覚え書き。

まず、 Vue.js / Nuxt.js の開発サーバを起動後、ブラウザのデベロッパーツールでコンソールを開く。この時点では、 $vm0 と打ち込んでもコンソールではVueオブジェクトが表示されない。 f:id:poyopoyo0:20181119193302p:plain

次に、 Vue Devtools ペインを表示させて、画像のようにVueコンポーネントをどれかクリックする。 f:id:poyopoyo0:20181119194620p:plain

その後で再びコンソールを表示させて $vm0 と打ち込むと、画像のようにVueオブジェクトが表示されるようになる。 f:id:poyopoyo0:20181119195002p:plain

試しに $vm0. と打ち込むと、Vueオブジェクトがサジェストされることが確認できる。 f:id:poyopoyo0:20181119195259p:plain

ソースコードにVuexが記述されている場合は、 $vm0.$store.getters$vm0.$store.state なども表示されるようになるので、めちゃめちゃ便利。

当初、 Vue Devtools をインストールしてみたもの、使いどころが分からず、 Vue.js / Nuxt.js プロジェクトをどうやってデバッグすればいいか長いこと悩んでいた。 Vue School を視聴することで、使い方が何となく分かってきた気がする。

分かってみれば、 Vue Devtools 上でVueコンポーネントをクリックするだけなので、なんてことないんだけど、解説してくれているページがなかなか見つからなかったので、めっちゃ苦労しました…。

【追記(2020.1.19)】今回紹介した方法は、ローカルの開発環境限定のやり方なのですが、デプロイ先でもVueオブジェクトを表示させる方法があったようです。

qiita.com

qiita.com

【GitLab.com】 Vue.js / Nuxt.js でビルドしたファイルを GitLab Pages でホスティングさせるには

こちらの記事 qiita.com で紹介されているように、 GitLab.com ではプライベートリポジトリが無制限で無料らしい。しかも GitHub Pages と同じく、 GitLab Pages というホスティング機能まであるのだとか!

GitLab Pages でのホスティング方法は、 master ブランチの直下に .gitlab-ci.yml を配置した後、ローカルから GitLab.com に push してやるだけ。後は GitLab.com の方で CI が走り、ビルドされた静的ファイルを

https://[ユーザー名].gitlab.io/[プロジェクト名]

のURLに自動でホスティングしてくれる。

ホスティング対象の静的ファイルを直接 push するのであれば、こちらの記事 http://okakacacao.wpblog.jp/technology/gitlab-pages-introductionokakacacao.wpblog.jp を参考にして、リポジトリ直下に配置する .gitlab-ci.yml

pages:
  stage: deploy
  script:
  - mkdir .public
  - cp -r * .public
  - mv .public public
  artifacts:
    paths:
    - public
  only:
  - master

と書いてやれば大丈夫だった。

Vue.js や Nuxt.js でビルドした静的ファイルをホスティングしたいのであれば、こちらの記事 qiita.com を参考にして、以下のようにすればOK。

【Vue.js の場合】

.gitlab-ci.yml

image: node:latest
pages:
  stage: deploy
  only:
    - master
  before_script:
    - 'yarn config set cache-folder .yarn'
    - 'yarn install'
  script:
    - 'yarn build'
    - 'cp -pr dist public'
  artifacts:
    paths:
      - public

のように書いた後、 config/index.jsassetsPublicPath'/' と書き換えてやればOK。

参考: qiita.com

【Nuxt.js の場合】

.gitlab-ci.yml

image: node:latest
pages:
  stage: deploy
  only:
    - master
  before_script:
    - 'yarn config set cache-folder .yarn'
    - 'yarn install'
  script:
    - 'yarn generate'
    - 'cp -pr dist public'
  artifacts:
    paths:
      - public

のように書いた後、リポジトリ直下の nuxt.config.jsmodule.exports = { } 中の末尾に

,
router: {
  base: '/リポジトリ名/'
}

を書き加えてやる。また、 favicon.ico のパスも変えてやる必要があるので、同じく nuxt.config.js

link: [
  { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]

link: [
  { rel: 'icon', type: 'image/x-icon', href: '/リポジトリ名/favicon.ico' }
]

のように書き加えてやればOK。

参考: fiahfy.blogspot.com

無料ユーザが GitHub Pages で Vue.js / Nuxt.js のファイルを public にしたくない場合、 Vue.js / Nuxt.js のリポジトリはローカルのみで管理するか、 Bitbucket などのプライベートリポジトリに push して、 dist ディレクトリのみを GitHub に push する、というやり方が必要だったが、 GitLab.com であれば、 Vue.js / Nuxt.js リポジトリを master に push するだけで、ビルドからホスティングまでやってくれる。

Vue.js / Nuxt.js リポジトリを GitLab.com に push するだけで ホスティングできる、というのはすごく気軽なので、これからは GitHub Pages ではなく GitLab Pages をメインにしていこうかな。

【 Ruby 】 配列の中の重複要素だけを抽出するには

Rubyの配列arrayを重複要素無しの配列にするには

array.uniq

または

array.uniq!

とすればいいだけなのだけど、一方で、「配列の中の重複要素だけを抽出する」というメソッドは、どうやら存在しないみたい。

Rubyは配列同士の差を計算できるので

array - array.uniq

でいけるかもと思ったけど、これだと空集合になってしまって役に立たなかった。

サンプルコードを検索しても、イテレータを回すことで解決できないみたいなので、重複要素だけを抽出するコードを自分でも書いてみた。

array_uniq = array.uniq
duplicate_element_array = []

(array_uniq).each do |element|
  if array.grep(element).size > 1
    duplicate_element_array.push(element)
  end
end

puts duplicate_element_array

としてやると、 duplicate_element_array が重複要素だけを抽出した配列となる。

grepメソッドの使い方については qiita.com を参考にした。

【 Ruby 】 Windows上で文字コードエラーになる場合

今まではWindowsRubyスクリプトを作成する際、スクリプトの冒頭に

# coding: Shift_JIS

と記述していたのだけど、あるとき

incompatible encoding regexp match (Shift_JIS regexp with Windows-31J string) (Encoding::CompatibilityError)

文字コードエラーが発生することがあった。

こちらの記事 qiita.com によると、 Shift_JISWindows-31J別モノ とのことで、どうやら、Windowsコマンドプロンプト上では Windows-31J が使われているらしい。

ちなみにこちら weblabo.oscasierra.netShift_JISWindows-31J が分岐した経緯が記されていた。

上記のエラーに関しては、スクリプトの冒頭を

# coding: Windows-31J

と記述し直してやると、発生しなくなった。Windowsのためのみに作成するスクリプトなら、この記述に統一しといた方が、余計な不具合を発生させてないでいいかも。

今まで Shift_JISWindows-31J の違いなんて気にしたことなかったな。。。

【 Git 】同じ作業ブランチを複数の人と共有していて、誰かが更新したときは

一つの作業ブランチをリモートで複数の人と共有していて、誰かがリモートブランチを更新させたとき、手元の作業ブランチでもその変更を反映させたいときがある。 その場合は

$ git pull --rebase

としてやれば、リモートの変更をローカルの作業ブランチに反映させてやることができる。

【 Rails 】コメント機能の付いたブログのサンプル

コメント機能の付いたブログを Rails で作りたいときは、 こちらの記事 d.hatena.ne.jp がまとまっていて、とても分かりやすかった。そのまま書き写せば、問題なく動くブログが作れてしまう。

初歩に立ち戻りたいときに、こういう記事はとても便利。