[Laravel8.46.0] 8系でChat機能を実装したい1

$ php artisan –version
Laravel Framework 8.46.0
$ composer require pusher/pusher-php-server
$ php artisan make:model Message -m

migrationfile

    public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->integer('sent_id')->index()->unsigned();
            $table->integer('recived_id')->index()->unsigned();
            $table->text('message');
            $table->timestamps();
        });
    }

model(Message.php)

    protected $fillable = [
        'sent_id',
        'recieved_id',
        'message',
    ];

$ php artisan serve –host 192.168.33.10 –port 8000

register画面からuserを3つぐらい作ります。

$ php artisan make:controller HomeController

route

use App\Http\Controllers\HomeController;

Route::get('/home', [HomeController::class, 'index']);

HomeController.php

use App\Models\User;
use Illuminate\Support\Facades\Auth;

class HomeController extends Controller
{
    //
    public function __construct(){
    	$this->middleware('auth');
    }

    public function index(){
    	$user = Auth::user();
    	$users = User::where('id','<>', $user->id)->get();

    	return view('chat.user_select', compact('users'));
    }
}

layout/app.blade.php
L bootstrap5を追加
L pusher.jsを追加

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">

// 省略
            <main>
                @yield('content')
            </main>
// 省略
<script src="https://js.pusher.com/7.0.3/pusher.min.js"></script>
<script
  src="https://code.jquery.com/jquery-3.6.0.min.js"
  integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
  crossorigin="anonymous"></script>
@yield('script')

user_select.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
	<div class="row">
		<div class="col-md-8 col-md-offset-2">
		</div>
	</div>

	<table class="table">
		<thead>
			<tr>
				<th>#</th>
				<ht>Name</ht>
				<th></th>
			</tr>
		</thead>
		<tbody>
		@foreach($users as $key => $user)
		<tr>
			<td>{{$loop->iteration}}</td>
			<td>{{$user->name}}</td>
			<td><a href="/chat/{{$user->id}}"><button type="button" class="btn btn-primary">Chat</button></a></td>
		</tr>
		</tbody>
	</table>
</div>
@endsection

$ php artisan make:controller ChatController

route

use App\Http\Controllers\ChatController;

Route::get('/chat/{recieved_id}', [ChatController::class, 'index'])->name('chat');
Route::post('/chat/send', [ChatController::class, 'store'])->name('chatSend');

chat/chat.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
	<div class="row">
		<div class="col-md-8 col-md-offset-2">
		</div>
	</div>

	<!-- チャットルーム -->
	<div id="room">
		@foreach($messages as $key => $message)
			@if($message->sent_id == \Illuminate\Support\Facades\Auth::id())
				<div class="send" style="text-align: right">
					<p>{{$message->message}}</p>
				</div>
			@endif

			@if($message->received_id == \Illuminate\Support\Facades\Auth::id())
				<div class="send" style="text-align: right">
					<p>{{$message->message}}</p>
				</div>
			@endif
		@endforeach
	</div>

	<form>
		<textarea name="message" style="width:100%"></textarea>
		<button type="button" id="btn_send" class="btn btn-primary">送信</button>
	</form>

	<input type="hidden" name="sent_id" value="{{$param['sent_id']}}">
	<input type="hidden" name="recieved_id" value="{{$param['recieved_id']}}">
	<input type="hidden" name="login" value="{{\Illuminate\Support\Facades\Auth::id()}}">
</div>
@endsection

@section('script')
<script>
	Pusher.logToConsole = true;

	var pusher = new Pusher('*', {
		cluster: '*',
		encrypted: true
	});

	var pusherChannel = pusher.subscribe('testApp');

	pusherChannel.bind('chat_event', function(data){

		let appendText;
		let login = $('input[name="login"]').val();

		if(data.sent_id === login){
			appendText = '<div class="send" style="text-align:right"><p>' + data.message + '</p></div> ';
		} else if(data.recieved_id === login){
			appendText = '<div class="recieve" style="text-align:left"><p>' + data.message + '</p></div> ';
		} else {
			return false;
		}

		$("#room").append(appendText);

		if(data.recieved_id === login){
			Push.creaet("新着メッセージ",
			{
				body: data.message,
				timeout: 8000,
				onClick: function(){
					window.focus();
					this.close();
				}
			})
		}
	});

	$.ajaxSetup({
		headers : {
			'X-CSRF-TOKEN' : $('meta[name="csrf-token').attr('content'),
		}
	});

	$('#btn_send').on('click', function(){
		$.ajax({
			type: 'POST',
			url: '/chat/send',
			data: {
				message : $('textarea[name="message"]').val(),
                sent_id : $('input[name="sent_id"]').val(),
                recieved_id : $('input[name="recieved_id"]').val(),
			}
		}).done(function(result){
			$('textarea[name="message"]').val('');
		}).fail(function(result){

		});
	});
