tag:crieit.net,2005:https://crieit.net/tags/Python/feed 「Python」の記事 - Crieit Crieitでタグ「Python」に投稿された最近の記事 2023-06-04T01:27:32+09:00 https://crieit.net/tags/Python/feed tag:crieit.net,2005:PublicArticle/18441 2023-05-29T09:25:32+09:00 2023-06-04T01:27:32+09:00 https://crieit.net/posts/GeForce-GPU-35-RINNA-AI ミドルGPUで35億の女(RINNA AI)のローカル実行 ことはじめ <p>RINNA社が <a target="_blank" rel="nofollow noopener" href="https://prtimes.jp/main/html/rd/p/000000042.000070041.html">5月17日</a> に、 <a target="_blank" rel="nofollow noopener" href="https://huggingface.co/rinna/japanese-gpt-neox-3.6b-instruction-sft">rinna/japanese-gpt-neox-3.6b-instruction-sft</a> という、対話言語 AI モデルを公開したと、プレスリリースを出した。<br /> その後さらに <a target="_blank" rel="nofollow noopener" href="https://rinna.co.jp/news/2023/05/20220531.html">5月31日</a>、人間の評価による強化学習したモデル <a target="_blank" rel="nofollow noopener" href="https://huggingface.co/rinna/japanese-gpt-neox-3.6b-instruction-ppo">rinna/japanese-gpt-neox-3.6b-instruction-ppo</a> を追加リリースしている。</p> <p>この AI モデルは、ご家庭にあるミドルレンジ GPU でもサクサク動く性能だったので、 AI 系に明るくない人にも導入できるように、詳しい導入手順を紹介しようと思う。<br /> <a href="https://crieit.now.sh/upload_images/4df8d093a01c34ee29df245fd53b00c06475320f4437d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4df8d093a01c34ee29df245fd53b00c06475320f4437d.gif?mw=700" alt="rinna-3.6b-chat-00-00-split.gif" /></a></p> <p>こう言うのって、登場して直ぐ情報がホットなうちに記事にすることに価値があって、こんな 1~2週間経ってから記事を書いたって誰にも届かないんだろうけど、とりあえず書いてみる。</p> <p>この界隈の情報はコンテクストが高いというか、あんまりイチから導入方法を説明している記事って目にしないので、多少は需要があるかなと。</p> <p>はじめに断っておくが、 Chat-GPT や 新しいBing 等と比べると数段落ちる回答精度となる。<br /> ただ、個人がお手軽に購入できる程度の GPU を使って、完全にローカルで動かす事ができるという点が、一種の浪漫だ。</p> <h3 id="前提条件"><a href="#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6">前提条件</a></h3> <p>以下のスペックを持った PC。</p> <ul> <li>OS は Windows 10 または Windows 11</li> <li>GeForce RTX 3060 以上、 VRAM 12GB 以上が載った NVIDIA の GPU <ul> <li>GPU ドライバはインストール済み</li> <li>具体的には 2023年5月現在 以下のいずれかモデル <ul> <li>GeForce RTX 3060 (12 GB)</li> <li>GeForce RTX 3080 (12 GB)</li> <li>GeForce RTX 3090</li> <li>GeForce RTX 3090 Ti</li> <li>GeForce RTX 4060 Ti (16 GB)</li> <li>GeForce RTX 4070</li> <li>GeForce RTX 4070 Ti</li> <li>GeForce RTX 4080</li> <li>GeForce RTX 4090</li> </ul></li> <li>3070 Ti, 3060 Ti, 3060 (8GB) だと VRAM が足りないので、多分動かすとエラーになる</li> </ul></li> </ul> <h2><strong>導入手順</strong></h2> <h3 id="Python のインストール"><a href="#Python+%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Python のインストール</a></h3> <p>Python と呼ばれるプログラミング言語(の、「ランタイム」と呼ばれるもの)をインストールする。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.python.org/downloads/windows/">https://www.python.org/downloads/windows/</a></p> <p>こちらのページから、 Python Releases for Windows の Latest Python 3 Release のバージョン (以下のスクショ だと 3.11.3) を確認し、下のリストからそのバージョンの "Windows Installer (64-bit)" をダウンロード。</p> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-01-01.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-01-01-300x225.png" alt="" /></a><br /> <a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-01-02.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-01-02-300x225.png" alt="" /></a></p> <p>ダウンロードしたインストーラーを実行し、デフォルトの "Use admin privileges when installing py.exe" にチェックが入っていることを確認して、インストールを実行。</p> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-01-03.jpg"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-01-03-300x185.jpg" alt="" /></a></p> <h3 id="Python 仮想環境の作成"><a href="#Python+%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E3%81%AE%E4%BD%9C%E6%88%90">Python 仮想環境の作成</a></h3> <p>先ほどインストールした Python に、追加で必要なツールをダウンロードする環境(仮想環境と呼ばれるもの)を作成する。</p> <p>まず、これから環境を作成していく元のフォルダをエクスプローラで開き、アドレスバーに <code>powershell</code> と入力する。</p> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-01-04.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-01-04-300x201.png" alt="" /></a></p> <p>すると、黒い画面が立ち上がるので、以下のコマンドを実行。</p> <pre><code>py -3 -m venv rinna-japanese-gpt-chat </code></pre> <p>すると、 <code>rinna-japanese-gpt-chat</code> という名前のフォルダ作成されているはず。</p> <h3 id="vscode のインストール"><a href="#vscode+%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">vscode のインストール</a></h3> <p>Visual Studio Code (vscode) という、プログラム用のテキストエディタ(コードエディタ)をインストールする。</p> <p><a target="_blank" rel="nofollow noopener" href="https://code.visualstudio.com/">https://code.visualstudio.com/</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-03-01.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-03-01-300x225.png" alt="" /></a></p> <p>Download for Windows からインストーラをダウンロードし実行する。</p> <p>途中、インストールタスクの選択で、「エクスプローラーのディレクトリ コンテキスト メニューに [Code で開く] アクションを追加する」を追加しておく。</p> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-03-02.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-03-02-300x233.png" alt="" /></a></p> <h3 id="スクリプトファイルのダウンロード"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89">スクリプトファイルのダウンロード</a></h3> <p>実行するスクリプトファイルをダウンロード。</p> <p>以下のページの右上らへんにある、「Download ZIP」をクリックしてダウンロードした ZIP を解凍し、中にある <code>rinna_gradio_chat.py</code> を、 先ほど作成された <code>rinna-japanese-gpt-chat</code> という名前のフォルダの中に置く。</p> <p><a target="_blank" rel="nofollow noopener" href="https://gist.github.com/advanceboy/717fde162a6f9ccb592f04898f0aacc1">https://gist.github.com/advanceboy/717fde162a6f9ccb592f04898f0aacc1</a></p> <h3 id="vscode の環境のセットアップ"><a href="#vscode+%E3%81%AE%E7%92%B0%E5%A2%83%E3%81%AE%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97">vscode の環境のセットアップ</a></h3> <p>後半の作業を簡単にするため、 vscode の環境を整える。</p> <p>先ほど作成された <code>rinna-japanese-gpt-chat</code> という名前のフォルダを右クリックして(Windows 11 の場合は、更に「その他のオプションを表示を」クリックして)、 「Code で開く」 を選択する。</p> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-03-03.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-03-03-300x225.png" alt="" /></a></p> <p>すると、 vscode が立ち上がる (フォルダを信用しますか?的な Trust 何ちゃらの画面が出たら、 yes を選択しておく)。</p> <p><code>Ctrl + Shift + E</code> キーを押すと開く vscode 上の Explorer ペインで、先ほどダウンロードした <code>.py</code> ファイルを選択する。<br /> すると、 "Do you want to install the recommented 'Python' Extension..." と聞かれるので、 <code>Install</code> を選択。</p> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-03-04.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-03-04-300x106.png" alt="" /></a></p> <p>インストールが終わったら、再び vscode 上の Explorer ペインで <code>.py</code> ファイルを選択して <code>Ctrl+@</code> キーを押し、 PowerShell ターミナルを表示させる。</p> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-03-05.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-03-05-300x145.png" alt="" /></a></p> <p>このとき、表示される TERMINAL ウィンドウでは <code>(rinna-japanese-gpt-chat) PS</code> と先頭に表示されるようになっているはずだが、もしそれが表示されない場合は、このターミナル上で <code>.\Scripts\Activate.ps1</code> と入力しておく。</p> <h3 id="Python の依存パッケージ (CUDA 対応 CUDA) の確認とインストール"><a href="#Python+%E3%81%AE%E4%BE%9D%E5%AD%98%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8+%28CUDA+%E5%AF%BE%E5%BF%9C+CUDA%29+%E3%81%AE%E7%A2%BA%E8%AA%8D%E3%81%A8%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Python の依存パッケージ (CUDA 対応 CUDA) の確認とインストール</a></h3> <p>Python で AI を実行するため、 AI 関連ライブラリーを、先ほど作成した Python の仮想環境内にインストールする。</p> <p>まず、 PyTorch という機械学習ライブラリのページに飛ぶ。<br /> <a target="_blank" rel="nofollow noopener" href="https://pytorch.org/get-started/locally/">https://pytorch.org/get-started/locally/</a></p> <p>以下のような組み合わせの選択肢を選び、表示されたコマンドを前述の vscode のターミナル上( <code>(rinna-japanese-gpt-chat) PS</code> と先頭に表示される状態)で実行する。</p> <ul> <li>PyTorch Build: Stable</li> <li>Your OS: Windows</li> <li>Package: Pip</li> <li>Language: Python</li> <li>Compute Platform: CUDA の最新版</li> </ul> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-04-01.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-04-01-300x210.png" alt="" /></a></p> <p>例えば、 CUDA 11.8 の環境をインストールする場合は、以下のようなコマンドになると思われる。</p> <pre><code>pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 </code></pre> <p>その後、残りの依存ライブラリもインストールしておく。</p> <pre><code>pip3 install ipython sentencepiece transformers accelerate gradio </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-04-02.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-04-02-300x60.png" alt="" /></a></p> <p><code>pip3 install</code> の実行順を逆にすると、 CUDA に対応していない torch がインストールされてしまい、後々エラーになってしまうので注意。</p> <h3 id="CUDA ツールキットのインストール"><a href="#CUDA+%E3%83%84%E3%83%BC%E3%83%AB%E3%82%AD%E3%83%83%E3%83%88%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">CUDA ツールキットのインストール</a></h3> <p>最後に、 CUDA と呼ばれる NVIDIA の GPU 上で AI を効率よく実行するためのツールキットをインストールする。</p> <p>以下の CUDA Toolkit Archive のページを開き、 前項で "Compute Platform" で選択したバージョンの CUDA Toolkit をダウンロードし、インストールする。</p> <p><a target="_blank" rel="nofollow noopener" href="https://developer.nvidia.com/cuda-toolkit-archive">https://developer.nvidia.com/cuda-toolkit-archive</a></p> <p>最新版ではなく、前項の PyTorch ライブラリのダウンロード時に選択したバージョンの CUDA をダウンロードする必要がある点に注意が必要だ。</p> <p>例えば、 2023年5月現在、 CUDA Toolkit の最新版は 12.1.1 だが、 前述の PyTorch ライブラリが対応する CUDA が 11.8 までなので、 CUDA Toolkit 11.8.0 をダウンロード&インストールする。</p> <p><a target="_blank" rel="nofollow noopener" href="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-05-01.png"><img src="https://aquasoftware.net/blog/wp-content/uploads/2023/05/rinna-3.6b-chat-05-01-300x224.png" alt="" /></a></p> <h2><strong>実行手順</strong></h2> <p>vscode のターミナル上( <code>(rinna-japanese-gpt-chat) PS</code> と先頭に表示される状態)で、以下のように実行する。</p> <pre><code>python .\rinna_gradio_chat.py </code></pre> <p>しばらくすると、ターミナルにローカルで立ち上がったサーバーの URL <a target="_blank" rel="nofollow noopener" href="http://127.0.0.1:7860">http://127.0.0.1:7860</a> が表示されるので、 Ctrl キーを押しながらクリックして、ブラウザでアクセスする。</p> <p><a href="https://crieit.now.sh/upload_images/4df8d093a01c34ee29df245fd53b00c06475320f4437d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4df8d093a01c34ee29df245fd53b00c06475320f4437d.gif?mw=700" alt="rinna-3.6b-chat-00-00-split.gif" /></a></p> <p>あとはお好きなようにいじくり倒すだけだ。</p> <p>前述の動画は生成部分も含めて等倍速だが、ミドルレンジの GPU でもそれなりの速度で生成できていることがわかる。</p> <p>停止させたい場合は、ターミナル上で <code>Ctrl + C</code> キーを押そう。</p> <h2><strong>補足</strong></h2> <h3 id="生成速度"><a href="#%E7%94%9F%E6%88%90%E9%80%9F%E5%BA%A6">生成速度</a></h3> <p>ChatGPT などでは元々生成速度が速いので気になりにくいが、 英語と比べて日本語テキストの生成は遅く、同じ情報量で生成時間も利用料金も数倍かかってしまう。<br /> その理由を雑に説明すると、「トークン」と呼ばれる学習・生成の単位が、英語では単語単位となる一方で、日本語は文字単位となっているためだ。</p> <p>一方、りんなさんこと japanese-gpt-neox-3.6b では、単語間にスペースが存在しない日本語でも、ある程度単語ごとにトークン化されてから処理されるので、生成も文字単位では無く単語単位となり、処理効率が大幅に向上しているのだろうと考えられる。<br /> ChatGPT などが存在しても、国産ないし国内向けの AI モデルが必要とされる理由の一つに、こういった背景があるようだ。</p> <h3 id="対応GPU"><a href="#%E5%AF%BE%E5%BF%9CGPU">対応GPU</a></h3> <p>対応GPUを RTX30 世代以降に限っているのは、VRAM の利用を節約するために bfloat16 量子化して動かしているのだが、この bloat16 での効率的な計算に対応しているのが Ampere 以降世代の GPU となるためだ。</p> <p>もし、 RTX 10, 20 の GPU で動かしてみたい場合、</p> <p><a target="_blank" rel="nofollow noopener" href="https://gist.github.com/advanceboy/717fde162a6f9ccb592f04898f0aacc1#file-rinna_gradio_chat-py-L35">https://gist.github.com/advanceboy/717fde162a6f9ccb592f04898f0aacc1#file-rinna_gradio_chat-py-L35</a></p> <pre><code class="python">torch_dtype = torch.bfloat16 </code></pre> <p>この部分を、搭載 VRAM に応じて <code>torch_dtype = torch.float16</code> や <code>torch_dtype = torch.float32</code> に書き換えれば上手く動くかもしれない。</p> <h3 id="CUI版"><a href="#CUI%E7%89%88">CUI版</a></h3> <p>わざわざブラウザ立ちあげず CUI 上でチャットしたい場合は、上記のスクリプトの替わりに以下のスクリプトを使うと良いだろう。</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">rinna_chat_streaming.pyrinna/japanese-gpt-neox-3.6b-instruction-sft を使ったチャット UI のサンプル実装です。 transformers.TextIteratorStreamer API を利用して、 ChatGPT のように生成したテキストを少しずつ表示し、ユーザー体験を向上させています。<a target="_blank" rel="nofollow noopener" href="https://t.co/tXtoN952un">https://t.co/tXtoN952un</a></p>— ながいの as a サービス (@longer_n) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/longer_n/status/1659595062496612354?ref_src=twsrc%5Etfw">May 19, 2023</a></blockquote> advanceboy tag:crieit.net,2005:PublicArticle/18315 2022-11-10T19:08:50+09:00 2023-01-14T21:46:42+09:00 https://crieit.net/posts/Youtube-Un-programme-Ruby-merdique-qui-rassemble-des-informations-sur-les-vid-os-youtube-en-utilisant-l-algorithme-youtube-merdique グレートクソアルゴリズム | くだらない youtube アルゴリズムを使用して youtube ビデオに関する情報を収集するくだらない Ruby プログラム <p><a target="_blank" rel="nofollow noopener" href="https://crieit-net.translate.goog/posts/Youtube-Un-programme-Ruby-merdique-qui-rassemble-des-informations-sur-les-vid-os-youtube-en-utilisant-l-algorithme-youtube-merdique?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=ja&_x_tr_pto=wapp">translate</a></p> <p><a href="https://crieit.net/posts/c2b7c645c32fda0b2cffd3aea91d6a01#狂ったアルゴリズムは チートされる">クソアルゴリズム</a></p> <p><a href="https://crieit.now.sh/upload_images/1e2d4057a6bad42b7364c8cd67d12d3b636b9e02e07f5.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1e2d4057a6bad42b7364c8cd67d12d3b636b9e02e07f5.png?mw=700" alt="" /></a></p> <h1 id="クソアルゴリズムにたいして、クソアルゴリズムを利用して、必要なものだけを選別していくものを書いてみる。"><a href="#%E3%82%AF%E3%82%BD%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%81%AB%E3%81%9F%E3%81%84%E3%81%97%E3%81%A6%E3%80%81%E3%82%AF%E3%82%BD%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6%E3%80%81%E5%BF%85%E8%A6%81%E3%81%AA%E3%82%82%E3%81%AE%E3%81%A0%E3%81%91%E3%82%92%E9%81%B8%E5%88%A5%E3%81%97%E3%81%A6%E3%81%84%E3%81%8F%E3%82%82%E3%81%AE%E3%82%92%E6%9B%B8%E3%81%84%E3%81%A6%E3%81%BF%E3%82%8B%E3%80%82">クソアルゴリズムにたいして、クソアルゴリズムを利用して、必要なものだけを選別していくものを書いてみる。</a></h1> <p>え、突然なに ? と思うかもしれないが、やっぱり怒ってるんだな。<br /> <a target="_blank" rel="nofollow noopener" target="_blank" rel="nofollow noopener" href="http://www.zariganiworks.co.jp/korejanairobo/">これじゃないロボ</a>ってわかるかなぁ。</p> <blockquote> <p>欲しかったロボはこれじゃない!世界中から子供たちの悲痛な叫びが聞こえる情操教育玩具。グッドデザイン賞受賞の伝説的玩具。<br /> コレジャナイロボ(The Original Model)<br /> <a target="_blank" rel="nofollow noopener" href="https://www.assiston.co.jp/1595">https://www.assiston.co.jp/1595</a></p> </blockquote> <p>わかんないと思うな。</p> <h2 id="コレジャナイ ユーチューブ"><a href="#%E3%82%B3%E3%83%AC%E3%82%B8%E3%83%A3%E3%83%8A%E3%82%A4+%E3%83%A6%E3%83%BC%E3%83%81%E3%83%A5%E3%83%BC%E3%83%96">コレジャナイ ユーチューブ</a></h2> <p>何を怒っているか整理すると、Youtube 検索結果について。</p> <p>簡単に言うと、探したいものがあって、探してるときに、あなたの探してるものと関係ありそうなもの教えてあげる的に頼んでもない集合知をほいって添えられて、それを断るすべがないということについて。</p> <p>探しているものがはっきりしていて、その言葉で検索かけているときに、ほいっ、あなたの前回見た動画から他の人が見たのこれだから、こんなの面白いみたいよ、どぞー !! って検索結果に混ぜられるの意味あると思うのか ?? まともに考えて。それ、おすすめ映画をレコメンドするアルゴリズムだよね。それ、バカでしかないからやめてほしいんだ。</p> <h2 id="それ、バカでしかないからやめてほしいんだ youtube . . ."><a href="#%E3%81%9D%E3%82%8C%E3%80%81%E3%83%90%E3%82%AB%E3%81%A7%E3%81%97%E3%81%8B%E3%81%AA%E3%81%84%E3%81%8B%E3%82%89%E3%82%84%E3%82%81%E3%81%A6%E3%81%BB%E3%81%97%E3%81%84%E3%82%93%E3%81%A0+youtube+.+.+.">それ、バカでしかないからやめてほしいんだ youtube . . .</a></h2> <p>例えば、<code>ransomware</code> というキーワードで検索したとして、それと前回たまたま見た何かの動画とは全く関係ない趣向で、今検索してるのに、じゃあこれもって一言も <code>ransomware</code> のことなんて発言しない youtuber のたくさん視聴された関連動画を検索結果に混ぜてくるのって、「機械学習してるからー」てことを人間が配慮してあげないとしたら、意味不明のバカでしかない。</p> <p>意味不明のバカでしかない . . .</p> <p>意味不明のバカな結果を出すアルゴリズムを権威的に出してくるって、意味不明なバカレベルである。だから、やめて、と思うだけなんだな。<br /> 他人の行動も、過去の自分のトレンドも全く関係がない TPO が読めないアルゴリズムって、ただの邪魔だ、ということ。<br /> そんなことは当たり前過ぎるのに、なぜか当然のように諦めさせらるとっても不毛なシステムだ。<br /> これがなんでもかんでも Collaborative filtering 。</p> <p>この配慮のない他人の行動をどんなときにも当てはめようとしてくる様式をクソアルゴリズムと呼ばずにはおれない。</p> <p>でも、クソとかバカとかいうのも、どうにもならないわかりきったことで、単に Google が正しくキュレーションされたものより、てっとりばやく消費される季節ネタのようなバズを見えるとこに置いた方が広告の流入になるという方針なだけで、そういった正攻法はかつて創業者によって「情報の精度が落ちる要因」とされているので、クソなことをわかってやっていて、かつて 2000 年代に蔓延したアホみたいなインデックス型のサーチエンジン並みのクオリティを実現するアルゴリズムを新参のカウンターとして、知的に駆逐した彼ら google 自身が「今」作っているということ。</p> <p>The Age of PageRank is Over<br /> 09 Nov, 2022<br /> Vladimir Prelovac<br /> CEO, Kagi Inc.<br /> <a target="_blank" rel="nofollow noopener" href="https://blog.kagi.com/age-pagerank-over">https://blog.kagi.com/age-pagerank-over</a></p> <p>もちろん、そんな 20 年以上レイドバックしたテクは 22 年以上前の<a href="https://crieit.net/posts/c2b7c645c32fda0b2cffd3aea91d6a01">板フロート掲示板王子</a>によってチートされている。「クソをクソだと見抜けない人が使っている」ということが、クソの臭い嗅ぎ王子には見透かされたと言っていい。たぶん、世界中同じような状況じゃないかと思う。だって、結局古いんだもん。</p> <p>というところまででクソアルゴリズムを悪く言うのはここまでにして、じゃあ、どうすればいいの ?<br /> 自分の決めたキーワードとの関連はどうやって判断するのか ? を考えてみる。</p> <h2 id="キーワードと youtube 動画の相関は、タイトルにキーワードが含まれるか ? だけで判断するということにする。"><a href="#%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89%E3%81%A8+youtube+%E5%8B%95%E7%94%BB%E3%81%AE%E7%9B%B8%E9%96%A2%E3%81%AF%E3%80%81%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB%E3%81%AB%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89%E3%81%8C%E5%90%AB%E3%81%BE%E3%82%8C%E3%82%8B%E3%81%8B+%3F+%E3%81%A0%E3%81%91%E3%81%A7%E5%88%A4%E6%96%AD%E3%81%99%E3%82%8B%E3%81%A8%E3%81%84%E3%81%86%E3%81%93%E3%81%A8%E3%81%AB%E3%81%99%E3%82%8B%E3%80%82">キーワードと youtube 動画の相関は、タイトルにキーワードが含まれるか ? だけで判断するということにする。</a></h2> <p>含まれていたら、関連動画としてリストに追加するし、含まれていなければそれ以上関係性を考慮しない。これだけのストレートなルールを設定する。<br /> なので、<strong>youtube 動画のタイトルが web ページのデータ上のどこにあるのかを割り出すことが必要</strong>。</p> <p>キーワードで youtube 検索するには、</p> <p><a target="_blank" rel="nofollow noopener" target="_blank" rel="nofollow noopener" href="https://www.youtube.com/results?search_query=ransomware">https://www.youtube.com/results?search_query=ransomware</a></p> <p>で、get する。そうすると、検索結果を表示するリダイレクトが youtube ページで行われる。</p> <p><a href="https://crieit.now.sh/upload_images/c7f4777602b710c4097ff1781f2c2303636c61830680c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c7f4777602b710c4097ff1781f2c2303636c61830680c.png?mw=700" alt="" /></a></p> <p>この行為を Ruby コードで書くと、</p> <h3 id="code : 01"><a href="#code+%3A+01">code : 01</a></h3> <pre><code class="ruby">require 'uri' require 'net/http' words = "ransomware" keywords = URI.encode_www_form(search_query: words) target = 'https://www.youtube.com/results?' << keywords resp_0 = Net::HTTP.get_response(URI.parse(target)) </code></pre> <p>ページのなかの、<code><script></code> のうちの1つに検索結果の情報が詰まっている。</p> <p><code><script></code> というタグはいくつもあって、その <strong>34 番目</strong>が検索結果の JSON に該当するよ。<br /> <strong>44 番目</strong>になったかも ?</p> <h3 id="code : 02"><a href="#code+%3A+02">code : 02</a></h3> <pre><code class="ruby">require 'nokogiri' doc = Nokogiri::HTML.parse(resp_0.body, nil,'utf-8') script_tag = doc.css('script') json_str = "" script_tag.each_with_index {|element,i| if i == 33 then json_str = element.to_s[58..-11] #<script nonce="fX_rKtuwcvo7T-wFeZz4CQ">var ytInitialData = end } doc = nil </code></pre> <p><strong>34 番目</strong>の <code><script></code> に入っているものを取り出すと、 JSON データ構造としては余計なスクリプトが含まれているので、後に JSON としてパースするのに邪魔になるので、<code>element.to_s[58..-11]</code> というように、ノードからテキストにして、インデックスを使ってスライスして、JSON データとして扱える strings にします。この youtube レクチャーを参考にしましたよ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://youtu.be/QNLBBGWEQ3Q">Python Web Scraping: JSON in SCRIPT tags : John Watson Rooney</a></p> <p><strong>Python code:</strong><br /> <a href="https://crieit.now.sh/upload_images/26d0de6370fbe42940027436f916009663709fc93d713.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/26d0de6370fbe42940027436f916009663709fc93d713.png?mw=700" alt="image" /></a><br /> <a href="https://crieit.now.sh/upload_images/9bbcc2323c41efdc25de88ec00c1cf2463709f2ca78bc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9bbcc2323c41efdc25de88ec00c1cf2463709f2ca78bc.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/u89yc">https://rentry.co/u89yc</a><br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/t94yo">https://rentry.co/t94yo</a></p> <h3 id="code : 03"><a href="#code+%3A+03">code : 03</a></h3> <pre><code class="ruby">require 'json' begin script33 = JSON.parse(json_str) rescue => e puts e # return nil end </code></pre> <p>JSON を parse するとは hash にするということなので、key と value のひたすら折り重なる廻廊となる、key も value(s) もあらかじめ知ってたら、スムーズだけれども、この JSON のデータ構造は知らない、知りたくないという場合は、いったん JSON をテキストファイルにして、vim エディターでよく見る。必要なら編集してキーワードサーチして、じっと見る。必要なら何時間も、何日間も見る。<br /> 大体わかったら、JSON らしくないレギュラーエクスプレッションズで処理できる。<br /> 取り出したいのは videoId と title 。</p> <p>videoId は、url 上ではこうなっている。<br /> <code>https://www.youtube.com/watch?v=</code> + videoId<br /> なので基本的な url から videoId を取り出すのは、<code>watch?v=</code> 以降の <strong>11 文字</strong>を切り出したらいい。</p> <p>rf.<br /> <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/3452546/how-do-i-get-the-youtube-video-id-from-a-url">stackoverflow.com : How do I get the youtube video-id from a URL</a></p> <p>これが基本事項だけども、以下は JSON データから <code>"videoId"="●●●●●●●●●●●"</code> というところから抜き出していくコードになっている。</p> <h3 id="code : 04"><a href="#code+%3A+04">code : 04</a></h3> <pre><code class="ruby">script33 = JSON.parse(json_str) # JSON to Hash videoid_list = [] # temp work space for youtube 'video-id' s videotitle_list = [] # temp work space for youtube 'video title' s script33.each do |y,x| if y == 'contents' x.each do |yy,xx| match = xx.to_s.match(/(\"videoId\"=\>.{13})/) if match != nil temp = $&[11..-1] videoid_list.push(temp) while $'.match(/(\"videoId\"=\>[^\[].{12})/) != nil do videoid_list.push($&[11..-1]) end end match2 = xx.to_s.match(/\"title\"=\>\{\"runs.+?\[\{\"text\"\=\>(.*?)\}\],/) if match2 != nil temp = $1 videotitle_list.push(temp) while $'.match(/\"title\"=\>\{\"runs.+?\[\{\"text\"=\>(.*?)\}\],/) != nil do videotitle_list.push($1) end end end end end </code></pre> <p>youtube でキーワード検索した結果の <code>videoId</code> と、 <strong>title ぽいもの</strong>が、いくつづつかそれぞれ配列に入る。<br /> <code>videoId</code>については以下のページの一番最初のほうで少し書いたので参考にしてほしい。</p> <blockquote> <p><strong>アルゴリズムはチートされる 注目と広告、アテンション エコノミー / attention economy</strong><br /> <a href="https://crieit.net/posts/c2b7c645c32fda0b2cffd3aea91d6a01">https://crieit.net/posts/c2b7c645c32fda0b2cffd3aea91d6a01</a></p> </blockquote> <p>ここからは、データは重複していく可能性があることに気をつけていく。<br /> <strong>title ぽいもの</strong>は、いちばん最後に <strong>title じゃないもの</strong>が配列にプッシュされているので取り除いておく。これは、Hash の処理で取り出さずにレギュラーエクスプレッションズの文字列処理で条件を書いてスキャンしたので起こったことなので、きっちり Hash で取り出せばうまいこといくと思われる。ただ、今回はレギュラーエクスプレッションズの処理にした。</p> <h3 id="code : 05"><a href="#code+%3A+05">code : 05</a></h3> <pre><code class="ruby">videoid_list.uniq! videotitle_list.pop # trash videotitle_list.uniq! </code></pre> <p>strings の値、それぞれの配列の値、並び方は、とにかくよーく確かめてね。<br /> 確かめないと、以後の行程で全く意味ないからね。</p> <h3 id="code : 06"><a href="#code+%3A+06">code : 06</a></h3> <pre><code class="ruby">id_list = [] title_list = [] videoid_list.each_with_index {|content,ind| if videotitle_list[ind] != nil # puts &quot;-&quot;*20 # puts &quot;#{ind} https://www.youtube.com./watch?v=#{content[1..-2]}&quot; # puts videotitle_list[ind] mmmm = videotitle_list[ind].match(/#{words}/i) if mmmm != nil id_list.push(content) title_list.push(videotitle_list[ind]) end else # puts &quot;-&quot;*20 # puts &quot;no title found&quot; # puts &quot;#{ind} https://www.youtube.com./watch?v=#{content[1..-2]}&quot; end } videoid_list.clear videotitle_list.clear </code></pre> <p>検索した結果のタイトルにキーワードにした <code>ransomware</code> が含まれていればリストに追加するし、キーワードが含まれていない場合は無関係という判断でリストから外します。<br /> 残すリストをそれぞれの配列 <code>id_list = []</code> <code>title_list = []</code> に追加していく。</p> <h3 id="code : 07"><a href="#code+%3A+07">code : 07</a></h3> <pre><code class="ruby">id_list = id_list.zip(title_list) </code></pre> <p>zip してひとまとめにしておく。</p> <p>こうなってるかな。</p> <h3 id="code : 08"><a href="#code+%3A+08">code : 08</a></h3> <pre><code class="ruby">id_list.each_with_index do |list,ind| puts "#{ind}: videoId => #{list[0]}" puts "#{ind}: title => #{list[1]}" end </code></pre> <p>じゃあ、まず、この第一回目の検索の結果を使って、10 スレッドづつ https get するようにしてテスト。</p> <h3 id="code : 09"><a href="#code+%3A+09">code : 09</a></h3> <pre><code class="ruby">#mute = Mutex.new counter = 0 db_counter = 0 while id_list.size > 0 && counter < 20000 do counter += 1 threads = [] 10.times do |k| if id_list.size > 0 threads << Thread.new do #mute.synchronize do tempwork = id_list.shift if tempwork == nil next end target = "https://www.youtube.com/watch?v=" << tempwork[0][1..-2] id_title_list = work(target,words) if id_title_list != nil id_list.concat(id_title_list) end #end end end end threads.each(&:join) id_list.uniq! puts id_list.size end </code></pre> <h3 id="code : 10"><a href="#code+%3A+10">code : 10</a></h3> <p>work 関数<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/dzyu8">https://rentry.co/dzyu8</a></p> <p>work 関数は https get から始まる <strong>code : 01</strong> ~ <strong>code : 04</strong> とよく似ているが、今度は最初の https get で得たキーワード検索結果の JSON とは違っているので、<code><script></code> の順番も違い、<strong>41 番目</strong>の <code><script></code> から JSON データをとってきている。<br /> ということで当然 JSON のデータ構造も、キーワード検索結果の <strong>code : 04</strong> ものとは別物なので、そこから videoId や title の値をスクレイプするレギュラーエクスプレッションズも新たなものになっている。</p> <p><strong>44 番目</strong>になったかも ?</p> <h3 id="code : 11"><a href="#code+%3A+11">code : 11</a></h3> <p><strong>code : 01</strong> ~ <strong>code : 10</strong> までを全部まとめて、さらに SQLite3 データベースに保存していくようにするとこうなる。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/359r5">https://rentry.co/359r5</a></p> <p>ここまでで、ようやく半分。Step 1 として、これを補完する Step 2。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/b4ugy">https://rentry.co/b4ugy</a></p> <p>これで、全部の半分。<br /> 並べて見ると</p> <h4 id="Step1"><a href="#Step1">Step1</a></h4> <pre><code class="ruby"># encoding: UTF-8 require 'net/http' require 'uri' require 'sqlite3' require 'time' require 'json' require 'nokogiri' SQL =<<EOS create table youtube ( id INTEGER PRIMARY KEY, videoid text, chan_id text, publ_id text, title text ); EOS system("mkdir" ,"youtube__") db = SQLite3::Database.open("./youtube__/youtube.db") db.execute(SQL) words = "ransomware" keywords = URI.encode_www_form(search_query: words) target = 'https://www.youtube.com/results?' << keywords resp_0 = Net::HTTP.get_response(URI.parse(target)) doc = Nokogiri::HTML.parse(resp_0.body, nil,'utf-8') script_tag = doc.css('script') json_str = "" script_tag.each_with_index {|element,i| if i == 33 then json_str = element.to_s[58..-11] #<script nonce="fX_rKtuwcvo7T-wFeZz4CQ">var ytInitialData = end } doc = nil script33 = JSON.parse(json_str) videoid_list = [] videotitle_list = [] script33.each do |y,x| if y == 'contents' x.each do |yy,xx| match = xx.to_s.match(/(\"videoId\"=\>.{13})/) if match != nil temp = $&[11..-1] videoid_list.push(temp) while $'.match(/(\"videoId\"=\>[^\[].{12})/) != nil do videoid_list.push($&[11..-1]) end end match2 = xx.to_s.match(/\"title\"=\>\{\"runs.+?\[\{\"text\"=\>(.*?)\}\],/) if match2 != nil temp = $1 videotitle_list.push(temp) while $'.match(/\"title\"\=\>\{\"runs.+?\[\{\"text\"\=\>(.*?)\}\],/) != nil do videotitle_list.push($1) end end end end end videoid_list.uniq! videotitle_list.pop # trash scan videotitle_list.uniq! id_list = [] title_list = [] videoid_list.each_with_index {|content,ind| if videotitle_list[ind] != nil # puts &quot;-&quot;*20 # puts &quot;#{ind} https://www.youtube.com./watch?v=#{content[1..-2]}&quot; # puts videotitle_list[ind] mmmm = videotitle_list[ind].match(/#{words}/i) if mmmm != nil id_list.push(content) title_list.push(videotitle_list[ind]) end else # puts &quot;-&quot;*20 # puts &quot;no title found&quot; # puts &quot;#{ind} https://www.youtube.com./watch?v=#{content[1..-2]}&quot; end } videoid_list.clear videotitle_list.clear id_list = id_list.zip(title_list) def work(target,words) begin resp_1 = Net::HTTP.get_response(URI.parse(target)) rescue => e puts e sleep 1 return nil end doc = Nokogiri::HTML.parse(resp_1.body, nil,'utf-8') script_tag = doc.css('script') json_str = "" script_tag.each_with_index {|element,i| if i == 40 then json_str = element.to_s[58..-11] end } script_tag = nil title_tag = doc.css('title') doc = nil mmmm = title_tag[0].to_s.match(/#{words}/i) if mmmm == nil return nil end title_tag = nil begin script40 = JSON.parse(json_str) rescue => e puts e return nil end videoid_list2 = [] videotitle_list2 = [] script40.each {|y,x| if y.to_s == "contents" match1 = x.to_s.match(/\{\"title\"=\>\{\"runs\"=\>\[\{\"text\"=\>\"(.*?)\"/) if match1 != nil # puts&quot;&quot; # puts&quot;-&quot;*30 # puts $1 # puts $~ # puts&quot;-&quot;*30 tempstrings = $' while tempstrings.match(/\"title\"=\>\{\"accessibility\"=\>\{\"accessibilityData\"=\>\{\"label\"=\>"(.*?)\"\}\},/) do if $0 == nil break end # puts&quot; _&quot;*20 # puts&quot;&quot; # puts $1 tempstrings = $' videotitle_list2.push($1) match_videoid = /\"commandMetadata\"=\>\{\"webCommandMetadata\"=\>\{\"url\"=\>\"\/watch\?v=(.{11}\"),/ =~ $' if match_videoid != nil # puts (&quot;\&quot;&quot; + $1) videoid_list2.push("\"" + $1) end end else # puts &quot;-&quot;*30 # puts y x # puts &quot;can't find the title&quot; next end end } videoid_list2.uniq! videotitle_list2.uniq! videoid_list3 = [] videotitle_list3 = [] videoid_list2.each_with_index {|content,ind| if videotitle_list2[ind] == nil # puts &quot;-&quot;*30 # puts &quot;error&quot; # puts ind,content # puts &quot;https://www.youtube.com./watch?v=#{content[1..-2]}&quot; # puts &quot;-&quot;*30 next end mmmm = videotitle_list2[ind].match(/#{words}/i) if mmmm == nil # puts &quot;-&quot;*30 # puts ind,content # puts videotitle_list2[ind] # puts &quot;skip&quot; next end #puts "-"*30 #puts ind,content videoid_list3.push(content[0..-1]) #puts "https://www.youtube.com./watch?v=#{content[1..-2]}" #puts videotitle_list2[ind] videotitle_list3.push(videotitle_list2[ind]) } videoid_list2.clear videotitle_list2.clear ziped_list = videoid_list3.zip(videotitle_list3) videoid_list3.clear # ziped_list.each_with_index do |list,ind| # puts &quot;#{ind}: #{list[0]}&quot; # puts &quot;#{ind}: #{list[1]}&quot; # end return ziped_list end #youtube = Struct.new("Youtube", :videoid, :title, :date) #youtube_data = youtube.new("video_id","title","date") #mute = Mutex.new counter = 0 db_counter = 0 while id_list.size > 0 && counter < 20000 do counter += 1 threads = [] 10.times do |k| if id_list.size > 0 threads << Thread.new do #mute.synchronize do tempwork = id_list.shift if tempwork == nil next end target = "https://www.youtube.com/watch?v=" << tempwork[0][1..-2] id_title_list = work(target,words) if id_title_list != nil id_list.concat(id_title_list) end #end end end end threads.each(&:join) id_list.uniq! db.transaction do id_list.each_with_index {|data,num| if num == db_counter v_id = data[0] title = data[1].delete("\t\r\n") sth = db.prepare("insert into youtube (id,videoid,title) values(?,?,?)") sth.execute(db_counter,v_id,title) db_counter += 1 end } end puts id_list.size end </code></pre> <h4 id="Step 2"><a href="#Step+2">Step 2</a></h4> <pre><code class="ruby"># encoding: UTF-8 require 'net/http' require 'uri' require 'sqlite3' require 'time' require 'json' require 'nokogiri' # ./youtube__/youtube.db # #SQL =<<EOS #create table youtube ( # id INTEGER PRIMARY KEY AUTOINCREMENT, # videoid text, # chan_id text, # publ_id text, # title text # ); #EOS db = SQLite3::Database.open("./youtube__/youtube.db") def working(target,id_pack) puts target begin resp_0 = Net::HTTP.get_response(URI.parse(target)) rescue =>e puts e.message sleep 1 return nil end doc = Nokogiri::HTML.parse(resp_0.body, nil,'utf-8') #title_tag = doc.css('title') script_tag = doc.css('script') json_str = "" script_tag.each_with_index {|element,i| if i == 40 then json_str = element.to_s[58..-11] #<script nonce="fX_rKtuwcvo7T-wFeZz4CQ">var ytInitialData = end } doc = nil begin script40 = JSON.parse(json_str) rescue => e puts e return nil end ids = id_pack.new("videoid","publisheddate","channelid","title") script40.each {|y,x| if y.to_s == "contents" puts"-"*30 puts "URL: #{target}" ids.vi = target.sub("https://www.youtube.com/watch?v=","") match_date = x.to_s.match(/\"dateText\"=\>\{\"simpleText\"=\>\"(.{10})/) if match_date != nil ids.da = match_date[1] ch_id = $'.match(/\"browseId\"=\>\"(.*?)\",/) if ch_id != nil #puts "channelId: #{$1}" ids.ch = $1 end end match1 = x.to_s.match(/\{\"title\"=\>\{\"runs\"=\>\[\{\"text\"=\>\"(.*?)\"/) if match1 != nil # puts&quot;&quot; # puts&quot;-&quot;*30 #puts "title: #{$1}" ids.ti = $1 # puts $~ end end } #struct data return ids end threads = [] iiii = 0 mute = Mutex.new id_pack = Struct.new("Id_pack",:vi,:da,:ch,:ti) lastid = db.execute("SELECT id FROM youtube order by id DESC limit 1") db.execute("SELECT id,videoid FROM youtube").each do |videoid| row = videoid[1] num = videoid[0] puts "#{row} #{num}" str1 = row.gsub("\"","") if iiii < 10 && num < lastid[0][0] iiii += 1 threads << Thread.new do target = 'https://www.youtube.com/watch?v=' << str1 ids = working(target,id_pack) if ids != nil mute.synchronize do db.transaction do v_id = str1 date = ids.da chid = ids.ch title = ids.ti sth = db.prepare("update youtube set chan_id=?, publ_id=? where id=?") sth.execute(chid,date,num) end end end end else iiii = 0 target = 'https://www.youtube.com/watch?v=' << str1 ids = working(target,id_pack) if ids != nil mute.synchronize do db.transaction do v_id = str1 date = ids.da chid = ids.ch title = ids.ti sth = db.prepare("update youtube set chan_id=?, publ_id=? where id=?") sth.execute(chid,date,num) end end threads.each(&:join) end end end exit </code></pre> <p>Step 1 で ransomware というキーワードで検索して、タイトルのなかに ransomware という言葉が含まれる youtube 動画の videoId , title の情報が youtube というデータベースのテーブルに保存されます。</p> <p>Step 2 で タイトルのなかに ransomware という言葉が含まれる youtube 動画の youtube というデータベースのテーブルから読み出された youtubeId をもとに、channnel id , published された日付が youtube テーブルに追記されます。</p> <p><strong>youtube table</strong></p> <pre><code> id INTEGER PRIMARY KEY, videoid text, chan_id text, publ_id text, title text </code></pre> <p>こういうデータベースができあがるようになりました。<br /> タイトルと年月日時で、年代の古いものから並べるなどに使えるデータです。<br /> Step 1 , 2 は、ひとつにまとめることができますね。<br /> ひとつにまとめて、さらに動画の長さのデータもあればいいと思います。</p> <p>youtube での動画の長さは、</p> <pre><code>"duration": "PT4M13S" </code></pre> <p>というように埋め込まれているようです。<code>PT</code> から始まって分と秒で表されています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/dyxuo">https://rentry.co/dyxuo</a></p> tomato tag:crieit.net,2005:PublicArticle/18275 2022-08-11T09:30:57+09:00 2022-08-11T22:46:08+09:00 https://crieit.net/posts/ConMas-Gateway-2 ConMas Gateway スクリプトのデバッグ (2) <p>ConMas Gateway スクリプトのテスト、デバッグをどうする、というテーマの続編です。</p> <p>前編 <a href="https://crieit.net/posts/ConMas-Gateway-1">ConMas Gateway スクリプトのデバッグ (1)</a></p> <p>結論は「スクリプト単体でテストをしましょう」です。<br /> 端末(i-Reporter)や ConMas Gateway を使うと何かと不便なので、テスト、デバッグの9割をスクリプト単体でテストをし、最後に端末と ConMas Gateway を使ったテストをしましょう、ということです。</p> <p>このような、スクリプトを例として解説を進めます。<br /> <a href="https://crieit.now.sh/upload_images/8cb3d1ab854cf71d0668d52dd6b7a8cf62f32f9c851c3.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8cb3d1ab854cf71d0668d52dd6b7a8cf62f32f9c851c3.PNG?mw=700" alt="image" /></a></p> <h2 id="準備"><a href="#%E6%BA%96%E5%82%99">準備</a></h2> <p>テストに必要となるデータの雛形を取得します。</p> <p>Gateway が稼働するサーバ上のディレクトリ action 下のファイルを以下のようにしておきます。</p> <blockquote> <p>Gateway は端末からのリクエストを受けると最初にこのファイルを探し出し参照します。次に Gateway は、"script"に記述されたスクリプトを起動します。</p> </blockquote> <p>ConMas/gateway/action/hogehoge.json</p> <pre><code>{ "datasource": "script", "script": "scripts/requestlog.py" } </code></pre> <p>"script"に記述するスクリプトは以下のようなスクリプトにしておきます。</p> <p>ConMas/gateway/scripts/requestlog.py</p> <pre><code class="python">import sys import json import traceback jsonData = json.loads(sys.stdin.readline()) # querydata = jsonData['query'] # postdata = jsonData['post']['clusters'] f = open('./logs/request.log', 'w') dp = json.dumps(jsonData) f.write(dp + '\n') f.close() mappings_data = [] mappings = dict(error="", mappings=mappings_data) print(json.dumps(mappings)) </code></pre> <p>この状態で、端末からGateway連携を起動させます。当然ですが、このスクリプトが実行されます。<br /> このスクリプトは、端末からのリクエストをファイルに書き込む以外に何の仕事もしません。端末は期待するデータを受け取ることができませんが、準備作業としては問題はありません。</p> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>前述のスクリプトが出力したファイルは、サーバ上のディレクトリ ConMas/gateway/logs にあります。<br /> テスト対象となる(前述の、リクエストをファイルに書く以外に何もしないスクリプトを偽物とした場合の)本物のスクリプトの入力には、端末からのリクエストの代わりに、このファイルを使用します。<br /> これでスクリプト単体でのテストが可能となります。ここからのテストには、Gateway も端末も不要です。つまり、スクリプト・エンジンがPythonであれば、Pythonスクリプトの通常のテストとまったく同様のテストが可能です。当然ですが、便利なテスト・ツールを使うことが出来ます。</p> <blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://runebook.dev/ja/docs/python/library/trace">trace-Python文の実行をトレースまたは追跡する。</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/kaitolucifer/items/dc58efebd72d72a8feb2">Pythonのデバッグを完全理解</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/sky11fur/items/d4e6e2041d3bddd7b657">Pythonプログラムを追跡する</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/garaemon/items/ca72432b1890f2d793df">pythonの例外でstack traceを表示する</a></p> </blockquote> <p>忘れないうちにディレクトリ action 下のファイルを書き変えて本物にしておきましょう。<br /> ConMas/gateway/action/hogehoge.json</p> <pre><code>{ "datasource": "script", "script": "scripts/hogehoge.py" } </code></pre> <p>テスト用に取得した入力ファイルにはJSONが書かれています。ただし、見にくいのでツール等を使ってキレイにしてから使用することをおすすめします。</p> <blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://tools.m-bsys.com/development_tooles/json-beautifier.php">JSONきれい ~JSON整形ツール~</a></p> </blockquote> <p>例えば、こうやってスクリプトを実行させます。</p> <pre><code>c:\ConMas\gateway\scripts>type ..\logs\request.log | python hogehoge.py </code></pre> <p>参考までに、本物のスクリプトの姿はこんな感じです。<br /> ConMas/gateway/script/hogehoge.py</p> <pre><code class="python">import sys import json import traceback try: #----------------------------------------------------- # 帳票から送信されたデータ #----------------------------------------------------- jsonData = json.loads(sys.stdin.readline()) mappings_data = [] #----------------------------------------------------- # 帳票にレスポンスするデータを作成する処理がこの辺に書かれているはず #----------------------------------------------------- mappings = dict(error="", mappings=mappings_data) print(json.dumps(mappings)) except Exception as e: logging.debug(traceback.format_exc()) mappings = {"error": "Error: " + str(e)} print(json.dumps(mappings)) </code></pre> <p>テストにおいて入力データを変更したければ、入力ファイルの内容を書き換えれば良いのです。</p> <p>ここで紹介したテストでは、端末(帳票)とのコンビネーションをテストすることは出来ませんが、それは後ですれば良い、という考え方に基づいています。それじゃダメ、という考えの場合は始めからコンビネーションでテストしてください。</p> <p><a href="https://crieit.net/posts/ConMas-Gateway-1">ConMas Gateway スクリプトのデバッグ (1)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18271 2022-08-06T19:02:04+09:00 2022-08-11T09:31:57+09:00 https://crieit.net/posts/ConMas-Gateway-1 ConMas Gateway スクリプトのデバッグ (1) <p>製造現場の紙帳票のデジタル化を担うi-Reporter。現段階ではまだベストなソリューションとは言えませんが、今後の機能アップが期待出来るとすればそのポテンシャルは大きく、紙帳票のデジタル化に留まらず、旧態依然な現場のパネルまでも、i-ReporterをのせたWindowsのタッチパネルに入れ替わるかもしれません。日本の製造業をチャンピオンに返り咲かせるDXの誘因として重要なソリューションのひとつであることに間違いはありません。。<br /> このi-Reporterシステムに必須といってもよいのが"ConMas Gateway"です。まだ機能面での不足がある ConMas Gateway ですが、i-Reporter の jQuery.ajax とも言える ConMas Gateway を使わずして i-Reporter の存在意義は語れません。</p> <p>この記事では、ConMas Gateway スクリプトの効率的なデバッグ手法について複数回に分けて解説します。</p> <blockquote> <p>この記事の内容は、IT系開発に不慣れなFA系システム開発者向けです。WEBシステムのサーバ・サイド開発等に長けたITエンジニアにとっては、当たり前のことが書かれています。</p> <p>この記事の内容を理解するために、i-Reporterに関する細かな知識を事前に得ている必要はありません。ただし、この知識がない方は、ある程度の想像力を働かせながら読み進める必要があります。</p> </blockquote> <h2 id="システム・モデル"><a href="#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%83%BB%E3%83%A2%E3%83%87%E3%83%AB">システム・モデル</a></h2> <p><a href="https://crieit.now.sh/upload_images/62f914a7bc277c864df20629b59fa44862ee2e6f8df37.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/62f914a7bc277c864df20629b59fa44862ee2e6f8df37.png?mw=700" alt="image" /></a></p> <ul> <li>ConMas Gateway から起動されたスクリプトは、i-Reporter からPOST(あるいはGET)されたデータを、標準入力を介して ConMas Gateway から受け取ります。</li> <li>スクリプトは i-Reporter へのレスポンスを標準出力を介して ConMas Gateway へ渡し、ConMas Gateway はこれを i-Reporter にレスポンスします。</li> </ul> <p>つまり、ConMas Gateway のスクリプトは、WEBシステムにおける CGIを使うスクリプトと何ら変わりがありません。</p> <blockquote> <p>面白いのは、ConMas Gateway と ConMas Server の間にインタフェースが全く存在せず、両者は何のやりとりもしないことです。<br /> もちろん、ConMas Server のデータベースは公開されているため、ConMas Gateway が直接あるいはスクリプトを介して、このデータベースをアクセスすることは可能です。</p> </blockquote> <p>なお、ConMas Gateway はスクリプトがなくても、自らの機能でデータベースをアクセスし結果を i-Reporter にレスポンスすることが出来ます。ただし、このデータベースのアクセスはシンプルなものに限定され、複雑な処理、あるいはデータベース・アクセス以外の処理を行うにはスクリプトが必要となります。<br /> この記事では、このスクリプトのデバッグについて述べており、ConMas Gateway 自身が持つデータベース・アクセスについては述べていません。</p> <h2 id="データ"><a href="#%E3%83%87%E3%83%BC%E3%82%BF">データ</a></h2> <p>i-Reporter とスクリプトの間でやりとりされるデータはJSONです。これは、データを処理する手続きをシンプルに記述できるという点で歓迎できます。</p> <ul> <li>POSTされるデータは、クラスタ(i-Reporterで処理される帳票上のデータの最小単位)の属性と(当該クラスタに入力された)データが key: value のタプルとなったJSONです。複数のタプルが配列で並びます。どのクラスタをPOSTするかは、事前に ConMas Designer 上で選択します。</li> <li>GETされるデータは任意です。ConMas Designer による設定でURLに続くデータを任意に記載することが出来ます。</li> <li>レスポンスは、帳票上のクラスタのIDと当該クラスタに表示されるデータをタプルにしたJSONです。もちろん、このタプルは配列で複数にすることができます。</li> </ul> <blockquote> <p>この記事では、データのフォーマットに関する細かな解説をしません。データのフォーマットに関する細かなルールや、ConMas Designer での設定については、シムトップス社から提供されるマニュアルを参照してください。これらの情報がなくても、この記事を読み進める上での支障にはなりません。<br /> マニュアルにアクセスするには、シムトップス社のサイトにアクセスするためのIDが必要になります。</p> </blockquote> <h2 id="標準と思われるデバッグ手法"><a href="#%E6%A8%99%E6%BA%96%E3%81%A8%E6%80%9D%E3%82%8F%E3%82%8C%E3%82%8B%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E6%89%8B%E6%B3%95">標準と思われるデバッグ手法</a></h2> <p>標準と思われるデバッグの手法は、i-Reporterで(必要であれば帳票にデータを入力し)帳票上のアクション・クラスタを発動させ、当該クラスタに設定されたスクリプトを動作させながらテストと不具合の解消を進めるという(何の工夫もない)いたって普通の方法です。<br /> 小さく単純でテストが簡単なスクリプトのデバッグでは、この方法でも良いかもしれません。ただし、大きく複雑で、複雑、高度なテストとデバッグを要するスクリプトの場合には問題が生じます。</p> <h4 id="問題"><a href="#%E5%95%8F%E9%A1%8C">問題</a></h4> <ul> <li>この方法ではスクリプトの動作を簡単にはトレースすることができない</li> <li>この方法でのスクリプトのトレースは、デバッグのための古典的なプリント、あるいはカバレージをファイル等に出力するなどが必要となる。一方で、Python で提供される Pdb や Trace 等のコマンドラインで容易に使えるツールが使用できない。</li> </ul> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>続きは次回とさせていただきます。<br /> 初心者の方々のために、ゆっくりとマッタリと進めさせていただきます。<br /> 次回は「ではどうすれば良いか」です。</p> <p><a href="https://crieit.net/posts/ConMas-Gateway-2">ConMas Gateway スクリプトのデバッグ (2)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18259 2022-07-25T21:19:36+09:00 2022-07-25T23:05:16+09:00 https://crieit.net/posts/Python-62de8a581dbea Python自習O_9o0 目次だぜ(^~^) <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de89a592a5e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de89a592a5e.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 Pythonの自習の記事を書いたら ここに目次を作っていこうぜ?」</p> <p><a href="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de89d208779.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de89d208779.png?mw=700" alt="202101__character__28--kifuwarabe-futsu.png" /></a><br /> 「 3日坊主になるのに……」</p> <p><a href="https://crieit.now.sh/upload_images/31f0f35be3a4b6b05ce597c7aab702b762de89e40f92a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/31f0f35be3a4b6b05ce597c7aab702b762de89e40f92a.png?mw=700" alt="202108__character__12--ohkina-hiyoko-futsu2.png" /></a><br /> 「 『目次』の記事ばかり増えるわよね」</p> <h1 id="Pythonソース"><a href="#Python%E3%82%BD%E3%83%BC%E3%82%B9">Pythonソース</a></h1> <p>📖 <a target="_blank" rel="nofollow noopener" href="https://github.com/muzudho/py-self-study">py-self-study</a> - Git hub</p> <h1 id="目次"><a href="#%E7%9B%AE%E6%AC%A1">目次</a></h1> <p>📖 <a href="https://crieit.net/posts/Python-62de830e6dd8e">Python自習O0o0 Pythonでクラスを動的に読み込もうぜ(^~^)</a><br /> 📖 <a href="https://crieit.net/posts/Python-O1o0-Non-numeric-Separated-Value">Python自習O1o0 Non-numeric Separated Value</a></p> <p>おわり</p> むずでょ tag:crieit.net,2005:PublicArticle/18258 2022-07-25T20:48:30+09:00 2022-07-25T23:11:34+09:00 https://crieit.net/posts/Python-62de830e6dd8e Python自習O0o0 クラスを動的に読み込もうぜ(^~^) <h1 id="O_9o0 以前の記事"><a href="#O_9o0+%E4%BB%A5%E5%89%8D%E3%81%AE%E8%A8%98%E4%BA%8B">O_9o0 以前の記事</a></h1> <p>📖 <a href="https://crieit.net/posts/Python-62de8a581dbea">Python自習O_9o0 目次だぜ(^~^)</a> - 目次</p> <h1 id="O0o0 今回の記事"><a href="#O0o0+%E4%BB%8A%E5%9B%9E%E3%81%AE%E8%A8%98%E4%BA%8B">O0o0 今回の記事</a></h1> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 Python でクラスを動的に読み込むの、どうやるんだぜ?」</p> <p>📖 <a target="_blank" rel="nofollow noopener" href="https://www.geeksforgeeks.org/how-to-dynamically-load-modules-or-classes-in-python/">How to Dynamically Load Modules or Classes in Python</a><br /> 📖 <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/547829/how-to-dynamically-load-a-python-class">How to dynamically load a Python class</a></p> <p><a href="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png?mw=700" alt="202101__character__28--kifuwarabe-futsu.png" /></a><br /> 「 👆 ググれだぜ」</p> <p><a href="https://crieit.now.sh/upload_images/31f0f35be3a4b6b05ce597c7aab702b762de69fef19a3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/31f0f35be3a4b6b05ce597c7aab702b762de69fef19a3.png?mw=700" alt="202108__character__12--ohkina-hiyoko-futsu2.png" /></a><br /> 「 いろんな記事があって どれがベストプラクティスか分かんないのよね」</p> <h1 id="O1o0 クラスの静的インポート"><a href="#O1o0+%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E9%9D%99%E7%9A%84%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88">O1o0 クラスの静的インポート</a></h1> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 まず ふつうにクラスをロードしてみようぜ。<br /> 👇 以下のファイルを作成してくれだぜ」</p> <pre><code class="plaintext"> └── 📂 src └── 📂 hello 👉 └── 📄 __init__.py </code></pre> <pre><code class="py">class Hello: def __init__(): pass @classmethod @property def message(clazz): return "Hello, world!!" </code></pre> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 次に<br /> 👇 以下のファイルを作成してくれだぜ」</p> <pre><code class="plaintext"> ├── 📂 src │ └── 📂 hello │ └── 📄 __init__.py └── 📂 tests 👉 └── 📄 hello_test.py </code></pre> <pre><code class="py">""" python -m tests.hello_test """ from src.hello import Hello print(Hello.message) </code></pre> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 じゃあ<br /> 👇 以下のコマンドを打鍵してくれだぜ」</p> <p>Input:</p> <pre><code class="py">python -m tests.hello_test </code></pre> <p>Output:</p> <pre><code class="plaintext">Hello, world!! </code></pre> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 これが よくやるクラスのロードだぜ」</p> <p><a href="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png?mw=700" alt="202101__character__28--kifuwarabe-futsu.png" /></a><br /> 「 これだと <code>from src.hello import Hello</code> と、クラスの名前が埋め込んであるから、このクラスを読み込むな」</p> <h1 id="O2o0 クラスの動的インポート"><a href="#O2o0+%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E5%8B%95%E7%9A%84%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88">O2o0 クラスの動的インポート</a></h1> <p><a href="https://crieit.now.sh/upload_images/31f0f35be3a4b6b05ce597c7aab702b762de69fef19a3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/31f0f35be3a4b6b05ce597c7aab702b762de69fef19a3.png?mw=700" alt="202108__character__12--ohkina-hiyoko-futsu2.png" /></a><br /> 「 そのファイルか、クラスの名前を コマンドラインから指定したいのよ。<br /> 例えば」</p> <pre><code class="shell">python -m tests.hello_test src.welcome </code></pre> <p><a href="https://crieit.now.sh/upload_images/31f0f35be3a4b6b05ce597c7aab702b762de69fef19a3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/31f0f35be3a4b6b05ce597c7aab702b762de69fef19a3.png?mw=700" alt="202108__character__12--ohkina-hiyoko-futsu2.png" /></a><br /> 「 👆 みたいな感じにできないの?」</p> <p><a href="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png?mw=700" alt="202101__character__28--kifuwarabe-futsu.png" /></a><br /> 「 from ~ import 文の名前ストラクチャーを教えてくれだぜ」</p> <pre><code class="plaintext">from src.hello import Hello --------- ----- 1 2 1. module name 2. class name </code></pre> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 👆 これが代表的な例だぜ。こうじゃないケースもあるが今は省くぜ」</p> <p><a href="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png?mw=700" alt="202101__character__28--kifuwarabe-futsu.png" /></a><br /> 「 じゃあ」</p> <pre><code class="plaintext">obj = load( module_name, class_name) </code></pre> <p><a href="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png?mw=700" alt="202101__character__28--kifuwarabe-futsu.png" /></a><br /> 「 👆 こんな感じで クラスのインスタンスを取得できる関数が作れればいいわけかだぜ」</p> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 👇 サンプルコードを真似ようぜ。<br /> 以下のファイルを作成してくれだぜ」</p> <pre><code class="plaintext"> ├── 📂 src │ ├── 📂 dimport 👉 │ │ └── 📄 __init__.py │ └── 📂 hello │ └── 📄 __init__.py └── 📂 tests └── 📄 hello_test.py </code></pre> <pre><code class="py">""" 📖 [How to Dynamically Load Modules or Classes in Python](https://www.geeksforgeeks.org/how-to-dynamically-load-modules-or-classes-in-python/) 📖 [How to dynamically load a Python class](https://stackoverflow.com/questions/547829/how-to-dynamically-load-a-python-class) """ class Dimport: @staticmethod def load(module_name, class_name): # __import__ method used # to fetch module module = Dimport.load_module(module_name) # getting attribute by # getattr() method return getattr(module, class_name) @staticmethod def load_module(name): components = name.split('.') mod = __import__(components[0]) for comp in components[1:]: mod = getattr(mod, comp) return mod </code></pre> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 👇 サンプルコードを真似ようぜ。<br /> 以下のファイルを作成してくれだぜ」</p> <pre><code class="plaintext"> ├── 📂 src │ ├── 📂 dimport │ │ └── 📄 __init__.py │ └── 📂 hello │ └── 📄 __init__.py └── 📂 tests 👉 ├── 📄 dimport_test.py └── 📄 hello_test.py </code></pre> <pre><code class="py">""" python -m tests.dimport_test """ from src.dimport import Dimport print(Dimport.load("src.hello", "Hello").message) </code></pre> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 👇 以下のコマンドを打鍵してくれだぜ」</p> <p>Input:</p> <pre><code class="py">python -m tests.dimport_test </code></pre> <p>Output:</p> <pre><code class="plaintext">AttributeError: module 'src' has no attribute 'hello' </code></pre> <p><a href="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e846bc7782a0e037a1665e6b3d51b02462de69e692e4f.png?mw=700" alt="202101__character__28--kifuwarabe-futsu.png" /></a><br /> 「 👆 <code>src</code> は <code>hello</code> なんか持ってない、というエラーが出たぜ」</p> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 動的にインポートするときは ディレクトリーを自動で検索してくれないのか。じゃあ 手動で設定しようぜ」</p> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 👇 以下のファイルを作成してくれだぜ」</p> <pre><code class="plaintext"> ├── 📂 src │ ├── 📂 dimport │ │ └── 📄 __init__.py │ ├── 📂 hello │ │ └── 📄 __init__.py 👉 │ └── 📄 __init__.py └── 📂 tests ├── 📄 dimport_test.py └── 📄 hello_test.py </code></pre> <pre><code class="py">from .hello import Hello </code></pre> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 👆 <code>src</code> が何を持ってるか 書いてやればいいわけだぜ」</p> <p>Input:</p> <pre><code class="py">python -m tests.dimport_test </code></pre> <p>Output:</p> <pre><code class="py">Hello, world!! </code></pre> <p><a href="https://crieit.now.sh/upload_images/31f0f35be3a4b6b05ce597c7aab702b762de69fef19a3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/31f0f35be3a4b6b05ce597c7aab702b762de69fef19a3.png?mw=700" alt="202108__character__12--ohkina-hiyoko-futsu2.png" /></a><br /> 「 👆 クラスの動的読取はできそうね」</p> <h1 id="O3o0 コマンドライン引数に対応"><a href="#O3o0+%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3%E5%BC%95%E6%95%B0%E3%81%AB%E5%AF%BE%E5%BF%9C">O3o0 コマンドライン引数に対応</a></h1> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 👇 以下のファイルを作成してくれだぜ」</p> <pre><code class="plaintext"> ├── 📂 src │ ├── 📂 dimport │ │ └── 📄 __init__.py │ ├── 📂 hello │ │ └── 📄 __init__.py │ └── 📄 __init__.py └── 📂 tests ├── 📄 dimport_test.py 👉 ├── 📄 dimport_test2.py └── 📄 hello_test.py </code></pre> <pre><code class="py">""" python -m tests.dimport_test2 -m src.hello -c Hello """ import argparse from src.dimport import Dimport # Command line arguments ap = argparse.ArgumentParser() ap.add_argument('-m', help='module') ap.add_argument('-c', help='class') args = ap.parse_args() print(Dimport.load(args.m, args.c).message) </code></pre> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 👇 以下のコマンドを打鍵してくれだぜ」</p> <p>Input:</p> <pre><code class="py">python -m tests.dimport_test2 -m src.hello -c Hello </code></pre> <p>Output:</p> <pre><code class="py">Hello, world!! </code></pre> <p><a href="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5b53e954894672b36c716412a272826b62de69c8ba5ab.png?mw=700" alt="202101__character__31--ramen-tabero-futsu2.png" /></a><br /> 「 👆 でけたな」</p> <h1 id="次の記事"><a href="#%E6%AC%A1%E3%81%AE%E8%A8%98%E4%BA%8B">次の記事</a></h1> <p>📖 <a href="https://crieit.net/posts/Python-O1o0-Non-numeric-Separated-Value">Python自習O1o0 Non-numeric Separated Value</a></p> <h1 id="関連する記事"><a href="#%E9%96%A2%E9%80%A3%E3%81%99%E3%82%8B%E8%A8%98%E4%BA%8B">関連する記事</a></h1> <p>📖 <a href="https://crieit.net/posts/Python-62de8a581dbea">Python自習O_9o0 目次だぜ(^~^)</a></p> <p>おわり</p> むずでょ tag:crieit.net,2005:PublicArticle/18255 2022-07-22T18:43:07+09:00 2022-07-30T16:03:47+09:00 https://crieit.net/posts/PLC-JSON-5 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> 前回は、データを保存するデータベースとテーブルを準備しました。</p> <p><a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a></p> <p>今回は、MQTTブローカーからデータを受信するサブスクライバーを実装します。少々駆け足になりますが、サブスクライバーには、データを受信しつつデータベースに書き込むまでを実装します。<br /> <a href="https://crieit.now.sh/upload_images/9a527b4c96ab1ffcf8096821036def3462d966e06c6c1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9a527b4c96ab1ffcf8096821036def3462d966e06c6c1.png?mw=700" alt="image" /></a></p> <h2 id="プログラミング"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0">プログラミング</a></h2> <p>Pythonを使用して実装します。Pythonでなければならない合理的な理由はありませんが、私の環境では、Pythonではだめだという理由もありません。</p> <p>PythonでMQTTをハンドリングには、paho-mqttというライブラリを使うと、新規コーディング量を減らすことができます。paho-mqttの導入については、このサイトをおすすめします。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/emqx_japan/items/b63c918fe137a6db4b37">Python で MQTT (Paho)</a></p> <p>同様に、PythonからMySQLをアクセスするには、mysql-connector-python-rfというライブラリを使うと、新規コーディング量を減らすことができます。mysql-connector-python-rfの導入については、このサイトをおすすめします。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/hoto17296/items/0cfe7cdd3c47b69cc892">Python 3 から MySQL を触る</a></p> <p>最低限の機能を実装してみました。</p> <pre><code class="python">import paho.mqtt.client as mqtt import json import mysql.connector #----------------------------- # mqtt host = '127.0.0.1' port = 1883 topic = 'sample' def on_connect(client, userdata, flags, respons_code): print('status {0}'.format(respons_code)) client.subscribe(topic) def on_message(client, userdata, msg): json_text = json.dumps(json.loads(msg.payload)) print(json_text) try: #----------------------------- # database conn = mysql.connector.connect( host='127.0.0.1', port=3306, user='hoge', password='hoge001', database='hoge' ) cur = conn.cursor() sql = "insert into from_plc (body) values (%s);" cur.execute(sql, (json_text,)) conn.commit() cur.close() conn.close() except Exception as e: print(e) print("Debug: Error at Database access.") return if __name__ == '__main__': client = mqtt.Client(protocol=mqtt.MQTTv311) client.on_connect = on_connect client.on_message = on_message client.connect(host, port=port, keepalive=60) client.loop_forever() </code></pre> <p>サンプル・プログラムは、MQTTブローカー(=mosquitto)に届く、Topic = sample のメッセージを待ち受けます。メッセージが届くと、メッセージ中のデータをJSON文字列に変換し、これをMySQLのテーブル"from_plc"に追加します。この動作をひたすら繰り返します。</p> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>Pythonスクリプトを起動しておきます。</p> <pre><code>c:\Users\hoge>py sample.py status 0 </code></pre> <p>これまでと同様に、PLCには、本物の代わりにModbus/TCPシミュレーターを使用します。<br /> Modbus/TCPシミュレーター"mod_RSsim.exe"を起動します。<br /> <a href="https://crieit.now.sh/upload_images/7c92f619579f92befb1d50f450ce74b062d96b0700adb.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7c92f619579f92befb1d50f450ce74b062d96b0700adb.png?mw=700" alt="image" /></a><br /> ゲートウェイのシミュレーターを起動するために、EasyBuilderのメニュー[プロジェクト]-[オンラインシミュレーション]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/a6e571139cf659163b06d6721ff09fbf62d96b6c39201.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a6e571139cf659163b06d6721ff09fbf62d96b6c39201.png?mw=700" alt="image" /></a><br /> シミュレーターの起動を確認します。<br /> <a href="https://crieit.now.sh/upload_images/12e87729c176c0ab6bf620f55f4ff1a262d96ba8913fc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/12e87729c176c0ab6bf620f55f4ff1a262d96ba8913fc.png?mw=700" alt="image" /></a><br /> Modbus/TCPシミュレータの、アドレス40001(注意: +0です)をクリックし、Valueに任意の数値を入力し値を変更します。[OK]ボタンで閉じます。<br /> <a href="https://crieit.now.sh/upload_images/f609d225a2af3983fd89312d7fb7770862d96bd14929c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f609d225a2af3983fd89312d7fb7770862d96bd14929c.png?mw=700" alt="image" /></a><br /> Modbus/TCPシミュレータの、アドレス40001の値を変更する都度、Pythonスクリプトがこの情報をJSONで受け取り、コンソールに表示します。<br /> Pythonスクリプトを止めるには、キーボードから<code>Ctrl+C</code>を押してください。</p> <pre><code>c:\Users\hoge>py sample.py status 0 {"value": 0, "ts": "2022-07-21T15:27:43.676079"} {"value": 1, "ts": "2022-07-21T15:27:49.704662"} {"value": 2, "ts": "2022-07-21T15:27:57.641088"} {"value": 3, "ts": "2022-07-21T15:28:07.644020"} {"value": 4, "ts": "2022-07-21T15:28:11.627588"} {"value": 5, "ts": "2022-07-21T15:28:14.659965"} Traceback (most recent call last): File "c:\Users\hoge\sample.py", line 44, in <module> client.loop_forever() File "C:\Users\hoge\AppData\Local\Programs\Python\Python310\lib\site-packages\paho\mqtt\client.py", line 1756, in loop_forever rc = self._loop(timeout) File "C:\Users\hoge\AppData\Local\Programs\Python\Python310\lib\site-packages\paho\mqtt\client.py", line 1150, in _loop socklist = select.select(rlist, wlist, [], timeout) KeyboardInterrupt ^C c:\Users\hoge>py sample.py </code></pre> <p>MySQLのテーブル"from_plc"を確認すると、ゲートウェイからのJSONデータが保存されているのはわかります。</p> <pre><code>c:\Users\hoge>mysql -u hoge -p Enter password: ******* Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 24 Server version: 8.0.29 MySQL Community Server - GPL Copyright (c) 2000, 2022, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use hoge Database changed mysql> select * from from_plc; +--------------------------------------------------+---------------------+ | body | time_insert | +--------------------------------------------------+---------------------+ | {"ts": "2022-07-21T15:27:43.676079", "value": 0} | 2022-07-21 23:27:43 | | {"ts": "2022-07-21T15:27:49.704662", "value": 1} | 2022-07-21 23:27:49 | | {"ts": "2022-07-21T15:27:57.641088", "value": 2} | 2022-07-21 23:27:57 | | {"ts": "2022-07-21T15:28:07.644020", "value": 3} | 2022-07-21 23:28:07 | | {"ts": "2022-07-21T15:28:11.627588", "value": 4} | 2022-07-21 23:28:11 | | {"ts": "2022-07-21T15:28:14.659965", "value": 5} | 2022-07-21 23:28:14 | +--------------------------------------------------+---------------------+ 6 rows in set (0.00 sec) mysql> </code></pre> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>PLCのデータをデータベースにJSONで書きました。もう次回は不要かもしれませんが、せっかくデータベースに書いたので、次回はこれをブラウザに表示させます。くどいなあ。</p> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-6">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18254 2022-07-21T09:17:30+09:00 2022-07-23T23:37:55+09:00 https://crieit.net/posts/PLC-JSON-4 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> 初回から前回にかけては、ゲートウェイを使って、PLCから取得したデータをMQTTでブローカーに送信するまでを実装しました。</p> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a></p> <p>今回はデータベースを準備します。<br /> <a href="https://crieit.now.sh/upload_images/348f9673721149a9206023ad2b3ca8e062d7eb7ab1449.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/348f9673721149a9206023ad2b3ca8e062d7eb7ab1449.png?mw=700" alt="image" /></a></p> <h2 id="データベース"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9">データベース</a></h2> <p>このサンプルではMySQLを使用します。MySQLでなければいけない理由はまったくありません。MySQLを選択した理由は、そこ(私の開発環境、作業場ですね)で動いていたからです。<br /> テーマを「JSONで保存」とした以上は、JSON型をサポートしているDBMSである必要はあるのですが、MySQL以外にもJSON型をサポートしているDBMSはあります。</p> <p>ユーザとデータベースは事前に作成されていたものを使います。これらの作成については解説はいたしません。</p> <p>MySQLにおける、データベースとユーザの作成については、こちらを参照ください。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.dbonline.jp/mysql/database/">データベースの作成</a><br /> <a target="_blank" rel="nofollow noopener" href="https://www.dbonline.jp/mysql/user/">ユーザーの作成</a></p> <h2 id="テーブルを作成する"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">テーブルを作成する</a></h2> <p>任意のユーザとデータベースでテーブルを作成します。</p> <pre><code>hoge@localhost C:\Users\hoge>mysql -u hoge -p Enter password: ******* Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 9 Server version: 8.0.29 MySQL Community Server - GPL Copyright (c) 2000, 2022, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use hoge Database changed mysql> </code></pre> <p>データを保存するためのテーブルをひとつだけ作成します。</p> <pre><code>mysql> create table from_plc ( -> body json, -> time_insert timestamp default current_timestamp -> ); Query OK, 0 rows affected (0.17 sec) mysql> show full columns from from_plc; +-------------+-----------+-----------+------+-----+-------------------+-------------------+---------------------------------+---------+ | Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment | +-------------+-----------+-----------+------+-----+-------------------+-------------------+---------------------------------+---------+ | body | json | NULL | YES | | NULL | | select,insert,update,references | | | time_insert | timestamp | NULL | YES | | CURRENT_TIMESTAMP | DEFAULT_GENERATED | select,insert,update,references | | +-------------+-----------+-----------+------+-----+-------------------+-------------------+---------------------------------+---------+ 2 rows in set (0.07 sec) mysql> </code></pre> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>続きは次回とさせていただきます。<br /> MySQLのセットアップを行われた方がいるとすれば、その方にとっては、この記事の中で最も時間を要した回であったはずです。そういった方に配慮し、今回は短めとし続きは次回とさせていただきます。<br /> 私の記事は先を急ぎませんので。<br /> 次回は、いよいよデータをデータベースに書き込みます。</p> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-5">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-6">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18252 2022-07-20T08:40:18+09:00 2022-07-23T23:36:59+09:00 https://crieit.net/posts/PLC-JSON-3 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> 前回は、ゲートウェイを用いてPLCからデータを取得し、ゲートウェイのメモリに保存しました。<br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a></p> <p>今回はこのデータをサーバに送信します。<br /> <a href="https://crieit.now.sh/upload_images/99f8027812fe841fafbb82cb316db3e062d514f019ff6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/99f8027812fe841fafbb82cb316db3e062d514f019ff6.png?mw=700" alt="image" /></a></p> <p>サンプルで使用するゲートウェイは、引き続きWEINTEK製ゲートウェイの開発環境であるEasyBuilderのシミュレーターです。WEINTEK製ゲートウェイはサーバへのデータ送信に2種類のインタフェースを持っており、そのひとつがMQTTです。サンプルではMQTTを使用してサーバにデータを送信します。</p> <p>IT技術より生産現場の技術に強い方は、"MQTT"とか登場した時点で「もういやだな」となるかもしれません。そういった方はロガーの類が欲しくなりますよね。わかります。この記事ではロガーの類はまったく登場しません。この先を読み進めるかどうか、ご自由にご判断ください。</p> <p>サーバ側にはMQTTのブローカーが必要となります。サンプルでは、MQTTブローカーとしてMosquittoを使用します。<br /> <a href="https://crieit.now.sh/upload_images/ad4d70ef98a1d741deedb1e19d7eef3762d52f8173167.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ad4d70ef98a1d741deedb1e19d7eef3762d52f8173167.png?mw=700" alt="image" /></a></p> <p>Mosquittoはテストの場面で必要となります。サンプルの実装に加えて、テストをしてみたいという方は、自己責任でMosquitto、あるいは他のMQTTブローカーを準備してください。Mosquittoの準備については以下を参照してください。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/koichi_baseball/items/8fa9e0bdbe6d0aebe57d">【MQTT】MQTTの導入 mosquittoのインストール/動作確認まで</a></p> <h2 id="ゲートウェイのMQTT送信設定"><a href="#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AEMQTT%E9%80%81%E4%BF%A1%E8%A8%AD%E5%AE%9A">ゲートウェイのMQTT送信設定</a></h2> <p>データをサーバに送信するために、前回まで作成したプロジェクトに設定を加えます。</p> <p>EasyBuilderのメニュー[IIoT/エネルギー]-[MQTT]をクリックすます。<br /> <a href="https://crieit.now.sh/upload_images/27403bdd1beb17ed91a9e1faad0c1ca462d5180119baf.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/27403bdd1beb17ed91a9e1faad0c1ca462d5180119baf.png?mw=700" alt="image" /></a></p> <p>[有効にする]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/419a1bed20d2da61b4a260fcfaa1a92f62d518b58b47d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/419a1bed20d2da61b4a260fcfaa1a92f62d518b58b47d.png?mw=700" alt="image" /></a></p> <p>[一般的な属性]はデフォルトのままです。サンプルでは、ゲートウェイのシミュレーターと同じWindowsでMosquittoを動作させるので、IPアドレスは127.0.0.1のままです。[OK]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/3643faef7ae563403b18ecd92652d34562d5191b1eb6a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3643faef7ae563403b18ecd92652d34562d5191b1eb6a.png?mw=700" alt="image" /></a></p> <p>[トピック発行者]タブで[新規作成]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/0602c7b6837e71a182c385cd85680f2862d51a01c7f24.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/0602c7b6837e71a182c385cd85680f2862d51a01c7f24.png?mw=700" alt="image" /></a><br /> <a href="https://crieit.now.sh/upload_images/999d37a64349d3064b39bf08d0b2694f62d51a4c34273.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/999d37a64349d3064b39bf08d0b2694f62d51a4c34273.png?mw=700" alt="image" /></a></p> <p>[一般的な属性]タブで以下のように設定します。<br /> 中ほどの設定、[アドレス]のLBと0は、LB0がONになったらMQTTでサーバにデータを送信する、という意図です。<br /> <a href="https://crieit.now.sh/upload_images/01b4e17a1817470a004336900fa7ffbb62d51b4209fc2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/01b4e17a1817470a004336900fa7ffbb62d51b4209fc2.png?mw=700" alt="image" /></a></p> <p>[アドレス]タブに移り、[新規作成]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/59910b8f3292a98b49714a41816f8c3762d51c263f6ec.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59910b8f3292a98b49714a41816f8c3762d51c263f6ec.png?mw=700" alt="image" /></a></p> <p><a href="https://crieit.now.sh/upload_images/58b56e5ee811a948f574326534769a5762d51c2fa8f19.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/58b56e5ee811a948f574326534769a5762d51c2fa8f19.png?mw=700" alt="image" /></a></p> <p>ダイアログに以下のように設定し、[OK]ボタンをクリックします。<br /> [アドレス]の設定LWと0は、LW0にあるデータを送信する、という意図です。LW0には、PLCから取得したデータが置かれています。<br /> <a href="https://crieit.now.sh/upload_images/de2bc7ffeecf75b76dec7522f7a27fd162d51cb082328.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/de2bc7ffeecf75b76dec7522f7a27fd162d51cb082328.png?mw=700" alt="image" /></a></p> <p>[MQTTトピック発行者オブジェクト属性]ウィンドウの[OK]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/bc638bc4ee1e717109c9a3a6157e5d5f62d51d48b7489.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/bc638bc4ee1e717109c9a3a6157e5d5f62d51d48b7489.png?mw=700" alt="image" /></a></p> <p>[MQTT]ウィンドウの[終了]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/9534a197b16903a10274457bdd2e71e662d51dbac95f8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9534a197b16903a10274457bdd2e71e662d51dbac95f8.png?mw=700" alt="image" /></a></p> <p>以上で、ゲートウェイのMQTT設定は完了です。</p> <h2 id="ゲートウェイのプログラミング"><a href="#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AE%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0">ゲートウェイのプログラミング</a></h2> <p>前回作成したゲートウェイのプログラムに、若干のステップを追加します。</p> <p>EasyBuilderのメニュー[プロジェクト]-[マクロ]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/b6df62bab4d0a16cee1095a55dcb836f62d5216eb34e4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b6df62bab4d0a16cee1095a55dcb836f62d5216eb34e4.png?mw=700" alt="image" /></a><br /> <a href="https://crieit.now.sh/upload_images/5f0828812556ea6a775135414a365cc362d52185a7fbe.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5f0828812556ea6a775135414a365cc362d52185a7fbe.png?mw=700" alt="image" /></a></p> <p>[ID : 001]をダブル・クリックします。<br /> <a href="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d52233bc50d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d52233bc50d.png?mw=700" alt="image" /></a></p> <p>以下のように、ステップを追加します。<br /> <a href="https://crieit.now.sh/upload_images/48e72c305c89ae10a73ee74cb389156462d5295243ba1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/48e72c305c89ae10a73ee74cb389156462d5295243ba1.png?mw=700" alt="image" /></a><br /> 6〜10行目では、今回取得したデータと前回取得したデータを比較して、同じデータであったら何もしない、と書いています。業務上、連続する同じデータは不要という場合に、サーバに無駄なデータを送信しません。<br /> 13〜14行目は、MQTT送信をキックするために、ゲートウェイのLB0をオンにします。LB0はMQTT送信後に自動でオフになります(そのようにMQTTを設定しました)。</p> <p>コピーしたい方はこちらからどうぞ。</p> <pre><code>macro_command main()         unsigned short        x40001 = 0         GetData(x40001, "MODBUS TCP/IP (32-Bit)", 4x, 1, 1)                  unsigned short        LW0 = 0         GetData(LW0, "Local HMI", LW, 0, 1)         if x40001 == LW0 then                 return         end if         SetData(x40001, "Local HMI", LW, 0, 1)         bool        LB0 = 1                 SetData(LB0, "Local HMI", LB, 0, 1) end macro_command </code></pre> <p>エディタの[閉じる]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/901952ea06c21742a0a40d878071bca462d56c6e2048c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/901952ea06c21742a0a40d878071bca462d56c6e2048c.png?mw=700" alt="image" /></a></p> <p>[コンパイルが成功]に[ID : 000]があることを確認します。<br /> <a href="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d56ca4cea61.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d56ca4cea61.png?mw=700" alt="image" /></a></p> <blockquote> <h3 id="連続する同じデータは不要"><a href="#%E9%80%A3%E7%B6%9A%E3%81%99%E3%82%8B%E5%90%8C%E3%81%98%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AF%E4%B8%8D%E8%A6%81">連続する同じデータは不要</a></h3> <p>連続する同じデータは不要という仕様は、よくある仕様です。例えば、工場の生産マシンで生産される製品の数をPLCで数えている場合、そのカウンタがこれに該当します。変化のないカウント値は不要なはずです。この不要なデータをネットワークやサーバに垂れ流すと、ネットワークやサーバにとって無駄な負荷となります。データベースのサイズも無駄に膨れ上がります。「IoTのデータは無駄なデータばかりだけど、そういうものだ」という誤った悟りを持った設計者を多く見かけますが、実は、設計次第でエレガントなシステムになります。想像が足りないだけなのです。<br /> 時系列データベースが提供する重複排除機能などは、あったほうが便利、という程度です。排除する前に発生させなければ良いのです。ただし、ロガーなどを使って無秩序に記録したデータをデータベースにロードする、というような場合は、不要なデータの排除に苦労します。<br /> 連続する同じデータをサーバ側で捨てるテクニックもあります。これについては機会があれば、別途解説をさせていただきます。</p> </blockquote> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>テストには、MQTTブローカーと、一時的にテストで使用するMQTTの汎用サブスクライバーが必要となります。<br /> これらは、Mosquittoが提供してくれます。Mosquittoの準備については以下を参照してください。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/koichi_baseball/items/8fa9e0bdbe6d0aebe57d">【MQTT】MQTTの導入 mosquittoのインストール/動作確認まで</a></p> <p>私の開発・テスト環境では、MosquittoブローカーはWindowsの設定で自動起動にしてあり、常に実行中です。<br /> <a href="https://crieit.now.sh/upload_images/d3c132e88775428a7334c810a8606fc362d5203d78fd4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d3c132e88775428a7334c810a8606fc362d5203d78fd4.png?mw=700" alt="image" /></a></p> <blockquote> <p>注意: 他のサーバ上のMosquittoへ送信する場合には注意が必要です。Mosquittoは、標準の設定では外部からのメッセージを受け付けません。この場合は、外部からのメッセージを受け付けるよう、Mosquittoの設定を変更する必要があります。</p> </blockquote> <p>受信側となるMQTTサブスクライバーを起動しておきます。<br /> Mosquittoにはコマンド起動可能な簡単なサブスクライバーが付属しています。Windowsでは、このコマンドはMosquittoのインストール・フォルダにあります。<br /> 以下のように起動しておきます。</p> <pre><code>c:\Program Files\mosquitto>mosquitto_sub -h localhost -t sample </code></pre> <p>オプション -h localhost は、localhost上のブローカーから受信することを示しています。-t sample はトピック名を示しており、ゲートウェイのMQTT設定で設定したトピック名と同じである必要があります。mosquitto_sub はトピックsampleのメッセージをひたすら待ち続けます。</p> <p>前回のテストと同様に、Modbus/TCPシミュレーター"mod_RSsim.exe"を起動します。</p> <blockquote> <p>注意: "mod_RSsim.exe"は連続起動に時間制限がある点に注意してください。制限を超えた場合は再起動させます。</p> </blockquote> <p><a href="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d52790afb85.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d52790afb85.png?mw=700" alt="image" /></a></p> <p>EasyBuilderのメニュー[プロジェクト]-[オンラインシミュレーション]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/439fd0e9432caba5e9ed0546875106a462d528085da76.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/439fd0e9432caba5e9ed0546875106a462d528085da76.png?mw=700" alt="image" /></a></p> <p>シミュレーターが起動したことを確認します。<br /> <a href="https://crieit.now.sh/upload_images/75590b5b1988d3633f2403c0eda4c4f762d5285c8e0d4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/75590b5b1988d3633f2403c0eda4c4f762d5285c8e0d4.png?mw=700" alt="image" /></a></p> <p>前回のテストと同様に、Mdobus/TCPシミュレーターのアドレス40001のデータを任意の数値に書き換えます。<br /> <a href="https://crieit.now.sh/upload_images/117891e3a7d9cfb3ad2aee7bc5db0e2662d5287c3564a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/117891e3a7d9cfb3ad2aee7bc5db0e2662d5287c3564a.png?mw=700" alt="image" /></a></p> <p>Mosquittoのサブスクライバーが、受信したデータをコンソールに出力します。<br /> <a href="https://crieit.now.sh/upload_images/e6004c9810a653ca72e7b22384c6d9b862d525e0e1ffc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e6004c9810a653ca72e7b22384c6d9b862d525e0e1ffc.png?mw=700" alt="image" /></a></p> <p>ゲートウェイのプログラムで、データに変化があった場合にのみ送信、としているため同じデータが次々と送られてくることはありません。ゲートウェイを単なるプロトコル変換器だと考えているなら、それは誤りです。</p> <p>ゲートウェイのMQTT設定にて、タイムスタンプを付与するよう設定したため、tsという名前でゲートウェイの時刻が送られてきます。</p> <blockquote> <p>このゲートウェイの時刻はサーバの時刻と必ずしも一致しない、という点を設計上留意してください。時刻の取扱いについては別途、解説をさせていただきます。</p> </blockquote> <p>以上でテストは終了です。</p> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>続きは次回とさせていただきます。<br /> 次回は、データベースを準備します。</p> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-5">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-6">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18251 2022-07-19T16:02:44+09:00 2022-07-23T23:36:00+09:00 https://crieit.net/posts/PLC-JSON-2 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> 今回はその2回目、ゲートウェイによるPLCからのデータ取得です。<br /> <a href="https://crieit.now.sh/upload_images/c2c94364c86325cb400ed616c683221562d4d91f05808.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c2c94364c86325cb400ed616c683221562d4d91f05808.png?mw=700" alt="image" /></a><br /> サンプルの環境については第一回で解説しています。必ず第一回を参照し、前提となる環境を理解した上で以下の解説を参照してください。<br /> <a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a></p> <h2 id="想定: PLC上のデータ"><a href="#%E6%83%B3%E5%AE%9A%3A+PLC%E4%B8%8A%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF">想定: PLC上のデータ</a></h2> <p>サンプルでは、取得するPLC上のデータを以下のように想定しています。<br /> <a href="https://crieit.now.sh/upload_images/520f28de070aceae292d971ae449b7a762d4e11edce3b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/520f28de070aceae292d971ae449b7a762d4e11edce3b.png?mw=700" alt="image" /></a></p> <blockquote> <h3 id="Modbus/TCP"><a href="#Modbus%2FTCP">Modbus/TCP</a></h3> <p>Modbus/TCPについては、このドキュメントが理解しやすいドキュメントです。M・SYSTEM技研さまに感謝です。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.m-system.co.jp/mssjapanese/kaisetsu/nmmodbus.pdf">M・SYSTEM技研さま Modbusプロトコル概説書</a></p> <p>間違っていただきたくないのは、"Modbus/TCP"とは、PLCが自身のメモリを後悔するためのインタフェースのひとつにすぎない、ということです。"Modbus/TCP"が貴重なプロトコルである理由を述べます。PLCが自身のメモリを公開するためのプロトコルは他にもたくさんあります。そして、それらの多くは各PLCメーカー固有のものです。Modbus/TCPの特徴は、複数のPLCメーカー間で横断で採用されているプロトコルという点です。複数のメーカーのPLCは、標準ではModbus/TCPが使用できずオプションとして提供され、標準では当該メーカー固有のプロトコルがサポートされます。</p> <h3 id="Holding Registers"><a href="#Holding+Registers">Holding Registers</a></h3> <p>"Holding Registers"とは、Modbus/TCPが定める複数のメモリ領域のうちのひとつです。主に、数値や文字を記憶するために用いられます。アドレスは40001〜です。サンプルでは、40001上のデータをターゲットにします。<br /> 今回、PLCの代わりに活躍してくれる"mod_RSsim.exe"では、このような画面で眺めることが出来ます。<br /> <a href="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4ea62383a2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4ea62383a2.png?mw=700" alt="image" /></a></p> </blockquote> <h2 id="ゲートウェイの設定とプログラミング"><a href="#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%81%A8%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0">ゲートウェイの設定とプログラミング</a></h2> <p>前回は、ゲートウェイを設定し、PLCに見立てたModbus/TCPシミュレーターに接続しました。<br /> 今回は、ゲートウェイを使って、PLCに見立てたModbus/TCPシミュレーターからデータを取得します。サンプルでは前回と同様に、ゲートウェイの代わりにWEINTEK製ゲートウェイのソフトウェア開発ツールであるEasyBuilderのシミュレーターを使用します。</p> <p>前回作成したEasyBuilderのプロジェクトを開き、メニュー[プロジェクト]-[マクロ]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/b6df62bab4d0a16cee1095a55dcb836f62d4f019b0314.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b6df62bab4d0a16cee1095a55dcb836f62d4f019b0314.png?mw=700" alt="image" /></a><br /> <a href="https://crieit.now.sh/upload_images/5f0828812556ea6a775135414a365cc362d4f0418d4a6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5f0828812556ea6a775135414a365cc362d4f0418d4a6.png?mw=700" alt="image" /></a></p> <p>[マクロ]ウィンドウから[新規作成]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/3dc7453b874086ec512519c0c059939862d4f063d178d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3dc7453b874086ec512519c0c059939862d4f063d178d.png?mw=700" alt="image" /></a></p> <p>[マクロエディター]に以下のようにコードを記述します。<br /> [周期的に実行する]オプションをオンにし、時間間隔を10にします。<br /> <a href="https://crieit.now.sh/upload_images/418d778add1fbee6a591cd7db72ef99462d4f0998d976.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/418d778add1fbee6a591cd7db72ef99462d4f0998d976.png?mw=700" alt="image" /></a><br /> コピーしたい方はこちらからどうぞ。</p> <pre><code>macro_command main()         unsigned short        x40001 = 0         GetData(x40001, "MODBUS TCP/IP (32-Bit)", 4x, 1, 1)         SetData(x40001, "Local HMI", LW, 0, 1) end macro_command </code></pre> <p>[マクロエディター]の下にある[閉じる]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/901952ea06c21742a0a40d878071bca462d4f113d33c3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/901952ea06c21742a0a40d878071bca462d4f113d33c3.png?mw=700" alt="image" /></a></p> <p>[マクロ]ウィンドウの[コンパイルが成功]に新たなマクロが追加されたら、ひとまずプログラミングは終了です。<br /> <a href="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d4f137a479e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d4f137a479e.png?mw=700" alt="image" /></a></p> <blockquote> <h3 id="マクロ=プログラムの意図"><a href="#%E3%83%9E%E3%82%AF%E3%83%AD%3D%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E6%84%8F%E5%9B%B3">マクロ=プログラムの意図</a></h3> <p>Modbus/TCPが定める"Holding Register"(アドレス: 40001〜)は、EasyBuilderでは"4x"で解釈されます。<br /> EasyBuilderでは、対象装置(今回はModbus/TCPシミュレーター)のメモリ・マップを参照することができます。EasyBuilderの[アドレス]ペイン(ウィンドウ)にて、装置名、ステーション番号、アドレスタイプを以下のように設定すると、ひとつめのアドレスが赤になっているはずです。これは、作成したマクロがこのアドレスを参照している、という証です。<br /> <a href="https://crieit.now.sh/upload_images/26365e0800f525da7b98106b7e7bd71062d4f28eba07f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/26365e0800f525da7b98106b7e7bd71062d4f28eba07f.png?mw=700" alt="image" /></a><br /> 今回作成したコードでは、行4で、対象装置の"4x"のひとつめのアドレスから1ワードだけデータを取得しています。対象装置は”MODBUS TCP/IP (32-Bit)”です。これは、前回、システムパラメータで設定した対象装置の「装置名」です。<br /> 行4で取得したデータを、行5で自らのメモリ"LW"のひとつめのアドレスにコピーしています。EasyBuilder(=WEINTEK製ゲートウェイ)も複数のメモリ領域をもっており、LWはそのうちのひとつです。LWは16bitワードであり、Modbus/TCPのHolding Registerと同じワード・サイズです。<br /> <a href="https://crieit.now.sh/upload_images/5e787b399f3f70441d4ec0226b16d5de62d4f52debe24.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5e787b399f3f70441d4ec0226b16d5de62d4f52debe24.png?mw=700" alt="image" /></a><br /> [周期的に実行する]オプションのオンはこのプログラムを周期的に実行することを意味します。そしてその周期を今回は1000m秒(=1秒)としています。<br /> このプログラムで、PLCからデータを取得する、という今回の目的は達成されるはずです。</p> <h3 id="周期的に実行する"><a href="#%E5%91%A8%E6%9C%9F%E7%9A%84%E3%81%AB%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B">周期的に実行する</a></h3> <p>とても残念なことに、PLCからデータを取得するゲートウェイは、PLC側のデータ変更等のイベントをもって振る舞いを起こすということが出来ません。これは、完全にPLC側の都合によるものです。このため、ゲートウェイがPLC上のデータを参照するとき、ゲートウェイは空振りすることを承知でPLCのメモリを周期的に参照します。いまどきのシステムとしては実に残念です。"Factory Automation"の技術者は、ITの世界ではとっくに古典となった"Message Passing"や"Data Driven"といった考え方すら学んでいないのです。こういった問題は当面は放置され続けるでしょう。</p> </blockquote> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>前回と同様に、PLCには、本物の代わりにModbus/TCPシミュレーターを使用します。</p> <p>Modbus/TCPシミュレーター"mod_RSsim.exe"を起動しておきます。<br /> <a href="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4f9862eaeb.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4f9862eaeb.png?mw=700" alt="image" /></a></p> <p>EasyBuilderのメニュー[プロジェクト]-[オンラインシミュレーション]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/e57da9ac757974ac53d74202ab3f186962d4f99fb1415.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e57da9ac757974ac53d74202ab3f186962d4f99fb1415.png?mw=700" alt="image" /></a></p> <p>シミュレーターが起動したら、メニュー[Object]を選択し、右側にある[+]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/f596a7ef9d3f9ee63dadb64cd89f981d62d4fa3b2cb36.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f596a7ef9d3f9ee63dadb64cd89f981d62d4fa3b2cb36.png?mw=700" alt="image" /></a></p> <p>ダイアログにて以下のように入力し[OK]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/8fc3bd495e156941cea03c12cf981d6062d4faaf89f7f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8fc3bd495e156941cea03c12cf981d6062d4faaf89f7f.png?mw=700" alt="image" /></a></p> <p>Modbus/TCPシミュレータの、アドレス40001(注意: +0です)をクリックし、Valueに任意の数値を入力、[OK]ボタンで閉じます。<br /> <a href="https://crieit.now.sh/upload_images/117891e3a7d9cfb3ad2aee7bc5db0e2662d4fc4dbfc1b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/117891e3a7d9cfb3ad2aee7bc5db0e2662d4fc4dbfc1b.png?mw=700" alt="image" /></a><br /> アドレス40001の表示が変わります。<br /> <a href="https://crieit.now.sh/upload_images/4b7477028132e4aa88253b72874d6e0d62d50fdb774c8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4b7477028132e4aa88253b72874d6e0d62d50fdb774c8.png?mw=700" alt="image" /></a></p> <p>EasyBuilderのシミュレータ上で、LW-0も変更されることを確認します。<br /> <a href="https://crieit.now.sh/upload_images/75590b5b1988d3633f2403c0eda4c4f762d4fb4d7cf76.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/75590b5b1988d3633f2403c0eda4c4f762d4fb4d7cf76.png?mw=700" alt="image" /></a></p> <p>これでテストは終わりです。数行のプログラムのテストとしては十分でしょう。</p> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>続きは次回とさせていただきます。<br /> 今回は、初心者には少しだけ難解であったかもしれません。慌てずじっくり、しっかり理解して進んでください。</p> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-5">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-6">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18249 2022-07-18T11:42:52+09:00 2022-07-23T23:35:09+09:00 https://crieit.net/posts/PLC-JSON-1 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> <a href="https://crieit.now.sh/upload_images/4bef24e301bab8c385bc5659d66fab1262d4b8451782b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4bef24e301bab8c385bc5659d66fab1262d4b8451782b.png?mw=700" alt="image" /></a><br /> 初回は、ゲートウェイでPLCのデータを取得します。<br /> <a href="https://crieit.now.sh/upload_images/c2c94364c86325cb400ed616c683221562d4b8537ad2c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c2c94364c86325cb400ed616c683221562d4b8537ad2c.png?mw=700" alt="image" /></a><br /> サンプルは、Windowsがあれば環境の準備と動作が可能です。よろしければ、自己責任のもとで環境を準備し、サンプルを動作させてみてください。</p> <h2 id="PLC"><a href="#PLC">PLC</a></h2> <p>この解説でのサンプルでは、PLCの代わりにModbus/TCPシミュレーターを使用します。これにより、PLCの本物がなくても、実装の学習とテストが可能となります。<br /> 今回使用したModbus/TCPシミュレーターは、貴重なmod_RSsim.exeです。もちろん、他のシミュレータでも構いません。</p> <ul> <li>Modbus/TCPシミュレーター: MOD-RSsim Version 8.20</li> </ul> <p><a href="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4afc50041e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4afc50041e.png?mw=700" alt="image" /></a></p> <h2 id="ゲートウェイ"><a href="#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4">ゲートウェイ</a></h2> <p>この解説のサンプルでは、WEINTEK製ゲートウェイを想定します。WEINTEK製ゲートウェイのソフトウェア開発ツールは、Windows上でソフトウェアを書くことができることに加え、ゲートウェイの動きをシミュレートすることが出来ます。これにより、ゲートウェイの本物がなくても、実装の学習とテストが可能となります。</p> <ul> <li>WEINTEK製ゲートウェイのソフトウェア開発環境: EasyBuilder Pro</li> </ul> <p>EasyBuilderはWEINTEKのサイトからダウンロードすることが出来ます。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.weintek.com/globalw/Download/Download.aspx">https://www.weintek.com/globalw/Download/Download.aspx</a></p> <h2 id="ゲートウェイの設定とプログラミング"><a href="#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%81%A8%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0">ゲートウェイの設定とプログラミング</a></h2> <p>ゲートウェイは各社、各モデルで実装のスタイルが大きく異なります。しかし、この解説では、多くのゲートウェイでの機能実装が可能となるよう勘案し、よりシンプルな手順になるよう考慮しています。<br /> 前述のとおり、この解説では、ゲートウェイの本物を使用しません。代わりにEasyBuilderという、WEINTEK製ゲートウェイの開発環境を使用します。</p> <p>EasyBuilder Pro を起動します。<br /> <a href="https://crieit.now.sh/upload_images/70b6249e55a43645fa24782555dba08a62d4bfe2afa50.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/70b6249e55a43645fa24782555dba08a62d4bfe2afa50.png?mw=700" alt="image" /></a></p> <p>プロジェクトを新たに作成します。<br /> これから行う設定が、どのゲートウェイの設定であるかをゲートウェイのモデルの一覧から選択します。このサンプルでは、最もシンプルなモデル"cMT-G01"を選択します。<br /> <a href="https://crieit.now.sh/upload_images/73133d6f86ad19a49ce3d64ec95ce1a662d4c0da0fe55.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/73133d6f86ad19a49ce3d64ec95ce1a662d4c0da0fe55.png?mw=700" alt="image" /></a><br /> "cMT-G01"とはこのようなハードウェアです。<br /> <a target="_blank" rel="nofollow noopener" href="https://dl.weintek.com/public/cMT/eng/Datasheet/cMT-G01_Datasheet_ENG.pdf">cMT-G01</a></p> <p>このゲートウェイが対象にするPLCを設定します。メニュー[ホーム]-[システムパラメータ]を選択します。<br /> <a href="https://crieit.now.sh/upload_images/e1fd4a5385cd74a43bada117619cb52762d4c1ed7671d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e1fd4a5385cd74a43bada117619cb52762d4c1ed7671d.png?mw=700" alt="image" /></a></p> <p>[装置]タブを選択します。<br /> <a href="https://crieit.now.sh/upload_images/173c66d906d17f0d2905b9713ccd038c62d4c305cc7ca.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/173c66d906d17f0d2905b9713ccd038c62d4c305cc7ca.png?mw=700" alt="image" /></a></p> <p>[装置/サーバを新規追加]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/116c9134b0a53cfc0182069b0ee3f37a62d4e5e3e346e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/116c9134b0a53cfc0182069b0ee3f37a62d4e5e3e346e.png?mw=700" alt="image" /></a></p> <p>以下のように設定します。<br /> <a href="https://crieit.now.sh/upload_images/b4813a2fca2935a56983ddc1dfcefddc62d4c2a3aec24.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b4813a2fca2935a56983ddc1dfcefddc62d4c2a3aec24.png?mw=700" alt="image" /></a><br /> PLCに相当するModbus/TCPシミュレータは、EasyBuilderと同じWindowsマシン上で稼働させる予定です。このため装置(=PLC=Modbus/TCPシミュレータ)のIPアドレスを127.0.0.1にしています。<br /> 装置のポートはModbus/TCPの標準である502を選択しています。</p> <blockquote> <p>上記設定は、この解説におけるサンプルのための設定です。現実は、対象とするPLCやリモートIOなどに応じた設定が必要となります。<br /> 例えば、対象の装置が三菱電機のMELSECであれば、該当のモデルを選択します。下は、Mitsubishi QJ71E71の例です。<br /> <a href="https://crieit.now.sh/upload_images/cf6a77fc1f36d91ac1b06786216393f462d634aac0aa8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/cf6a77fc1f36d91ac1b06786216393f462d634aac0aa8.png?mw=700" alt="Mitsubishi QJ71E71 の例" /></a></p> </blockquote> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>ここまでで、PLC(=Modbus/TCPシミュレータ)に接続する設定が終わりました。接続のテストをします。</p> <p>mod_RSsim.exeを起動しておきます。<br /> <a href="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4c596c61b2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4c596c61b2.png?mw=700" alt="image" /></a></p> <p>次はEasyBuilderです。<br /> もちろん、これまでの設定をゲートウェイ(cTM-G01)にロードしてゲートウェイを動かすことも可能ですが、今回はシミュレーターを使います。<br /> メニュー[プロジェクト]-[オンラインシミュレーション]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/e57da9ac757974ac53d74202ab3f186962d4c5d9df4b8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e57da9ac757974ac53d74202ab3f186962d4c5d9df4b8.png?mw=700" alt="image" /></a></p> <p>シミュレーターが起動します。"MODBUS TCP/IP (32-Bit)"に接続済みであるように見えますが、これはあてにはなりません。<br /> <a href="https://crieit.now.sh/upload_images/2b4ac9177034a63ae4627b253d2cade562d4c6d9f2b98.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2b4ac9177034a63ae4627b253d2cade562d4c6d9f2b98.png?mw=700" alt="image" /></a></p> <p>Modbus/TCPシミュレーターのウィンドウの左上にある"Connected"の表示を確認します。ゲートウェイからの接続が行われていれば、[1/10]となります。<br /> <a href="https://crieit.now.sh/upload_images/320155af5baceef2c37476485fd2741962d4e7455062b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/320155af5baceef2c37476485fd2741962d4e7455062b.png?mw=700" alt="image" /></a></p> <h2 id="次回へ"><a href="#%E6%AC%A1%E5%9B%9E%E3%81%B8">次回へ</a></h2> <p>続きは次回とさせていただきます。<br /> 初心者の方々のために、ゆっくりとマッタリと進めさせていただきます。<br /> 次回は、ゲートウェイによるPLCからのデータ取得です。</p> <p><a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-5">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-6">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18212 2022-06-10T12:06:32+09:00 2022-06-19T10:52:23+09:00 https://crieit.net/posts/pycall pycall ( python 用の機械学習向けのライブラリーを Ruby で使えるようにした Ruby gem ) を機械学習向けのライブラリーじゃないもので使う。 <p><a target="_blank" rel="nofollow noopener" href="https://crieit-net.translate.goog/posts/pycall?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=ja&_x_tr_pto=wapp">translate to </a></p> <p><a href="https://crieit.net/posts/why">_why ( === why the lucky stiff )</a> のことを知って、Why's ( poignant ) Guide to Ruby <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Why's_%28poignant%29_Guide_to_Ruby#:~:text=why's%20%28poignant%29%20Guide%20to%20Ruby,%20sometimes%20called%20w%28,Creative%20Commons%20Attribution-ShareAlike%20license.">:wikipedia</a> の日本語訳版 翻訳 青木靖を勝手に epub に再編集しはじめてずっとずっと 何日も作業していると ( 訳 ( どころか全てが丁寧 ) が悪いわけではなく 2 バイト文字とそうでない文字がくっついてると自分はディスクレシアのようにゲシュタルト崩壊になるので、そういうところとアーカイブされているオリジナルに近いサイトで生きているものにリンクを張り替えたりということを調べていた。つまり 2004 年くらいにレイドバックして文章の意味をおしはかっている期間が続いて )、<a target="_blank" rel="nofollow noopener" href="https://jp.quora.com/naze-Ruby-ha-kihon-teki-ni-shi-nde-iru-node-shou-ka">Ruby</a> が気になり始めた。</p> <p>Rf. http://www.aoky.net/articles/why_poignant_guide_to_ruby/</p> <p><a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Sung_Tongs">2004</a></p> <p>Scala の古本を買って読みたいと考えていたのに、なかなか進めないうちに Ruby のことにひきよせられていく。しかも、20 年近く前の。<br /> バージョン 1.8 以前。</p> <p>Ruby についてはよく知らないが、知らないうちに書いてると、これ基本的なかたこと英語になっているよなということがわかる言語で、その後に python をわからないなりに書くと、どうしても間違うところがあって、そこが最も Ruby の気に入っているポイントだった、半年から 1 年まえあたり。<br /> その間違う、お気に入りポイントは、「 <a target="_blank" rel="nofollow noopener" href="https://docs.ruby-lang.org/ja/latest/method/Kernel/m/p.html"> p </a>」。<br /> python でも自然に <code>p</code> で書き続けて、実行すると当然エラーになるが、見直してもわからない。あってる、あってるのに ... とイライラしはじめて、コーヒーをいれながら豆の香りをかいでると、嗤いだしてしまう。<br /> 「 p 」だ。</p> <p>それくらい p は自然に手になじんでしまう。というところが、すごいなと感じていて、<a target="_blank" rel="nofollow noopener" href="http://www.nct9.ne.jp/m_hiroi/func/ocaml13.html">クラス</a>とか、Ruby を使ったフレームワークだとかにはまるで興味が持てないので、とりあえず Ruby についてはもういいと思っていた。というところで、ふいに _why の存在で、急激に、そういう人がフィーリングが合ったという、その背景について知りたくなった。<br /> フラタニティー的な一色世界に見えてたけど、そうでもないの?と、アンチポストモダンと位置づけられたりする _why のその手作り風のおもしろさは、<a target="_blank" rel="nofollow noopener" href="http://www.aoky.net/articles/why_poignant_guide_to_ruby/chapter-2.html">Ruby のどのへんに共感したのだろうか</a>。</p> <p>1999 年発行の <a target="_blank" rel="nofollow noopener" href="https://logmi.jp/tech/articles/322453">matz</a> の本を買おうかどうか、結局読まないとなった紙の本がふえると、かなり精神的ダメージとなって責めてくるので、何日か迷っている。</p> <p>迷っている間に、おそらく初期の Ruby ユーザーマニュアルを見てみる。<br /> もともと matz が日本語で書いたものを英語にしたものとして _why が<a target="_blank" rel="nofollow noopener" href="https://poignant.guide/book/">本で紹介したもの</a>だが、当然、リンク切れで、同じとされているもののうちこちらが、ディスクレシアでもなんとかなりそうな感じがしたので、それを無難に翻訳してもらうものを書いた。たぶん、古いためか翻訳前の日本語のテクストは現在公開されていないようだ。まあ、日本語のテクストだと読めないかもしれない、ゲシュタルト崩壊で。おおよその古いプログラミング言語本はゲシュタルト崩壊しやすいデザインで止まっていて、現在もそれを継承していることが多い ( と思います )。</p> <p>Rf. http://www.math.bas.bg/bantchev/place/ruby-ug.html</p> <p>python</p> <pre><code class="python">!pip install googletrans==4.0.0-rc1 !python -m pip install requests beautifulsoup4 import requests,re from bs4 import BeautifulSoup from googletrans import Translator translator = Translator() url = "http://www.math.bas.bg/bantchev/place/ruby-ug.html" res = requests.get(url) soup = BeautifulSoup(res.text, "html.parser") ptag_list_0 = soup.find_all('p') for index,lines in enumerate(ptag_list_0): if(re.match(r'\w',lines.text) != None ): texts = re.sub(r'\.\s+','. ',lime.text) texts = re.sub(r'\?\s+','? ',texts) texts = re.sub(r'\!\s+','! ',texts) texts = re.sub(r'\,\s+',', ',texts) print(texts) try: translated = translator.translate(texts, dest='ja') print(index, translated.text) ptag_list_0[index].string = translated.text except IndexError: # check #res2 = requests.get(url) #soup2 = BeautifulSoup(res2.text, "html.parser") #ptag_list_2 = soup2.find_all('p') # # #for index,lines in enumerate(ptag_list_2): # print(lines.text) # print(ptag_list_0[index].text) metatag = soup.new_tag('meta') metatag.attrs['charset'] = "utf-8" soup.head.append(metatag) import os filename = os.path.basename(url) with open(filename, "wb") as f_output: f_output.write(soup.prettify("utf-8")) </code></pre> <p>web サイトのソースのなかの p タグで囲まれた文字列を順番に google の翻訳にかけると、だいたい文章が都合よく翻訳できる、という前提で、反対に、都合悪いというのは、それ ( <code><p></code> タグ ) 以外のとこがプログラムコードを表現した箇所だったりしたら、コードのメソッドが直訳されたり、ピリオドが 。になったりで利用しづらくなる。つまり、自然言語のできるだけ訳してほしいところだけを選んで機械翻訳したいので、大まかに選別するということだ。</p> <p>そして、その前提で、じゃあ、p タグのところだけ機械的に翻訳して、またもとあった HTM ソースに戻したら、70% くらい翻訳されてるんじゃない?という予想を立ててやってみたというものだ。</p> <p>もちろんそのままを web ページ翻訳にしてもいいけど、そうすると都合悪いとこもその時点でwebサービスの機械学習の結果の精度が完全にいいとこまできてないと払拭できないので、直していく作業がでてくる。読めなくなるし。<br /> だから、<code><p></code> から <code></p></code> までだけ、あとは絶対に触らなくていいからねと <a target="_blank" rel="nofollow noopener" href="https://vielhuber-de.translate.goog/en/blog/google-translation-api-hacking/?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja">google tanslate</a> に依頼して、翻訳済みの p タグ内テキストを HTML に書き戻すというプログラムになっている。</p> <p><a target="_blank" rel="nofollow noopener" href="https://0bin.net/paste/7Shvy8cs#XuXpgMp0-t3HUu/idtJSO16Y3JpHDG5ydCoY9+JEhuW">例えばこのページでやってみるとこうなる。</a></p> <p>やってみておかしいなと思ったのは、python beautifulsoup4 だとパースされたオブジェクト ( 上の例では <code>doc</code> のことを指す ) って深いコピーじゃなく、element として変更すると ( 上の例では <code>ptag_list_0</code> ) <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/65106473/how-to-change-make-changes-and-update-source-code-using-beautifulsoup">オブジェクトの持つ値も変更されている</a>んだということだった。奇妙に感じたがそういうものだった。</p> <hr /> <p>PyCall 実験</p> <p>PyCall は Ruby 深層学習のために python をバインド ( ではなく、ブリッジらしいけど、バインド?) する感じで、python 用の機械学習のライブラリを Ruby で使えるようにする目的で用意されているので、機械学習のライブラリ以外はどうなのかな?と、この上の python コードで使用したライブラリを Ruby で呼んでみる。</p> <pre><code class="ruby">html = `curl -s 'http://www.math.bas.bg/bantchev/place/ruby-ug.html'` require 'oga' #require 'nokogiri' require 'pycall/import' include PyCall::Import pyfrom 'googletrans',import: :Translator translator = Translator.new doc = Oga.parse_html(html) #doc = Nokogiri::HTML.parse(html) doc.css('p').each {|x| # puts &quot;#{x.text}&quot; if x.text != "" && (x.text.match?(/^\w/) || x.text.match?(/^\(/)) x.text.gsub!(/^s*/,'') x.text.to_s.gsub!(/\.\s*/,'') begin translated = translator.translate(x.text,dest='ja') p x['text'] = translated.text rescue => evar p $! end else # p x.text end } p doc.to_xml #p doc.to_html </code></pre> <p>できますね。</p> <p>では、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/7da2e5f14c965da18106">vosk はどうだろう</a>?</p> tomato 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/18152 2022-03-13T17:19:46+09:00 2022-03-13T17:20:40+09:00 https://crieit.net/posts/Python-unittest-ResourceWarning-unclosed-file Pythonのunittest実行時に、「ResourceWarning: unclosed file」が発生する場合の対処方法 <p>Pythonでunittestを実行していた際に、「ResourceWarning: unclosed file」が出力されたので、その対処方法について、記載。</p> <pre><code>global logins def log_setting(log_level): global logins home = '<ログファイルパス>' pid = os.getpid() # create logger logins = logging.getLogger(__name__) logins.setLevel(logging.DEBUG) # create File Handler fh = logging.FileHandler('{0}/log/log{1}.log'.format(home, pid)) fh.setLevel(logging.INFO) # create Formatter fh_formatter = \ logging.Formatter( '%(asctime)s - %(levelname)s - \ %(filename)s - %(name)s - %(funcName)s - %(message)s') # set Formatter fh.setFormatter(fh_formatter) # add Handler logins.addHandler(fh) # test logging logins.info('logtest:addHandler') </code></pre> <p>↑みたいな関数があり、unittest側からは下記でテストメソッド単位で呼び出したとする。</p> <pre><code>def setUp(self): logtest.log_setting(logging.DEBUG)` def tearDown(self): logtest.logins.handlers = [] </code></pre> <p>この場合、unittestを実行すると、ログファイル内に「ResourceWarning: unclosed file」が出力される。<br /> 理由はログファイルがクローズされていない状態で、ハンドラをクリアしているから。<br /> ハンドラをクリアしている理由は、そうしないとテストメソッドの実行の都度、「log_setting」が呼び出されてハンドラが追加され、ログが重複して出力されてしまうから。</p> <p>ログファイルをクローズする処理を入れればいいので、下記のようにする。</p> <pre><code>def tearDown(self): logtest.logins.handlers[0].close() logtest.logins.handlers = [] </code></pre> <p>こうすると、ログファイルをクローズした後にハンドラをクリアするので、警告は出力されない。<br /> ログも重複しない。</p> <p>本当にいいのは、テスト対象側のプログラムを修正することだと思う。<br /> ファイル出力しかしない想定であれば、addHandlerを使う必要はなく、下記で対応ができる。↓のほうがコード短いし、↑よりもいいかな。</p> <p>```<br /> def log_setting(log_level):<br /> global logins<br /> home = '/home/aoiro/work/logtest'<br /> pid = os.getpid()</p> <pre><code># create logger logins = logging.getLogger(__name__) log_format = \ '%(asctime)s - {} - %(levelname)s - %(message)s'.format("tester") logging.basicConfig( filename='{0}/log/log{1}.log'.format(home, pid), level=log_level, format=log_format) </code></pre> <p>```</p> ao-iro tag:crieit.net,2005:PublicArticle/18054 2022-02-11T20:11:36+09:00 2022-02-11T20:11:36+09:00 https://crieit.net/posts/python-standard-library-abc Python 標準ライブラリ abc 抽象基底クラス <p>Python標準のabcクラスを使用すると抽象基底クラスを表現できる。</p> <h2 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h2> <p>デコレータ<code>@abstractmethod</code>をメソッドの一番内側につけることで、<br /> 抽象メソッドやプロパティを定義できる。</p> <pre><code class="python">from abc import ABC, abstractmethod class SampleABC(ABC): @abstractmethod def sample_abstract_method(self): pass @classmethod @abstractmethod def sample_abstract_classmethod(cls): pass @staticmethod @abstractmethod def sample_abstract_staticmethod(): pass @property @abstractmethod def sample_abstract_property(self): pass @sample_abstract_property.setter @abstractmethod def sample_abstract_property(self, val): pass @abstractmethod def _get_prop(self): pass @abstractmethod def _set_prop(self, val): pass def _get_prop2(self): pass def _set_prop2(self, val): pass def concrete_method(): print('hello') prop = property(_get_prop, _set_prop) prop2 = property(_get_prop2, _set_prop2) </code></pre> <p><code>@abstractmethod</code>を実装せずに継承したクラスを定義し、インスタンス化すると例外が発生する。</p> <pre><code class="python">class ExtendSampleABC(SampleABC): pass esabc = ExtendSampleABC() # TypeError: Can't instantiate abstract class ExtendSampleABC with # abstract methods _get_prop, _set_prop, prop, # sample_abstract_classmethod, sample_abstract_method, # sample_abstract_property, sample_abstract_staticmethod </code></pre> <p>以下のように実装を行うと例外は発生しない。</p> <pre><code class="python">class ConcreteSampleABC(SampleABC): def __init__(self): self._property = 0 self._prop = '' def sample_abstract_method(self): pass @classmethod def sample_abstract_classmethod(cls): pass @staticmethod def sample_abstract_staticmethod(): pass @property def sample_abstract_property(self): return self._property @sample_abstract_property.setter def sample_abstract_property(self, value): self._property = value def _get_prop(self): return self._prop def _set_prop(self, value): self._prop = value prop = property(_get_prop, _set_prop) csabc = ConcreteSampleABC() </code></pre> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/python/cpython/blob/3.10/Lib/abc.py">cpython/abc.py at 3.10 · python/cpython</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.python.org/ja/3/library/abc.html">abc --- 抽象基底クラス — Python 3</a></li> </ul> maru3kaku4kaku tag:crieit.net,2005:PublicArticle/18053 2022-02-11T20:08:59+09:00 2022-02-11T20:08:59+09:00 https://crieit.net/posts/python-standard-library-contextlib Python 標準ライブラリ contextlib with文用コンテキスト <p>Python標準のライブラリcontextlibを使うと、with文に対応したコンテキストを表現できます。</p> <h2 id="デコレータでコンテキストマネージャーを使用"><a href="#%E3%83%87%E3%82%B3%E3%83%AC%E3%83%BC%E3%82%BF%E3%81%A7%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%BC%E3%82%92%E4%BD%BF%E7%94%A8">デコレータでコンテキストマネージャーを使用</a></h2> <p>ContextDecoratorを継承してクラスを宣言することで、コンテキストマネージャーとしてデコレータで使えるようになる。<br /> 非同期版のAsyncContextDecoratorもある。</p> <pre><code class="python">from contextlib import ContextDecorator class SampleContext(ContextDecorator): def __enter__(self): print('__enter__') return self def __exit__(self, *args): print('__exit__') return False @SampleContext() def sample_function(): print('sample_function') sample_function() </code></pre> <pre><code class="tex">__enter__ sample_function __exit__ </code></pre> <h2 id="with文から抜ける時にcloseしてくれるコンテキストマネージャー"><a href="#with%E6%96%87%E3%81%8B%E3%82%89%E6%8A%9C%E3%81%91%E3%82%8B%E6%99%82%E3%81%ABclose%E3%81%97%E3%81%A6%E3%81%8F%E3%82%8C%E3%82%8B%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%BC">with文から抜ける時にcloseしてくれるコンテキストマネージャー</a></h2> <p><code>closing</code>で指定したクラスのcloseメソッドを終了時に呼び出してくれるコンテキストマネージャーを取得できる。<br /> 非同期版はaclosingを使用する。</p> <pre><code class="python">from contextlib import closing class SampleClass: def close(self): print('close') def hello(self): print('hello') with closing(SampleClass()) as c: c.hello() </code></pre> <pre><code class="text">hello close </code></pre> <h2 id="関数をコンテキストマネージャーとして使えるようにする"><a href="#%E9%96%A2%E6%95%B0%E3%82%92%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%BC%E3%81%A8%E3%81%97%E3%81%A6%E4%BD%BF%E3%81%88%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B">関数をコンテキストマネージャーとして使えるようにする</a></h2> <p><code>@contextmanager</code>デコレーを付与することで、関数をコンテキストマネージャーとして使えるようにできる。<br /> 関数はyieldを返す必要がある。<br /> 非同期版は<code>@asynccontextmanager</code>。</p> <pre><code class="python">from contextlib import contextmanager @contextmanager def sample_context(): print('create sample context') try: yield 'yield here' finally: print('exit sample context') with sample_context() as sc: print(sc) </code></pre> <pre><code class="text">create sample context yield here exit sample context </code></pre> <h2 id="コンテキストマネージャーが取得できない場合"><a href="#%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%BC%E3%81%8C%E5%8F%96%E5%BE%97%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88">コンテキストマネージャーが取得できない場合</a></h2> <p>openに失敗した場合なども含め、with文で統一して処理を記述したいような時に<code>nullcontext</code>を使うことができる。</p> <pre><code class="python">import contextlib def get_context_or_nullcontext(): f = None try: f = open('./sample.txt') except Exception as e: print(e) finally: if f is None: f = contextlib.nullcontext('enter_result') return f with get_context_or_nullcontext() as c: print(c) # TextIOWrapper or 'enter_result' </code></pre> <h2 id="標準出力先を変更"><a href="#%E6%A8%99%E6%BA%96%E5%87%BA%E5%8A%9B%E5%85%88%E3%82%92%E5%A4%89%E6%9B%B4">標準出力先を変更</a></h2> <p><code>redirect_stdout</code>で標準出力先を変更できる。<br /> 以下はprintで標準出力ではなくsample.txtに内容が書き込まれる。</p> <pre><code class="python">with open('./sample.txt', 'w') as f: with contextlib.redirect_stdout(f) as a: print('write!') </code></pre> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/python/cpython/blob/3.10/Lib/contextlib.py">cpython/contextlib.py at 3.10 · python/cpython</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.python.org/ja/3/library/contextlib.html">contextlib --- with 文コンテキスト用ユーティリティ — Python 3</a></li> </ul> maru3kaku4kaku tag:crieit.net,2005:PublicArticle/18052 2022-02-11T20:06:38+09:00 2022-02-11T20:06:38+09:00 https://crieit.net/posts/python-standard-library-builtins Python 標準ライブラリ builtins 組み込みオブジェクト <p>builtinsライブラリを使うとPythonの標準に用意されている<br /> <a target="_blank" rel="nofollow noopener" href="https://marusankakusikaku.jp/python/grammer/built-in-functions/">組み込みの関数</a>や<br /> <a target="_blank" rel="nofollow noopener" href="https://marusankakusikaku.jp/python/grammer/built-in-constants/">定数</a>や<br /> にアクセスできます。</p> <h2 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h2> <p>以下のように<a target="_blank" rel="nofollow noopener" href="https://marusankakusikaku.jp/python/built-in-types/list/">list()</a>にアクセスできます。</p> <pre><code class="python">items = builtins.list() items # => </code></pre> <p>アクセスできる関数・定数は以下の通り。</p> <pre><code class="python">[print(x) for x in dir(builtins) if not str(x).startswith('_') ] </code></pre> <pre><code class="text">ArithmeticError AssertionError AttributeError BaseException BlockingIOError BrokenPipeError BufferError BytesWarning ChildProcessError ConnectionAbortedError ConnectionError ConnectionRefusedError ConnectionResetError DeprecationWarning EOFError Ellipsis EnvironmentError Exception False FileExistsError FileNotFoundError FloatingPointError FutureWarning GeneratorExit IOError ImportError ImportWarning IndentationError IndexError InterruptedError IsADirectoryError KeyError KeyboardInterrupt LookupError MemoryError ModuleNotFoundError NameError None NotADirectoryError NotImplemented NotImplementedError OSError OverflowError PendingDeprecationWarning PermissionError ProcessLookupError RecursionError ReferenceError ResourceWarning RuntimeError RuntimeWarning StopAsyncIteration StopIteration SyntaxError SyntaxWarning SystemError SystemExit TabError TimeoutError True TypeError UnboundLocalError UnicodeDecodeError UnicodeEncodeError UnicodeError UnicodeTranslateError UnicodeWarning UserWarning ValueError Warning WindowsError ZeroDivisionError abs all any ascii bin bool breakpoint bytearray bytes callable chr classmethod compile complex copyright credits delattr dict dir display divmod enumerate eval exec filter float format frozenset get_ipython getattr globals hasattr hash help hex id input int isinstance issubclass iter len license list locals map max memoryview min next object oct open ord pow print property range repr reversed round set setattr slice sorted staticmethod str sum super tuple type vars zip </code></pre> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://docs.python.org/ja/3/library/builtins.html">builtins --- 組み込みオブジェクト — Python 3</a></li> </ul> maru3kaku4kaku tag:crieit.net,2005:PublicArticle/18051 2022-02-11T20:02:35+09:00 2022-02-11T20:02:35+09:00 https://crieit.net/posts/python-standard-library-sysconfig Python 標準ライブラリ sysconfig Python構成情報 <p>Pythonの構成情報を取得する。</p> <h2 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h2> <pre><code class="python">import sysconfig sysconfig.get_python_version() # =&gt; 3.9 sysconfig.get_platform() # =&gt; 'win-amd64' sysconfig.get_scheme_names() # =&gt; ('nt', # 'nt_user', # 'osx_framework_user', # 'posix_home', # 'posix_prefix', # 'posix_user') sysconfig.get_path_names() # =&gt; ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include', 'scripts', 'data') sysconfig.get_paths() # =&gt; {'stdlib': 'C:\\Users\\testuser\\AppData\\Local\\Programs\\Python\\Python39\\Lib', # 'platstdlib': 'c:\\home\\workspace\\sample\\Lib', # 'purelib': 'c:\\home\\workspace\\sample\\Lib\\site-packages', # 'platlib': 'c:\\home\\workspace\\sample\\Lib\\site-packages', # 'include': 'C:\\Users\\testuser\\AppData\\Local\\Programs\\Python\\Python39\\Include', # 'platinclude': 'C:\\Users\\testuser\\AppData\\Local\\Programs\\Python\\Python39\\Include', # 'scripts': 'c:\\home\\workspace\\sample\\Scripts', # 'data': 'c:\\home\\workspace\\sample'} </code></pre> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/python/cpython/blob/3.10/Lib/sysconfig.py">cpython/sysconfig.py at 3.10 · python/cpython</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.python.org/ja/3/library/sysconfig.html">sysconfig --- Python の構成情報にアクセスする — Python 3</a></li> </ul> maru3kaku4kaku tag:crieit.net,2005:PublicArticle/18050 2022-02-11T19:58:56+09:00 2022-02-11T19:58:56+09:00 https://crieit.net/posts/Python-http-client-HTTP Python 標準ライブラリ http.client HTTPクライアント <p>Pythonの標準のHTTPクライアントライブラリhttp.clientは<a target="_blank" rel="nofollow noopener" href="https://marusankakusikaku.jp/python/standard-library/urllib.request/">urllib.request</a>で使われている。</p> <h2 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h2> <p>以下のようにHTTP要求を投げることができる。</p> <pre><code class="python">import http.client connection = http.client.HTTPSConnection("example.jp") connection.request("GET", "/") response = connection.getresponse() connection.close() print(response.getheaders()) print(response.getheader('Content-Length')) print(response.headers) print(response.msg) print(response.closed) # => True print(response.reason) # => OK print(response.status) # => 200 print(response.version) # => 11 print(response.read()) </code></pre> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/python/cpython/blob/3.10/Lib/http/client.py">cpython/client.py at 3.10 · python/cpython</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.python.org/ja/3/library/http.client.html">http.client --- HTTP プロトコルクライアント — Python 3</a></li> </ul> maru3kaku4kaku tag:crieit.net,2005:PublicArticle/18049 2022-02-11T19:50:46+09:00 2022-02-11T19:50:46+09:00 https://crieit.net/posts/python-standard-library-http Python 標準ライブラリ http HTTP <p>http.HTTPStatusには各種HTTPステータスコードの列挙が登録されている。</p> <h2 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h2> <p>HTTPStatus.OK(200)やHTTPStatus.NOT_FOUND(404)等、HTTPステータスコードにアクセスできる。</p> <pre><code class="python">from http import HTTPStatus HTTPStatus.OK HTTPStatus.NOT_FOUND HTTPStatus.NOT_FOUND.name # => 'Not Found' HTTPStatus.NOT_FOUND.phrase # => 'Not Found' HTTPStatus.NOT_FOUND.value # => 404 </code></pre> <h2 id="HTTPステータスの列挙"><a href="#HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%81%AE%E5%88%97%E6%8C%99">HTTPステータスの列挙</a></h2> <p>HTTPステータスコード全体はforで列挙できる。</p> <pre><code class="python">for status in HTTPStatus: print(f"{status.name}|{status.value}|{status.phrase}|{status.description}") </code></pre> <pre><code class="text">CONTINUE|100|Continue|Request received, please continue SWITCHING_PROTOCOLS|101|Switching Protocols|Switching to new protocol; obey Upgrade header PROCESSING|102|Processing| EARLY_HINTS|103|Early Hints| OK|200|OK|Request fulfilled, document follows CREATED|201|Created|Document created, URL follows ACCEPTED|202|Accepted|Request accepted, processing continues off-line NON_AUTHORITATIVE_INFORMATION|203|Non-Authoritative Information|Request fulfilled from cache NO_CONTENT|204|No Content|Request fulfilled, nothing follows RESET_CONTENT|205|Reset Content|Clear input form for further input PARTIAL_CONTENT|206|Partial Content|Partial content follows MULTI_STATUS|207|Multi-Status| ALREADY_REPORTED|208|Already Reported| IM_USED|226|IM Used| MULTIPLE_CHOICES|300|Multiple Choices|Object has several resources -- see URI list MOVED_PERMANENTLY|301|Moved Permanently|Object moved permanently -- see URI list FOUND|302|Found|Object moved temporarily -- see URI list SEE_OTHER|303|See Other|Object moved -- see Method and URL list NOT_MODIFIED|304|Not Modified|Document has not changed since given time USE_PROXY|305|Use Proxy|You must use proxy specified in Location to access this resource TEMPORARY_REDIRECT|307|Temporary Redirect|Object moved temporarily -- see URI list PERMANENT_REDIRECT|308|Permanent Redirect|Object moved permanently -- see URI list BAD_REQUEST|400|Bad Request|Bad request syntax or unsupported method UNAUTHORIZED|401|Unauthorized|No permission -- see authorization schemes PAYMENT_REQUIRED|402|Payment Required|No payment -- see charging schemes FORBIDDEN|403|Forbidden|Request forbidden -- authorization will not help NOT_FOUND|404|Not Found|Nothing matches the given URI METHOD_NOT_ALLOWED|405|Method Not Allowed|Specified method is invalid for this resource NOT_ACCEPTABLE|406|Not Acceptable|URI not available in preferred format PROXY_AUTHENTICATION_REQUIRED|407|Proxy Authentication Required|You must authenticate with this proxy before proceeding REQUEST_TIMEOUT|408|Request Timeout|Request timed out; try again later CONFLICT|409|Conflict|Request conflict GONE|410|Gone|URI no longer exists and has been permanently removed LENGTH_REQUIRED|411|Length Required|Client must specify Content-Length PRECONDITION_FAILED|412|Precondition Failed|Precondition in headers is false REQUEST_ENTITY_TOO_LARGE|413|Request Entity Too Large|Entity is too large REQUEST_URI_TOO_LONG|414|Request-URI Too Long|URI is too long UNSUPPORTED_MEDIA_TYPE|415|Unsupported Media Type|Entity body in unsupported format REQUESTED_RANGE_NOT_SATISFIABLE|416|Requested Range Not Satisfiable|Cannot satisfy request range EXPECTATION_FAILED|417|Expectation Failed|Expect condition could not be satisfied IM_A_TEAPOT|418|I'm a Teapot|Server refuses to brew coffee because it is a teapot. MISDIRECTED_REQUEST|421|Misdirected Request|Server is not able to produce a response UNPROCESSABLE_ENTITY|422|Unprocessable Entity| LOCKED|423|Locked| FAILED_DEPENDENCY|424|Failed Dependency| TOO_EARLY|425|Too Early| UPGRADE_REQUIRED|426|Upgrade Required| PRECONDITION_REQUIRED|428|Precondition Required|The origin server requires the request to be conditional TOO_MANY_REQUESTS|429|Too Many Requests|The user has sent too many requests in a given amount of time ("rate limiting") REQUEST_HEADER_FIELDS_TOO_LARGE|431|Request Header Fields Too Large|The server is unwilling to process the request because its header fields are too large UNAVAILABLE_FOR_LEGAL_REASONS|451|Unavailable For Legal Reasons|The server is denying access to the resource as a consequence of a legal demand INTERNAL_SERVER_ERROR|500|Internal Server Error|Server got itself in trouble NOT_IMPLEMENTED|501|Not Implemented|Server does not support this operation BAD_GATEWAY|502|Bad Gateway|Invalid responses from another server/proxy SERVICE_UNAVAILABLE|503|Service Unavailable|The server cannot process the request due to a high load GATEWAY_TIMEOUT|504|Gateway Timeout|The gateway server did not receive a timely response HTTP_VERSION_NOT_SUPPORTED|505|HTTP Version Not Supported|Cannot fulfill request VARIANT_ALSO_NEGOTIATES|506|Variant Also Negotiates| INSUFFICIENT_STORAGE|507|Insufficient Storage| LOOP_DETECTED|508|Loop Detected| NOT_EXTENDED|510|Not Extended| NETWORK_AUTHENTICATION_REQUIRED|511|Network Authentication Required|The client needs to authenticate to gain network access </code></pre> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/python/cpython/blob/3.10/Lib/http/__init__.py">cpython/<strong>init</strong>.py at 3.10 · python/cpython</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.python.org/ja/3/library/http.html">http --- HTTP モジュール群 — Python 3</a></li> </ul> maru3kaku4kaku