Title: Linuxサーバやプログラムに関する覚書

投稿者:まさつ

ページの末尾へジャンプ

〔0001〕 Debian12にNode.jsを導入し、Puppeteerを利用可能にする

Webスクレイピングを行おうとする際、PHPなどのサーバサイドプログラミング言語では静的なコンテンツしか取得できません。JavaScript等によって動的に埋め込まれるデータを取得することができないのです。
この問題への対処方法としては、Webサーバ側でChrome等のブラウザと同等の機能を動かし、そこで動的データを取得するという方法が考えられます。 これにはいくつかの候補が存在しますが、その中の一つ『Puppeteer』を導入して環境構築した際の覚書をここに記します。

1.Node.js及びnpmのインストール

# apt install nodejs npm [Enter]

2.必要なモジュールのインストール

# apt install gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libgconf-2-4 libgdk-pixbuf2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libx11-6 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 fonts-liberation libayatana-appindicator1 libnss3 lsb-release xdg-utils libdrm2 libgbm1 [Enter]
# exit [Enter]

3.Puppeteerのインストール

$ node -v [Enter] → v16.3.0以上ならばOK
$ mkdir nodejs [Enter]
$ cd nodejs [Enter]
$ npm init -y [Enter]

$ npm i puppeteer [Enter]
投稿日時:2024年03月21日 10:23:00
更新日時:2024年03月21日 10:28:00

〔0002〕 Puppeteerを使ったプログラムの注意点

1.ブラウザオブジェクトの生成

  const browser = await puppeteer.launch(
    {
      headless: 'new',
      args: ['--no-sandbox', '--disable-setuid-sandbox']
    }
  );

2.try catch finallyを使用

  try {
    const page = await browser.newPage();

    ・・・

  } catch(e) {

    ・・・

  } finally {
    await browser.close();  //ブラウザオブジェクトの破棄
  }

3.動的コンテンツの表示を待つ

  await page.goto(url, { waitUntil: 'networkidle2' });  //ページが読み込まれるまで待つ
  await page.waitForSelector('.○○○');   //必要なセレクターが表示されるまで待つ
投稿日時:2024年03月21日 10:24:00
更新日時:2024年03月21日 10:28:00

〔0003〕 CakePHP4でフォームヘルパーを使用する際、Textareaでおかしな挙動

CakePHP4のフォームヘルパーを使って

<?= $this->Form->textarea('△△', array('label' => false, 'div' => false, 'style' => '××', 'value' => $○○)) ?>

と記述することにより、Textareaタグを表示させる場合、

【A】

<textarea name="△△" style="××" rows="5">($○○の内容)</textarea>

となります。

そしてこれは以下の記述と等価です。

【B】

<textarea name="△△" style="××" rows="5">
($○○の内容)</textarea>

もしも変数『$○○』の内容として、その先頭に改行文字が入力されていた場合、どうなるでしょうか? そう、その改行文字は無視されることになりますね(なぜなら【A】と【B】は同じものなので…)。

うーん、どうすれば?

・・・

しかしながら、その対策は超簡単なのです。 はたしてそれは?

<?= $this->Form->textarea('△△', array('label' => false, 'div' => false, 'style' => '××', 'value' => $○○)) ?>

<?= $this->Form->textarea('△△', array('label' => false, 'div' => false, 'style' => '××', 'value' => "\n".$○○)) ?>

とするだけ!

すなわち『$○○』の前に『"\n".』をくっつけるだけでOKってことです(改行文字をドットで連結させるだけ)。 なんて簡単な!

投稿日時:2024年03月21日 10:27:00
-

〔0004〕 CakePHP5においてモデルをロードする方法

あるコントローラ内で関係のないモデルを使用するには、そのモデルのロードが必要です。 CakePHP4までは『○○』クラスをロードするには

$this->loadModel('○○');

と記述するだけでOKでした。

しかし、CakePHP5以降はこれが使用できません。 ググっても今一つ訳の分からない記述しか出てきません。


…が、しかし! 実はなんてことはなかったのです。

従来の記述が

$this->loadModel('○○');

であれば、その個所を

$this->○○ = $this->fetchTable('○○');

に置き換えるだけでした。 いや、ただそれだけ。


[2024/5/4追記] 上記の○○って、クラスのプライベートメンバー(プロパティ)として宣言しておかないと、PHP8.2以降では警告が出ます(エラーではありませんが…)。

要するに

