本日出版される本をまとめて表示するサイトをラズパイサーバーで公開する

##はじめに
昨今の新型コロナ騒動で時間が有り余っていたので、以前より構想していた「本日出版される本のデータを集めて一覧で表示するサイト」を勉強を兼ねて作り、ラズパイの自作サーバーに実装しました。

http://mono-today-book.work/today_book.php

github
https://github.com/mono-taro/today_book

####作る動機
毎日約200冊の本が新刊として出版され、月刊では約6000冊の本が出版されていますが([日本著者販促センター](http://www.1book.co.jp/000076.html)より)、そのうちの多くの本(特に自費出版)は書店に並ぶことがないか、並んだとしてもすぐに返本されてしまうことが多いので、新刊図書で実際に書店等で目に触れる本はほんのひとにぎりで殆どの本は人目に触れないまま埋もれてしまっています。
（ここらへんの事情はこちらのブログ等が参考になります。
[発売したはずの本が書店に売ってない！たった一つの明確な理由](https://note.com/suzukakeshin/n/n0455913a9aee),
[自費出版した本がなぜ書店に並ばないか](https://mmpolo.hatenadiary.com/entry/20070707/1183757012),
[出版した本（特に自費出版）が書店に並ばず、売れない理由](https://limixceed.co.jp/blog/%e8%91%97%e4%bd%9c)）

人目に触れることなくひっそりと消えていく本の中に良書はあるだろうし、見ていて気になるような本もあると思うので、毎日の新刊図書をチェックして本を知る機会を増やしたいと思い、新刊図書情報を集めて表示するサイトを作ろうと思い立った次第です。

で、調べてみたらあったんですよね。「[版元ドットコム](https://www.hanmoto.com/)」さんに
なので、自分は版元ドットコムさんでまとめられてる新刊情報を取得し、OpenBDから書誌・書影データを取得して、自分なりにアレンジしたサイトを作ってみました。


##機能
新刊情報は版元ドットコムさんの[RSS フィード](https://www.hanmoto.com/ci/bd/search/sdate/today/edate/today/hdt/%E6%96%B0%E3%81%97%E3%81%84%E6%9C%AC/order/desc/vw/rss20)を利用し、そこで取得した本のISBNを元にOpenBDにて書誌・書影データを取得してデータベースに保存し、webページではデータベースに保存しているデータを表示しています。
作成したページは本日出版の本をまとめたページ、明日出版の本をまとめたページ、今までに取得した本を検索するページ、の3ページです。
それぞれに本日出版用データベース(today_book)、明日出版用データベース(soon_book)、今までの蓄積データベース(book)を用意しています。なお、本日出版・明日出版用のデータベースのデータは出版データを更新する際に削除しています。
当初は直接データを取得・表示する方式にしていましたが、ページを開くのに1分以上の時間を要したので高速化を図るため、この方式にしました。
イメージとしてはこんな感じ
![新刊図書データ収集.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/587120c2-54b1-2384-7f67-60a1d20ae0ba.png)


phpmyadminでの管理画面ではこんな感じです。
![Screenshot from 2020-05-03 20-20-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/0b773dd4-df28-e621-04fb-bf5f83808d67.png)
・テーブル構造
上から順に、13桁ISBN,書名,副題,内容紹介,著者,発行元出版社,販売元出版社,書影,価格,出版日,対象読者情報,成人指定情報,分類コード(Cコード),キーワード,10桁ISBN,amazonの商品ページURL,hontoの商品ページURL,データ追加日を保存しています。
13桁ISBN,書名,著者以外はデータが未登録で結構抜けてることが多いので、表示や検索に支障が出ないように代わりのデータを入れているカラムもあります。
![Screenshot from 2020-05-04 22-28-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/9d2cfeb6-0a5a-2fab-7322-ecbc92528a0c.png)
![Screenshot from 2020-05-04 22-25-27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/12d085c9-99d0-5fb3-c5cb-7bdf4334c5fd.png)

なお、成人指定の本(いわゆるエ◯本など)に関しても一応データを集めていますが、表示や検索の対象から除外してあります。
ただ、成人指定が未登録だったりする場合は除外されずそのまま表示されてしまいます。


・本日の本
today_bookに保存されている本日の本(5月3日)のデータを取得して表示しています。
![Screenshot from 2020-05-03 21-53-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/397bfeeb-f0ca-33ea-da6b-971a29ad1ce8.png)

・明日の本
soon_bookに保存されている明日の本(5月4日)のデータを取得して表示します。
この日の場合4日に出版される本はないので、何も表示されません。
![Screenshot from 2020-05-03 22-57-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/2e431cc9-b30d-b62f-be70-c5ef923b5d4f.png)

この画像をキャプチャした5月3日時点でデータを集め始めた4月30日以降の合計493冊、ゴールデンウィーク中なので本日出版された本は3冊で翌日の出版予定の本は0冊です。

・本を探す
データの蓄積を初めた4月30日以降から本日分までの本を検索し、表示します。
書名(副題含む),本の内容,著者名,発行元出版社,出版日,日本図書コードの分類コード(Cコード)を元にした分類検索に対応しています。 
ページのデザインは[【コードDL可】phpとMySQLで検索機能を作る方法【2020年版】](https://www.suzuco.net/entry/php-search/)を参考にさせていただきました。
![Screenshot from 2020-05-04 20-06-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/6bd288b5-a26d-96dd-ebcd-1cd690241a6c.png)
![Screenshot from 2020-05-04 20-06-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/834b68c6-53a7-3fc9-dbf5-c37505893f8d.png)


##苦労した点
1つめとしては、RSSフィードからデータを取ってきて、取ってきたJson形式のデータ処理の方法がわからなかったのでこの点は結構大変でした。
以下、一部抜粋

```php:data.php

            $book_content=null;
            $C_code=null;
            $Subject_word=null;

            $book_isbn = @$arr[0]["onix"]["RecordReference"];                                                                         //ISBN
            $book_title = @$arr[0]["onix"]["DescriptiveDetail"]["TitleDetail"]["TitleElement"]["TitleText"]["content"];               //タイトル            
            $book_subtitle = @$arr[0]["onix"]["DescriptiveDetail"]["TitleDetail"]["TitleElement"]["Subtitle"]["content"];             //サブタイトル
            $book_contributor = @$arr[0]["onix"]["DescriptiveDetail"]["Contributor"][0]["PersonName"]["content"];                     //著者

            if(!empty(@$arr[0]["onix"]["CollateralDetail"]["TextContent"])){                                        
                for($i=4;$i>=0;$i--){                                                                                                     //本の内容
                    if(!empty(@$arr[0]["onix"]["CollateralDetail"]["TextContent"][$i]["Text"])){   
                        $book_content=@$arr[0]["onix"]["CollateralDetail"]["TextContent"][$i-1]["Text"].'<br><br>';                           
                        $book_content = $book_content.@$arr[0]["onix"]["CollateralDetail"]["TextContent"][$i]["Text"].'<br>';
                        break;
                    }
                }
            }

            $book_imprint = @$arr[0]["onix"]["PublishingDetail"]["Imprint"]["ImprintName"];                                           //発行元出版社
            $book_publisher = @$arr[0]["onix"]["PublishingDetail"]["Publisher"]["PublisherName"];                                     //販売元出版社
            $book_picture = @$arr[0]["onix"]["CollateralDetail"]["SupportingResource"][0]["ResourceVersion"][0]["ResourceLink"];      //書影 
            $book_price = @$arr[0]["onix"]["ProductSupply"]["SupplyDetail"]["Price"][0]["PriceAmount"];                               //価格
            $book_date = @$arr[0]["onix"]["PublishingDetail"]["PublishingDate"][0]["Date"];                                           //出版日
        
            $audience_type=@$arr[0]["onix"]["DescriptiveDetail"]["Audience"][0]["AudienceCodeType"];                                       //読者対象
            $audience_value=@$arr[0]["onix"]["DescriptiveDetail"]["Audience"][0]["AudienceCodeValue"];                                 //成人指定

            if(!empty(@$arr[0]["onix"]["DescriptiveDetail"]["Subject"])){                                                             
                for($i=2;$i>=0;$i--){
                    if(@$arr[0]["onix"]["DescriptiveDetail"]["Subject"][$i]["SubjectSchemeIdentifier"]==78){                          //C-code
                        $C_code = @$arr[0]["onix"]["DescriptiveDetail"]["Subject"][$i]["SubjectCode"];
                    }
                    if(@$arr[0]["onix"]["DescriptiveDetail"]["Subject"][$i]["SubjectSchemeIdentifier"]==20){                          //キーワード
                        $Subject_word = @$arr[0]["onix"]["DescriptiveDetail"]["Subject"][$i]["SubjectHeadingText"];
                    }
                }
            }
            if($audience_type==""){  //指定なしの場合便宜的に99とする
                $audience_type=99;  
            }
            if($audience_value==""){
                $audience_value=99;
            }

            if(empty($C_code)){
                $C_code="____";
            }

            if(empty($book_picture)){
                $book_picture="no_image.png";
            }else{
                $book_picture = str_replace("cover.openbd.jp/", "cover.openbd.jp//", $book_picture);
            }

            if($book_title==null){
                echo "title_error";
                continue;
            }
```
こちら([出版情報登録センター「データ仕様　第3版」（ONIX for Books 3.0.x準拠）](http://www.jpo.or.jp/topics/data/20160113a_jpoinfo.pdf))のOpenBDデータ仕様を元にして必要なデータのみ取り出しています

以下の部分は本の内容紹介データに関する部分ですが、実はこの部分はどこに何の内容が入っているのかという規則性が未だによくわかってません

```php
            if(!empty(@$arr[0]["onix"]["CollateralDetail"]["TextContent"])){                                        
                for($i=4;$i>=0;$i--){                                                                                                     //本の内容
                    if(!empty(@$arr[0]["onix"]["CollateralDetail"]["TextContent"][$i]["Text"])){   
                        $book_content=@$arr[0]["onix"]["CollateralDetail"]["TextContent"][$i-1]["Text"].'<br><br>';                           
                        $book_content = $book_content.@$arr[0]["onix"]["CollateralDetail"]["TextContent"][$i]["Text"].'<br>';
                        break;
                    }
                }
            }
```

`["TextContent"][$i]`の$iが4を最大に内容紹介情報データが入っています。(実際は未登録の場合が多く2が最大値の場合が多い)
以下の例では`["TextContent"][$i]`の$i最大値が2の場合の登録されている本の内容紹介
例:「移動革命」(三菱総合研究所)[https://api.openbd.jp/v1/get?isbn=9784140886168](https://api.openbd.jp/v1/get?isbn=9784140886168)

```
移動革命
9784140886168
Array
(
    [0] => Array
        (
            [TextType] => 02
            [ContentAudience] => 00
            [Text] => ＥＶ・自動運転・シェアリング。立ち上がるモビリティ関連市場。誰が覇権を握るのか? 激動の業界を徹底追跡!!
        )

    [1] => Array
        (
            [TextType] => 03
            [ContentAudience] => 00
            [Text] => 100年に1度のパラダイムシフトに乗り遅れるな！

ＥＶや自動運転、ドローンといった技術革新がめざましく進展し、空飛ぶタクシーまで夢ではなくなる一方で、スマホアプリによるタクシー配車やカーシェアリングなどの新しいサービスが次々と立ち上がる現在。ソフトとハードの両面で「モビリティ（移動）」の形が大きく変わるこの機をとらえて、関連する自動車・鉄道・ＩＴ業界などでは、一大競争が始まっている。日本の産業界の地図は、今後どのように塗り替えられるのか？　そして、私たちの暮らしはこれからどのように変わっていくのか？　経済・企業経営・公共分野などの広い領域にわたって調査・研究を行っているシンクタンクが、いま起きつつある大変化の実相を解き明かす。
        )

    [2] => Array
        (
            [TextType] => 04
            [ContentAudience] => 00
            [Text] => はじめに　立ち上がる巨大市場

序　章　2030年代のモビリティ生活

キーワードは「つながる」「パーソナライズ」「付加価値向上」
すでに始まっている移動革命―理想のモビリティ実現にむけて
拡大するオンデマンド相乗り交通
都市部における無償型ライドシェアの可能性
超小型モビリティ・低速自動配車サービスの出現
究極のラストワンマイル支援
人を動かすことなく荷物を動かす

第1章　CASEで占う自動車産業の未来


~~中略~~

終　章　街が変わる。暮らしが変わる。

広がるCASE―ソニー製自動車の衝撃
トヨタがコネクテッドシティで目指すもの
スマートシティからスーパーシティへ
大阪・関西万博は移動革命のショーケース
MaaSが豊かな生活をもたらす

共同研究レポート
行動変容のメカニズムとBeyond MaaSの可能性
        )

)
```
経験則的にですが`["TextContent"][$i]`の$iの値が大きいほど内容紹介の文章量が多くなる傾向があり、一番大きい値に目次やあらすじが入ることが多いです。
なので、$iが最大値の時の内容紹介データと、最大値から一つ小さい値の時の内容紹介データを、内容紹介として表示する内容に採用しています。

また以下の部分では、未登録のデータがあった場合は便宜的に代替のデータを入れています。
タイトルが未登録の場合は続くデータベース登録作業ををスキップします。

```php

            if($audience_type==""){  //指定なしの場合便宜的に99とする
                $audience_type=99;  
            }
            if($audience_value==""){
                $audience_value=99;
            }

            if(empty($C_code)){    //指定なしの場合"____"
                $C_code="____";
            }
            if(empty($book_picture)){
                $book_picture="no_image.png";  //代替画像
            }else{
                $book_picture = str_replace("cover.openbd.jp/", "cover.openbd.jp//", $book_picture);
            }

            if($book_title==null){
                echo "title_error";
                continue;
            }
```


苦労した点の2つ目としては検索機能の部分が苦労しました。
ここでは詳細なコードの解説を省略しますが、SQL文をどう組み立てたら期待する結果が得られるのか、試行錯誤を繰り返しました。

##ラズパイサーバーにて公開
Webページが出来上がったら、次はラズパイサーバーで作ったWebページを公開します

GMOのお名前ドットコムでドメインを取得しておき、DDNS(ここではMyDNS.jp)を使ってドメイン名に対するDNS設定をします。

こちらの記事を参考にさせていただきました([ドメイン取得からDDNS設定まで](https://qiita.com/mizuki_takahashi/items/b0c5adebea48b9f2f7a6))

MyDNSのDOMAIN INFOで取得したドメイン名を登録します。
![Screenshot from 2020-05-10 14-02-58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/6fd9f0f2-6736-a4b5-454c-125c8e1f80b5.png)

![Screenshot from 2020-05-10 14-01-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/b5fc6fe4-3f19-28a0-8dc9-44129f30f6da.png)

次にMyDNSのIP ADDRESS DIRECTで自宅のグローバルIPアドレスを登録します。
![Screenshot from 2020-05-10 14-08-53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/c2b65be2-2413-7c8e-e723-d777e5981f22.png)

そして、お名前ドットコムでネームサーバーにMyDNSを使用する設定をします。
お名前ドットコムのお名前.com Naviを開き、ドメインタブ >  ドメイン機能一覧 > ネームサーバー設定 > その他タブ > その他のネームサーバーを使う　を選択肢し、以下を入力
Primary DNS : 210.197.74.200 (ns0.mydns.jp)
Secondary DNS : 210.197.74.201 (ns1.mydns.jp)
Secondary DNS : 210.197.74.202 (ns2.mydns.jp)
![Screenshot from 2020-05-10 14-48-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/8590bd23-c461-0af6-d827-e37868630038.png)

次にルーターの設定をします。(ここではNTTのPR-400MIを例とします)
ルーターの管理ページを開き、詳細設定 > 静的IPマスカレード設定 を開き、空いているエントリ番号を編集し、対象ポートと対象アドレスを設定します。
対象アドレスにはラズパイのIPアドレスを設定します。
![Screenshot from 2020-05-10 15-00-33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584794/1711e14b-38dc-7b36-8877-f7dbdca9c710.png)

これで、WebページにLAN外からアクセス出来たら設定完了

##さいごに
いつものことですが、独学で色々調べまくりながら作ったので、多分やり方がおかしかったり間違っていたりする箇所があると思います。もし改善点や間違っている箇所がありましたら、大変勉強になりますのでご教示いただけると幸いです。

##追記
5/17:ご指摘頂いた改善点を修正しました。
・data.php、connect.php、Feed.phpはDocumentRootとは別の場所に移動しました。
・SQLインジェクション脆弱性については可変的な書籍の検索に如何にプレースホルダを利用すべきかわからなかったので今後の課題点です。代わりに、対策になっているかわからないですが、空白を無効化するなどしておきました。
・全件SELECT後で除外している箇所は削除し、SELECT時にWHEREで除外に統一しました。
・HTML出力にhtmlspecialcharsを通しました。

5/13:ご指摘いただいたミスを修正しました。
・data.php内の不要なelse文を削除しました。
・data.php内のif文で比較演算子にするところを代入演算子にしていた部分を修正しました。

5/13:画像表示に関する修正
・OpenBDにて登録されている書影URLの一部分で、単一スラッシュで登録されているにも関わらず、2重スラッシュにしないと書影が表示されない書籍が多かったので、全ての書籍の書影URLを2重スラッシュになおして極力書影が表示されるようにしました。(尚もURLが登録されているにも関わらず書影が表示されない書籍が多数存在しています。出版社単位で画像が表示されない書籍があるので側のミス？)
例：OpenBDに登録されているURL`「https://cover.openbd.jp/[ISBN番号]」`
修正後のURL`「https://cover.openbd.jp//[ISBN番号]」`

5/19:画像表示に関して
・集◯社の書籍の書影URLが出版の度に、単一スラッシュ・2重スラッシュ統一されずにバラバラな状態でOpenBDに登録されているので、日によって集◯社の出版する書籍の書影が表示されない場合があります。

##参考文献
版元ドットコム:https://www.hanmoto.com/
OpenBD:https://openbd.jp/
  出版情報登録センター「データ仕様　第3版」（ONIX for Books 3.0.x準拠）:
    http://www.jpo.or.jp/topics/data/20160113a_jpoinfo.pdf
Syncer PHPでRSSやAtomのフィードを取得する方法:https://syncer.jp/php-how-to-get-feed
Syncer PHPでJSONのデータを処理する方法:https://syncer.jp/how-to-use-json
マイヤーの開発ブログ PHP:超マニアック ISBN10とISBN13の変換 その２:https://mayer.jp.net/?tag=php-isbn10-isbn13-%E5%A4%89%E6%8F%9B
きり漫 【コードDL可】phpとMySQLで検索機能を作る方法【2020年版】:https://www.suzuco.net/entry/php-search/
お便利.com ダイナミックDNSのおさらい:http://www.obenri.com/_minset_wbel3/ddns_wbel3.html
Qiita ドメイン取得からDDNS設定まで:https://qiita.com/mizuki_takahashi/items/b0c5adebea48b9f2f7a6
日経BP 日経BPパソコンベストムック:Linuxサーバーがゼロから作れる本