</script>
@endsection

$ php artisan make:event ChatMessageRecieved
ChatMessageRecieved.php

public function __construct($request)
    {
        //
        $this->request = $request;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('testApp');
    }

    public function broadcastWith(){
        return [
            'message' => $this->request['message'],
            'sent_id' => $this->request['sent_id'],
            'recieved_id' => $this->request['recieved_id'],
        ];
    }

    public function broadcastAs(){
        return 'chat_event';
    }

あ、想定通りに動いてないけど、ちょっと思い出してきた。
pusherは基本jsでonclickでpostしてstoreし、eventを発火させて新しいデータを取得するんだった。
でも確かpusherのcrudentialは.envだった記憶があるんだが。。。

fontawesomeをinputのvalueに表示させたい時

fontawesomeのアイコンをformのinputで表示させたい。

通常は以下のように、i要素で指定する。

<a href="/cart.html"><button type="button" class="btn btn-danger"><i class="fas fa-cart-plus"></i>	Add to cart</button></a>

formでは、classにfas、valueに、”&#x” と ”f**;”を入れる。ここでは、cartのアイコンで、f217

<form action="/cart/store" method="POST">
<input type="submit" class="btn btn-danger fas pt-2 pb-2" value="&#xf217; Add to cart">
</form>

使い方はわかったが、inputの中で使うと、bootstrapのfontと異なる表示に見えるな。。まあ良しとしましょう。

[Laravel8.46.0] 多言語(日・英・中国語)でバリデーションを切り分けて表示したい時

Laravelで多言語(日本語/中国語/英語)のアプリケーションを作っています。
ユーザのロールによって、販売画面は英語・中国語、管理画面は日本語・英語に切り分ける仕様。
その為、config/app.phpの localは英語(en)で設定します。

'locale' => 'en',

管理画面でユーザ登録や商品登録をしようとした時に、日本語のバリデーションを表示させたい。

### バリデーション(Request)作成
$ php artisan make:request CreateClientRequest

CreateClientRequest.php

public function rules()
    {
        return [
            'company' => ['required','max:100'],
            'name' => ['required','unique:users,name','min:2','max:24'],
            'lang' => ['required'],
            // 省略
        ];
    }

-> これだと、英語のバリデーションファイルを読みに行ってしまうので、日本語のバリデーションファイルを作ります。

/resources/lang/ja/validation.php

ここに日本語のvalidationを書きます。

return [
       // 省略
       'required' => ':attributeは必須です。',
       // 省略

    'attributes' => [
        'company'=>'会社名',
        'id'=>'ユーザID',
        'lang'=>'言語',
        // 省略
    ],
];

それで、CreateClientRequestで、この日本語のバリデーションを読み込むように、setlocaleで日本語を指定します。

CreateClientRequest.php

use Illuminate\Support\Facades\App;
    public function rules()
    {

        App::setLocale('ja');
        return [
            //
            'company' => ['required','max:100'],
            'name' => ['required','unique:users,name','min:2','max:24'],
            'lang' => ['required'],
            // 省略
        ];
    }

そうすると、このバリデーションでは、/ja/validation.phpを読みに行ってくれます。

App::setLocale(”); は、バリデーションファイル(Request)だけでなく、Controller側で指定しても、同じように、日本語のバリデーションファイルを読みにいきます。

つまり、config/app.phpで指定した言語以外のバリデーションも、App::setLocale(”)で自由に呼び出すことができるとういわけです。

うおおおおおおおおおおおおおお
俺がやりたかったのコレ。
久々にプログラミングで興奮した。

[Laravel8.46.0] Laravel CollectiveでCountry Listを使いたい時

Select BoxでCountry Listから選択するUIを作る為、Front Endの構築時は以下のmediacollegeのHTMLをはめこんだが、いざLaravel Collectiveで実装する際には、配列で持ってないないといけないので困った。
https://www.mediacollege.com/internet/samples/html/country-list.html

どうやらCountry Listのパッケージがあるみたい。
Github: Monarobase/country-list

