2022年5月24日火曜日

【Python】BeautifulSoup と Pandas を用いて青果物市況情報を自動で取得する

青果物市況情報は農林水産省のページより確認することができます。

データ自体は PDF、HTML のほか、CSV の形式でも提供されているため扱いやすいのですが、そのデータを載せているページが Javascript で URL の遷移を行っているため、データの自動取得が若干しづらくなっています。

スプレッドシートの IMPORTXML 関数で素直に持ってくるようなことは(たぶん)できません。

ならばと GAS(Google Apps Script)を使ってみたところ、おそらくリージョンが米国のどこかになっているので、"The Amazon CloudFront distribution is configured to block access from your country." というメッセージが出てあっさりはじかれました。国内からのアクセスでないと見れない設定なのですね。

そんなわけで、今回 Python を使って青果物市況情報の自動取得を試みました。

ちなみに、農水省のサイトには Web スクレイピングを禁止するような記述は特にないようです。節度あるアクセスであれば問題ないと判断しました。今回の場合であれば、アクセスは1日1回になりますから負荷としては手動で1回閲覧するのと変わりません。


***

東京都中央卸売市場大田市場の最新の青果物市況情報を CSV 形式で取得するスクリプトが以下になります。

(BeautifulSoup や requests といったライブラリが未インストール場合はあらかじめインストールしてください)

import requests
from bs4 import BeautifulSoup
import re
import io
import pandas as pd

#市況情報取得
def getMcData():
    #最新データの日付(dd)取得
    url_1 = "https://www.seisen.maff.go.jp/seisen/bs04b040md001/BS04B040UC010SC999-Evt001.do"
    req_1 = requests.get(url_1)
    soup_1 = BeautifulSoup(req_1.text, "html.parser")
    dd = soup_1.find("a", href=re.compile("s0070"))
    dd = str(dd).replace("<a href=\"javascript:document.getElementById(\'s0070\').submit();\" id=\"s007_Evt001_0\">", "").replace("</a>", "").replace("\n", "").replace(" ", "")
    dd = re.findall(r'\d+', dd)
    dd = str(dd[0]) + str(dd[1].zfill(2)) + str(dd[2].zfill(2))

    #帳票ファイル名(cn)取得
    url_2 = "https://www.seisen.maff.go.jp/seisen/bs04b040md001/BS04B040UC010SC001-Evt001.do?s006.dataDate=" + dd
    req_2 = requests.get(url_2)
    soup_2 = BeautifulSoup(req_2.text, "html.parser")
    cn = soup_2.find_all("a", href=re.compile("form003"))
    cn = str(cn[12]).replace("<a href=\"javascript:chohyoSubmit(\'form003\',\'", "").replace("\');\"> CSV</a>", "") #東京都中央卸売市場大田市場(野菜)
    
    #CSVファイル(mcd)取得
    url_3 = "https://www.seisen.maff.go.jp/seisen/bs04b040md001/BS04B040UC010SC001-Evt004.do?s004.chohyoKanriNo=" + cn
    req_3 = requests.get(url_3)
    mcd = pd.read_csv(io.BytesIO(req_3.content), encoding="shift-jis", sep=",")

    return mcd

print(getMcData())

23行目の cn[12] の数字で市場及び野菜・果物の指定をしていますので適宜変更してください。例えば、東京都中央卸売市場大田市場の果実であれば cn[13]、東京都中央卸売市場豊島市場であれば cn[14] となります(投稿日時点ですのでご確認を)。


ざっくり動きとしては、BeautifulSoup ライブラリを用いた Web スクレイピングで最新データの日付と帳票ファイル名の取得を行い、そこから Pandas ライブラリで CSV ファイルの取得を行っています。


更新は休市の日以外で、時間はファイル名を見るに14:30頃以降のようです。




***

数年前に農業データプラットフォームとして始まった WAGRI では今回取得した青果物市況情報も API として提供されているのですが、やはりそれなりの利用料がかかります。説明等を読むに個人農家の利用は想定してなさそうですね。

WAGRI のようなプラットフォームはさておき、そもそもとしてデータ提供元(青果物市況情報であれば農水省)が使いやすい形でデータを出してくれれば農業におけるデータ利用の裾野も個人レベルで広がっていくんじゃないかと思うのですよね。気象庁の非公式 API のような形で。

せっかく学校でプログラミングの授業が必修化したことですし、農家が個人利用できるようなデータも整えてもらえるといいですね。

スマート農業というと、高額な最新設備や農業機械を導入して……みたいな印象が少なからずあり、そのせいで小規模な経営体ではスルーされがちになっているんじゃないかと思います。

