# WEBスキルチェック(第三回) WEBスキルチェックの第三回です。一回目、二回目に取り組んだ内容の復習も含まれています。わからなくなったら、リンク先を読み直しながら進めましょう。 [第一回](https://hackmd.io/@tamura2004/HkuiKEFpu) [第二回](https://hackmd.io/@tamura2004/rJYaebH1F) ## webページ ブラウザで`http://web.sofia3dd.net`を表示してみましょう。かわいい動物うらないのページが表示されます。これは教材用に作成したダミーアプリです。 適当な星座を選んで、選ぶボタンを押したり、戻るボタンを押したりして操作してみましょう。 ## dnsプロトコル ブラウザで操作をした場合、幾つかの処理が順に行われます。 - urlからipアドレスを求める(dns) - httpを利用すると決める。ポート番号が80番に定まる - ポート番号80にtcpで接続 - httpのGETメソッドでhtmlファイルを取得する - htmlファイルから画面を描画する 処理をターミナルで実施してみることにより、理解が深まります。まずは、`web.sofia3dd.net`のIPアドレスを調べてみましょう。 `dig`コマンドを使用します。 ``` $ dig web.sofia3dd.net ; <<>> DiG 9.11.3-1ubuntu1.16-Ubuntu <<>> web.sofia3dd.net ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32379 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 65494 ;; QUESTION SECTION: ;web.sofia3dd.net. IN A ;; ANSWER SECTION: web.sofia3dd.net. 171 IN A 104.214.92.146 ;; Query time: 0 msec ;; SERVER: 127.0.0.53#53(127.0.0.53) ;; WHEN: Fri Dec 24 07:58:09 JST 2021 ;; MSG SIZE rcvd: 60 ``` `status: NOERROR`とあるので、エラーがなく情報を取得できたことが分かります。該当が無い場合、`NXDOMAIN`と表示されます。`;; ANSWER SECTION`にIPアドレスが取得されています。Aと記載されているのは、Aレコードという種類であることを示します。IPアドレスは`104.214.92.146`です。 結果だけを確認したい場合は、`+short`オプションを使用します。 ``` $ dig web.sofia3dd.net +short 104.214.92.146 ``` ## IPアドレス IPアドレスとは、サーバごとに割り当てられた数字の組でした。8bitの数字(0〜255)の4つ組を、ピリオドで区切って表記します。 例: 192.168.0.10 システムを管理する側になると、割り当てられたIPアドレスの範囲から、個々のサーバにIPアドレスを割り当てたり、接続許可IPの設定を行ったりしますので、もう少し詳しい理解が必要です。 ## CIDR表記 クラウドから、192.168.200.180/30というIPアドレス範囲の割当を受けたとします。この場合、実際に割り当てることのできるIPはどう考えれば良いのでしょうか。(業務上良く発生するケースです) まず、これはCIDR(サイダー)表記と呼ばれるもので、`/`の後ろの30は、「ipアドレスを32bitの2進数にした時に、先頭30bitが左側のipと一致しているなら同じグループ」という意味です。このグループのことを、サブネットと呼びます。 さて、「ipアドレスを32bitの2進数にする」というのはどうすれば良いのでしょうか。2進数変換がしたくなった場合の方法についていくつか紹介します。 ## bcコマンド linuxには計算機機能である`bc`があり、また、obaseオプションで結果を2進数出力しますので、以下のように10進数から2進数を求めることができます。数字を入れると変換して、quitコマンドで終了します。 ``` $ bc obase=2 192 11000000 168 10101000 200 11001000 180 10110100 quit ``` |10進数|2進数|桁数| |:--|:--|:--| |192|11000000|8| |168|10101000|8| |200|11001000|8| |180|101101<span style="color:red">00</span>|6| 左から8桁+8桁+8桁+6桁=30桁ですから、1番目〜3番目の数字が一致していて、4番目の数字である180と、6bit分一致しているものが同じグループのipアドレスになります。赤字部分の2bitは何でも良いので、00~11の4つが同じグループになります。 bcコマンドで`ibase=2`とすると、2進数から10進数に変換できます。180と先頭6bitが一致している4つの数字は以下の通りであることが分かります。 ``` $ bc ibase=2 10110100 180 10110101 181 10110110 182 10110111 183 quit ``` |2進数|10進数| |:--|:--| |10110100|180| |10110101|181| |10110110|182| |10110111|183| 仕組み上、連続した数字の区間になることが分かります。今回は/30でしたので4つでしたが、例えば/12であれば、約100万個になります。その場合、一つ一つ求めず、先頭と末尾を求めれば良いことが分かります。 ## ruby 何か別の処理と組み合わせる場合、プログラムで変換します。例としてrubyの`irb`コマンドを利用してみましょう。 ``` $ irb > "%08b" % 192 => "11000000" > 0b11000000 => 192 > exit ``` `%08b`はフォーマット指定子などと呼ばれ、08はゼロ埋め8桁、bは二進数であることを示します。ruby以外にもc言語、Javaなどからも利用できます。 また、数字の先頭に0bを付けると2進数であることを示します。(2進数のリテラル表記) プログラムですので、以下のようにdigコマンドの出力結果を直接加工することが出来ます。プログラムが1行ですので、ワンライナーと呼ぶことがあります。 ``` $ dig web.sofia3dd.net +short | ruby -ne 'puts $_.split(/\./).map{|v| "%08b" % v}' 01101000 11010110 01011100 10010010 ``` ## 範囲指定 CIDR表記はIPアドレスの範囲を示すことを学びました。例に挙げた割当範囲以外に、クラウドへの接続許可(ファイアウォール機能)設定などでよく使われます。 特に一つのIPアドレスしか許可しない場合、/32を使用します。 10.251.124.129/32 ちょっと奇妙な感じがしますが、すべてのbitが一致するIPは自分自身以外に無いのでこれで良いことが分かります。特定の端末のみ接続を許可する場合など、良く使われます。 ## TCPポート httpプロトコルは80番ポートです。 接続可能であるか、`nc`コマンドで確かめてみましょう。 先ほど調べたIPアドレスは外部用でしたが、接続には内部用IPアドレスを使っています。 ``` $ nc -v -w 1 10.0.0.9 80 Connection to 10.0.0.9 80 port [tcp/http] succeeded! ``` 接続可能であることが分かります。 ctrl+cで中断します。 81番ポートなど、接続できないポートで試した場合のエラーメッセージも確認しておきましょう。 ## httpプロトコル それではターミナルから、webページの情報を取得してみましょう。`curl`コマンドを使用します。 ``` $ curl http://10.0.0.9 ``` ```html <!DOCTYPE html> <html> <head> <title>Uranai</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="csrf-param" content="authenticity_token" /> <meta name="csrf-token" content="kC3OWZHx5aEoPzNRWnHJEVMMHrcsaLFukZ6ZBN5GPI_QXcoSh9qDDVHRykY6NmscYDKX5-AR68jqMxCzrDib3w" /> <link rel="stylesheet" media="all" href="/assets/application.debug-fb38ac3110a68d8ef317814b97fbbe73e0e4a4945d98cc33585fa811ebe62df3.css" data-turbolinks-track="reload" /> <script src="/packs/js/application-e421b4aa3f716bebdab1.js" data-turbolinks-track="reload"></script> </head> <body> <header> <nav class="navbar navbar-dark bg-dark fixed-top"> <div class="container-fluid"> <span class="navbar-brand h1 mb-0">かわいい動物占い</span> </div> </nav> </header> <div class="container mt-8"> <img width="400" src="/assets/seiza-d4e63050ea13dd8c88b1b7d135d8848eddfee3a02b74a40174b2134f349cd21b.png" /> <h3 class="mt-2">あなたの星座は?</h3> <form action="/animals/seiza" accept-charset="UTF-8" method="get"> <div class="field"> <select class="form-select form-select-lg mt-4" name="seiza" id="seiza"><option value="1">おひつじ座</option> <option value="2">おうし座</option> <option value="3">ふたご座</option> <option value="4">かに座</option> <option value="5">しし座</option> <option value="6">おとめ座</option> <option value="7">てんびん座</option> <option value="8">さそり座</option> <option value="9">いて座</option> <option value="10">やぎ座</option> <option value="11">みずがめ座</option> <option value="12">うお座</option></select> </div> <div class="actions"> <input type="submit" name="commit" value="選ぶ" class="btn btn-lg btn-primary mt-2" data-disable-with="選ぶ" /> </div> </form> </div> </body> </html> ``` サーバのIPアドレスの前の`http://`は、httpプロトコル、すなわち80番ポートで接続することを意味します。 `https://`と指定した場合、httpsプロトコル、すなわち443番ポートで接続します。 webページの内容が`html`形式で取得できました。<>で囲まれた要素はタグといい、それぞれ意味を持ちます。例えば`<img>`タグは画像を表します。取得したhtmlファイルから探してみましょう。 ブラウザのwebページと、htmlを見比べてみましょう。画面上の表示と一致する部分があることが分かります。また、プルダウンメニューの選択肢のように、表示されない部分の情報も持っていることが分かります。 ## GETパラメータ ターミナルから、さそり座を選んだ時の情報を取得してみましょう。httpでパラメータを送るには、GET方式と、POST方式がありますが、このアプリケーションではGET方式で送信します。 ``` $ curl http://10.0.0.9/animals/seiza?seiza=8 ``` ```html <!DOCTYPE html> <html> <head> <title>Uranai</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="csrf-param" content="authenticity_token" /> <meta name="csrf-token" content="VV49sAbT7jQ1o-8OBjSRgbVz_ib0GMtTZW7gP19uSpl_Awj6l9f4qGaZo_ZPHsJgEq_r86QbjOOEekcFB-4oRw" /> <link rel="stylesheet" media="all" href="/assets/application.debug-fb38ac3110a68d8ef317814b97fbbe73e0e4a4945d98cc33585fa811ebe62df3.css" data-turbolinks-track="reload" /> <script src="/packs/js/application-e421b4aa3f716bebdab1.js" data-turbolinks-track="reload"></script> </head> <body> <header> <nav class="navbar navbar-dark bg-dark fixed-top"> <div class="container-fluid"> <span class="navbar-brand h1 mb-0">かわいい動物占い</span> </div> </nav> </header> <div class="container mt-8"> <img width="400" src="/assets/animal_dance-c8dc2aafd5e62b65849b31bb9d56e9b620cb92e4a5be23f1975b2a148192ffa4.png" /> <h3 class="mt-2">さそり座のあなたにおすすめの動物は</h3> <div class="display-1"> ももんが </div> <h3>です!</h3> <div class="btn btn-primary"> <a class="btn btn-lg bth-primary" style="color: inherit" href="/animals/omikuji">戻る</a> </div> </div> </body> </html> ``` ブラウザと同じように回答がhtmlで取得できました。また、星座番号のパラメータとして8を指定しています。これは、htmlファイルの、optionタグから確認しています。 ## スクレイピング 「XXのあなたにおすすめの動物はXXです」の部分だけ取得してみましょう。前回も使用した`grep`コマンドを使います。 検索ワードですが、`<h3`などが使えそうです。 ``` curl http://10.0.0.9/animals/seiza/seiza=8 | grep <h3 bash: そのようなファイルやディレクトリはありません: h3 ``` 奇妙なエラーメッセージが出ました。h3がファイル名として認識されています。これは、`<`記号が、ファイル入力のリダイレクト記号として認識されているからです(前回か前々回のどこかで使用しています。忘れていたら読み直してみよう) このような引数に特殊記号やスペースが含まれる場合、検索ワードを`"`で囲います。 ``` $ curl http://10.0.0.9/animals/seiza/seiza=8 | grep "<h3" <h3 class="mt-2">さそり座のあなたにおすすめの動物は</h3> <h3>です!</h3> ``` 動物が抜けてしまいました。動物名の行は、前後にタグが無く、取得が難しそうです。 grepコマンドは、一致した行に続く何行かを追加で出力するオプションがあります。 `-A`オプションを試してみましょう。 ``` $ curl http://10.0.0.9/animals/seiza\?seiza\=8 | grep "<h3" -A 4 <h3 class="mt-2">さそり座のあなたにおすすめの動物は</h3> <div class="display-1"> ももんが </div> <h3>です!</h3> <div class="btn btn-primary"> <a class="btn btn-lg bth-primary" style="color: inherit" href="/animals/omikuji">戻る</a> </div> ``` すこしだけうまく行きましたが、「です」を含んだ行からも4行取り出されて余分です。先頭のみ一致するよう、"<h3 class"までを検索ワードとしてみましょう。 ``` $ curl http://10.0.0.9/animals/seiza\?seiza\=8 | grep "<h3 class" -A 4 <h3 class="mt-2">さそり座のあなたにおすすめの動物は</h3> <div class="display-1"> ももんが </div> <h3>です!</h3> </div> ``` 必要な部分のみ取り出すことができました。このように、webページから必要な情報のみ取り出すことを、スクレイピングと呼ぶことがあります。 今回は、"<h3 class"という、ページ内で一つしか無いとは限らない値を検索ワードとしました。これはあまり良い方式ではありません。なぜなら、ページ内に別のh3タグ(中見出し)が増えると、そちらも検索対象になってしまうからです。 htmlのタグの要素には、ページ内で一意(一つしか無い)であるid属性など持たせることができるので、そちらを利用することが望ましい方式です。ただし、これはwebアプリケーション側が埋め込む必要があります。例:`<h3 id="seizaAnimalAnswer">` 一般的なwebページ(グーグル)で、id属性の利用を確認してみましょう。 ``` $ curl www.google.com | grep " id=" 省略 ``` 多数の利用があることが分かります。 id属性のアルファベット順に並べて表示してみましょう。コマンドでの対応が難しい部分ですので、rubyプログラムのワンライナーで取り出してみましょう。 `/\sid=[\s\>\;]*/`が呪文のようで意味が取りにくいですね。これは前にも少しやった正規表現です。 |正規表現|意味| |:--|:--| |\s|空白かtab文字に一致| |id=|そのまま`id=`に一致| |[\s>;]|空白文字か`>`か`;`のいずれかに一致| |[^\s>;]|空白文字か`>`か`;`**以外**に一致| |[^\s>;]*|*は直前の一つ以上の繰り返し| まとめると、「空白に続いてid=とあり、空白でも>でも;でも無い文字まで」が取り出されます。 また、-nオプションを付けると、入力を1行づつ分けて$_変数に渡してくれます。 scanメソッドは、正規表現を受けて、一致箇所を配列で返します。 ``` $ curl www.google.com | ruby -ne 'puts $_.scan /\sid=[^\s>;]*/' | sort id="WqQANb" id="footer" id="gac_scont" id="gbv" id="hplogo" id="lga" id="lgpd" id="mngb" id="prm" id="tsuid1" id='tsuid1' id=gb_70 id=gbar id=gbe id=gbf id=gbn id=guser ``` ## json htmlは表示用の情報も含んでいるため、情報を取得するためにはひと工夫必要でした。情報のみをやり取りしたい場合、`json`フォーマットが利用されています。APIサーバの応答電文などに利用されています。 試してみましょう。 ``` $ curl http://httpbin.org/json ``` ```json { "slideshow": { "author": "Yours Truly", "date": "date of publication", "slides": [ { "title": "Wake up to WonderWidgets!", "type": "all" }, { "items": [ "Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets" ], "title": "Overview", "type": "all" } ], "title": "Sample Slide Show" } } ``` サンプルのスライドショー情報がjsonフォーマットで取得できました。一つのスライドショー情報が、2つのスライド情報を持っていることが見て取れます。 プログラムからは、オブジェクトに変換して利用することが多いと思います。rubyでは以下のように標準ライブラリを利用してハッシュオブジェクトに変換して取扱ができます。 ``` curl http://httpbin.org/json | ruby -r json -e 'puts JSON.parse(STDIN.read)["slideshow"]["slides"]' {"title"=>"Wake up to WonderWidgets!", "type"=>"all"} {"items"=>["Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets"], "title"=>"Overview", "type"=>"all"} ``` jsonは、プログラム言語の中でそのままオブジェクトの表記(リテラル)として利用できます。リダイレクトでファイルに保存して、プログラム部分を追加することにより、そのまま実行できるプログラムにすることもできます。 ``` $ curl http://httpbin.org/json > app.rb $ vim app.rb ``` ワンライナーではオブジェクトへの取り込みがわかりにくく、書きにくいと思ったら、以下のようにプログラムファイルを作成して実施することもできます。 `show.rb` ```ruby:show.rb obj = { "slideshow": { "author": "Yours Truly", "date": "date of publication", "slides": [ { "title": "Wake up to WonderWidgets!", "type": "all" }, { "items": [ "Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets" ], "title": "Overview", "type": "all" } ], "title": "Sample Slide Show" } } puts obj[:slideshow][:slides] ``` 先頭の`obj=`と末尾の`puts`以降を追加しています。これでプログラムとして成立しています。 ``` $ ruby show.rb {:title=>"Wake up to WonderWidgets!", "type"=>"all"} {:items=>["Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets"], "title"=>"Overview", "type"=>"all"} ``` ほぼ同じ結果が出力されることが分かります。(厳密にはキー情報の型が文字列とシンボルで異なっていますが、実用上の差はありません) vimのコマンドを覚えていますか。 |キー|操作| |:--|:--| |gg|ファイル先頭へ| |O|カーソルの上に1行追加して入力| |esc|入力モード終了| |G|ファイル末尾へ| |o|カーソルの下へ1行追加して入力| |:wq|上書き保存して終了| 先程のjsonファイルは、オブジェクトを一つ持つのみでしたが、一般には複数のオブジェクト(コレクション)を扱います。サンプルを確認してみましょう。 ``` $ curl 10.0.0.9:8005/sample.json ``` ```json= [ { "color": "red", "items": [ { "name": "apple", "price": 100 }, { "name": "cherry", "price": 55 } ] }, { "color": "yellow", "items": [ { "name": "banana", "price": 300 } ] }, { "color": "blue" } ] ``` オブジェクトを3つ含んだjsonファイルです。それぞれのオブジェクトは"color=色"属性を持ち、"items=物品"コレクションを配下に持ちます。物品はname=名前と、価格=price情報を持つようです。 色毎に、保有する物品の価格の合計を集計してみましょう。物品を保有しない場合(color=blueなど)はゼロとします。 ```ruby= require "json" JSON.parse(STDIN.read).each do |group| total = 0 group["items"]&.each do |item| total += item["price"] end puts "#{group['color']} #{total}" end ``` |記述|説明| |:--|:--| |require "json"|jsonライブラリをインポートしています。JSONオブジェクトを初期化して、parseメソッドを利用可能にしています。| |JSON.parse|文字列をjsonとして読み込んでruby言語のオブジェクトに変換します| |STDIN.read|標準入力からすべて読みこんで文字列として返却します| |group["items"]&.each|"items"はnilの場合があります。メソッド呼び出しに際し、&.で呼び出すと、nilの場合は単にスキップしてくれます。これをしないと、nilに対してメソッドを呼び出してしまい、大抵の場合はエラーになります(undefined method xx of nil Nic class)| ``` $ curl 10.0.0.9:8005/sample.json | ruby total.rb red 155 yellow 300 blue 0 ``` ## 課題1 CIDR表記のサブネット、192.168.168.168/30について、所属するIPアドレスを、小さい順に1行づつ回答ファイルに入力して下さい。 課題1: 201/ip.txt(10点) 回答例 192.168.100.1 192.168.100.2 192.168.100.3 192.168.100.4 ## 課題2 CIDR表記のサブネット、192.168.0.0/6について、サブネットに属する先頭と末尾のIPを、先頭>末尾の順に2行で回答ファイルに入力して下さい。 課題1: 201/cidr.txt(10点) 回答例 192.168.0.0 192.168.0.255 ## 課題3 以下の3つのurlについて、ipアドレスを調べて下さい。うち2つについて、同じネットワークに所属しています。 ap.ogisui.name db.ogisui.name web.sofia3dd.net 回答用のテキストファイルに、上記url記載順に1行づつ、url名、ipアドレスを、半角スペース1文字区切りで入力して下さい。 また、4行目に、2つが所属するネットワークを、上位から一致するbitはすべてネットワーク部とみなして、CIDR表記で入力してください。 課題2:202/dns.txt(10点) 回答例 ap.ogisui.name 192.168.0.1 db.ogisui.name 192.168.0.2 web.sofia3dd.net 10.200.0.1 192.168.0.0/30 ## 課題4 以下の3つのIPアドレスについて、サーバ種別を調査して下さい。 10.0.0.6 10.0.0.7 10.0.0.9 それぞれについて、webサーバ、apサーバ、dbサーバ、mailサーバ、dnsサーバのいずれかであることがわかっています。 また、mailサーバは25番ポート、dnsサーバは53番ポート、webサーバは80番ポート、apサーバは3000番ポート、dbサーバは5432番ポートが接続可能であることもわかっています。 回答用のテキストファイルに、上記ip記載順に1行づつ、ipアドレス、サーバ種別を、半角スペース1文字区切りで入力してください。 なお、サーバ種別は、英半角小文字で、mail,dns,web,ap,dbのいずれかとします。 課題3: 202/tcp.txt(10点) 回答例 10.0.0.6 mail 10.0.0.7 dns 10.0.0.9 ftp ## 課題5 番号の付いた1番から6番の島があり、橋を使って行き来することができます。橋以外の方法で行き来することはできません。 `[課題4で調べたwebサーバのipアドレス]/8004/[島番号]`にhttpでアクセスすると、ある島から橋で直接行ける島を確認することができます。存在しない島番号を入れると、スタックトレースを出力します(分かりにくいエラーになります) 操作例 curl 10.0.0.100/8004/100 99 98 現在1番の島にいるとして、なるべく**多くの**橋を使って6番の島に行きたいと思います。ただし、同じ島を二回以上通ったり、同じ橋を二回以上使ってはいけません。 回答用のテキストファイルに、1行で、通る島(1番と6番を含む)の番号を、半角スペース1文字区切りで1行で入力して下さい。 課題5: 203/curl.txt(10点) 回答例 1 3 2 5 4 6 ## 課題6 競技プログラミングサイト、AtCoderのトップページ`https://atcoder.jp`のhtmlファイルから、参照できる記事(posts)の番号を調べて、回答用のテキストファイルに、小さい順に1行づつ入力して下さい。 記事の番号とは、htmlのaタグのhref要素について、href="/posts/[番号]"とある箇所の番号のことです。 課題6: 203/atcoder.txt(10点) 回答例 10 20 21 100 200 201 ## 課題7 課題4のwebサーバのポート8005番から、jsonファイルが取得できます。 ``` $ curl [ipアドレス]:8005/index.json ``` これは利用者情報の配列になっており、"name"属性が氏名を表します。同姓同名がいる利用者の氏名を、文字列の昇順に並べて、回答用ファイルに入力して下さい。 課題7: 203/duplicate.txt(10点) 回答例 adam yamada bob marly conan great ## 課題8 課題7で取得したjsonファイルについて、各利用者は、複数の口座("accounts")を持っています。金額は"qtty"属性です。口座の合計金額が最も大きい利用者と、合計金額を、半角スペース区切りで、回答用ファイルに1行で入力して下さい。金額が大きい場合、三桁毎にカンマ(,)で区切って下さい。 課題8: 203/max.txt(10点) 回答例 adam yamada 100,000,012 ## 課題9 以下の仕様に従いテストデータを作成せよ。 |No|仕様| |:--|:--| |1|jsonフォーマットとする| |2|形式は`curl 10.0.0.9:8005/sample.json`に準拠| |3|1番から100番の100件のオブジェクトを持つ| |4|"color"属性は、番号に合わせて"color001"~"color100"とする| |5|"items"の個数は、番号を10で割った余りとする。番号が10の倍数なら"items"属性を持たない| |6|"items"は同じオブジェクト内で1番から始まるアイテム番号を持つ| |7|"items"の"name"属性は、"item" + オブジェクト番号 + アイテム番号とする。 オブジェクト番号は3桁ゼロ埋め、アイテム番号は2桁ゼロ埋めとする。| |8|"items"の"price"属性は、オブジェクト番号 * 10 + アイテム番号とする| 課題9: 203/testdata.json(10点) ## 課題10 課題9で作成したtestdata.jsonファイルに含まれる、物品=itemの価格=priceの合計を、回答ファイルに一行で入力せよ。 課題10: 203/total.txt(10点)