キャッチコピー自動生成のWEBアプリケーションを開発した話【MeCab】

# サービスサイト
MakeLike
https://melikeke.sakura.ne.jp/malike/intro

# 概要
PythonのMecabを使用して、様々な教師データから「っぽい文章」を生成します
・カテゴリから生成
・ファイルから生成
・直接入力から生成
の3パターンから教師データを指定できます。

# 機能
## キャッチコピー自動生成
・カテゴリから生成
・ファイルから生成
・直接入力から生成

## ログイン機能
会員登録
ログイン
会員登録通知
パスワード再設定

## お気に入り機能

# 実装
## 環境
さくらサーバー

## フロントエンド
### Laravel
#### 基本部分
##### テンプレートの活用
LaravelのBladeテンプレートでレイアウト部分を共通にする
http://cly7796.net/wp/php/to-a-common-layout-in-blade-template-of-laravel/

---

##### ログイン認証系
Laravelでサクッとログイン機能を実装する方法
https://php-junkie.net/framework/laravel/login/

「Command "make:auth" is not defined.」が出たら
https://qiita.com/daisu_yamazaki/items/a914a16ca1640334d7a5

【Laravel7でユーザー認証_8】メールアドレス変更時にメール認証を行う
https://qiita.com/crosawassant/items/018b29ab770c0a373bc9

Laravelの標準Authentication(Auth)の動きを調べてみる
https://qiita.com/zaburo/items/9fcf0f4c771e011a4d35

---

##### 会員登録時の自動通知
下記のどちらかの方法で、メール通知やSlack通知が可能

###### Laravel
これを読めばLaravelのイベントとリスナーが設定できる
https://reffect.co.jp/laravel/laravel-event-listener#i-8

###### Google Analytics
Google アナリティクスからのアラートを受信しよう
https://www.ad-market.jp/column/2020/04/20200420.html

---

###### メール機能
パスワードリセットメールを送信するため、Laravelのメール機能を設定します。

ローカル開発時には「mailTrap」
本番環境では「さくらサーバ」のSMTPを設定し、疎通確認をしました。

Laravel mailTrapでメール送信テスト　備忘録
https://qiita.com/ryomaDsakamoto/items/e9d3a2c258dbfc66c524

LaravelのSMTPサーバにさくらサーバのSMTPを設定
https://laraweb.net/tutorial/1265/

洗練された『Laravel』のメール送信機能を使ってみる
https://liginc.co.jp/369690

Laravel5.7: 日本語のパスワードリセット用のメールを送信する
https://qiita.com/sutara79/items/0ea48847f5565aacceea

---

##### 日本語設定
Laravelで日本語設定をする場合は、
ja.jsonへ記述することで設定を適応することができます。

```json:/resources/lang/ja.json
{
    "Click link below and reset password.": "下記のURLにアクセスして、パスワードを再設定してください。",
    "If you did not request a password reset, no further action is required.": "このメールに心当たりがない場合は、このまま削除してください。",
}
```

```php:blade.php
<p>
    {{ __('Click link below and reset password.') }}<br>
    {{ __('If you did not request a password reset, no further action is required.') }}
</p>
```

実際の表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/735c71e3-ade3-1560-0a31-ffd5b253c0e1.png)

---

#### 生成部分
##### 「カテゴリから生成」表示部分の実装

カテゴリプルダウンは「カテゴリ1」に紐づいて「カテゴリ2」の選択肢が変動するようにします。

---
・カテゴリ1が「歌詞」の場合
<img width="400px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/031d11d9-bc56-dd9c-257a-f7e912adac6f.png">

---
・カテゴリ1が「小説」の場合
<img width="400px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/f0890eff-9d29-832f-5e77-1bb5e8e977c3.png">

---


###### リレーション
category1.id = category2.parent_id

---

###### categori1
<img width="400px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/e4f5a84e-1ac9-b0ca-51e6-bbeeddd02898.png">

---

###### categori2
<img width="400px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/5d09da79-fd05-2558-5272-75b5cb83658f.png">

---

