りなっくすとらずぱい!

Raspberry Pi初心者に向けた各コマンドの説明、プログラムの作り方について紹介しています!

Raspberry PiでWEBアプリケーションを作る (3 - RSSリーダーの作成)

f:id:ibuquicallig:20190516023802p:plain

こんにちは、たねやつです。

今回は前回に続いてapacheでWEBページを作っていきます。いったんここまでで一区切りとなります。

この記事でできること

  • PHPを駆使してRSS配信しているサイトの最新記事を一覧で表示する。
f:id:ibuquicallig:20190516023703p:plain

前の記事

PHPについて

PHPとは、JavaやPythonと同じようにプログラミング言語であり、一番の特徴はHTML文書内に埋め込み、処理結果を簡単に表示できる点だと思います。

JavaScriptでも同じように処理結果をHTML内に埋め込んだ風にできますが、JavaScriptはブラウザ内で実行されるのに対し、PHPはサーバー側(Raspberry Pi内)で実行されます。

プログラムを実行した結果をHTML文書にしてブラウザに渡すので、JavaScriptのように入力値に応じて別の場所の内容を変えるというようなユーザーからの入力をページ更新せずに実装することはできません。

WordPressやLaravelといったフレームワークが有名ですが、ごく小さな開発なのでそのままのPHPを使用します。

手順

それでは開発していきましょう!今回はPHPをRaspberry Piにインストールするところからです。

PHPをインストールする

もうおなじみのコマンドになってきましたが、以下のコマンドでphpをインストールします。バージョンが5系と7系で大きな変更点があるようですが、新規開発では関係ありませんね(笑) 最新をインストールします。

$ sudo apt-get update
$ sudo apt-get install php php-xml 

php-xmlはxmlファイルの処理を行うために必要な拡張機能となっています。Raspbianではデフォルトでインストールしてくれないので手動で追加します。

phpinfo()を実行する

インストールが完了したらコマンドを実行するページを作成していきます。今回はrss.phpというファイル名で進めていきます。PHPを使用する場合はファイル名の拡張子が.htmlではなく.phpとなります。

$ cd /var/www/html/
$ vim rss.php

中身はこんな感じです。

<?php
    phpinfo();
?>

保存したら、一旦apacheを再起動しておきます。ここまで再起動コマンドなどの説明をしていませんでした。。。(笑)

# 再起動コマンド
$ sudo service apache2 restart
# 以下でもOK
$ sudo /etc/init.d/apache2 restart

再起動が完了したらhttp://[Raspberry PiのIPアドレス]/rss.phpにアクセスしてみてください。サーバーやPHPの情報が表示されます。

f:id:ibuquicallig:20190516023716p:plain

正常に表示できていればインストールは成功です👏

RSSを取得して、そのまま生データを表示

とりあえず、いきなり一覧形式で表示する処理を作るよりも、ステップバイステップで作っていきましょう!

まずはRSSを取得、そのまま画面に表示させてみます。

<?php
$rss = simplexml_load_file('http://feeds.feedburner.com/hatena/b/hotentry.css'); //取得先のRSSフィードを指定

echo "<pre>";
var_dump($rss);
echo "</pre>";
?>

simplexml_load_file()という関数は、PHP標準のものであり指定したURLのファイルをXML形式の値として取得する、というものです。

指定しているRSSははてなブックマークの総合ホットエントリです。取得結果を$rssに格納してvar_dump()で画面に出力します。パッと値を確認したいときにとても便利な関数です。ついでに前後をpreタグで囲んでおくとさらに見やすくなります。

object(SimpleXMLElement)#1 (2) {
  ["channel"]=>
  object(SimpleXMLElement)#2 (4) {
    ["title"]=>
    string(60) "はてなブックマーク - 人気エントリー - 総合"
    ["link"]=>
    string(34) "http://b.hatena.ne.jp/hotentry/all"

このような値が表示されていれば成功です。