### country-listのインストール
$ composer require monarobase/country-list

### configのproviderとaliasesにclass追加。
./config/app.php

'providers' => [
    // ...
    Monarobase\CountryList\CountryListServiceProvider::class,
  ],
 
'aliases' => [
 // ...
 'Countries' => Monarobase\CountryList\CountryListFacade::class,
],

### controller
AdminClientsController.php

use Monarobase\CountryList\CountryListFacade;

    public function create() {

        $countries = CountryListFacade::getList('en');
        // dd($countries);
    	return view('admin.client.input', compact('countries'));
    }

### Resources
input.blade.php

<div class="form-group">
                  {!! Form::label('country', '国・地域') !!}
                  {!! Form::select('country',  $countries, null, ['class' => 'form-control col-md-4', 'placeholder' => 'Select Country...']) !!}
                </div>

上手くできました。
CountryListFacade::getList(‘en’)でcountry listを配列で取得できます。
助かった、危うく無駄な時間を過ごすところだった。。。

[Laravel8.46.0] 8.x系でLaravel Collectiveを使いたい

$ php artisan –version
Laravel Framework 8.46.0

– Laravel Collective公式のドキュメントをざっと見ます。最新バージョンは6.x系のようです。
Laravel Collective

どうもLaravelのバージョンと同じでないと不安になりますが、気にせずインストールしていきます。

$ composer require laravelcollective/html

composer.json

    "require": {
        // 省略
        "laravelcollective/html": "^6.2",
        // 省略
    },

6.2系が入ったようです。実際に使っていきます。

### configのproviderとaliasesにclass追加。
./config/app.php

'providers' => [
    // ...
    Collective\Html\HtmlServiceProvider::class,
    // ...
  ],

'aliases' => [
 // ...
 'Form' => Collective\Html\FormFacade::class,
 'Html' => Collective\Html\HtmlFacade::class,
 // ...
],

### データ挿入
– データは空の状態からコマンドラインでデータを挿入します。migrationは省略。passwordはbycrptの変換サイトで作成します。
INSERT INTO users (name, company, role_id, lang, client_name, password) VALUES (“admin”, “Test Inc.,”, 1, 1, “Taro Yamada”, “$2y$12$4EHyL825F7RvCPRMiAhyEOHjOeP4a9jOqEMxy2LdaU5wBUZXF64U2”);

### Controller
AdminController
– 設計上ではログインユーザのユーザ情報を取得する仕組みですが、テストの為(まだログイン機能を実装していない)、先ほど挿入したデータを引っ張り出します。

use App\Models\User;

public function setting(){
        // $user = Auth::user();
        $user = User::find(1);

    	return view('admin.setting', compact('user'));
    }

### Resource
setting.blade.php

<div class="card-body" width="" height="">
                {!! Form::Model($user, ['method'=>'POST', 'url' => '/admin/setting/confirm']) !!}
                <input type="hidden" name="company" value="{{ $user['company'] }}">
                <input type="hidden" name="name" value="{{ $user['name'] }}">
                <div class="form-group">
                  {!! Form::label('company', '会社名') !!} <span class="badge badge-secondary">必須</span>
                  {!! Form::text('company', null, ['class' => 'form-control col-md-12', 'disabled'=>'disabled']) !!}
                </div>
                <div class="form-group">
                  {!! Form::label('name', 'ユーザID(半角英数字)') !!} <span class="badge badge-secondary">必須</span>
                  {!! Form::text('name', null, ['class' => 'form-control col-md-6', 'disabled'=>'disabled']) !!}
                </div>
                // 省略 
              </div>
              <div class="card-footer ">
                <div class="inner">
                    {!! Form::submit('確認', ['class'=>'btn']) !!}
                </div>
              </div>
              {!! Form::close() !!}
            </div>
          </div>

大丈夫そうです。

simplexml_load_file() でRSSHubから取得できない時

ブラウザからは閲覧できるが、simplexml_load_fileで取得できない。

$rss = simplexml_load_file('https://rsshub.app/36kr/newsflashes');
print_r($rss);

こちらの記事を参考に、ユーザエージェントを指定します。
https://stackoverflow.com/questions/26896633/why-simplexml-load-file-can-not-use-some-xml-rss

$ch = curl_init('https://rsshub.app/36kr/newsflashes');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36');

$feed = curl_exec($ch);
$rss = new SimpleXMLElement($feed);