###### 参考
laravel5.1で、カテゴリー・サブカテゴリーのような親子セレクトボックスを作る方法
https://www.messiahworks.com/archives/12202

---

##### 「カテゴリから生成」処理部分の実装
カテゴリで指定した値を元にStorageで読み込む教師データのパスを設定します。

```php:contoloer.php
$input = $request->input();
$category1  = Arr::get($input, 'category1');
$category2  = Arr::get($input, 'category2');
$path = 'public/constText/';
$path .= $category1 . '/';
$path .= $category2 . '.txt';
```

constTextの構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/b4c83131-dfa9-f0d7-b67c-81f1ef4dcfe0.png)

---

##### 「ファイルから生成」部分の実装
LaravelのStorage機能を使用し、アップロード処理を実装します。
今回はテキストデータのみに制限するため拡張子を以下のように設定します。
・txt
・csv
・xls
・xlsx

参考
Laravelでファイルをアップロードする方法を詳細解説
https://reffect.co.jp/laravel/how_to_upload_file_in_laravel

LaravelでStorageを使ったファイルおよびディレクトリの操作をまとめてみた
https://qiita.com/t1k2a/items/50081988363cf2fa1bca

---

##### 「直接入力から生成」部分の実装
「ファイルから生成」は上記のようなフローで実行していますが、
「直接入力から生成」もStorage機能を活用して実行しています。

直接入力から生成フロー
textareaの値をテキストファイルでStorage保存
以下、「ファイルから生成」と同様の処理

## サーバエンド
### Python
MeCabによる自動生成部分については以下の記事にまとめています。

参考
自動生成キャッチコピー【Python】
https://qiita.com/SyogoSuganoya/items/ba542f686104811e2d6b

---

### PHPからPython実行
Laravelで実装したinput button等の発火イベントで
Pythonを実装できるようにします。

PHPのexec関数を使用し、Pythonを実行します。

参考
ゼロから作るPHPとPythonの連携 on Laravel
https://qiita.com/SwitchBlade/items/96ed4ea425ef2d758f71

PHP - exec()のエラーハンドリングと標準エラー出力の関係をまとめる
https://qiita.com/smd8122/items/65b552f1d53bfb7fad9a

---

### Ajax
#### 基本
Laravel、Ajaxを用いて動的なコメント閲覧画面の作成
https://www.merges.co.jp/archives/1980

LaravelでAjax（jQuery）を使用する
https://pointsandlines.jp/server-side/php/laravel-ajax

Laravel と Ajax で値の受け渡して非同期処理。
https://niwacan.com/1619-laravel-ajax/

---

#### 生成文章の表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/ef1c0ab5-fb8b-f6a4-a7f4-fa230a41d381.png)

---

生成ボタン押下後の「生成文章」エリアの表示にはAjaxを使用します。
PHPのexec関数の「\$outputs」を生成文章に表示します。
$outputsにはPython実行中のprintした文字列が格納されています

```php:controller.php
exec($command , $outputs, $return_var);

$data = [
	'outputs' => $outputs,
	'return_var' => $return_var,
];
return response()->json($data);
```

```javascript:pythonCall.js
var params = {
	headers: {  
		'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
	},
	url: url,
	method: 'post',
	dataType: 'json',
	data: paramData,
	//リクエストが完了するまで実行される
	beforeSend: function(){
		$('h3').text('読み込み中');
		$("input").attr("disabled","true"); 
		$('.wrapper .button').addClass('pushed');
		$('.heart-check').prop('checked', false); //アイテムを全部checkedはずす
	}
};

$.ajax(
	params
).done(function( data ) {
	// 生成文章の初期化
	$('input.sentence').val('');
	
	// 生成文章の入力
	for(var i = 0; i < data.outputs.length && i < 5; i++){
		$('input.sentence').eq(i).val(data.outputs[i]);
	}
}
```

---

#### お気に入り機能
生成文章のお気に入り登録および
マイページのお気に入り文章の管理
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/55bd7659-87f7-0c60-1d1d-73889799b8cf.png)

---