RSSではXML形式のファイルを取得できます。このファイルはHTMLファイルと同じような木構造となっていて、親子関係をしっかりと理解することで任意のタグと値をうまく参照することができます。

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://purl.org/rss/1.0/" xmlns:admin="http://webns.net/mvcb/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#" xmlns:syn="http://purl.org/rss/1.0/modules/syndication/" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/">
<channel rdf:about="http://b.hatena.ne.jp/hotentry/all">
    <title>はてなブックマーク - 人気エントリー - 総合</title>
    <link>http://b.hatena.ne.jp/hotentry/all</link>
    <description>最近の人気エントリー</description>
    <items>...</items>
    <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rdf+xml" href="http://feeds.feedburner.com/hatena/b/hotentry"/>
    <feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="hatena/b/hotentry"/>
    <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/"/>
</channel>
<item rdf:about="https://www.businessinsider.jp/post-190596">
    <title>
        「マクドナルドの店内BGM」のディープさに感動して“中の人”に直撃したら本当にスゴかった話 | BUSINESS INSIDER JAPAN
    </title>
    <link>https://www.businessinsider.jp/post-190596</link>
    ...
</item>
<item rdf:about="https://togetter.com/li/1355077">...</item>
<item rdf:about="https://abematimes.com/posts/7003122">...</item>
...

こんな感じの構造になっています。最新記事のタイトルとリンクを取得するには、item > titleitem > linkとたどっていきます。

<?php
$rss = simplexml_load_file('http://feeds.feedburner.com/hatena/b/hotentry.css'); //取得先のRSSフィードを指定
foreach ($rss -> item as $item) {
    echo $item -> title;
    echo "<br>";
}
?>

このようにすると取得できる一覧のタイトルを最新順にすべて表示します。echo ""とするとブラウザ渡すHTML文書に追記する、というような感じです。実際にブラウザ側でソースの表示(右クリックから)をしてみると、

タイトル0<br>タイトル1<br>タイトル2<br>...

という感じになっています。

PHPのユーザーエージェントを設定する

