【Node.js】UTF8以外のページをlibxmljsでスクレイピングするには
【追記】metaタグのcharsetの置換について、当初はreplaceメソッドを使用していましたが、replaceは最初に一致した文字列だけを置換するので、metaタグのcharsetの置換が失敗することがあることが分かりました。そのため、置換の方法をsplitとjoinメソッドを用いる全置換に変更しました。
libxmljs でスクレイピングする際、対象ページの文字コードがUTF8以外だとエラーが発生してしまう。そこで、取得したページをUTF8に変換するために
を参考に
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)
の文字コード判別機能 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パラメータを覗くと画像のようなパラメータが存在している。
このsysパラメータを使って、ある条件に基づいたentryを取得したい場合は、公式ドキュメント
にあるように、
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では、公式ドキュメント
で説明されているように
- 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 API と Preview API が用意されているのだけど、 Content Management API だけで未公開entryのみを取得したいときもあるので、sysパラメータを指定することで選り分けられないかと、公式ドキュメントやコミュニティ/Slackを検索しまくった。
ても、それらしき方法が自分には見つからなくて、なぜContentfulではpublishedという名前のbooleanパラメータが用意されていないのだろうか…、とボヤいていたところ、 @_slont さんに
publishedAtもしくはByの存在自体がboolを兼ねるからではないですかね?カラム増やすと管理コストも上がりがちなので僕も結構やります
— slont@Finatext (@_slont) 2019年5月4日
と教えて頂いた。試しに
getEntries({ content_type: 'hoge', 'sys.publishedAt[exists]': false, })
と書いてみたところ、見事、未公開entryだけを取得することに成功…!
一度公開したentryは、未公開に戻してもpublishedAtが存在し続けると勝手に思い込んでいたのだけど、どうやら、entryを未公開に戻すとsys.publishedAtが消える仕様だったみたい。
念のため、公開から未公開に戻したentryのsysパラメータを覗いてみたところ、画像のように、publishedAtやpublishedByが消えていることも確認できた。
こういう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オブジェクトが表示されるようになる。
this.$nuxt.
と入力すると、Nuxtオブジェクトがサジェストされることが確認できる。
試しに this.$nuxt.$route
と入力すると、下記の画像のように $route
オブジェクトの中身が表示される。
前回 の方法では、ローカルの開発環境のときしかVueオブジェクトを表示させることができなかったが、今回のやり方であれば、generateしたSPAのホスティング先でもNuxtオブジェクトを表示させることができる。
試しに、generateしたSPAをFirebaseにデプロイして、ホスティング先のページで this.$nuxt
と入力すると、画像のように、コンソールにNuxtオブジェクトが表示されるようになった。
ローカルの開発環境とデプロイ先とで微妙に挙動が違っていることがあるので、デプロイ先のページでNuxtオブジェクトを表示できるというのは、かなり嬉しい。
残念ながら、NuxtではなくVueでデプロイ先のオブジェクトを表示させるやり方は分からないので、VueではなくNuxtを使い続ける理由がまた一つ増えてしまった😃
【追記(2020.1.19)】デプロイ先でもVueオブジェクトを表示させる方法があったようなので、 前回 の記事に追記をしました。
【Vue.js / Nuxt.js】 ブラウザのコンソールでVueオブジェクトを表示させるには
【追記】Nuxt.jsに限定すれば、もっと簡単なやり方があったので、こちらに書きました
知ってる人からすると、ほんとに今さらの知識なんだろうけど、ブラウザのコンソールでVueオブジェクトを表示させる方法が分かったので、覚え書き。
まず、 Vue.js / Nuxt.js の開発サーバを起動後、ブラウザのデベロッパーツールでコンソールを開く。この時点では、 $vm0
と打ち込んでもコンソールではVueオブジェクトが表示されない。
次に、 Vue Devtools ペインを表示させて、画像のようにVueコンポーネントをどれかクリックする。
その後で再びコンソールを表示させて $vm0
と打ち込むと、画像のようにVueオブジェクトが表示されるようになる。
試しに $vm0.
と打ち込むと、Vueオブジェクトがサジェストされることが確認できる。
ソースコードにVuexが記述されている場合は、 $vm0.$store.getters
や $vm0.$store.state
なども表示されるようになるので、めちゃめちゃ便利。
当初、 Vue Devtools をインストールしてみたもの、使いどころが分からず、 Vue.js / Nuxt.js プロジェクトをどうやってデバッグすればいいか長いこと悩んでいた。 Vue School を視聴することで、使い方が何となく分かってきた気がする。
分かってみれば、 Vue Devtools 上でVueコンポーネントをクリックするだけなので、なんてことないんだけど、解説してくれているページがなかなか見つからなかったので、めっちゃ苦労しました…。
【追記(2020.1.19)】今回紹介した方法は、ローカルの開発環境限定のやり方なのですが、デプロイ先でもVueオブジェクトを表示させる方法があったようです。
【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.js
の assetsPublicPath
を '/'
と書き換えてやれば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.js
の module.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。
無料ユーザが 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
が重複要素だけを抽出した配列となる。
【 Ruby 】 Windows上で文字コードエラーになる場合
今まではWindowsでRubyスクリプトを作成する際、スクリプトの冒頭に
# coding: Shift_JIS
と記述していたのだけど、あるとき
incompatible encoding regexp match (Shift_JIS regexp with Windows-31J string) (Encoding::CompatibilityError)
と文字コードエラーが発生することがあった。
こちらの記事
qiita.com
によると、 Shift_JIS
と Windows-31J
は 別モノ とのことで、どうやら、Windowsのコマンドプロンプト上では Windows-31J
が使われているらしい。
ちなみにこちら
weblabo.oscasierra.net
に Shift_JIS
と Windows-31J
が分岐した経緯が記されていた。
# coding: Windows-31J
と記述し直してやると、発生しなくなった。Windowsのためのみに作成するスクリプトなら、この記述に統一しといた方が、余計な不具合を発生させてないでいいかも。
今まで Shift_JIS
と Windows-31J
の違いなんて気にしたことなかったな。。。
【 Git 】同じ作業ブランチを複数の人と共有していて、誰かが更新したときは
一つの作業ブランチをリモートで複数の人と共有していて、誰かがリモートブランチを更新させたとき、手元の作業ブランチでもその変更を反映させたいときがある。 その場合は
$ git pull --rebase
としてやれば、リモートの変更をローカルの作業ブランチに反映させてやることができる。
【 Rails 】コメント機能の付いたブログのサンプル
コメント機能の付いたブログを Rails で作りたいときは、 こちらの記事 d.hatena.ne.jp がまとまっていて、とても分かりやすかった。そのまま書き写せば、問題なく動くブログが作れてしまう。
初歩に立ち戻りたいときに、こういう記事はとても便利。