20210106のLinuxに関する記事は7件です。

Google画像検索を保存するPythonライブラリの使い方。【最新版】

あけましておめでとうございます。

結構知ってる人が多いとは思いますが私が何度も迷うので書き留めておきます。
機械学習の際の学習データにご利用ください。

2020年8月ごろ?に検索結果の提供方法が大きく変わったらしく、Windows用ソフトの「ImageSpider」が使えなくなりました。(結構便利だったんですけどね...)
そこでこれからはPython用ライブラリの「google_images_download」を使うことにしました。

インストール

【注意】pipではインストールしないでください!

pip3 install google_images_download

PIPにあるバージョンではGoogle側の変更に対応できていないため、インストールしても使えません。
なので直接インストールします。

git clone https://github.com/Joeclinton1/google-images-download.git
cd google-images-download && sudo python setup.py install

これでインストールは終わりです。

使い方

ここでは代表的な使い方としてコマンドライン上での利用方法を載せておきます。

 googleimagesdownload --keywords "apple" --limit 20

上記の例だと「apple」の検索結果を20件、カレントディレクトリに保存します。
Python上での使い方やその他の条件設定等は公式ドキュメント*を見てください。

100件以上を取得する場合

取得したい画像が100件を超える場合はchromedriverをインストールする必要があります。
※Ubuntu20.04 LTSでの操作方法です。Windowsの場合は各自お調べくださいm(_ _)m

まずドライバの最新バージョンを確認
以下のサイトに行って最新版の「chromedriver_linux64.zip」のURlをコピーしてください。
*https://sites.google.com/a/chromium.org/chromedriver/downloads
image.png
因みに2021年1月6日現在はhttps://chromedriver.storage.googleapis.com/88.0.4324.27/chromedriver_linux64.zipでした。

次に以下のコマンドを続けて入力してください。こちらの記事を参考にさせていただきました。)

sudo apt install unzip
cd /tmp/
curl -O <<<<<ここにさっき確認したURLを入れる>>>>>
unzip chromedriver_linux64.zip
mv chromedriver /usr/local/bin/
rm chromedriver_linux64.zip

これでインストールは以上です。

100件以上取得する際のコマンド

googleimagesdownload --keywords "apple" --limit 120 --chromedriver /usr/local/bin/chromedriver

さっき紹介したやつに --chromedriver オプションでインストール先を選択するだけです。

↓取得結果
image.png

ちゃんと取得できていますね。
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【最新】Google画像検索を保存するPythonライブラリの使い方&ChromeDriverをubuntuで使う

あけましておめでとうございます。

結構知ってる人が多いとは思いますが私が何度も迷うので書き留めておきます。
機械学習の際の学習データにご利用ください。

2020年8月ごろ?に検索結果の提供方法が大きく変わったらしく、Windows用ソフトの「ImageSpider」が使えなくなりました。(結構便利だったんですけどね...)
そこでこれからはPython用ライブラリの「google_images_download」を使うことにしました。

インストール

【注意】pipではインストールしないでください!

pip3 install google_images_download

PIPにあるバージョンではGoogle側の変更に対応できていないため、インストールしても使えません。
なので直接インストールします。

git clone https://github.com/Joeclinton1/google-images-download.git
cd google-images-download && sudo python setup.py install

これでインストールは終わりです。

使い方

ここでは代表的な使い方としてコマンドライン上での利用方法を載せておきます。

 googleimagesdownload --keywords "apple" --limit 20

上記の例だと「apple」の検索結果を20件、カレントディレクトリに保存します。
Python上での使い方やその他の条件設定等は公式ドキュメント*を見てください。

100件以上を取得する場合

取得したい画像が100件を超える場合はchromedriverをインストールする必要があります。
※Ubuntu20.04 LTSでの操作方法です。Windowsの場合は各自お調べくださいm(_ _)m

まずドライバの最新バージョンを確認
以下のサイトに行って最新版の「chromedriver_linux64.zip」のURlをコピーしてください。
*https://sites.google.com/a/chromium.org/chromedriver/downloads
image.png
因みに2021年1月6日現在はhttps://chromedriver.storage.googleapis.com/88.0.4324.27/chromedriver_linux64.zipでした。

次に以下のコマンドを続けて入力してください。こちらの記事を参考にさせていただきました。)

sudo apt install unzip
cd /tmp/
curl -O <<<<<ここにさっき確認したURLを入れる>>>>>
unzip chromedriver_linux64.zip
mv chromedriver /usr/local/bin/
rm chromedriver_linux64.zip

これでインストールは以上です。