しかしながら、パソコン1台からできることもたくさんあるので、その基盤をいかに整えるかがこれからの数年は重要なんじゃないかと改めて。


新しい植物を入手!


Twitter(@nkkmd)日々更新中です。

2022年5月19日木曜日

徒然じゃない日々(14)

古ホダ木を乾燥気味に管理するのにいいかなぁ、と思って置き始めた新しい保管場所、見事にトリコデルマ菌が発生して撃沈……難しいですねぇ。

いや、この結果はこれまでの経験から予測可能だった気もするのですよ。うーん……まだまだ未熟ですね。

しかし、失敗したら失敗したで出来ることも。せっかくトリコデルマ菌が発生したので、これはこれで増殖・分解して堆肥化を試みてみます。

廃ホダを堆肥化する場合、シイタケ菌オンリーではなくむしろトリコデルマ菌を発生させた方が、より良い(早く?)堆肥になるのではなかろうかと思ったり思わなかったり。どうなのでしょう?むしろ廃棄 → 堆肥化の過程で自ずとトリコデルマ菌か発生しているような気も?何事も勉強、そして実験でございます。

また、肝心の保管場所の方については、小道具で環境を調整しつつ様子を見てみます。

トリコデルマ菌、以前に新ホダ木で発生してつらい思いをしたためトラウマとして心に刻まれていたようで、今回見つけた時にしばらくフリーズしました(笑)。


トリコデルマ菌

こんなに日当たりのいい、雨ざらしの場所にホダ木を置いてはいけません……。


***

新ホダ木の方は非常に良い感じにホダ化が進んでいます。この調子で油断せずに、これからの梅雨と夏場を乗り切りたいと思います。


Twitter(@nkkmd)日々更新中です。

2022年5月16日月曜日

データセットの欠損値・重複値の探索を行う GAS ライブラリを作成・公開しました

スプレッドシートのデータセットの中から欠損値や重複値の探索をする GAS ライブラリを作成、実験的に公開してみました。

例えば、IoT で気温・湿度等のデータを取得した際に、データがうまく取れないタイミングがあったり、逆に二重で取れてしまったりということがそれなりの頻度で起こります。そうしたデータセットにおける欠損値や重複値の探索をするライブラリになります。

大した処理でもないのですが、万単位の行をチェックするとなると便利です(何行まで正常に作動するかは未確認、1〜2万行程度までは動きました)。

もともと自分用に作ったものなので、現状では使用パターンも限られますが、今後もバージョンアップはしていこうと思っています。


スクリプト ID:

1sJCHsjsWlWEJwK9ficycEa9PBCA92P_OI0fgQO1wgm858rQGw7eOqlBO


ライブラリの追加方法:

  1. ライブラリの追加から上記スクリプト ID を入力して「検索」を押下
  2. バージョンを選択、任意の ID を指定して「追加」を押下

バージョンは最新のもの、もしくは「HEAD(開発モード)」を指定してください。

「HEAD(開発モード)」であれば常に最新の状態が維持されますが、バージョンに反映前の変更も加わりますので、予期せぬ不具合が発生する可能性があります(まぁ、最新バージョンでもありますが)。


使い方:

スクリプトの実行方法は、

ライブラリの追加の際に指定した任意の ID.searchData(スプレッドシート ID, シート名, データ型("number" or "date" で指定), 探索スタート行番号, 探索列番号, 上限値, 下限値)

となります。

現状、縦方向の探索のみ可能です。

上下の行の値を比較して上限値を上回る(変動幅 > 上限値)場合は欠損値、下限値の下回る(変動幅 < 下限値)場合は重複値として扱います。望ましい上下の行の値の差が一意の場合は、上限値と下限値を一致させます。

データ型が日時("date")の場合、上限値及び下限値はミリ秒で指定します。1分は 60000、 1時間は 3600000、1日は 86400000 となります。

リファレンスはこちら


例えば、こんな感じのデータセットに対して、



こんな感じでライブラリを使いますと、

function myFunction() {
  const spId = "スプレッドシート ID";
  const shName1 = "シート名1(データセット)";
  const shName2 = "シート名2(結果入力)";

  let result = test.searchData(spId, shName1, "number", 3, 1, 2, 1);

  const sp = SpreadsheetApp.openById(spId);
  const sh = sp.getSheetByName(shName2);
  sh.getRange(1,1, result.length, 3).setValues(result);
}


こんな感じでログが表示されます。



戻り値は、(ログと同様の)結果の2次元配列になりますのでスプレッドシートに書き出すことも可能です。行数は、結果によって変動しますので 戻り値.length で指定します。



***