さて、先ほどのはてなブックマークのURLは問題なく取得できたのですが、同じようにはてなブックマークのIT新着のRSS(http://b.hatena.ne.jp/entrylist/it.rss)を取得しようとするとなぜか取得できません。同じURLをブラウザでアクセスしてみると問題なく表示されます。

$rssで始まる行の下に、var_dump($http_response_header);と追加することでHTTPリクエストのヘッダーを見ることができます。失敗しているURLでは403 Forbiddenが表示されます。

どうやらいくつかのサイトでは、ユーザーエージェントの設定されていないリクエストは拒否するようになっているようで、PHPではデフォルトではユーザーエージェントは設定されていません。そのため取得することができないのです。

ユーザーエージェントとは、HTTPリクエスト元がどのような端末なのか、ブラウザなのかを表す値です。リクエストを受けたサーバーはOSやブラウザの種類などを判断して処理することができます。

空でなければ拒否されないようなので、何か値を設定します。php.iniという設定ファイルがあるのでそこからデフォルト値を設定できます。

$ sudo vim /etc/php/7.0/apache2/php.ini

apacheから実行されるPHPの設定はこちらのファイルを使用します。コマンドライン上で実行するPHPの設定は/etc/php/7.0/cli/php.iniの方を設定します。

ファイルを開いたら/agentと入力してエンターを押し、該当の場所まで飛びます。

; Define the User-Agent string. PHP's default setting for this is empty.
; http://php.net/user-agent
user_agent="PHP"

user_agentという項目がコメントアウトされているので文頭の;を消して有効化します。

保存して終了したらapacheを再起動して設定を有効化します。

$ sudo service apache2 restart
# 設定をリロードするだけなら以下でもOK
$ sudo service apache2 reload

これで設定完了です。

もう一つコード上で設定する方法もあります。任意の場所でini_set("user_agent", "PHP");と書きます。これでphp.iniで設定されている値を上書きできます。

rss.phpの体裁を整える

ちゃんとしたHTML文章として体裁を整えておきます。前回のfavorite.phpと同じようにして以下のような内容にします。

<!DOCTYPE html>
<html lang="jp">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>RSS</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <style>
        .list-group-margin {
            margin-top: 30px;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-dark bg-dark">
        <div class="container">
            <p class="navbar-brand">RSS</p>
        </div>
    </nav>

    <div class="container">
        <div class="row"></div>
    </div>
</body>
</html>
<?php ?>

タイトルなどは適宜お好きなように変更してください。末尾にPHP用のタグを用意しています。<?php ... ?>で囲った中にプログラムを書くと実行されます。

一覧形式にして表示

データを取得してそのまま表示することができたので、これをお気に入りリストのように一覧化していきます。テンプレートとして以下のような感じの一覧を使います。

<div class="col-sm-12 col-md-6">
    <div class="list-group list-group-margin">
        <a class="list-group-item active">タイトル</a>
        <a class="list-group-item">記事名</a>
    </div>
</div>

この塊を、RSSで取得した値を使いながら動的に一覧を作成します。HTML単体ではこのような動的なサイトは作ることはできませんでした。

まずは一覧を埋め込みたい場所にPHPの関数を呼び出すように追記します。場所はお気に入りリストと同じく<div class="row"></div>の中です。以下のようにします。

<div class="row">
    <?php createRssList(); ?>
</div>

そして、rss.phpの末尾の<?php ?>の部分を以下のように変更します。

<?php
function createRssList() {
    echo("<div class=\"col-sm-12 col-md-6\">");
    echo("<div class=\"list-group list-group-margin\">");
    echo("<a href=\"#\" class=\"list-group-item active\">タイトル</a>");
    echo("<a href=\"#\" class=\"list-group-item\">記事名</a>");
    echo("</div></div>");
}
?>

とりあえずecho("")で出力します。文字列内に含まれるダブルクォーテーションはエスケープする必要がありますので、\を前に追加します。そうしないとStringの終端とみなされて不正確な文字列となってしまいます。

まとめるとrss.phpは以下のようなソースとなります。

<!DOCTYPE html>
<html lang="jp">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>RSS</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <style>
        .list-group-margin {
            margin-top: 30px;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-dark bg-dark">
        <div class="container">
            <p class="navbar-brand">RSS</p>
        </div>
    </nav>

    <div class="container">
        <div class="row">
            <?php createRssList(); ?>
        </div>
    </div>
</body>
</html>
<?php
function createRssList() {
    echo("<div class=\"col-sm-12 col-md-6\">");
    echo("<div class=\"list-group list-group-margin\">");
    echo("<a href=\"#\" class=\"list-group-item active\">タイトル</a>");
    echo("<a href=\"#\" class=\"list-group-item\">記事名</a>");
    echo("</div></div>");
}
?>

これで表示される内容がこんなかんじです!

f:id:ibuquicallig:20190516023727p:plain

ちゃんと一覧として表示されていますね😉 あとはこの形を維持しながらRSSの値を入れていくだけです!

RSSの値で動的に一覧を生成する

まずは、HTML内でPHPの関数を読んでいる部分からRSSのURLを引数で渡すようにします。

<?php createRssList("http://feeds.feedburner.com/hatena/b/hotentry.css"); ?>

それに従って関数の方も変更します。

function createRssList(string $url) { ... }

次に、関数の中身も編集していきます。一気に作ってしまいますが以下のような感じにします。

<?php
function createRssList(string $url) {
    $count    = 0;  // カウント保持用
    $count_mx = 10; // 表示する行数

    $rss = simplexml_load_file($url);   // RSSファイルを取得
    if ($rss === false) return false;   // 取得失敗時、処理を終了

    echo("<div class=\"col-sm-12 col-md-6\">");
    echo("<div class=\"list-group list-group-margin\">");
    echo("<a href=\"" . $rss -> channel -> link  . "\" class=\"list-group-item active\">" . $rss -> channel -> title . "</a>");

    // 記事一覧の処理
    foreach ($rss -> item as $item) {
        if ($count >= $count_mx) break; // 最大数に達したら終了

        echo("<a href=\"" . $item -> link . "\" class=\"list-group-item\">" . $item -> title . "</a>");
        $count++;
    }

    echo("</div></div>");
}
?>

$count_mxに指定している数値で一覧に表示する行数を制御できます。foreach処理内で$countをカウントアップしていき、到達したらbreakでループを抜けるようになっています。

不完全な一覧を表示しないために、simplexml_load_file($url)でファイルの取得に失敗したときは、後続処理は一切行いません。

PHPでは、アロー演算子を使うことでオブジェクト内の変数や関数にアクセスすることができます。JavaScriptでいえば、fruits.appleみたいな感じで使用するドットです。

$rss -> channel -> titleとするとRSS配信しているページのタイトルを取得できます。この辺は実際のRSS配信されているXMLファイルをにらめっこしながら判断します。また、文字列は.で結合することができます。ほかの言語だと+ですね。

これで以下のように表示することができます。

f:id:ibuquicallig:20190516023734p:plain

それっぽくなってきましたね!これでほぼほぼ完成です。

一覧を複数表示する

RSSの一覧が一つだけでは特に意味がないので増やしてみましょう。すでに処理は関数化しているので関数を呼び出すだけでOKです。

<div class="row">
    <?php
        createRssList("http://b.hatena.ne.jp/hotentry/it.rss");
        createRssList("http://b.hatena.ne.jp/entrylist/it.rss");
    ?>
</div>

これで複数表示できます。関数内でクラスも指定しているので問題なくイイ感じに表示できていますね😎 好きなニュースページのRSSを指定してみてください。はてなブックマークの構造と異なるXML構造をしているページでは頑張ってプログラムを書いてみましょう!

f:id:ibuquicallig:20190516023744p:plain

最終的なコード

最後に、今回のソース(rss.php)の全体です。関数には適切なコメントをつけておくのが運用の鉄則です。

<!DOCTYPE html>
<html lang="jp">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>RSS</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <style>
        .list-group-margin {
            margin-top: 30px;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-dark bg-dark">
        <div class="container">
            <p class="navbar-brand">RSS</p>
        </div>
    </nav>

    <div class="container">
        <div class="row">
            <?php
                createRssList("http://b.hatena.ne.jp/hotentry/it.rss");
                createRssList("http://b.hatena.ne.jp/entrylist/it.rss");
            ?>
        </div>
    </div>
</body>
</html>
<?php
function createRssList(string $url) {
    $count    = 0;
    $count_mx = 10;
    $rss      = simplexml_load_file($url);

    if ($rss === false) return false;

    echo("<div class=\"col-sm-12 col-md-6\">");
    echo("<div class=\"list-group list-group-margin\">");
    echo("<a href=\"" . $rss -> channel -> link  . "\" class=\"list-group-item active\">" . $rss -> channel -> title . "</a>");

    foreach ($rss -> item as $item) {
        if ($count >= $count_mx) break;

        echo("<a href=\"" . $item -> link . "\" class=\"list-group-item\">" . $item -> title . "</a>");
        $count++;
    }

    echo("</div></div>");
}
?>

最後に

以上でRSSリストの作成は完了となります。どうでしょうか?あまりプログラムを書いたことなかったり、HTMLの内容を動的に生成するという感覚に慣れていないとちょっと変なやり方やな、と感じるかもしれません。

個人的にもこの書き方はあまり好きではないです(笑)。<template>からクローンしてうにゃうにゃ、という方がスマートでしょう。そうなってくるとPHPではなくJavaScript(Vue.jsなど)で作り上げる方が向いていると思います。

しかし、PHPの方が環境構築が簡単で、HTML内に簡単に文字列を足せるのでこちらを選択しました。

これでみなさん、Raspberry Pi上でWEBアプリケーションを作成することができました!!!

たかがRSSリストかよ!と思われるかもしれませんが、インターネット上からファイルを取得して、それに応じて動的にページを生成しているだけでも十分アプリケーションです👏👏👏

もっと発展すると入力欄の値をデータベースに登録してそれを一覧化する、といった本格的なアプリケーション(TODOリスト)となります。もちろんそこまでこの連載でカバーしていこうと思っているのでお楽しみに!!

そして、Raspberry Piの最大の特徴といっても過言でないGPIOから部屋の温度や湿度を取得したり、カメラから写真を撮ったりという部分もやっていこうと思います!

次の記事

GPIOを利用して、LEDをチカチカさせる通称Lチカをやっていこうと思います。

[:embed]

参考

以下のサイトの情報を引用・参考にしました。

↑PHPの公式ドキュメントです。ここに載っていない標準の関数は無いので、使い方などはまずはここを参照するといいです。