100件以上取得する際のコマンド

googleimagesdownload --keywords "apple" --limit 120 --chromedriver /usr/local/bin/chromedriver

さっき紹介したやつに --chromedriver オプションでインストール先を選択するだけです。

↓取得結果
image.png

ちゃんと取得できていますね。
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Apache】preforkの話

概要

preforkの話。preforkってなんだっけって話から広げていく

そもそもTCP通信のフロー

LinuxでTCPクライアント/サーバで通信するにはサーバ側は以下の手順を踏んで通信を待ち受ける。

socket() -> bind() -> listen() -> acceps()

https://qiita.com/Michinosuke/items/0778a5344bdf81488114

単一プロセスで動くTCPサーバであれば上記の手順で十分だがクライアントが複数になった際に同時接続をされた際にクライアント1は接続可能だがクライアント2は接続を行うことができない。

複数クライアントの同時接続に対応するため,fork() システムコールを使用する。一番簡単なやり方として親プロセスでlistenまでを行い子プロセスを生成しその続きを子プロセスで行ってもらう方式。以下は簡単なサンプル。この方法によって単一プロセスのTCPサーバで起きる問題が子プロセスの生成によって起きなくなるという仕組み。

// listenまでは親でやっておきループ内で子プロセスを生成し通信を行う。
  for (;;) {
    len = sizeof(client);
    sock = accept(sock0, (struct sockaddr *)&client, &len);

    pid = fork();

    if (pid == 0) {
      n = read(sock, buf, sizeof(buf));
      if (n < 0) {
        perror("read");
        return 1;
      }

      write(sock, buf, n);
      close(sock);

      return 0;

    } 

とりあえずそれっぽく書ければ良いので詳細は端折ってます。

accept後にforkしてもsocketは親子で共有されるの?

    sock = accept(sock0, (struct sockaddr *)&client, &len);

    pid = fork();

    if (pid == 0) {
      n = read(sock, buf, sizeof(buf));
      if (n < 0) {
        perror("read");
        return 1;
      }

この部分。forkした後にさらっと親で開いたsocketを使ってるけどこれってなんでだろうっていう疑問。解答自体は以下の記事がとてもわかりやすかったです。ざっくり書くとオープンファイル記述(ファイルオフセットとか)はforkすると共有される。つまりacceptしたsocketを子プロセスが使って通信が可能ということ。(あくまでも共有であって子プロセスがそれぞれソケットを作っているわけではないので注意)。ちなみにいろいろ調べてて見つけたのだがRubyの場合はexec呼び出し時にfdをcloseするらしい。

http://kotaroito.hatenablog.com/entry/20120108/1326030769

manとかでfork(2)とかで調べるとでてくる。

子プロセスは親プロセスが持つ オープンファイルディスクリプタの集合のコピーを引き継ぐ。 子プロセスの各ファイルディスクリプタは、 親プロセスのファイルディスクリプタに対応する 同じオープンファイル記述 (file description) を参照する (open(2) を参照)。 これは 2 つのディスクリプタが、ファイル状態フラグ・ 現在のファイルオフセット、シグナル駆動 (signal-driven) I/O 属性 (fcntl(2) における F_SETOWN, F_SETSIG の説明を参照) を共有することを意味する。

forkモデルのTCPサーバで起きる問題

ここで起きるのがfork(2)のコストの高さです。forkはCoWが効くとは言えプロセスの生成自体は大変な作業です。詳しくは以下の記事をry

https://naoya-2.hatenadiary.org/entry/20071010/1192040413

じゃあどうするかスレッドを生成する方法やイベント駆動なんかが流行ってますが記事の主題でもあるpreforkを使う方法を取り上げます。forkのコストが高いなら先にforkさせておいてクライアントが来ても生成しておいたsocketで賄おうという考え方です。ざっくり流れとしては

  • ソケットを親プロセス で生成
  • forkして子プロセスを作る
  • 子プロセスがそれぞれaccept待ちでblockする(処理が正常に完了した場合、受け付けたソケットの 記述子である非負整数を返します。)

といった状態を作ります。この時3番目のaccept待ちでブロックしている子プロセスはクライアントが来ると複数のうちひとつのプロセスだけを起床させ起床したプロセスがその後の通信を行うことができるといった仕組みです。この時クライアントの最大同時接続数はforkした数となります。1000ユーザ接続させたいなら1000のプロセスが必要です。この辺は巡り巡ってC10k問題とか言われているやつです。

https://ja.wikipedia.org/wiki/C10K%E5%95%8F%E9%A1%8C

Nginxなんかだと少ないプロセス(内部的にも1スレッド)で大量のアクセスを捌いていますがpreforkとかスレッドではなくイベント駆動で処理をしています。ざっくりいうとworkerごとにイベントを監視して都度処理を行うことでコンテキストスイッチなんかの問題を軽減しています。本題とは何も関係ないのでとりあえずepoll(2)が凄すぎるって感じで辞めておきます。

https://linuxjm.osdn.jp/html/LDP_man-pages/man7/epoll.7.html

同一のソケットに複数プロセスからacceptして大丈夫なの?

結論としては大丈夫らしいです。ソースを読んだわけではないですがこの辺はカーネルがよしなにやってくれるらしいです。古いカーネルだとthundering herdなんかも起きていたらしいです。(acceptmutexをアプリで実装してるケースもあるらしく古いapacheなんかはその方式で排他を行っていたらしい)

https://naoya-2.hatenadiary.org/entry/20070311/1173629378

おまけ① Gracefull shutdownってどういう実装なの?

グレースフルシャットダウンの定義を以下とするなら

  • 停止指示後に、新しい接続を受付しない
  • 残った処理中の接続が完了するのを待ってから、プロセスを安全に停止する

TCPレイヤでやることとしたらlisten socketをcloseする。 -> 新規接続を受付しない

子プロセスは親から受けたシグナルを元に処理完了で終了するように実装する -> 安全停止

といった流れになる。Nginxをサンプルにみてみる(めちゃめちゃ端折ってます)

static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    for ( ;; ) {
        // SIG_QUITを受け取った子プロセスは以下に入る
        if (ngx_quit) {
            ngx_quit = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                          "gracefully shutting down");
            ngx_setproctitle("worker process is shutting down");

            if (!ngx_exiting) {
                ngx_exiting = 1;  // 終了フラグを立てる
                ngx_set_shutdown_timer(cycle); // shutdownタイマーを設定する
                ngx_close_listening_sockets(cycle); // リスニングソケットをcloseする
                ngx_close_idle_connections(cycle); // アイドルコネクションをcloseする
            }
        }
        // 終了フラグが立っているので以下に入る
        if (ngx_exiting) {
            if (ngx_event_no_timers_left() == NGX_OK) { // ngx_event_no_timers_leftはアクティブな接続がある限りはOKにならない
                ngx_worker_process_exit(cycle);  // 終了関数を呼び出す
            }
        }
    }
}

