tag:crieit.net,2005:https://crieit.net/tags/axios/feed 「axios」の記事 - Crieit Crieitでタグ「axios」に投稿された最近の記事 2022-05-04T13:28:56+09:00 https://crieit.net/tags/axios/feed tag:crieit.net,2005:PublicArticle/18175 2022-04-26T17:01:11+09:00 2022-05-04T13:28:56+09:00 https://crieit.net/posts/493a752726fa6c47c162fcb7379c2c6d 気象庁の公開データを node.js と chrome ブラウザ (browserify) で表示する。 <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/nc73d">https://rentry.co/nc73d</a></p> <p>雨なので気象庁の公開データを Javascript を使って見てみる。</p> <h2 id="pathCode (json) を表示してみる"><a href="#pathCode+%28json%29+%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">pathCode (json) を表示してみる</a></h2> <p>気象データを問い合わせるのに、定数になっている日本全国の場所のコード( pathCode )を見てみる。<br /> なぜなら、それをしらないとエリアごとの気象データを問い合わせできない。</p> <h3 id="javascript"><a href="#javascript">javascript</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmPrjHjXe6pSwh39Qjh326uLQWsoWtBJqNvdtPnMxsRDHq"><code>tenki_area.js</code></a></p> <pre><code class="javascript">const axios = require("axios"); const url = "https://www.jma.go.jp/bosai/common/const/area.json"; (get_area = async () => { try { const response = await axios.get(url); console.log(response.data); } catch (error) { console.error(error); } })(); </code></pre> <p>terminal:</p> <pre><code class="bash">> node tenki-area.js > tenki-area.json </code></pre> <p>Rf:<br /> <a target="_blank" rel="nofollow noopener" href="https://mindtech.jp/?p=1754">https://mindtech.jp/?p=1754</a></p> <h3 id="Ruby"><a href="#Ruby">Ruby</a></h3> <pre><code class="ruby">require "json" require "net/http" area_code_url = "https://www.jma.go.jp/bosai/common/const/area.json" response = Net::HTTP.get_response(URI.parse(area_code_url)) h_area = JSON.parse(response.body) pp h_area["offices"] </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmT3YkWhro7xMQ3uNwF4QNXUnt6aAjoGuaupzKg5ZtdqNs">tenki_area.rb</a></p> <pre><code class="ruby">require "json" require "net/http" area_code_url = "https://www.jma.go.jp/bosai/common/const/area.json"; response1 = Net::HTTP.get_response(URI.parse(area_code_url)) h_area_code = JSON.parse(response1.body) uri = "https://www.jma.go.jp/bosai/forecast/data/forecast/" #area = "260000" #Kyoto h_area_code["offices"].each {|v| area = v[0] begin response2 = Net::HTTP.get_response(URI.parse("#{uri}#{area}.json")) h_area_data = JSON.parse(response2.body) h_area_data.each {|vv| pp vv["publishingOffice"] pp vv["reportDatetime"] vv["timeSeries"].each {|x| x["areas"].each {|xx| pp xx['area']} } puts puts "--------------------------------------------------" puts } rescue puts end } </code></pre> <h3 id="Python"><a href="#Python">Python</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/Qmat48qKAeh6LovrqfzdisYBhbAtmKb5dpPHXZvxrisM7P">tenki_area.py</a></p> <pre><code class="python">import requests import json area_code_url = "https://www.jma.go.jp/bosai/common/const/area.json"; headers = {"content-type": "application/json; charset=utf-8"} response = requests.get(area_code_url, headers=headers) data = response.json() print(json.dumps(data['offices'],ensure_ascii=False,indent = 4)) </code></pre> <h3 id="Nim"><a href="#Nim">Nim</a></h3> <pre><code class="nim">#nim -d:ssl import httpclient,strutils,uri import json const area_code_url = "https://www.jma.go.jp/bosai/common/const/area.json" let client0 = newHttpClient() let response = client0.get(area_code_url) let jsObj = parseJson(response.body) client0.close() echo jsObj["offices"].pretty() </code></pre> <h2 id="get weather data"><a href="#get+weather+data">get weather data</a></h2> <p>Rf:<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/youtoy/items/932bc48b03ced5a45c71">https://qiita.com/youtoy/items/932bc48b03ced5a45c71</a></p> <h3 id="Ruby"><a href="#Ruby">Ruby</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmPsnpodrqshrnwJAd3AjQdgmzxHcoWvTa6F1UZVYPjjxq">tenki.rb</a></p> <p>例えば京都という場所の天気を問い合わせる。</p> <h3 id="javascript"><a href="#javascript">javascript</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmZBPSgQwSspdQeaFpCCo9fPfrrbKfZi1A3j6aW8cY8W6Y"><code>tenki.js</code></a></p> <pre><code class="javascript">const axios = require("axios"); const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/"; const area = "260000"; // Kyoto (getWeatherForecast = async () => { try { const response = await axios.get(`${url}${area}.json`); console.log(response.data); console.log(response.data[0].publishingOffice); console.log(response.data[0].timeSeries[0].areas); for(const area of response.data[0].timeSeries[0].areas){ console.log(`----${area.area.name}----`); for(const weather of area.weathers){ console.log(weather); } } } catch (error) { console.error(error); } })(); </code></pre> <p>terminal:</p> <pre><code class="bash">> node tenki.js </code></pre> <pre><code>> node tenki.js [ { publishingOffice: '京都地方気象台', reportDatetime: '2022-04-26T17:00:00+09:00', timeSeries: [ [Object], [Object], [Object] ] }, { publishingOffice: '京都地方気象台', reportDatetime: '2022-04-26T17:00:00+09:00', timeSeries: [ [Object], [Object] ], tempAverage: { areas: [Array] }, precipAverage: { areas: [Array] } } ] 京都地方気象台 [ { area: { name: '南部', code: '260010' }, weatherCodes: [ '300', '311', '100' ], weathers: [ '雨 所により 夜遅く 雷を伴い 激しく 降る', '雨 昼前 から くもり 後 晴れ 所により 明け方 まで 雷を伴い 激しく 降る', '晴れ' ], winds: [ '南の風 後 南西の風', '南西の風 後 北西の風', '北の風' ] }, { area: { name: '北部', code: '260020' }, weatherCodes: [ '300', '311', '100' ], weathers: [ '雨 所により 夜遅く 雷を伴い 激しく 降る', '雨 昼前 から くもり 後 晴れ 所により 明け方 まで 雷を伴い 激しく 降る', '晴れ' ], winds: [ '南の風 やや強く 海上 では 南の風 強く', '南西の風 後 北東の風  海上 では 南西の風 やや強く', '北の風' ], waves: [ '1.5メートル 後 2メートル', '2メートル 後 1.5メートル', '1 .5メートル' ] } ] ----南部---- 雨 所により 夜遅く 雷を伴い 激しく 降る 雨 昼前 から くもり 後 晴れ 所により 明け方 まで 雷を伴い 激しく 降る 晴れ ----北部---- 雨 所により 夜遅く 雷を伴い 激しく 降る 雨 昼前 から くもり 後 晴れ 所により 明け方 まで 雷を伴い 激しく 降る 晴 </code></pre> <p>これをサンプルにして Javascript を学習する。</p> <h2 id="browserify"><a href="#browserify">browserify</a></h2> <p>node.js で動いたプログラムを browserify によってブラウザで動作するようにする。</p> <p><a target="_blank" rel="nofollow noopener" href="https://browserify.org/">https://browserify.org/</a></p> <blockquote> <p>Browsers don't have the require method defined, but Node.js does. With Browserify you can write code that uses require in the same way that you would use it in Node.</p> </blockquote> <p>インストール<br /> terminal:</p> <pre><code>> npm install -g browserify </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmW7gBLMnDdpEy4rp9NVp5jWnmsFwoxYyvSKA1y5fXtVzd"><code>tenki_2.js</code></a></p> <p>他の言語である println のように使っているコンソール.ログがブラウザでどうなるのかわからないので、require しておいてみる。</p> <pre><code class="javasript">const console = require("console"); const axios = require("axios"); const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/"; const area = "260000"; // Kyoto (getWeatherForecast = async () => { try { const response = await axios.get(`${url}${area}.json`); // console.log(response.data); // console.log(response.data[0].publishingOffice); // console.log(response.data[0].timeSeries[0].areas); for(const area of response.data[0].timeSeries[0].areas){ console.log(`----${area.area.name}----`); for(const weather of area.weathers){ console.log(weather); } } } catch (error) { console.error(error); } })(); </code></pre> <p><code>tenki_2.js</code> をブラウザ仕様の javascript ファイル <code>bundle.js</code> に変換する。</p> <p>terminal:</p> <pre><code class="bash">> browserify tenki_2.js -o bundle.js </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/Qmd3cJNFTft8gbA444XgefmWXFGvY4QECVqYyicQ1dbs5C"><code>bundle.js</code></a><br /> <a target="_blank" rel="nofollow noopener" href="https://pastebin.com/szscGNsR">https://pastebin.com/szscGNsR</a></p> <p>html ファイルを作って、 bundle.js を読み込むようにする。<br /> 例えば tenki という名前でフォルダを作って、index.html, bundle.js を tenki フォルダに配置する。</p> <p><a target="_blank" rel="nofollow noopener" href="https://ipfs.infura.io/ipfs/QmW5oGdEBSJJpazfds7LSqyMttxNYj8BVBphtfJYpP7wWv"><code>index.html</code></a></p> <pre><code class="html"><!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>tenki_area browserify example</title> <script src="bundle.js"></script> </head> <body> </body> </html> </code></pre> <p>chrome ブラウザーで tenki フォルダの中の html ファイルを開く。</p> <p>chrome browser:</p> <p><code>CTRL</code> + <code>shift</code> + <code>i</code></p> <p>DevTools</p> <p><code>index.html</code>:</p> <p><a href="https://crieit.now.sh/upload_images/d6edfe05648ad36e61e2c39bcce18cb06267ee2adf460.JPG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d6edfe05648ad36e61e2c39bcce18cb06267ee2adf460.JPG?mw=700" alt="'chromebrowser developper tools'" /></a></p> tomato tag:crieit.net,2005:PublicArticle/14553 2018-09-27T15:16:50+09:00 2018-10-23T09:24:26+09:00 https://crieit.net/posts/HTML-5bac75d259324 HTMLの文字コード決定プロセス <p>スクレイピングしていたら文字化けしているものがあったので、HTTPでやりとりされるHTMLの文字コード判定が、どのようなプロセスを経て行われているのか調べてみました。</p> <h2 id="HTTPでやりとりするHTMLでの文字コード"><a href="#HTTP%E3%81%A7%E3%82%84%E3%82%8A%E3%81%A8%E3%82%8A%E3%81%99%E3%82%8BHTML%E3%81%A7%E3%81%AE%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89">HTTPでやりとりするHTMLでの文字コード</a></h2> <p>基本的には以下の情報を見ていくようです。</p> <ol> <li>BOM</li> <li>HTTPのContent-Typeヘッダ</li> <li>HTMLのmetaタグ <ul> <li>charset属性</li> <li><code>http-equiv="Content-Type"</code>なもののcontent属性</li> </ul></li> </ol> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://www.w3.org/International/questions/qa-html-encoding-declarations.ja">https://www.w3.org/International/questions/qa-html-encoding-declarations.ja</a></p> <h2 id="axiosの場合"><a href="#axios%E3%81%AE%E5%A0%B4%E5%90%88">axiosの場合</a></h2> <p>元々はnodeでaxiosを使っていて困った部分だったので、axiosで文字コードを考慮してどう処理するかをTypeScriptで書いていきます。</p> <p>axiosはデフォルトでは上の情報はどれも利用されずにutf-8決め打ちでデコードされてしまいます。なのでoptionに<code>responseType: 'arraybuffer'</code>を渡し<code>response.data</code>を<code>Buffer</code>として受け取って処理していきます。</p> <p>最終目標は</p> <pre><code class="typescript">import axios from 'axios'; import iconv = require('iconv-lite'); import * as charset from './charset'; (async () => { const response = await axios.get(url, { responseType: 'arraybuffer' }); const body = iconv.decode(response.data, charset.detect(response)); })() </code></pre> <p>のように使える<code>charset.detect</code>を実装することです。</p> <p>各判定処理ごとに関数にして、決定できなかった場合にはutf-8にフォールバックするようにします。</p> <pre><code class="typescript">import { AxiosResponse } from 'axios'; type Charset = string; type IntermediateResult = Charset | null; export const detect = (res: AxiosResponse): Charset => fromBOM(res.data) || fromHeader(res.headers["content-type"]) || fromMetaTag(res.data) || Charset.UTF8; </code></pre> <p><code>Charset</code>はきちんとやるなら <a target="_blank" rel="nofollow noopener" href="https://www.iana.org/assignments/character-sets/character-sets.xhtml">https://www.iana.org/assignments/character-sets/character-sets.xhtml</a> にあるもののunion typeとかstring enumsとかの方が良いのかもしれません。</p> <h3 id="BOM"><a href="#BOM">BOM</a></h3> <p>BOMはByte Order Markで先頭数バイトを特定のパターンにすることで、ユニコードであることとそのエンコーディング、エンディアンを示すものです。<br /> <a target="_blank" rel="nofollow noopener" href="https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding">https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding</a> から持ってきています。</p> <pre><code class="typescript">// to assert elements as tuple (inferred Array<string | Buffer>) const bomify = ([c, bytes]) => ([c, Buffer.from(bytes)] as [Charset, Buffer]); const BOMS: ReadonlyMap<Charset, Buffer> = new Map([ ['utf-8', [0xEF, 0xBB, 0xBF]], ['utf-16be', [0xFE, 0xFF]], ['utf-16le', [0xFF, 0xFE]], ['utf-7', [0x2B, 0x2F, 0x76, 0x38]], ['utf-7', [0x2B, 0x2F, 0x76, 0x39]], ['utf-7', [0x2B, 0x2F, 0x76, 0x2B]], ['utf-7', [0x2B, 0x2F, 0x76, 0x3F]], ['utf-7', [0x2B, 0x2F, 0x76, 0x38, 0x2D]], ['utf-1', [0xF7, 0x64, 0x4C]], ['utf-ebcdic', [0xDD, 0x73, 0x66, 0x73]], ['scsu', [0x0E, 0xFE, 0xFF]], ['bocu-1', [0xFB, 0xEE, 0x28]], ['gb-18030', [0x84, 0x31, 0x95, 0x33]], ].map(bomify)); export const fromBOM = (buf): IntermediateResult => { const startsWith = (bom) => buf.slice(0, bom.length).equals(bom) for (let [charset, bom] of BOMS) { if (startsWith(bom)) return charset; } return null; } </code></pre> <h3 id="Content-Type Header"><a href="#Content-Type+Header">Content-Type Header</a></h3> <p>Content-Typeヘッダのフォーマットは<a target="_blank" rel="nofollow noopener" href="https://tools.ietf.org/html/rfc7231#section-3.1.1.5">RFC 7231のSection 3.1.1.5</a>で決められています。<br /> それに基づいて実装されている<a target="_blank" rel="nofollow noopener" href="https://github.com/jshttp/content-type">jshttp/content-type</a>を利用します。</p> <pre><code class="typescript">import contentType = require('content-type'); export const fromHeader = (ctype): IntermediateResult => { const res = contentType.parse(ctype); return res.parameters.charset || null; } </code></pre> <h3 id="metaタグ"><a href="#meta%E3%82%BF%E3%82%B0">metaタグ</a></h3> <p>Bufferをasciiにデコードして</p> <ul> <li>metaタグのcharset属性</li> <li><code>http-equiv="Content-Type"</code>なmetaタグのcontent属性</li> </ul> <p>を<a target="_blank" rel="nofollow noopener" href="https://github.com/cheeriojs/cheerio">cheerio</a>を使って探します。</p> <pre><code class="typescript">import cheerio = require('cheerio'); export const fromMetaTag = (buf): DetectionResult => { const $ = cheerio.load(buf.toString('ascii')); let res = $('meta[charset]').attr('charset'); if (res) return res; res = $('meta[http-equiv="Content-Type"]').attr('content'); if (res) return fromHeader(res); return null; } </code></pre> <h3 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h3> <p>以上です。全体像を貼っておきます。</p> <pre><code class="typescript">import { AxiosResponse } from 'axios'; import contentType = require('content-type'); import cheerio = require('cheerio'); type Charset = string; type IntermediateResult = Charset | null; // to assert elements as tuple (inferred Array<string | Buffer>) const bomify = ([c, bytes]) => ([c, Buffer.from(bytes)] as [Charset, Buffer]); const BOMS: ReadonlyMap<Charset, Buffer> = new Map([ ['utf-8', [0xEF, 0xBB, 0xBF]], ['utf-16be', [0xFE, 0xFF]], ['utf-16le', [0xFF, 0xFE]], ['utf-7', [0x2B, 0x2F, 0x76, 0x38]], ['utf-7', [0x2B, 0x2F, 0x76, 0x39]], ['utf-7', [0x2B, 0x2F, 0x76, 0x2B]], ['utf-7', [0x2B, 0x2F, 0x76, 0x3F]], ['utf-7', [0x2B, 0x2F, 0x76, 0x38, 0x2D]], ['utf-1', [0xF7, 0x64, 0x4C]], ['utf-ebcdic', [0xDD, 0x73, 0x66, 0x73]], ['scsu', [0x0E, 0xFE, 0xFF]], ['bocu-1', [0xFB, 0xEE, 0x28]], ['gb-18030', [0x84, 0x31, 0x95, 0x33]], ].map(bomify)); export const fromBOM = (buf): IntermediateResult => { const startsWith = (bom) => buf.slice(0, bom.length).equals(bom) for (let [charset, bom] of BOMS) { if (startsWith(bom)) return charset; } return null; } export const fromHeader = (ctype): IntermediateResult => { const res = contentType.parse(ctype); return res.parameters.charset || null; } export const fromMetaTag = (buf): IntermediateResult => { const $ = cheerio.load(buf.toString('ascii')); let res = $('meta[charset]').attr('charset'); if (res) return res; res = $('meta[http-equiv="Content-Type"]').attr('content'); if (res) return fromHeader(res); return null; } export const detect = (res: AxiosResponse): Charset => fromBOM(res.data) || fromHeader(res.headers["content-type"]) || fromMetaTag(res.data) || 'utf-8'; </code></pre> <h2 id="最後に"><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB">最後に</a></h2> <p>僕は普段主にRubyを使っているので、Rubyの場合どうなのかも気になって少し調べてみたのですが、標準ライブラリの<code>Net::HTTP</code>で<code>Content-Type</code>をハンドルすべきかについての<a target="_blank" rel="nofollow noopener" href="https://bugs.ruby-lang.org/issues/2567">Issueがありました</a>。</p> <p>現実的には複数の方法で違う文字コードとして指定されていたり、実際使われているものと違ったりということもあるようで、絶対に信用できるメタデータというわけではないようです。</p> <p>頻度から分類するアプローチもあるようで、現実的にはこちらの方がうまく動くかもしれません。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/runk/node-chardet">runk/node-chardet</a><br /> データがあれば機械学習の実験課題としてちょうど良さそうですね。</p> <p>最初は雑な正規表現で書いていたのですが、記事を書いているうちに正しいフォーマットはどうなのか気になってRFCを見にいったりして結構勉強になりました。全部utf-8だと嬉しいですね。</p> en30