これでテストしてOKです。
サーバ側でどうやって制御してるのかはきになるところです。

[laravel8.5.19] LaravelでPython3を実行する書き方

Laravelで作成するアプリケーションで、一部の言語に関する処理のところをPython(自然言語処理)で実行したい。
方法としては、1.PHP組み込み関数のexec関数を使用する、2.symfony/processライブラリを使う の2通りがあるようだ。
今回は「exec関数」を使用する。python実行のテストのみなので、以下簡略化して記述している。

### exec関数とは?
exec関数はPHPの組み込み関数で、外部プログラムを実行できる

### Laravelプロジェクトインストール
$ composer create-project –prefer-dist laravel/laravel laravel-python
$ cd laravel-python
$ composer require laravel/jetstream
$ php artisan jetstream:install livewire
$ npm install && npm run dev
$ php artisan migrate
$ php artisan serve –host 192.168.33.10 –port 8000

### Routing
web.php
 L index.bladeからpostした際に、ExecControllerのexecutePython() メソッドでpythonを実行する

use App\Http\Controllers\ExecController;
use App\Http\Controllers\TestController;

Route::post('/python', [ExecController::class, 'executePython']);
Route::get('/test', [TestController::class, 'index']);

### View
index.blade.php
 L フォームのactionから、ExecController@executePythonにpostする

	<h1>Test</h1>
	<form action="python" method="post">
		@csrf
		<input type="submit" name="submit">
	</form>

### Controller
ExecController.php
L exec($command, $output);で実行する。
L $commandは、「python3 ${絶対パス}.py」で実行する
L laravelでは、app_path()でappディレクトリのpathを取得するので、app_path()とする
L execの第二引数である$outputは、コマンドで*.pyを実行した際の出力が返ってくる。

class ExecController extends Controller
{
    //
    public function executePython(Request $request){
    	$path = app_path() . "/Python/app.py";
    	$command = "python3 " . $path;
    	exec($command, $output);
    	dd($output);
    	// return view('index', compact('output'));
    }
}

※テストなのでdd($output)で出力を確認します。

laravel-python/app/Python/app.py
※今回はlaravelプロジェクトのapp/Python/配下にapp.pyを作成した。絶対パスなので、どこれも良いっぽいが、appフォルダに作るのが一般的のよう。今回はテストなので単にprint()するだけにした

print("hello")

### Laravelでpythonを実行した実行結果
bladeで「送信」ボタンを押すと、pythonで*.pyを実行する

実行後
L 配列で返ってくる

返ってきた変数(値)は、viewに再度返却できますね。
うおおおおおおおおおおおおおおおおおおおおおおおおお
やべえええええええええええええええええ
やりたいこと(自然言語処理)の9割くらいはイメージできた!!!!!!!!!!!!!

当初、Pythonの処理(nlpなど)の部分は、サブドメインを取得して別サーバを立ててDjangoで実装しようかと思ってたが、Laravelでpythonを実行できるなら、わざわざそんな冗長なことしなくて良いですね。
よっしゃああああああああああああああああああああああ
設計書作るぞーーーーーーーー

【株式投資】twitterで買い煽り・売り煽りが多いかpython・MeCabで調べる

優秀なトレーダーで、買い煽りが多いか、売り煽りが多いか、定性的に調べたいと思う方も多いだろう。というのも、「買い煽り」「売り煽り」というのは、ポジションを持ったイナゴの心理状態をかなり表しているからだ。当然、株が上がって欲しい時は買い煽りをするし、株価が下落して欲しい時は夢中で売り煽りを行う。外野から見たらやや滑稽に見えるかもしれないが、本人にとってはいたって真剣に行っている。なぜなら、案外、この煽りに影響を受けて売買をする人が多いからだ。話がややズレたが、したがって「買い煽り」「売り煽り」を定性的に分析さえできれば、より冷静に判断することができるので、株式投資のパフォーマンスは飛躍的に向上すると期待できる。そこで、本投稿では、Pythonを使って、買い煽り・売り煽り分析を行う。
yahooファイナンスの掲示板で調べたかったが、スクレイピングが禁止なので、twitterで銘柄コードと一緒につぶやかれている内容で、買い煽りが多いか、売り煽りが多いかを調べる。

