OSSにコミットしてサービスの課題を解決した話
はじめに
ZennではOGPを利用したリンクカードの埋め込み表示にopenGraphScraperというパッケージを使っています。
まさに上記のGitHubリンクカードもopenGraphScraperが使われています。
そして先日、Zennの課題に対処するためにopenGraphScraperにプルリクエストを出したところ、マージしてもらうことができました。
本記事ではZennが抱えていたopenGraphScraperに関する課題とその解決について紹介します。
アップデートしたら文字化けする課題
openGraphScraperのv5.2.0で文字コードに応じたデコード処理がなくなってしまい、アップデートすると一部のサイトで文字化けするようになりました[1]。
文字化けはShift_JISやEUC-JPなどのUTF-8でない文字コードのサイトで再現します。
Shift_JISサイトの例として、阿部寛のホームページのOGP情報をopenGraphScraperで取得してみます(openGraphScraperはv6.3.4)。
import ogs from "open-graph-scraper";
const main = async () => {
const { result } = await ogs({ url: 'http://abehiroshi.la.coocan.jp/' })
console.log(JSON.stringify(result, null, 2))
}
main()
以下の出力を得ます。
{
"ogTitle": "�������̃z�[���y�[�W",
"charset": "Shift_JIS",
"requestUrl": "http://abehiroshi.la.coocan.jp/",
"success": true
}
このように値が文字化けしてしまっています。 �
は欠損してしまっている情報であり、ここからエンコードやデコードを重ねても元の文字列を復元することはできませんでした。
�(多くの場合、白い疑問符の付いた黒い菱形または空の四角)は、 Unicode規格のSpecialsにおいてコードポイントU + FFFDに割り当てられている記号であり、システムがデータ内の文字列を正しいシンボルにレンダリングできない場合の問題を示すために使用される。通常はデータが無効であるか、どの文字とも一致しない場合に表示される。
(中略)
置換はすべてのエラーで同じであるため、元の文字を復元することはできない。
そのため、バージョン5.2.0以前に固定する状態がしばらく続いており、それが課題になっていました。
Issueを発見
openGraphScraperのIssueを探したところ、関連するものを見つけました。
正確には文字コードの取得に関する内容なのですが、背景としてはやはり文字化けの発生を回避したいというものであり我々が抱える課題と同じであるように思いました。ちなみにここでも阿部寛のホームページが例として挙げられていました。
読んだところ、 ogs()
にはURLだけでなくHTMLをそのまま渡すこともできるため、Issue起票者としては
// 自分でWebサイトをfetchして適切なデコードもしておく
// ...
// 適切にデコードされたHTML情報
const html: string = // ...
// HTML情報を直接渡してOGP情報を抽出
const res = await ogs({ html })
といったやり方で解決できたので良しとしているようでした。
このやり方を採用しても良かったのですが、やはりopenGraphScraper自体がfetchもデコードもやってくれた方が利用者側で考えることも少ないですし便利だろうと考え、上記Issueに紐づける形でプルリクエストを出してみることにしました。
プルリクエストを作成
というわけでプルリクエストを作成しました。
Shift_JISなどのサイトをいきなりUTF-8でデコードすると文字化けしてしまうので、まずfetchのresponseオブジェクトから .arrayBuffer()
を取得しておき、HTMLヘッダもしくはchardetライブラリによって文字コードを特定し、それに応じたデコードを行うようにしました。デコードのために依存関係にiconv-liteを追加しています。
上記の過程でメンテナのGitHub Pagesにホスティングされたテスト用WebサイトをスクレイピングするIntegrationテストが通らなかったため、そちらにもプルリクエストを出しました。
ここでテスト用サイトをブラウザで閲覧した際にどうやっても文字化けするという事象にやや悩まされましたが、これは別途記事にしています(結論、スルーで良かった)。
無事マージされる🎉
いろいろとやりとりや確認待ちもあり、2週間ほどかかりましたがマージされました。
そしてほどなくして上記プルリクエストの変更内容を取り込んだv6.4.0がリリースされました。
付き合っていただいたメンテナに感謝です。
v6.4.0の動作確認
v6.4.0で阿部寛のホームページのOGP情報を取得してみます。
import ogs from "open-graph-scraper";
const main = async () => {
const { result } = await ogs({ url: 'http://abehiroshi.la.coocan.jp/' })
console.log(JSON.stringify(result, null, 2))
}
main()
以下の出力を得ます。
{
"ogTitle": "阿部寛のホームページ",
"charset": "Shift_JIS",
"requestUrl": "http://abehiroshi.la.coocan.jp/",
"success": true
}
文字化けせずに意図した取得ができることが確認できました。
おわりに
Zennのコードベースでもv6.4.0にアップデートして文字化けは起こらないことを確認し、リリースして本課題は解決としました。
今回のOSSコミットにより、Zennの課題を解決できたことはもちろん、今後同じ課題に直面する可能性がある他のサービスにも貢献できたのではないかと思っています。
また、雰囲気で扱っていた文字コードの解像度が少しだけ上がった気がします。
自分のコミットであまりきれいでなかったり重複したコードが生まれた部分もあるので、余裕が生まれた際にリファクタリングを提案してみようかと思います。
参考
- Unicode における置換文字(replacement character)について - 30歳からのプログラミング
- 黒いひし形にはてなマークが出る文字(�)はUTF-8変換時のときに変換後の対象がない置き換え文字 - コード日進月歩
- 特殊用途文字 (Unicodeのブロック) - Wikipedia
Discussion