ngx_event_no_timers_leftがactiveな接続を監視してなければプロセスが終了する。読んでいて気づいたがこれはバックエンドのアプリのどこかで刺さった場合はNginx自体のこの処理もTimeoutを設定してなければ永遠に動かない実装の模様。そもそもそんなのアプリ側でなんとかしろよって話だけどいつかハマりそう。ngx_event_no_timers_leftはこの辺。イベントツリーを確認して未処理イベントが無ければOKを呼び出し元に返す。今回はOKが帰ってきたらNginxのプロセスが正常終了する流れなのでそれを持ってGracefull shutdownとなっている。

ngx_int_t
ngx_event_no_timers_left(void)
{
    ngx_event_t        *ev;
    ngx_rbtree_node_t  *node, *root, *sentinel;

    sentinel = ngx_event_timer_rbtree.sentinel;
    root = ngx_event_timer_rbtree.root;

    // イベントを管理している木がroot = sentinel nodeならOKを返す
    if (root == sentinel) {
        return NGX_OK;
    }

    for (node = ngx_rbtree_min(root, sentinel);
         node;
         node = ngx_rbtree_next(&ngx_event_timer_rbtree, node))
    {
        ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));

        // cancel不可能状態のイベントがあれば終了させない
        if (!ev->cancelable) {
            return NGX_AGAIN;
        }
    }

    return NGX_OK;
}

http://nginx.org/en/docs/ngx_core_module.html#worker_shutdown_timeout

ngx_rbtree_*っていう名前からも想像できるようにイベントの状態は赤黒木で持っている模様。平衡二分木の一種なのは知ってるけど詳細は何も分からんので調べよう。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSのショートカットアプリでsshスクリプト実行:Rasberery PiやLinux操作が捗る!

はじめに

皆さんはiOSのショートカットAppはお使いでしょうか?私は最近まで全く使っていませんでした…。
ただ最近Rasberry Piとの組み合わせ(※他の機器でも使えますが)に無限の可能性を感じるようになり、この感動を共有したいと思って筆を取りました!
image.png