自然言語処理の感情分析で極めて重要なのはガゼッタ(辞書)であろう。
東工大の高村教授が作成したPN Tableや東北大学の乾・岡崎研究室の日本語評価極性辞書が有名だが、買い煽り・売り煽りとは全く関係ないだろう。そのため、自分で辞書を作る必要がある。検討を重ね、以下の単語が入ってれば、それぞれ買い煽りのツイート、売り煽りのツイートとして評価することにした。

### 買い煽りのガゼッタ
‘買い’,’買う’,’特買い’,’安い’,’全力’,’爆益’,’ノンホル’,’超絶’,’本物’,’成長’,’反転’,’拾う’,’ホールド’,’踏み上げ’,’国策’,’ストップ高’,’上がる’,’初動’,’クジラ’,’売り豚’,’売り方’,’ハイカラ’,’頑張れ’,’貸借’,’反転’,’応援’,’強い’,’逆日歩’

### 売り煽りのガゼッタ
‘売り’,’売る’,’下がる’,’大損’,’ナイアガラ’,’ガラ’,’ジェットコースター’,’ダメ’,’嵌め込み’,’養分’,’退場’,’追証’,’赤字’,’ワラント’,’仕手’,’特売り’,’高い’,’アホルダー’,’イナゴ’,’撤退’,’倒産’,”,’買い豚’,’買い方’,’信用買’,’逃げろ’,’疑義’,’空売り’,’利確’,’損切’,’振い落とし’

### 銘柄に関連した呟きを取得
– tweepyで取得してテキストデータとして保存する

import tweepy
import datetime
import re
 
keyword = "6343 -RT"
dfile = "test.txt"
 
jsttime = datetime.timedelta(hours=9)
 
Consumer_key = ''
Consumer_secret = ''
Access_token = ''
Access_secret = ''
 
auth = tweepy.OAuthHandler(Consumer_key, Consumer_secret)
auth.set_access_token(Access_token, Access_secret)
 
api = tweepy.API(auth, wait_on_rate_limit = True)
 
q = keyword
 
tweets_data = []
 
 
for tweet in tweepy.Cursor(api.search, q=q, count=5,tweet_mode='extended').items(200):
	tweets_data.append(tweet.full_text + '\n')
 
fname = r"'" + dfile + "'"
fname = fname.replace("'", "")
 
with open(fname, "w", encoding="utf-8") as f:
    f.writelines(tweets_data)

### スコア分類
– つぶやきをMecabで形態素解析を行う
– 買い煽りのガゼッタのワードがつぶやかれたら買い煽り+1ポイント、売り煽りのガゼッタのワードがつぶやかれたら売り煽り+1ポイントとして評価した。

import MeCab

dfile = "test.txt"
 
fname = r"'" + dfile + "'"
fname = fname.replace("'","")
 
mecab = MeCab.Tagger("-Owakati")
 
words = []
 
with open(fname, 'r', encoding="utf-8") as f:
 
    reader = f.readline()
 
    while reader:
 
        node = mecab.parseToNode(reader)
 
        while node:
            word_type = node.feature.split(",")[0]
            if word_type in ["名詞", "動詞", "形容詞", "副詞"]:
                words.append(node.surface)
            node = node.next
        reader = f.readline()


# 買い煽りのgazetteer
buying_set = set(['買い','買う','特買い','安い','全力','爆益','ノンホル','超絶','本物','成長','反転','拾う','ホールド','踏み上げ','国策','ストップ高','上がる','初動','クジラ','売り豚','売り方','ハイカラ','頑張れ','貸借','反転','応援','強い','逆日歩'])

# 売り煽りのgazetter
selling_set = set(['売り','売る','下がる','大損','ナイアガラ','ガラ','ジェットコースター','ダメ','嵌め込み','養分','退場','追証','赤字','ワラント','仕手','特売り','高い','アホルダー','イナゴ','撤退','倒産','','買い豚','買い方','信用買','逃げろ','疑義','空売り','利確','損切','振い落とし'])

def classify_category(text):

	buying_score = 0
	selling_score = 0

	for ele in words:
		element = ele.split("\t")
		if element[0] == "EOS":
			break

		surface = element[0]

		if surface in buying_set:
			buying_score += 1
		if surface in selling_set:
			selling_score += 1

	print("買い煽りスコア:" + str(buying_score))
	print("売り煽りスコア:" + str(selling_score))

classify_category(words)

今回は、先日の金曜日にストップ高をつけたフリージア・マクロス(6343)で調べた。
$ python3 app.py
買い煽りスコア:13
売り煽りスコア:0