class ××Controller extends AppController
{
    private ○○;

とすべきってこと。

他の方法としては、クラスの直前に『#[\AllowDynamicProperties]』を入れるという方法もあります。

#[\AllowDynamicProperties]
class ××Controller extends AppController
{

という書き方です。

なお個人的には、クラスフィールドとしての宣言をすべきだと思っています。

投稿日時:2024年04月02日 09:58:00
更新日時:2024年05月04日 12:38:00

〔0005〕 CakePHP4にBASIC認証を導入

ミドルウェアとしてのBASIC認証の仕組みを構築する手順は以下のサイトに詳しく記述されています。

CakePHP4でBasic認証を導入する方法

ただ、私が行いたいのは「config/routes.php」を書き換える方法ではなく、「src/Application.php」の変更により全てのコントローラにBASIC認証を適用することでもありません。 既存のある特定のコントローラ(複数)にBASIC認証をかけたいだけなのです。

少々試行錯誤したので、覚書として残しておきます。

BASIC認証のミドルウェア名が『HttpBasicAuthMiddleware』であり、BASIC認証を適用したいコントローラが「○○Controller」であるならば、


use App\Middleware\HttpBasicAuthMiddleware;

class ○○Controller extends AppController
{
    public function initialize(): void
    {
        parent::initialize();

        $this->middleware(new HttpBasicAuthMiddleware());    //BASIC認証
    }

…

}

要するに、initializeメソッドを作成し、その中に『$this->middleware(new HttpBasicAuthMiddleware());』の一文を記述するだけ(既にinitializeメソッドが存在するならば、その中に)。

分かってみれば簡単なことでした。

投稿日時:2024年04月26日 09:42:00
-

〔0006〕 CakePHP4のWebアプリに対し、引数に応じたfaviconを設定する

会員制ブログは以下のように(ログインせずに)ブラウザ上に表示することができます。

https://friction-river.jp/blog/viewer/index/2

(プロジェクト名は「blog」、コントローラは「ViewerController」、メソッドは「index」で引数となるのがブログIDです)

この引数に応じてfaviconを変更する仕組みを作成したので、手順を覚書として記しておきます。


1.src/Controller/ViewerController.php

indexメソッドの記述が『public function index($theme_id = null)』である場合、このメソッドの末尾に『$this->set('blogid', $theme_id);』を追加する。


2.templates/layout/default.php

レイアウトファイルのhead内にある『<?= $this->Html->meta('icon') ?>』を削除またはコメントアウトし、代わりに

    <link rel="icon" href="/blog/favicon/<?= $blogid ?>" type="image/x-icon">

を追記する。 キャッシュ無効のために以下の二文を追記しても良い(これは任意)。

    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Cache-Control" content="no-cache">

3.config/routes.php

    //$builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
    $builder->connect('/', ['controller' => 'Viewer', 'action' => 'index']);
    $builder->connect('/favicon/:blogid', ['controller' => 'Icons', 'action' => 'display'])
           ->setPatterns(['blogid' => '\d+'])
           ->setPass(['blogid']);

デフォルトの上記一行目はコメントアウトし、二行目以降を追記。 (faviconへのアクセス要求は「IconsController」の「display」メソッドを呼び出すという意味)


4.src/Controller/IconsController.php

「IconsController」を新規作成し、

    class IconsController extends AppController
    {
        public function display($blogid = null)
        {
            //アイコンファイルのパスを決定
            ・・・(省略)・・・ → $filePathに格納

            //ファイルが存在するかチェック
            ・・・(省略)・・・

            //レスポンスにアイコンデータを設定
            $response = $this->response->withFile($filePath, [
                'type' => 'image/x-icon',
                'download' => false,
            ]);

            return $response;
        }
    }

というメソッドを記述する(要するにfaviconを返却するメソッド)。


以上で引数に応じて異なるfaviconを表示できるようになります。

投稿日時:2024年06月08日 08:48:00
-

〔0007〕 yt-dlpの最新版をWebアプリケーションで実行する方法

『yt-dlp』というプログラムを使ったWebアプリケーションを作成した際、引っ掛かった点を覚書として記しておきます。他の方の参考になれば幸いです。

最初、開発環境にはaptでインストールしました。これは2023年のバージョンでしたが問題なく動いたのです。 ところが、いざ実運用環境(さくらVPS)で同様の手順を踏んだところ、動かない…(ちなみに、開発環境も実運用環境もOSはDebian12)。

どうやら『yt-dlp』のバージョンの問題っぽい(真実は不明)ので、【yt-dlp -U】と打ち込んで最新版にバージョンアップしようとしましたが、aptインストールした場合はできないみたいです。

そこで下記のサイトを参考に、『yt-dlp』の最新版をダウンロードしました。

Linuxに最新のyt-dlpコマンド(youtube-dlの機能強化派生版)をインストールするには?

SSHで直接実行してみると問題なく動作しましたし、これで問題は解決だ!…と思ったらWebアプリケーションでは動きません。

あれ?


あ、よくよく考えたら上記のサイトの導入法って、ユーザ単位での導入であって、システム全体で有効な導入法じゃないじゃん! そしてWebアプリケーションの実行者はWebサーバ(ユーザは「www-data」)なので、『yt-dlp』へのパスが通ってなかったという…。

結論としては、以下のようにシンボリックリンクを張ることで解決しました(もちろん他の方法もあります)。

# cd /use/bin
# ln -s /home/(ユーザ名)/.local/bin/yt-dlp yt-dlp

なお、上記のコマンドはrootユーザで実行してください(言うまでもないですが)。

投稿日時:2024年06月19日 00:10:00
-

〔0008〕 CakePHP4でInvalidCsrfTokenExceptionをキャッチしたい

CakePHP4で作成したWebアプリケーションにおいて、「error.log」内に『Missing or incorrect CSRF cookie type.』というエラーが記録される場合があります(たまにですが…)。

これはCSRF(クロスサイトリクエストフォージェリ)攻撃対策としてフォーム内に埋め込まれているCSRFトークンを取得できないというメッセージです。 この原因はいくつかありますが、主としてブラウザ側の設定でクッキーが無効になっている場合が考えられます。

で、このとき発生する「InvalidCsrfTokenException」という例外をキャッチできなければ、ブラウザ上には例外画面が表示されてしまい、あまり美しくありません。 以下は、上記の例外をキャッチして適切なメッセージを表示するための仕組みです。


1.カスタムミドルウェアを作成

$ cd (プロジェクトのホームディレクトリ) $ bin/cake bake middleware CsrfErrorHandler

