2020年もなると文字コード関連のノウハウなんてものはすっかりプログラミングサイト上からも消えています。世間は当たり前のようにutf-8化しています。いまだにutf-8化していないのはwindowsの一部くらいなものでしょう。
で、webの世界は当然utf-8で全部済むと思っていたのですが、最近、node-fetchでeuc-jpのページを拾ってくる作業にぶつかりました。node-fetchとはnode.jsでjavascriptのfetch関数を使うライブラリです。fetchとはXMLHttprequestのモダンなやつです。
こういう場合はfetchにURL渡して引っ張ってきてその文字列を何かのライブラリーで変換というのがセオリー中のセオリーですが、そんなのは20世紀のプログラミングらしいです。perlのJcodeとかの時代のセオリーのようです。
モダンなプログラミングというのは「誰かがどこかで勝手にutf-8に変換してくれてる」というもので、特にウェブの場合はhtmlに文字コードがcharsetとして記載されており、それを読んで変換、あるいはソースの文字コード自動判別して現代的標準であるutf-8に変換してくる、というのが当たり前のようです。ですが、そうはうまくは行きません。世界のプログラマはasciiからutf-8になったのであり、eucやらShiftJISやらCN、KRなんてものは頭の片隅にもありません。
というわけで、結局は生の文字列を現代のJcode、nkfで変換してやるという泥臭いが慣れている作業をやればいいのですが、これがうまく行かない。「誰かがどこかで中途半端にutf-8に変換してる」というトラブルが待っているのです。
詳しくはわかりませんが、「8bitだからLatin-1だろうからそれを強引にutf-8にしておいてあげましたよ」的なことをやられているのでしょう、生の文字列だと受け取った文字列は化けており、それをeuc-jpに変換してやっても化けているという、ありがちなにっちもさっちも行かない事態になります。
さてここからソースを交えた説明になります。
使用しているiconv-liteというライブラリは文字コード変換です。iconvでピンとくるかもしませんがlinuxやらglibc周りで聞いた名前ですね。
いまだにeuc-jpを使ってるページとして
https://www.rakuten.co.jp/
を参考例にしています。
まずはiconvを使わずにnode-fetchが自動変換してくれるという期待を込めたパターン。
const node_fetch = require('node-fetch');
const url = "https://www.rakuten.co.jp/"
node_fetch(url)
.then(res => res.body.text())
.then(body => console.log(body));
化けているのか、もとのeuc-jpのページがそのまま表示されているのこの段階では追いませんが、とにかくうまく行かなかったので次へ
次はtext()で生の文字列が得られると仮定してiconvで変換
const node_fetch = require('node-fetch');
const iconv = require("iconv-lite")
const url = "https://www.rakuten.co.jp/"
node_fetch(url)
.then(res => res.text())
.then(body => console.log(iconv.decode(body)));
これもうまく行きません。おそらく生の文字列は取得できてないようです。つまりnode-fetch(かjavascriptのバッファー周り)が変換に失敗しているかと
途方に暮れているとエラーメッセージの中に公式ヘルプへのリンクが表示され
https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding
このページのサンプル通りにやったのがこれ
const node_fetch = require('node-fetch');
const iconv = require("iconv-lite")
const url = "https://www.rakuten.co.jp/"
node_fetch(url)
.then(res => res.body)
.then(body => body.pipe(iconv.decodeStream('euc-jp').collect((err, res) => console.log(res))));
これでやっとうまく行きました。
node-fetchのソースを軽く見てみると、bufferからの型変換がいろいろ複雑なようです。bufferといってもいろいろあるようで、深く追うのは面倒なので諦めました。
javascriptは型が無い(弱い)ようでガッチガチに面倒なのでそのへんは名前の通りjavaだなーと。
というわけで2020年のバッドノウハウ(?)でした