結果、買い煽りだらけとなった。
空売りを入れよう。

CornellのsentimentデータでTextClassification

### dataset
Cornell Natural Language Processing Groupの映画レビューのデータセットを使います。
http://www.cs.cornell.edu/people/pabo/movie-review-data/review_polarity.tar.gz
txt_sentokenフォルダ配下にnegativeとpositiveのデータが入っています。

### Sentiment Analysis with Scikit-Learn
1. import libraries and dataset
2. text preprocessing
3. converting text to numbers
4. training and test sets
5. training text classification model and predicting sentiment
6. evaluating the model
7. saving and loading the model

import numpy as np
import re
import nltk
from sklearn.datasets import load_files
nltk.download('stopwords')
import pickle
import nltk
nltk.download('wordnet')
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

# データ取得
movie_data = load_files("txt_sentoken")
X, y = movie_data.data, movie_data.target

# remove all the special characters
stemmer = WordNetLemmatizer()

documents = []

for sen in range(0, len(X)):
	# remove all the special character
	document = re.sub(r'\W', ' ', str(X[sen]))
	# remove all the single character
	document = re.sub(r'\s+[a-zA-Z]\s+', ' ', document)
	# remove all the single character from the start
	document = re.sub(r'\^[a-zA-Z]\s+', ' ', document)
	# substituting multiple spaces with single space
	document = re.sub(r'\s+', ' ', document, flags=re.I)
	# Removing prefixed 'b'
	document = re.sub(r'^b\s+', '', document)
	# converting to lowercase
	document = document.lower()
	# lemmatization (見出し語に変換)
	document = document.split()

	document = [stemmer.lemmatize(word) for word in document]
	document = ' '.join(document)

	documents.append(document)

# Bag of wordsとWord Embedding があるがここではBag of wordsを使う
# max_featuresはmost occuring world of 1500, min_dfはminimum number of documents contain this feature, max_dfはfraction corresponds to a percentage 最大70%
vectorizer = CountVectorizer(max_features=1500, min_df=5, max_df=0.7, stop_words=stopwords.words('english'))
# fit_transformでnumeric featuresに変換
X = vectorizer.fit_transform(documents).toarray()
# tfidf
tfidfconverter = TfidfTransformer()
X = tfidfconverter.fit_transform(X).toarray()

# training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

# random forest algorithm, predicting sentiment
classifier = RandomForestClassifier(n_estimators=1000, random_state=0)
classifier.fit(X_train, y_train)

y_pred = classifier.predict(X_test)

print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))
print(accuracy_score(y_test, y_pred))

$ python3 model.py
[[180 28]
[ 30 162]]
precision recall f1-score support

0 0.86 0.87 0.86 208
1 0.85 0.84 0.85 192

accuracy 0.85 400
macro avg 0.85 0.85 0.85 400
weighted avg 0.85 0.85 0.85 400

0.855

# save model
with open('text_classifier', 'wb') as picklefile:
    pickle.dump(classifier,picklefile)

import pickle
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

with open('text_classifier', 'rb') as training_model:
	model = pickle.load(training_model)

y_pred2 = model.predict(X_test)

print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))
print(accuracy_score(y_test, y_pred))

Scikit-Learnでやるんやな。

中国語のRSSテックニュース(36Kr)をバッチで取得しよう

### 課題
– 中国語の学習を習慣化させ、効果的に身に付けたい
– アプリ(Busuu)で隙間時間に学ぼうとやってみたが、イマイチ続かない
– 単語などの基礎とは別に、生きた文章を並行して習得したい

ということで、経済ニュースのRSSをバッチ処理で送ることにした。
中国のテックメディアで36Krが大手らしいので、rsshub.appから取得して、毎朝Gメールに送るようにする。
mb_send_mailのmb_languageは”uni”、encodingは”utf-8″

$rss = simplexml_load_file('https://rsshub.app/36kr/newsflashes');

mb_language("uni");
mb_internal_encoding("UTF-8");

$to = "hoge@gmail.com";
$date = date("m月d日");
$subject = "快讯 36氪 (" . $date.")";

$message = "";
$i =0;
foreach($rss->channel->item as $value){
	if($i < 10){
	$k = $i + 1;
	$message .= $k.".".$value->title ."\n";
	$message .= $value->link . "\n";
	}
	$i++;
}

$email = "hoge@hoge.jp";
mb_send_mail($to, $subject, $message, "From:".$email);

悪くない