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