何度も使う同じような処理はライブラリでまとめていった方が楽だということに今さらながらに気がつきました。

特に汎用性の高そうなものについては、せっかくなので公開して(自分にプレッシャーをかけて笑)いこうと思います。




・関連投稿

2022年5月14日土曜日

【世界一周自転車の旅】7年越しの楽しみな本を予約

かつて Gigazine で世界一周自転車の旅の様子を連載していたチャリダーマン・周藤卓也さんが、満を持して紙の本を出版されるそうです。

学生の頃、連載を毎回楽しみに読んでいました。私が旅行好きになったまさに一因。

その当時、たぶん世界一周ブーム?みたいな感じで世界一周とか海外旅行の様子をブログ等に書いている方がけっこういたのですが、その中でもチャリダーマンさんの記事はどこか落ち着いたというか冷静というか、ありのままをありのままに書いているような印象で、非常に好きだったのですよね。

(あと自転車移動が多い大学だったので自転車での旅というのが気になって笑)

そんなわけで、いつか紙の本を出版する際には連絡がほしいというメールに登録していて、(実は登録したことは忘れていたのですがメールアドレス生きててよかった)、先日実に7年越しにそのメールが届いたのです。

ブレないというのは本当すごいですよねぇ。

それで、今クラウドファンディングされているとのことで早速予約しました。

いやぁ、めちゃくちゃ楽しみです!



ぜひちょっとでも気になった方はサイトの方を見てみてください。


***

レモンキュウリ

メールを登録した日に自分が何をしていたのか Google フォトを遡ってみたら、レモンキュウリを収穫していました。


Twitter(@nkkmd)日々更新中です。

【定期】家計の見直し(2022年5月)

定期的な家計の見直しです。

経営と家計が一体化した家族農業では、家計の最少化が心強い生存基盤になります。

また、家族や自身にとって本当に価値あるものが何なのか、確認する良い機会になります。


・楽天モバイルの0円終了

楽天モバイルの1回線目1GBまで無料が終了との発表がありましたね。

Rakuten Link による無料通話と海外旅行用にサブ回線として持っていたのですが、結局海外には行く前に終わってしまいました。

このままサブ回線として使い続けるか、自宅の光回線の代替とするか、どこかにMNPするか……といろいろ思案した結果、とりあえず解約。移行キャンペーン的なものもあるみたいですが、忘れそうなのでさっぱりと。

また用途ができたらその時に契約しようと思います。

無理に用途を作っても仕方ないですからね。節約のコツは、損得に惑わされず必要なものを必要なだけ、と思っています。

ちなみに、メイン回線の UQ mobile は、「くりこしプランS+5G」にキャンペーンで1年間無料になっている増量オプションⅡ を付け、自宅セット割 インターネットコースで990円/5GB で使っています。通話は半額になる楽天でんわを使用。快適です。


・Visa LINE PAY クレジットカードの2%還元終了

Visa LINE PAY クレジットカードの2%還元が先月末で終了しましたね。3%還元の頃から大変お世話になりました。

今後は三井住友カード ゴールド(NL)、エポスゴールドカード、ビックカメラSuicaカード(Suica 支払い)、au PAY カード(au PAY 支払い)あたりを駆使して1.5%還元を狙っていきたいと思います。


・エポスカード会員向け社債

エポスカード会員向け社債を1口(1万円分)だけ申し込んでみたのですが残念ながら落選。

どうやら20億円分も申し込みがあったそうで、そりゃ落ちるよなと(笑)。

面白そうな取り組みなので今後もあったらちょっと申し込もうかなと思います(たぶん)。


・いろいろ値上がり

ここのところ円安、インフレ、株安……と耳にしつつ、何が何にどう作用しているのかはよく分かっていませんが、生活の中でもいろいろと値上がりはしてきていますね。

まぁ、長らく日本は物価も賃金も停滞していてデフレだデフレだと言われていたので、正常な値上がりはいいことなのしょうけど、最近のは急激ですからね。振り落とされないよう、うまく対応していかねばと思っています。

また、農業の現場では肥料の値上がりがすごいみたいです。みたい、というのはうちではまだ肥料を買うタイミングになっていないので実感がないのですが、次回購入時が怖いですねぇ……。


***

家計の見直しはあくまで節約。使うところではしっかり使い、吝嗇にならないように気をつけていきたいです。

特に、しばらくできていなかった旅行にはしっかりつぎ込んでいきたいですね。


きな粉ときくらげ


・関連投稿



Twitter(@nkkmd)日々更新中です。

2022年5月6日金曜日

徒然じゃない日々(13)

先日、近所ではない農園の方とおしゃべりする機会がありまして、いくつか思うところがありましたので忘れる前にメモを。