本記事では主に下記内容を取扱います。

  • SSH接続設定方法(iOS13から対応の公開鍵認証)
  • 任意のスクリプトを実行
    • 簡単な例
    • 連続実行するプログラムをバックグラウンドで実行する
    • バックグラウンドで実行しているプログラムを終了する
    • リモートマシンをシャットダウン/再起動する

なお私は最近このあたりに手を出し始めたため、もし間違っている内容などあればコメントを頂けると幸いです。

SSH接続設定方法(iOS13から対応の公開鍵認証)

iOS12まではパスワード認証による接続のみでしたが、iOS13以降は公開鍵認証に対応しています。接続方法はとても簡単です。

1.iPhoneでの操作:公開鍵の共有

まずはショートカットAppを立ち上げ、検索欄から「SSH経由でスクリプトを実行」を選択します。
image.png

そうすると下記のようにSSH接続設定を入力する画面が表示されます。
ホスト・ポート・ユーザはご自分の環境に合わせて入力ください。
認証方式はデフォルトではパスワードですが、SSHキーを選択することが可能です。
image.png

SSHキーを選択すると下記のような画面が表示されます。
まず①の「新しいキーを生成」を選択して、所望の種類・ビット長を選択します。
次に②の「公開鍵を共有」を選択すると公開鍵が表示されます。メールで送信するなど、自分の都合の良い共有方法を選択します。
SSH接続したいマシンがパスワード認証を無効化していなければ、ここで生成された公開鍵をクリップボードにコピーして、パスワードでSSH接続→公開鍵を設定ファイルに追記などすれば、iPhoneだけで一通りの操作が可能かと思います。
私はこの時点でパスワード認証を無効化してしまっていたので、いったん公開鍵認証済みの別PCにiPhoneの公開鍵をメールで送り、別PCを使って公開鍵を追加しました。

image.png

2.SSH接続したい相手先マシンでの操作

こちらはいろいろな記事があるので詳細は割愛し、リンク類だけ記載いたします。私はRasberry Piを使いたかったので、ソースがそちら寄りになる点はご容赦ください。

一次ソース;
https://www.raspberrypi.org/documentation/remote-access/ssh/passwordless.md

二次ソース:
https://tool-lab.com/raspi-key-authentication-over-ssh/

これで接続設定は完了です。 あとはスクリプトを記載していきます。具体的な例に沿って説明していきます。

任意のスクリプトを実行

簡単な例

まずはリモートPCで実行中のタスク一覧を取得してiPhone上に表示する簡単な例を実行してみます。
タスク一覧の取得はpsで実行可能ですが、それだけですと結果をiPhone上で表示することができません。そこで「表示」のアクションを追加して上げると、デフォルトで「シェルスクリプトの結果」を受け取ることができます。
これを実行すると通知領域にリモートPCで実行中のタスク一覧が表示されるはずです。

image.png

連続実行するプログラムをバックグラウンドで実行する

次の例です。
「サーバを立ち上げる」ような、連続実行するプログラムの場合、ショートカットAppはプログラムの終了を待ち続けてしまい、アクションが完了しません。
また上記問題を回避しても、SSH接続切断後もリモートPC側で処理を継続させる必要があります。

今回は試しに ~/myapp/mainというプログラムをバックグラウンドで実行するケースについて記載します。

cd ~/myapp
nohup ./main >/dev/null 2>&1 &
  • nohup: このコマンドによってSSH接続後も処理を続行させることができます
  • 最後の &: これによってバックグラウンドで処理を実行します
  • >/dev/null 2>&1: 上記のようにnohup ./main &だけを実行すると、諸々の標準出力が表示されてしまうためか、アクションが完了しませんでした。そこで標準出力・エラーを読み捨てるために本コマンドを追記します

以上の処理で、バックグラウンドで処理を実行し、かつSSH接続切断後も動作を継続させることができました。

バックグラウンドで実行しているプログラムを終了する

さて、バックグラウンドで処理を実行できたは良いものの、これをkillできないのは問題です。
そんなときは下記スクリプトを実行します。(mainの部分は終了したいタスク名に置き換えてください)
こちらを参照させて頂きました。

ps aux | grep main | grep -v grep | awk '{print "kill -9", $2}' | sh

リモートマシンをシャットダウン/再起動する

さてさてさて、Rasberry Pi等を使っていると地味に面倒なのがマシンのシャットダウンです。
毎回sudo shutdown -h nowしているのですが、できればボタン一つで実行したいところです。

そんなときにもショートカットAppが使えます :innocent:

手順としては簡単で、上記例と同じようにスクリプトを記載するだけですが、sudoする際にパスワードを入力する必要があり、それをショートカットAppで実現するのに一手間必要でした。