 ↓

これによりsrc/Middlewareディレクトリ内に「CsrfErrorHandlerMiddleware.php」が作られるので、下記のように修正。

<?php
declare(strict_types=1);

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

//追加
use Cake\Http\Exception\InvalidCsrfTokenException;
use Cake\Http\Middleware\CsrfProtectionMiddleware;

class CsrfErrorHandlerMiddleware implements MiddlewareInterface
{
    protected $csrf;

    public function __construct()
    {
        $this->csrf = new CsrfProtectionMiddleware([
            'httponly' => true,
        ]);
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        try {
            //CSRFミドルウェアを適用
            return $this->csrf->process($request, $handler);
        } catch (InvalidCsrfTokenException $e) {
            //CSRFエラーをキャッチ
            $response = new \Cake\Http\Response();
            return $response->withStringBody('ここにメッセージを記述')->withStatus(403);
        }
    }
}

2.上記のミドルウェアを組込み

src/Application.php内のmiddlewareメソッドを修正します。

 ↓

    //->add(new CsrfProtectionMiddleware([
    //    'httponly' => true,
    //]));

の3行をコメントアウトし、替わりに以下の1行を追加してください。

    ->add(new CsrfErrorHandlerMiddleware());

これでOK!

投稿日時:2024年06月25日 00:16:00
-

〔0009〕 『開発環境のサーバでは動作するが、本番環境のサーバでは動作しない』という現象の検証と改善策

1.開発環境において

「Google Cloud」にログインし、「Text-to-Speech API」を利用するための各種設定を行ったあと、Credentialクレデンシャル情報であるJSONファイルを入手しました。 開発環境にCakePHP5のプロジェクトを新規に作成し、composerで「google/cloud-text-to-speech」をインストール。 コントローラに音声データ生成処理を記述したところ、特に問題なく動作しました(テキストデータから音声データを生成することに成功)。 何と言うか、めっちゃ簡単でした(笑)


2.本番環境において

全てのファイルを一括して、本番環境のサーバへアップロード。 テストしてみたところ…う、動かない…?

ここから長い(と言っても一日程度)戦いが始まりました。

①try catchしていたため、error.logへの出力が無く、どこで例外が発生しているのかが分からない。そこでcatch後にgetMessageメソッドの結果をLogクラスに渡して、error.logに出力するようにしました。

②しかし、どの行で問題が発生しているのかが分からなかったため、一時的にtry catchをコメントアウト。これによりTextToSpeechClientクラスのインスタンス化には成功しているものの、次のSynthesisInputクラスの生成に失敗していました(GPBDecodeException例外が発生)。

③本番環境にも手動で「google/cloud-text-to-speech」を入れ直してみましたが、改善せず。

④「Text-to-Speech」はgRPC経由で動作するとの情報を得たため、aptで「grpc-proto」をインストールしてみるも改善せず(peclでgrpcを入れるまではやらない…なぜって超面倒だから)。

⑤開発環境だけに入れていた「google-cloud-cli」を本番環境にもインストール。…が、関係なかった。

⑥開発環境では「http」アクセスだったので、本番環境と同じ「https」に変更してみるも、問題なく動作(つまりプロトコルの問題では無かった)。

⑦とにかく二つのサーバ間の差分を取るしかない。

# dpkg --get-selection > ○○.txt

を開発環境と本番環境の両方で実行し、差分を検証。 ちなみに580個の差分が有った…まじか(笑)

で、怪しげな次の二つを本番環境においてaptインストール。

# apt install libprotobuf32 libprotobuf-c1

な、なんとこれが大成功! 本番環境でもプログラムが問題なく動作するようになりました。


なお、こうやって文章に起こすと簡単そうに見えますが、相当苦労してますからね(笑)

投稿日時:2024年09月13日 10:42:00
-

ページの先頭へジャンプ