update fitur
This commit is contained in:
parent
591962379a
commit
5fa8005eb1
|
|
@ -14,7 +14,8 @@ public function index()
|
|||
'negatif_persen' => 20,
|
||||
'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
|
||||
public function ambilData()
|
||||
{
|
||||
// nanti scraping otomatis disini (sementara dummy)
|
||||
return redirect()->route('ulasan.index');
|
||||
}
|
||||
public function ambilData()
|
||||
{
|
||||
$pythonPath = '"C:\\Users\\Mufrida Farah\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"';
|
||||
$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
|
||||
public function analisisData()
|
||||
{
|
||||
// jalankan python naive bayes
|
||||
shell_exec("python sentimen.py");
|
||||
{
|
||||
$pythonPath = '"C:\\Users\\Mufrida Farah\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"';
|
||||
|
||||
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;
|
||||
|
||||
protected $table = 'ulasan_mentah';
|
||||
protected $table = 'ulasan';
|
||||
|
||||
protected $fillable = [
|
||||
'nama_wisata',
|
||||
'reviewer_name',
|
||||
'reviewer',
|
||||
'rating',
|
||||
'ulasan',
|
||||
'tanggal',
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public function up(): void
|
|||
$table->integer('rating')->nullable();
|
||||
$table->text('ulasan');
|
||||
$table->string('tanggal')->nullable();
|
||||
$table->datetime('scraping_date')->nullable();
|
||||
$table->datetime('gununggambir_date')->nullable();
|
||||
$table->boolean('is_processed')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
labels: [
|
||||
"Papuma",
|
||||
"Watu Ulo",
|
||||
"Sukorambi",
|
||||
"Air Terjun Tancak Tulis",
|
||||
"teluklove",
|
||||
"Kebun Teh Gunung Gambir ",
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class="slide hidden absolute inset-0 w-full h-full object-cover">
|
|||
<img src="{{ asset('images/botani.jpg') }}"
|
||||
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">
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class="slide hidden absolute inset-0 w-full h-full object-cover">
|
|||
<img src="{{ asset('images/botani.jpg') }}"
|
||||
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">
|
||||
|
||||
<!-- Overlay -->
|
||||
|
|
|
|||
|
|
@ -5,20 +5,35 @@
|
|||
<div class="max-w-7xl mx-auto">
|
||||
|
||||
{{-- 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">
|
||||
Dashboard
|
||||
</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
|
||||
focus:ring-2 focus:ring-blue-400 focus:outline-none">
|
||||
<option>Semua Destinasi</option>
|
||||
<option>Pantai Papuma</option>
|
||||
<option>Pantai Watu Ulo</option>
|
||||
<option>Taman Botani Sukorambi</option>
|
||||
<option>Air Terjun Tancak Tulis</option>
|
||||
</select> -->
|
||||
|
||||
<option value="">Semua Destinasi</option>
|
||||
<option value="papuma">Pantai Papuma</option>
|
||||
<option value="watuulo">Pantai Watu Ulo</option>
|
||||
<option value="teluklove">Teluk Love (Payangan)</option>
|
||||
<option value="gununggambir">Kebun Teh Gunung Gambir </option>
|
||||
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Statistik Cards --}}
|
||||
|
|
@ -55,14 +70,13 @@ class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
|||
</div>
|
||||
|
||||
{{-- 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 -->
|
||||
<div class="bg-white rounded-xl shadow p-6">
|
||||
<h3 class="font-semibold mb-4 text-gray-700">
|
||||
Distribusi Sentimen
|
||||
</h3>
|
||||
|
||||
<div class="h-[280px] flex justify-center items-center">
|
||||
<canvas id="pieChart"></canvas>
|
||||
</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">
|
||||
Sentimen per Destinasi
|
||||
</h3>
|
||||
|
||||
<div class="h-[280px]">
|
||||
<canvas id="barChart"></canvas>
|
||||
</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>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
<!-- 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 -->
|
||||
<div class="bg-white rounded-xl shadow p-6 text-center">
|
||||
<h3 class="text-gray-600 font-semibold mb-2">
|
||||
Isu Utama:
|
||||
</h3>
|
||||
<p class="text-lg font-bold text-gray-800">
|
||||
{{ $isuUtama }}
|
||||
</p>
|
||||
<!-- Isu Utama -->
|
||||
<div class="bg-white rounded-xl shadow p-6">
|
||||
<h3 class="text-gray-700 font-semibold mb-4">
|
||||
Isu Utama
|
||||
</h3>
|
||||
|
||||
<div class="text-sm text-gray-600 space-y-2">
|
||||
<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>
|
||||
|
||||
<!-- KEYWORD -->
|
||||
<div class="bg-white rounded-xl shadow p-6 text-center">
|
||||
<h3 class="text-gray-600 font-semibold mb-2">
|
||||
Kata Kunci Dominan:
|
||||
</h3>
|
||||
<p class="text-lg font-bold text-gray-800">
|
||||
{{ $kataDominan }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Kata Kunci Dominan -->
|
||||
<div class="bg-white rounded-xl shadow p-6">
|
||||
<h3 class="text-gray-700 font-semibold mb-4">
|
||||
Kata Kunci Dominan
|
||||
</h3>
|
||||
|
||||
<!-- SARAN -->
|
||||
<div class="bg-white rounded-xl shadow p-6 text-center">
|
||||
<h3 class="text-gray-600 font-semibold mb-2">
|
||||
Saran:
|
||||
</h3>
|
||||
<p class="text-lg font-bold text-gray-800">
|
||||
{{ $saran }}
|
||||
</p>
|
||||
</div>
|
||||
<ul class="text-sm text-gray-600 space-y-2">
|
||||
<li class="flex justify-between">
|
||||
<span>kotor</span>
|
||||
<span class="font-semibold">15x</span>
|
||||
</li>
|
||||
<li class="flex justify-between">
|
||||
<span>parkir</span>
|
||||
<span class="font-semibold">10x</span>
|
||||
</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>
|
||||
|
||||
@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>Pantai Papuma</option>
|
||||
<option>Pantai Watu Ulo</option>
|
||||
<option>Taman Botani Sukorambi</option>
|
||||
<option>Air Terjun Tancak Tulis</option>
|
||||
<option>Teluk Love (Payangan)</option>
|
||||
<option>Kebun Teh Gunung Gambir </option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
|
@ -39,33 +39,37 @@ class="bg-blue-600 hover:bg-blue-700 text-white font-semibold
|
|||
Data Ulasan Mentah
|
||||
</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">
|
||||
<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">Rating</th>
|
||||
<th class="px-4 py-3">Ulasan</th>
|
||||
<th class="px-4 py-3">Tanggal</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@forelse($mentah as $row)
|
||||
<tr class="border-b">
|
||||
<td class="px-4 py-3">{{ $row->nama_wisata }}</td>
|
||||
<td class="px-4 py-3">{{ $row->reviewer_name }}</td>
|
||||
<td class="px-4 py-3">{{ $row->ulasan }}</td>
|
||||
<td class="px-4 py-3">{{ $row->tanggal }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-6 text-gray-400">
|
||||
Belum ada data mentah.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
@forelse($mentah as $data)
|
||||
<tr>
|
||||
<td class="px-4 py-3">{{ $data->destinasi }}</td>
|
||||
<td class="px-4 py-3">{{ $data->reviewer }}</td>
|
||||
<td class="px-4 py-3">{{ $data->rating }}</td>
|
||||
<td class="px-4 py-3">{{ $data->ulasan }}</td>
|
||||
<td class="px-4 py-3">{{ $data->tanggal }}</td>
|
||||
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-6 text-gray-400">
|
||||
Belum ada data mentah.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -78,7 +82,7 @@ class="bg-blue-600 hover:bg-blue-700 text-white font-semibold
|
|||
<button
|
||||
class="bg-green-600 hover:bg-green-700 text-white font-semibold
|
||||
px-6 py-2 rounded-lg shadow">
|
||||
Preprocessing Data
|
||||
Proses Analisis
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -91,33 +95,42 @@ class="bg-green-600 hover:bg-green-700 text-white font-semibold
|
|||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm text-left">
|
||||
<thead>
|
||||
<tr class="bg-gray-100 text-gray-600">
|
||||
<th class="px-4 py-3">Ulasan Asli</th>
|
||||
<th class="px-4 py-3">Cleaning + Case Folding</th>
|
||||
<th class="px-4 py-3">Ulasan Bersih Final (Stemming)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<thead class="sticky top-0 bg-gray-100">
|
||||
<tr class="bg-gray-100 text-gray-600">
|
||||
<th class="px-4 py-3">Destinasi</th>
|
||||
<th class="px-4 py-3">Ulasan Mentah</th>
|
||||
<th class="px-4 py-3">Ulasan Bersih</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@forelse($preprocessing as $row)
|
||||
<tr class="border-b">
|
||||
<td class="px-4 py-3">{{ $row->ulasan_asli }}</td>
|
||||
<tbody>
|
||||
@forelse($preprocessing as $row)
|
||||
<tr class="border-b">
|
||||
|
||||
<!-- gabungan cleaning + lowercase -->
|
||||
<td class="px-4 py-3">{{ $row->cleaning }}</td>
|
||||
<td class="px-4 py-3">
|
||||
{{ $row->destinasi }}
|
||||
</td>
|
||||
|
||||
<!-- hasil akhir setelah stopword + slang + stemming -->
|
||||
<td class="px-4 py-3">{{ $row->stemming }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="3" class="text-center py-6 text-gray-400">
|
||||
Belum ada hasil preprocessing.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
<td class="px-4 py-3">
|
||||
{{ $row->ulasan_asli }}
|
||||
</td>
|
||||
|
||||
<td class="px-4 py-3">
|
||||
{{ $row->stemming }}
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
@empty
|
||||
<tr>
|
||||
|
||||
<td colspan="3" class="text-center py-6 text-gray-400">
|
||||
Belum ada hasil preprocessing.
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
Route::get('/ulasan', [UlasanController::class, 'index'])->name('ulasan.index');
|
||||
Route::post('/ulasan/ambil', [UlasanController::class, 'ambilData'])->name('ulasan.ambil');
|
||||
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');
|
||||
Route::get('/rekomendasi', [RekomendasiController::class, '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