お気に入り機能はAjaxを使用し、非同期で処理します。

```php:controller.php
// 一覧画面の表示
public function index(Request $request) {

	$posts = Favorite_sentence::where('user_id', $request->user()->id) //$userによる投稿を取得
		->orderBy('created_at', 'desc') // 投稿作成日が新しい順に並べる
		->paginate(10); // ページネーション; 

	return view('user.mypage', [
		'posts' => $posts, // $userの書いた記事をviewへ渡す
	]);
}

// ハートマークのクリック(登録 or 削除)
public function ajaxlike(Request $request) {

	// $user_id = $request->user()->id;
	$user_id = Auth::user()->id;
	$post_id = $request->post_id;
	$sentence = $request->sentence;

	$like = new Favorite_sentence;
	$exist = Favorite_sentence::where('id', $post_id)->get();
	$isExist = $exist->isEmpty();

	if ($isExist) {
		// 空（まだ「いいね」していない）ならlikesテーブルに新しいレコードを作成する
		$like = new Favorite_sentence;
		$like->user_id = $user_id;
		$like->sentence = $sentence;
		$like->save();

		$post_id = $like->id;
		$command = 'insert';

	} else {
		// お気に入りフレーズの存在確認
		Favorite_sentence::findOrFail($post_id);
		// likesテーブルのレコードを削除
		$like = Favorite_sentence::where('id', $post_id)
		->delete();

		$command = 'delete';
	}

	$data = [
		'user_id' => $user_id,
		'post_id' => $post_id,
		'sentence' => $sentence,
		'command' => $command,
	];
	//下記の記述でajaxに引数の値を返す
	return response()->json($data);
}
```

```javascript:likeSentence.js
var like = $('.heart-label');
var likePostId;
var sentence;

like.on('click', function () {

	var $this = $(this);
	// お気に入り文章ID
	likePostId = $this.parent().parent().find(".sentence").attr('data-postid');
	// お気に入り文章
	sentence = $this.parent().parent().find(".sentence").val();
	
	// お気に入り文章が空白
	if (!sentence) {
		return;
	}
	
	$.ajax({
		headers: {
			'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
		},
		url: '/ajaxlike',
		type: 'POST',
		data: {
			'post_id': likePostId ,
			'sentence': sentence , 
		},
	}).done(function (data) {            
		if(data.command = 'insert') {
			$this.parent().parent().find(".sentence").attr('data-postid', data.post_id);
		}
});
```

# リリース
本番環境では「さくらサーバー」を使用

## Python/MeCab環境構築
さくらサーバ・UTF-8に対応したWEBブラウザにMecabで形態素解析した結果を出力する
https://qiita.com/Jshirius/items/ac3ca66a2d5262b98b58

さくら共有サーバーにmecabをインストールしてpythonから呼び出してみる
https://qiita.com/Jshirius/items/3a067486e2a693544c32


---

### MeCabがターミナルからは実行できるが、PHP呼び出しから使えない時…
#### まずはPythonをフルパスで呼び出してみる
エックスサーバーでphpからexec関数でpythonを呼び出そうとして苦労した話
https://hazukei.com/1259/

---

#### site-packagesを追加指定
それでもうまくいかない時は、「site-packages」をターミナル実行とPHP実行で比較

```python
import site
site.USER_SITE
```

差分を追加し、インストール済みパッケージを実行可能な状態にする

```python
import sys
sys.path.append('vendor\Lib\site-packages')
```

参考
さくらのレンタルサーバに Python モジュールをインストール
https://emptypage.jp/notes/sakura-python.html

Pythonモジュールのインストール先の確認方法や設定方法を現役エンジニアが解説【初心者向け】
https://techacademy.jp/magazine/46510

## Laravel
[0001]さくらのレンタルサーバーにSSHで入りLaravelをインストール
https://www.failibere.com/development_memo/lalavel/0001

さくらサーバでstorageへのシンボリックリンクを張る
https://blog.hiroyuki90.com/articles/laravelで作成したwebアプリをレンタルサーバに公開する/