update fitur
This commit is contained in:
parent
591962379a
commit
5fa8005eb1
|
|
@ -14,7 +14,8 @@ public function index()
|
||||||
'negatif_persen' => 20,
|
'negatif_persen' => 20,
|
||||||
'netral_persen' => 12,
|
'netral_persen' => 12,
|
||||||
];
|
];
|
||||||
|
$lastUpdate = '27 Februari 2026, 22:45';
|
||||||
|
|
||||||
return view('dashboard', compact('stats'));
|
return view('dashboard', compact('stats', 'lastUpdate'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,18 +31,31 @@ public function index()
|
||||||
|
|
||||||
|
|
||||||
// Tombol ambil data terbaru
|
// Tombol ambil data terbaru
|
||||||
public function ambilData()
|
public function ambilData()
|
||||||
{
|
{
|
||||||
// nanti scraping otomatis disini (sementara dummy)
|
$pythonPath = '"C:\\Users\\Mufrida Farah\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"';
|
||||||
return redirect()->route('ulasan.index');
|
$scriptPath = base_path('scraper/scraping_pipeline.py');
|
||||||
}
|
|
||||||
|
$command = $pythonPath . " " . $scriptPath . " 2>&1";
|
||||||
|
|
||||||
|
shell_exec($command);
|
||||||
|
|
||||||
|
return redirect()->route('ulasan.index')
|
||||||
|
->with('success', 'Data berhasil diambil dari Google Maps');
|
||||||
|
}
|
||||||
|
|
||||||
// Tombol analisis data
|
// Tombol analisis data
|
||||||
public function analisisData()
|
public function analisisData()
|
||||||
{
|
{
|
||||||
// jalankan python naive bayes
|
$pythonPath = '"C:\\Users\\Mufrida Farah\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"';
|
||||||
shell_exec("python sentimen.py");
|
|
||||||
|
|
||||||
return redirect()->route('ulasan.index');
|
$scriptPath = base_path('scraper/preprocessing.py');
|
||||||
}
|
|
||||||
|
$command = $pythonPath . " " . $scriptPath . " 2>&1";
|
||||||
|
|
||||||
|
shell_exec($command);
|
||||||
|
|
||||||
|
return redirect()->route('ulasan.index')
|
||||||
|
->with('success','Preprocessing berhasil dilakukan');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ class Ulasan extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $table = 'ulasan_mentah';
|
protected $table = 'ulasan';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'nama_wisata',
|
'nama_wisata',
|
||||||
'reviewer_name',
|
'reviewer',
|
||||||
'rating',
|
'rating',
|
||||||
'ulasan',
|
'ulasan',
|
||||||
'tanggal',
|
'tanggal',
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public function up(): void
|
||||||
$table->integer('rating')->nullable();
|
$table->integer('rating')->nullable();
|
||||||
$table->text('ulasan');
|
$table->text('ulasan');
|
||||||
$table->string('tanggal')->nullable();
|
$table->string('tanggal')->nullable();
|
||||||
$table->datetime('scraping_date')->nullable();
|
$table->datetime('gununggambir_date')->nullable();
|
||||||
$table->boolean('is_processed')->default(0);
|
$table->boolean('is_processed')->default(0);
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
labels: [
|
labels: [
|
||||||
"Papuma",
|
"Papuma",
|
||||||
"Watu Ulo",
|
"Watu Ulo",
|
||||||
"Sukorambi",
|
"teluklove",
|
||||||
"Air Terjun Tancak Tulis",
|
"Kebun Teh Gunung Gambir ",
|
||||||
],
|
],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ class="slide hidden absolute inset-0 w-full h-full object-cover">
|
||||||
<img src="{{ asset('images/botani.jpg') }}"
|
<img src="{{ asset('images/botani.jpg') }}"
|
||||||
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
||||||
|
|
||||||
<img src="{{ asset('images/tancak.jpeg') }}"
|
<img src="{{ asset('images/gununggambir.jpeg') }}"
|
||||||
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ class="slide hidden absolute inset-0 w-full h-full object-cover">
|
||||||
<img src="{{ asset('images/botani.jpg') }}"
|
<img src="{{ asset('images/botani.jpg') }}"
|
||||||
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
||||||
|
|
||||||
<img src="{{ asset('images/tancak.jpeg') }}"
|
<img src="{{ asset('images/gununggambir.jpeg') }}"
|
||||||
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
||||||
|
|
||||||
<!-- Overlay -->
|
<!-- Overlay -->
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,35 @@
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="max-w-7xl mx-auto">
|
||||||
|
|
||||||
{{-- Header --}}
|
{{-- Header --}}
|
||||||
<div class="flex justify-between items-center mb-8">
|
<div class="flex justify-between items-start mb-8">
|
||||||
|
<div>
|
||||||
<h2 class="text-2xl font-semibold text-gray-800">
|
<h2 class="text-2xl font-semibold text-gray-800">
|
||||||
Dashboard
|
Dashboard
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<!-- <select
|
<!-- <p class="text-sm text-gray-500 mt-1">
|
||||||
|
Terakhir diperbarui: 27 Februari 2026, 22:45
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="text-xs text-gray-400">
|
||||||
|
Periode Data: Februari 2026
|
||||||
|
</p> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter Destinasi -->
|
||||||
|
<form method="GET" action="" class="ml-4">
|
||||||
|
<select name="destinasi"
|
||||||
class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
||||||
focus:ring-2 focus:ring-blue-400 focus:outline-none">
|
focus:ring-2 focus:ring-blue-400 focus:outline-none">
|
||||||
<option>Semua Destinasi</option>
|
|
||||||
<option>Pantai Papuma</option>
|
<option value="">Semua Destinasi</option>
|
||||||
<option>Pantai Watu Ulo</option>
|
<option value="papuma">Pantai Papuma</option>
|
||||||
<option>Taman Botani Sukorambi</option>
|
<option value="watuulo">Pantai Watu Ulo</option>
|
||||||
<option>Air Terjun Tancak Tulis</option>
|
<option value="teluklove">Teluk Love (Payangan)</option>
|
||||||
</select> -->
|
<option value="gununggambir">Kebun Teh Gunung Gambir </option>
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Statistik Cards --}}
|
{{-- Statistik Cards --}}
|
||||||
|
|
@ -55,14 +70,13 @@ class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Chart Section --}}
|
{{-- Chart Section --}}
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||||
|
|
||||||
<!-- Pie Chart -->
|
<!-- Pie Chart -->
|
||||||
<div class="bg-white rounded-xl shadow p-6">
|
<div class="bg-white rounded-xl shadow p-6">
|
||||||
<h3 class="font-semibold mb-4 text-gray-700">
|
<h3 class="font-semibold mb-4 text-gray-700">
|
||||||
Distribusi Sentimen
|
Distribusi Sentimen
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="h-[280px] flex justify-center items-center">
|
<div class="h-[280px] flex justify-center items-center">
|
||||||
<canvas id="pieChart"></canvas>
|
<canvas id="pieChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -73,12 +87,60 @@ class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
||||||
<h3 class="font-semibold mb-4 text-gray-700">
|
<h3 class="font-semibold mb-4 text-gray-700">
|
||||||
Sentimen per Destinasi
|
Sentimen per Destinasi
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="h-[280px]">
|
<div class="h-[280px]">
|
||||||
<canvas id="barChart"></canvas>
|
<canvas id="barChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div> {{-- TUTUP GRID CHART --}}
|
||||||
|
|
||||||
|
|
||||||
|
{{-- Section Bawah --}}
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
|
||||||
|
<!-- Total Data per Destinasi -->
|
||||||
|
<div class="bg-white rounded-xl shadow p-6 h-[320px]">
|
||||||
|
<h3 class="font-semibold mb-6 text-gray-700">
|
||||||
|
Total Data per Destinasi
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="space-y-4 text-sm text-gray-600">
|
||||||
|
<div class="flex justify-between border-b pb-3">
|
||||||
|
<span>Pantai Papuma</span>
|
||||||
|
<span class="font-semibold text-gray-800">120 ulasan</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between border-b pb-3">
|
||||||
|
<span>Pantai Watu Ulo</span>
|
||||||
|
<span class="font-semibold text-gray-800">85 ulasan</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between border-b pb-3">
|
||||||
|
<span>Teluk Love (Payangan)</span>
|
||||||
|
<span class="font-semibold text-gray-800">95 ulasan</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Kebun Teh Gunung Gambir</span>
|
||||||
|
<span class="font-semibold text-gray-800">60 ulasan</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tren Sentimen per Bulan -->
|
||||||
|
<!-- <div class="bg-white rounded-xl shadow p-6 h-[320px]">
|
||||||
|
<h3 class="font-semibold mb-6 text-gray-700">
|
||||||
|
Tren Sentimen per Bulan
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="h-[220px] flex items-center justify-center text-gray-400">
|
||||||
|
Grafik tren akan ditampilkan setelah data periode tersedia.
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
@extends('layouts.sentara')
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto space-y-8">
|
|
||||||
|
|
||||||
{{-- HEADER --}}
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<h2 class="text-2xl font-bold text-gray-800">
|
|
||||||
Data Ulasan Mentah
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<select
|
|
||||||
class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
|
||||||
focus:ring-2 focus:ring-blue-400 focus:outline-none">
|
|
||||||
<option>Semua Destinasi</option>
|
|
||||||
<option>Pantai Papuma</option>
|
|
||||||
<option>Sukorambi</option>
|
|
||||||
<option>Watu Ulo</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- BUTTON SCRAPING --}}
|
|
||||||
<div>
|
|
||||||
<a href="{{ route('scrape.ulasan') }}"
|
|
||||||
class="bg-blue-700 hover:bg-blue-800 text-white px-6 py-2 rounded-lg shadow">
|
|
||||||
Ambil Data Terbaru
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- DATA MENTAH TABLE --}}
|
|
||||||
<div class="bg-white rounded-xl shadow p-6">
|
|
||||||
|
|
||||||
<h3 class="font-semibold text-gray-700 mb-4">
|
|
||||||
Hasil Data Scraping
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="min-w-full text-sm text-left">
|
|
||||||
<thead>
|
|
||||||
<tr class="bg-gray-100 text-gray-600">
|
|
||||||
<th class="px-4 py-3">Destinasi</th>
|
|
||||||
<th class="px-4 py-3">Reviewer</th>
|
|
||||||
<th class="px-4 py-3">Ulasan</th>
|
|
||||||
<th class="px-4 py-3">Tanggal</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
|
|
||||||
@forelse($ulasan as $item)
|
|
||||||
<tr class="border-b">
|
|
||||||
<td class="px-4 py-3 font-medium">
|
|
||||||
{{ $item->nama_wisata }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
{{ $item->reviewer_name }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="px-4 py-3 text-gray-700">
|
|
||||||
{{ Str::limit($item->ulasan, 80) }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
{{ $item->tanggal }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@empty
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" class="text-center py-6 text-gray-500">
|
|
||||||
Data belum tersedia. Silakan scraping dulu.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforelse
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{{-- BUTTON ANALISIS --}}
|
|
||||||
<!-- <div>
|
|
||||||
<form action="{{ route('analisis.ulasan') }}" method="POST">
|
|
||||||
@csrf
|
|
||||||
<button type="submit"
|
|
||||||
class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg shadow">
|
|
||||||
Analisis Data
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
|
|
||||||
{{-- HASIL ANALISIS --}}
|
|
||||||
@if(isset($hasilAnalisis))
|
|
||||||
|
|
||||||
<!-- <div class="bg-white rounded-xl shadow p-6">
|
|
||||||
|
|
||||||
<h3 class="font-semibold text-gray-700 mb-4">
|
|
||||||
Hasil Analisis Sentimen
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="min-w-full text-sm text-left">
|
|
||||||
<thead>
|
|
||||||
<tr class="bg-gray-100 text-gray-600">
|
|
||||||
<th class="px-4 py-3">Destinasi</th>
|
|
||||||
<th class="px-4 py-3">Ulasan</th>
|
|
||||||
<th class="px-4 py-3">Sentimen</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
@foreach($hasilAnalisis as $hasil)
|
|
||||||
<tr class="border-b">
|
|
||||||
<td class="px-4 py-3 font-medium">
|
|
||||||
{{ $hasil->nama_wisata }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
{{ Str::limit($hasil->ulasan, 80) }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="px-4 py-3">
|
|
||||||
@if($hasil->sentimen == "positif")
|
|
||||||
<span class="px-3 py-1 rounded-full bg-green-100 text-green-700 text-xs font-semibold">
|
|
||||||
Positif
|
|
||||||
</span>
|
|
||||||
@elseif($hasil->sentimen == "negatif")
|
|
||||||
<span class="px-3 py-1 rounded-full bg-red-100 text-red-700 text-xs font-semibold">
|
|
||||||
Negatif
|
|
||||||
</span>
|
|
||||||
@else
|
|
||||||
<span class="px-3 py-1 rounded-full bg-yellow-100 text-yellow-700 text-xs font-semibold">
|
|
||||||
Netral
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@endsection
|
|
||||||
|
|
@ -30,40 +30,60 @@ class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CARD GRID -->
|
<!-- CARD GRID -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
|
||||||
<!-- ISU UTAMA -->
|
<!-- Isu Utama -->
|
||||||
<div class="bg-white rounded-xl shadow p-6 text-center">
|
<div class="bg-white rounded-xl shadow p-6">
|
||||||
<h3 class="text-gray-600 font-semibold mb-2">
|
<h3 class="text-gray-700 font-semibold mb-4">
|
||||||
Isu Utama:
|
Isu Utama
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-lg font-bold text-gray-800">
|
|
||||||
{{ $isuUtama }}
|
<div class="text-sm text-gray-600 space-y-2">
|
||||||
</p>
|
<span class="inline-block bg-red-100 text-red-700 px-3 py-1 rounded-full text-xs font-medium">
|
||||||
|
Kebersihan
|
||||||
|
</span>
|
||||||
|
<span class="inline-block bg-red-100 text-red-700 px-3 py-1 rounded-full text-xs font-medium">
|
||||||
|
Parkir
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- KEYWORD -->
|
<!-- Kata Kunci Dominan -->
|
||||||
<div class="bg-white rounded-xl shadow p-6 text-center">
|
<div class="bg-white rounded-xl shadow p-6">
|
||||||
<h3 class="text-gray-600 font-semibold mb-2">
|
<h3 class="text-gray-700 font-semibold mb-4">
|
||||||
Kata Kunci Dominan:
|
Kata Kunci Dominan
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-lg font-bold text-gray-800">
|
|
||||||
{{ $kataDominan }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- SARAN -->
|
<ul class="text-sm text-gray-600 space-y-2">
|
||||||
<div class="bg-white rounded-xl shadow p-6 text-center">
|
<li class="flex justify-between">
|
||||||
<h3 class="text-gray-600 font-semibold mb-2">
|
<span>kotor</span>
|
||||||
Saran:
|
<span class="font-semibold">15x</span>
|
||||||
</h3>
|
</li>
|
||||||
<p class="text-lg font-bold text-gray-800">
|
<li class="flex justify-between">
|
||||||
{{ $saran }}
|
<span>parkir</span>
|
||||||
</p>
|
<span class="font-semibold">10x</span>
|
||||||
</div>
|
</li>
|
||||||
|
<li class="flex justify-between">
|
||||||
|
<span>mahal</span>
|
||||||
|
<span class="font-semibold">7x</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Saran -->
|
||||||
|
<div class="bg-white rounded-xl shadow p-6">
|
||||||
|
<h3 class="text-gray-700 font-semibold mb-4">
|
||||||
|
Saran Perbaikan
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p class="text-sm text-gray-600 leading-relaxed">
|
||||||
|
Perlu peningkatan kebersihan area wisata serta evaluasi sistem
|
||||||
|
pengelolaan parkir untuk meningkatkan kenyamanan pengunjung.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
||||||
<option>Semua Destinasi</option>
|
<option>Semua Destinasi</option>
|
||||||
<option>Pantai Papuma</option>
|
<option>Pantai Papuma</option>
|
||||||
<option>Pantai Watu Ulo</option>
|
<option>Pantai Watu Ulo</option>
|
||||||
<option>Taman Botani Sukorambi</option>
|
<option>Teluk Love (Payangan)</option>
|
||||||
<option>Air Terjun Tancak Tulis</option>
|
<option>Kebun Teh Gunung Gambir </option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -39,33 +39,37 @@ class="bg-blue-600 hover:bg-blue-700 text-white font-semibold
|
||||||
Data Ulasan Mentah
|
Data Ulasan Mentah
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto max-h-[400px] overflow-y-auto">
|
||||||
<table class="w-full text-sm text-left">
|
<table class="w-full text-sm text-left">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-gray-100 text-gray-600">
|
<tr class="bg-gray-100 text-gray-600">
|
||||||
<th class="px-4 py-3">Destinasi</th>
|
<th class="px-4 py-3">Destinasi</th>
|
||||||
<th class="px-4 py-3">Reviewer</th>
|
<th class="px-4 py-3">Reviewer</th>
|
||||||
|
<th class="px-4 py-3">Rating</th>
|
||||||
<th class="px-4 py-3">Ulasan</th>
|
<th class="px-4 py-3">Ulasan</th>
|
||||||
<th class="px-4 py-3">Tanggal</th>
|
<th class="px-4 py-3">Tanggal</th>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse($mentah as $row)
|
@forelse($mentah as $data)
|
||||||
<tr class="border-b">
|
<tr>
|
||||||
<td class="px-4 py-3">{{ $row->nama_wisata }}</td>
|
<td class="px-4 py-3">{{ $data->destinasi }}</td>
|
||||||
<td class="px-4 py-3">{{ $row->reviewer_name }}</td>
|
<td class="px-4 py-3">{{ $data->reviewer }}</td>
|
||||||
<td class="px-4 py-3">{{ $row->ulasan }}</td>
|
<td class="px-4 py-3">{{ $data->rating }}</td>
|
||||||
<td class="px-4 py-3">{{ $row->tanggal }}</td>
|
<td class="px-4 py-3">{{ $data->ulasan }}</td>
|
||||||
</tr>
|
<td class="px-4 py-3">{{ $data->tanggal }}</td>
|
||||||
@empty
|
|
||||||
<tr>
|
</tr>
|
||||||
<td colspan="4" class="text-center py-6 text-gray-400">
|
@empty
|
||||||
Belum ada data mentah.
|
<tr>
|
||||||
</td>
|
<td colspan="6" class="text-center py-6 text-gray-400">
|
||||||
</tr>
|
Belum ada data mentah.
|
||||||
@endforelse
|
</td>
|
||||||
</tbody>
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -78,7 +82,7 @@ class="bg-blue-600 hover:bg-blue-700 text-white font-semibold
|
||||||
<button
|
<button
|
||||||
class="bg-green-600 hover:bg-green-700 text-white font-semibold
|
class="bg-green-600 hover:bg-green-700 text-white font-semibold
|
||||||
px-6 py-2 rounded-lg shadow">
|
px-6 py-2 rounded-lg shadow">
|
||||||
Preprocessing Data
|
Proses Analisis
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -91,33 +95,42 @@ class="bg-green-600 hover:bg-green-700 text-white font-semibold
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full text-sm text-left">
|
<table class="w-full text-sm text-left">
|
||||||
<thead>
|
<thead class="sticky top-0 bg-gray-100">
|
||||||
<tr class="bg-gray-100 text-gray-600">
|
<tr class="bg-gray-100 text-gray-600">
|
||||||
<th class="px-4 py-3">Ulasan Asli</th>
|
<th class="px-4 py-3">Destinasi</th>
|
||||||
<th class="px-4 py-3">Cleaning + Case Folding</th>
|
<th class="px-4 py-3">Ulasan Mentah</th>
|
||||||
<th class="px-4 py-3">Ulasan Bersih Final (Stemming)</th>
|
<th class="px-4 py-3">Ulasan Bersih</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse($preprocessing as $row)
|
@forelse($preprocessing as $row)
|
||||||
<tr class="border-b">
|
<tr class="border-b">
|
||||||
<td class="px-4 py-3">{{ $row->ulasan_asli }}</td>
|
|
||||||
|
|
||||||
<!-- gabungan cleaning + lowercase -->
|
<td class="px-4 py-3">
|
||||||
<td class="px-4 py-3">{{ $row->cleaning }}</td>
|
{{ $row->destinasi }}
|
||||||
|
</td>
|
||||||
|
|
||||||
<!-- hasil akhir setelah stopword + slang + stemming -->
|
<td class="px-4 py-3">
|
||||||
<td class="px-4 py-3">{{ $row->stemming }}</td>
|
{{ $row->ulasan_asli }}
|
||||||
</tr>
|
</td>
|
||||||
@empty
|
|
||||||
<tr>
|
<td class="px-4 py-3">
|
||||||
<td colspan="3" class="text-center py-6 text-gray-400">
|
{{ $row->stemming }}
|
||||||
Belum ada hasil preprocessing.
|
</td>
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
@endforelse
|
|
||||||
</tbody>
|
@empty
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td colspan="3" class="text-center py-6 text-gray-400">
|
||||||
|
Belum ada hasil preprocessing.
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
Route::get('/ulasan', [UlasanController::class, 'index'])->name('ulasan.index');
|
Route::get('/ulasan', [UlasanController::class, 'index'])->name('ulasan.index');
|
||||||
Route::post('/ulasan/ambil', [UlasanController::class, 'ambilData'])->name('ulasan.ambil');
|
Route::post('/ulasan/ambil', [UlasanController::class, 'ambilData'])->name('ulasan.ambil');
|
||||||
Route::post('/ulasan/analisis', [UlasanController::class, 'analisisData'])->name('ulasan.analisis');
|
Route::post('/ulasan/analisis', [UlasanController::class, 'analisisData'])->name('ulasan.analisis');
|
||||||
Route::get('/hasil-analisis', [AnalisisController::class, 'index'])
|
Route::get('/hasil-analisis', [AnalisisController::class, 'index'])
|
||||||
->name('analisis.index');
|
->name('analisis.index');
|
||||||
Route::get('/rekomendasi', [RekomendasiController::class, 'index'])
|
Route::get('/rekomendasi', [RekomendasiController::class, 'index'])
|
||||||
->name('rekomendasi.index');;
|
->name('rekomendasi.index');;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import mysql.connector
|
||||||
|
import re
|
||||||
|
from Sastrawi.Stemmer.StemmerFactory import StemmerFactory
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# DATABASE
|
||||||
|
# =============================
|
||||||
|
conn = mysql.connector.connect(
|
||||||
|
host="localhost",
|
||||||
|
database="sentara",
|
||||||
|
user="root",
|
||||||
|
password=""
|
||||||
|
)
|
||||||
|
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# STEMMER
|
||||||
|
# =============================
|
||||||
|
factory = StemmerFactory()
|
||||||
|
stemmer = factory.create_stemmer()
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# AMBIL DATA ULASAN
|
||||||
|
# =============================
|
||||||
|
cursor.execute("SELECT id, ulasan FROM ulasan")
|
||||||
|
|
||||||
|
data = cursor.fetchall()
|
||||||
|
|
||||||
|
print("Total ulasan:", len(data))
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
|
||||||
|
id_ulasan = row[0]
|
||||||
|
text = row[1]
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# CLEANING
|
||||||
|
# =============================
|
||||||
|
text_clean = re.sub(r'[^a-zA-Z\s]', '', text)
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# CASE FOLDING
|
||||||
|
# =============================
|
||||||
|
text_clean = text_clean.lower()
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# STEMMING
|
||||||
|
# =============================
|
||||||
|
text_stem = stemmer.stem(text_clean)
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# INSERT KE DATABASE
|
||||||
|
# =============================
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO preprocessing_data
|
||||||
|
(ulasan_asli, cleaning, stemming)
|
||||||
|
VALUES (%s,%s,%s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
text,
|
||||||
|
text_clean,
|
||||||
|
text_stem
|
||||||
|
))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print("Preprocessing selesai")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.chrome.options import Options
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
import mysql.connector
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# DATABASE
|
||||||
|
# =============================
|
||||||
|
conn = mysql.connector.connect(
|
||||||
|
host="localhost",
|
||||||
|
database="sentara",
|
||||||
|
user="root",
|
||||||
|
password=""
|
||||||
|
)
|
||||||
|
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# CHROME
|
||||||
|
# =============================
|
||||||
|
options = Options()
|
||||||
|
options.add_argument("--start-maximized")
|
||||||
|
|
||||||
|
driver = webdriver.Chrome(options=options)
|
||||||
|
wait = WebDriverWait(driver, 15)
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# DESTINASI
|
||||||
|
# =============================
|
||||||
|
destinations = {
|
||||||
|
"Pantai Papuma": "Pantai Papuma Jember",
|
||||||
|
"Pantai Watu Ulo": "Pantai Watu Ulo Jember",
|
||||||
|
"Teluk Love": "Teluk Love Jember",
|
||||||
|
"Kebun Teh Gunung Gambir": "Kebun Teh Gunung Gambir Jember"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# LOOP DESTINASI
|
||||||
|
# =============================
|
||||||
|
for nama_wisata, keyword in destinations.items():
|
||||||
|
|
||||||
|
saved = 0
|
||||||
|
print("\n====================")
|
||||||
|
print("Scraping:", nama_wisata)
|
||||||
|
print("====================")
|
||||||
|
|
||||||
|
driver.get("https://www.google.com/maps")
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# CARI TEMPAT
|
||||||
|
# =============================
|
||||||
|
try:
|
||||||
|
search = driver.find_element(By.ID, "searchboxinput")
|
||||||
|
except:
|
||||||
|
search = driver.find_element(By.XPATH, "//input[@aria-label='Search Google Maps']")
|
||||||
|
|
||||||
|
search.clear()
|
||||||
|
search.send_keys(keyword)
|
||||||
|
|
||||||
|
driver.find_element(By.ID, "searchbox-searchbutton").click()
|
||||||
|
|
||||||
|
time.sleep(6)
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# KLIK HASIL PERTAMA
|
||||||
|
# =============================
|
||||||
|
try:
|
||||||
|
first_result = wait.until(
|
||||||
|
EC.element_to_be_clickable((By.XPATH, "(//a[contains(@href,'/maps/place')])[1]"))
|
||||||
|
)
|
||||||
|
first_result.click()
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
except:
|
||||||
|
print("Tempat tidak ditemukan")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# BUKA TAB REVIEW
|
||||||
|
# =============================
|
||||||
|
try:
|
||||||
|
review_button = wait.until(
|
||||||
|
EC.element_to_be_clickable((By.XPATH, "//button[contains(@aria-label,'review')]"))
|
||||||
|
)
|
||||||
|
|
||||||
|
review_button.click()
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
except:
|
||||||
|
print("Review button tidak ditemukan")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# SCROLL REVIEW
|
||||||
|
# =============================
|
||||||
|
try:
|
||||||
|
scrollable_div = driver.find_element(By.XPATH, "//div[@role='region']")
|
||||||
|
except:
|
||||||
|
print("Container review tidak ditemukan")
|
||||||
|
continue
|
||||||
|
|
||||||
|
last_count = 0
|
||||||
|
same_count = 0
|
||||||
|
reviews = []
|
||||||
|
|
||||||
|
for i in range(200):
|
||||||
|
|
||||||
|
driver.execute_script(
|
||||||
|
"arguments[0].scrollTop = arguments[0].scrollHeight",
|
||||||
|
scrollable_div
|
||||||
|
)
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
reviews = driver.find_elements(By.XPATH, "//div[@data-review-id]")
|
||||||
|
|
||||||
|
current_count = len(reviews)
|
||||||
|
|
||||||
|
print("Scroll", i, "Review:", current_count)
|
||||||
|
|
||||||
|
if current_count == last_count:
|
||||||
|
same_count += 1
|
||||||
|
else:
|
||||||
|
same_count = 0
|
||||||
|
|
||||||
|
if same_count >= 6:
|
||||||
|
print("✔ Semua review sudah termuat")
|
||||||
|
break
|
||||||
|
|
||||||
|
last_count = current_count
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# AMBIL DATA REVIEW
|
||||||
|
# =============================
|
||||||
|
for review in reviews:
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
review_id = review.get_attribute("data-review-id")
|
||||||
|
|
||||||
|
if not review_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# cek duplikasi
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT COUNT(*) FROM ulasan WHERE review_id=%s",
|
||||||
|
(review_id,)
|
||||||
|
)
|
||||||
|
|
||||||
|
exists = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
if exists > 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# reviewer
|
||||||
|
try:
|
||||||
|
reviewer = review.find_element(
|
||||||
|
By.XPATH, ".//div[contains(@class,'d4r55')]"
|
||||||
|
).text
|
||||||
|
except:
|
||||||
|
reviewer = "Unknown"
|
||||||
|
|
||||||
|
# rating
|
||||||
|
try:
|
||||||
|
rating_text = review.find_element(
|
||||||
|
By.XPATH, ".//span[@role='img']"
|
||||||
|
).get_attribute("aria-label")
|
||||||
|
|
||||||
|
rating = float(rating_text.split()[0])
|
||||||
|
except:
|
||||||
|
rating = 0
|
||||||
|
|
||||||
|
# ulasan
|
||||||
|
try:
|
||||||
|
review_text = review.find_element(
|
||||||
|
By.XPATH, ".//span[contains(@class,'wiI7pd')]"
|
||||||
|
).text
|
||||||
|
except:
|
||||||
|
review_text = ""
|
||||||
|
|
||||||
|
# tanggal
|
||||||
|
try:
|
||||||
|
review_date = review.find_element(
|
||||||
|
By.XPATH, ".//span[contains(@class,'rsqaWe')]"
|
||||||
|
).text
|
||||||
|
except:
|
||||||
|
review_date = ""
|
||||||
|
|
||||||
|
if review_text == "":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# INSERT DATABASE
|
||||||
|
# =============================
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO ulasan
|
||||||
|
(review_id,destinasi,reviewer,ulasan,rating,tanggal,scraping_date)
|
||||||
|
VALUES(%s,%s,%s,%s,%s,%s,%s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
review_id,
|
||||||
|
nama_wisata,
|
||||||
|
reviewer,
|
||||||
|
review_text,
|
||||||
|
rating,
|
||||||
|
review_date,
|
||||||
|
datetime.now()
|
||||||
|
))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
saved += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("skip:", e)
|
||||||
|
|
||||||
|
print("Total tersimpan:", saved)
|
||||||
|
|
||||||
|
print("\n====================")
|
||||||
|
print("SCRAPING SELESAI")
|
||||||
|
print("====================")
|
||||||
|
|
||||||
|
driver.quit()
|
||||||
|
conn.close()
|
||||||
Loading…
Reference in New Issue