ちょうどこの間、近所で経営改善の勉強会をしようという話もあったところで、とても参考になりました。


そちらの農園というのは施設園芸をメインに家族で経営されていて、栽培規模としてはおそらくさわの6〜7倍程度(品目は別)。生産物の品質は高く(とてもおいしい)、実際に産直等での販売ではすぐに完売するそうです。経営状況は良好。

ただ現状の課題として、パートの方を数名雇用しつつも、年間360日以上働く体制になってしまっているとのことです。

そのため今後の方針としては、主に水平方向の多角化によってリスク分散と収益性向上を図り、同時に業務効率化によってある程度休日も確保できるようにしていきたいとのことでした。(ざっくり)


いくつかの農園の事例を見聞きするに、概ねこのくらいの規模感が家族農業の極相(最終段階)なのですかね。

ここからさらに売上等を伸ばしていこうとすると、常雇いによる労働力の確保や組織化を考える必要が出てくるのかもしれません。

しかし、常雇いや組織化となってきますと、経営的な難易度は確実に上がりますし、必要となる能力もまた異なる部分が多そうなので家族農業から地続きの地平として考えていいものなのかというのは疑問に思うところ。

大規模化のトレンドの中で、このハードルを超えないことには生き残るのは難しくなってくるよということなのですかね。うーむ。

この、家族農業の極相にたどり着いてからの判断、舵取り、いろいろと事例を調べてみたいですねぇ。


また、多角化というアプローチについては、非常に共感するところです。

もちろん、一口に多角化と言っても大事なのは具体的に何をやるかですが、先行き不透明な昨今、余裕のあるときにこそ備えておかねばなりません。

小さく試行錯誤を重ね、より良い農業経営の形を模索することもゆとりがあればこそです。


***


唐沢山神社 藤

あしかがフラワーパークは混んでいるので、マイ・フェイバリット・スペース、唐沢山神社の藤を観てきました。


Twitter(@nkkmd)日々更新中です。

2022年5月4日水曜日

GAS で LINE Bot 使用時のユーザー ID、グループ ID を簡単に取得する(ついでにユーザー名も)

LINE Bot 使用時に必要になるユーザー ID、グループ ID ですが、自身のユーザー ID 以外は取得にひと手間必要です。いろいろ取得する方法はあるようですが、GAS が比較的容易そうです。

(自分のものは LINE Developers 内、Channel の Basic settings に記載されています)





ということで、ユーザー ID、グループ ID、それから複数人のユーザー ID を取得する際にどれが誰のものだか分からなくならないようにユーザー名を取得する GAS を作りました。

動きとしては、Bot を含むグループでメッセージを送るとユーザー名、ユーザー ID、グループ ID を取得、スプレッドシートに書き出されます。LINE Bot と一対一のトークの場合はグループ ID は省略されます。

スプレッドシートはまっさらで大丈夫です。それぞれ、1列目にユーザー名、2列目にユーザー ID、3列目にグループ ID が入ります。ユーザー ID は「U」で、グループ ID は「C」で始まります。

スクリプトは以下の通りです。

const ACCESS_TOKEN = "チャンネルアクセストークン";

function doPost(e){
  let json = JSON.parse(e.postData.contents);
  let userId = json.events[0].source.userId;
  let groupId = json.events[0].source.groupId;

  const spst =  SpreadsheetApp.openById("スプレッドシート ID").getSheetByName("シート名");
  let row = spst.getLastRow();

  spst.getRange(row + 1,1).setValue(getUserName(userId));
  spst.getRange(row + 1,2).setValue(userId);
  spst.getRange(row + 1,3).setValue(groupId);
}

function getUserName(userId) {
  const url = "https://api.line.me/v2/bot/profile/" + userId;
  const response = UrlFetchApp.fetch(url, {
              "headers" : {
              "Authorization" : "Bearer " + ACCESS_TOKEN
              }
  });
  return JSON.parse(response.getContentText()).displayName;
}

1行目の「チャンネルアクセストークン」、8行目の「スプレッドシート ID」「シート名」を記載すれば完成です。

このスクリプトをウェブアプリとして公開。URL を LINE Developers で Webhook として設定します。

ちなみに、スクリプトを修正した場合、反映には新バージョンとしてデプロイが必要です。たまに忘れて混乱します(笑)。

これでメッセージを送れば ID 等が取得できます。

取得が済んだら Webhook を切っておきます。(切らないとメッセージを送るたびに延々と ID が書き込まれ続けます)


ツツジ

今年はツツジがきれいに咲きました。


Twitter(@nkkmd)日々更新中です。