再起動の場合

echo (毎回尋ねる) | sudo -S reboot 2>/dev/null
  • sudo -Sとすることで標準出力からパスワードを受け取ることができます。
  • そのためechoを実行しますが、なんとなくその後にパスワードを平文で保存しておくことは気後れします。そのためショートカットの「変数」機能を使用し、パスワードは毎回尋ねることにしました。

これでボタン一つでショートカット・再起動が可能です…!

おわりに

いかがでしたでしょうか。今回は私が実践したケースに基づいた例を紹介しましたが、組み合わせ次第では他にもいろいろな事例に使えるのではないでしょうか?
こんなことにも使えるよというようなコメント大歓迎です…!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Squidで複数のProxyを操る

※この記事はProxyを華麗にスルーしてWi-Fiを飛ばすには -Qiitaの環境を前提としています。まだお読みでない方はご一読の上こちらの記事をお読みください。

環境をざっくりと解説

  • PC: ThinkPad T490 Arch Linux
  • 認証付きProxy必須の環境
  • Azure上に構築したHTTP ProxyをSSHポートフォワーディングで持ってきている
  • create_apでWi-Fiを飛ばしていて,スマホはDronyを使いHTTP Proxy非対応のアプリもProxy経由で通信できるようにしている

困ったこと

  • スマホは認証付きProxyだとうまく接続できないため,常時Azure上に構築したプロキシに接続させているから,追加でAzureの通信料金が発生する
  • 認証付きProxyの方だと見れないサイトがある:Youtubeなど生活必需品

解決するために

ローカルにSquidを使ってProxyを構築し,時間帯・アクセス先のサイトに応じて代理接続するProxyサーバ先を選択できるようにした

192.168.1.1:8080に認証付きProxy,0.0.0.0:8888に外部Proxyがある環境の例 重要な設定を抜粋

/etc/squid/squid.conf
(前略)
cache_peer 192.168.1.1 parent 8080 0 no-query no-netdb-exchange no-digest login=ユーザ名:パスワード proxy-only
cache_peer 0.0.0.0     parent 8888 0 no-query no-netdb-exchange no-digest default

acl sorting time SMTWHF 1:00-6:00
acl blacklist dstdomain "/usr/local/etc/squid/blacklist"

(中略)

# ローカル環境には上位のプロキシを通さず直接接続
acl developersegments1 dst 192.168.1.1/24
always_direct allow developersegments1

never_direct allow all

# キャッシュしない
cache deny all

# 1:00から6:00にblacklistに掲載されているサイトにアクセスしたときは192.168.1.1に代理接続 **させない**
cache_peer_access 192.168.1.1 deny blacklist sorting
blacklist
.qiita.com
.youtube.com

.[domain]でそのドメイン全てを対象にできる

これで設定したポートに接続すると,192.168.1.1へ認証なしで優先的に接続され,1:00から6:00にblacklistに設定したサイトへアクセスしようとした場合は0.0.0.0のproxyへ接続される。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ターミナル、行単位で文字を一気に消したい時

ctrl + u

カーソルより後の文字列を全て削除する。

例
|aaaaaaaaaaaaaaaaaaaaaaa

↓

|

ctrl + k

カーソルより前の文字列を全て削除する。

例
aaaaaaaaaaaaaaaaaaaaaaa|

↓

|
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バッチ初心者が考えたバッチ

yyyymmdd.*.logと命名されたログファイルが溜まったので、ディレクトリ内にある同日付の付いたログを固める

!/bin/bash

CMDNAME=`basename $0`

#引数の確認 なければエラー
if [ $# -ne 1 ]; then
  echo "need a directory after the command" 1>&2
  echo "Ex. /var/log/10.0.0.254/" 1>&2
  exit 1
fi

#作業ディレクトリを引数から取る
d="$1"

#作業ディレクトリ内全体
FILES="$1"*

echo "work on directory $d"

#ループ
for f in $FILES
do
  echo "Processing $f"

  #日付のみ抽出
  #$f(/*/*/*/*/yyyymmdd.*.log)からまず
  #前方最後方一致で/を取り、残りのyyyymmdd.*.logから
  #前方最前方一致で.を取り、yyyymmddのみにし、fileへ代入
  file=$(echo "${f##*/}" | cut -f 1 -d '.')

#念のためファイルの存在確認
#あれば日付でまとめて.tar.gz化
if [ -f "$f" ]; then
  tar czf $d$file.log.tar.gz  $d$file.*
#  echo "$d$file.log.tar.gz  $d$file.*"
fi

done

exit 0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む