parent
b507cd40d6
commit
4a67610f55
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2015 The Chromium Authors
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
File diff suppressed because it is too large
Load Diff
BIN
chromedriver.exe
BIN
chromedriver.exe
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,49 @@
|
|||
Panduan
|
||||
Dashboard
|
||||
Publikasi
|
||||
Dosen
|
||||
PANDUAN PENGGUNAAN
|
||||
DASHBOARD PUBLIKASI DOSEN
|
||||
1. Pilih Menu “Dashboard”
|
||||
2. Pengguna dapat memuat ulang grafik visualisasi
|
||||
|
||||
data hasil scraping yang telah dikirim melewati
|
||||
halaman “Scraper”.
|
||||
|
||||
3. Pengguna dapat mengolah data sesuai kebutuhan, seperti
|
||||
mengunduh hasil data yang telah dimanipulasi dengan
|
||||
tombol ⬇ hijau, memuat ulang grafik visualisasi tertentu
|
||||
|
||||
dengan tombol <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>berwarna biru, Serta memanipulasi data
|
||||
dengan tombol/link “Edit Data Grafik”
|
||||
4. Saat Pengguna menekan tombol/link “Edit Data
|
||||
|
||||
Grafik” pada suatu grafik maka akan memunculkan
|
||||
tabel yang memungkinkan pengguna memanipulasi
|
||||
|
||||
data seperti menambahkan baris baru, menghapus
|
||||
data tertentu, mengubah isi data tersebut sesuai
|
||||
|
||||
kebutuhan.
|
||||
|
||||
5. Pengguna dapat meninjau total data secara keseluruhan
|
||||
data yang terhubung dengan grafik.
|
||||
|
||||
6. Pengguna dapat menyimpan perubahan / hasil
|
||||
manipulasi data dengan menekan tombol biru “Simpan
|
||||
|
||||
Perubahan”.
|
||||
7. Saat Pengguna menggulirkan halaman dashboard
|
||||
ke bawah maka akan muncul grafik yang meninjau
|
||||
data dari pie, untuk menampilkan grafik pie tersebut
|
||||
|
||||
maka pengguna perlu memilih date agar
|
||||
menampilkan data publikasi sesuai tahun
|
||||
|
||||
8. Pengguna dapat meninjau hasil grafik pie berdasarkan
|
||||
tahun yang telah dipilih untuk mendapatkan
|
||||
|
||||
insight(wawasan) dari kontribusi data publikasi dosen
|
||||
seperti data Hak kaya Intelektual, Pengabdian
|
||||
Masyarakat, dan Penelitian.
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
Panduan
|
||||
Scraping Data
|
||||
Publikasi
|
||||
Dosen
|
||||
|
||||
1. Panduan Penggunaan Scraper untuk Mengambil
|
||||
Data.
|
||||
|
||||
2. Panduan Pembersihan Data Hasil Scraping.
|
||||
PANDUAN PENGGUNAAN
|
||||
SCRAPER UNTUK MENGAMBIL
|
||||
|
||||
DATA
|
||||
1. Pilih Menu “Scraper”
|
||||
2. Pengguna dapat memulai proses scraping pada
|
||||
tombol - tombol berdasarkan tujuan dan kebutuhan
|
||||
|
||||
dari sumber portal SINTA dan Google Scholar.
|
||||
|
||||
3. Pengguna memerlukan atau menyimpan
|
||||
ID dari sebuah afiliasi/institusi pendidikan
|
||||
untuk bisa melakukan scraping.
|
||||
4. Setelah ID afiliasi/institusi pendidikan telah
|
||||
disimpan maka pengguna perlu mengisinya di
|
||||
|
||||
dalam kolom teks ini.
|
||||
|
||||
Tampilan halaman “Form Input/edit Pasien Rawat Inap”
|
||||
|
||||
5. Pengguna bisa memulai scraping data
|
||||
dengan menekan tombol “Scrape”
|
||||
|
||||
6. Pengguna perlu menunggu proses scraping
|
||||
hingga Chrome driver memulai proses
|
||||
scraping
|
||||
7. Chrome Driver akan muncul pada layar setelah
|
||||
proses scraping telah dieksekusi. Pengguna
|
||||
|
||||
diharapkan mengisi akun/kredensial dan login ke
|
||||
Portal SINTA
|
||||
|
||||
6. Sistem Scraper akan mulai bekerja dan terus
|
||||
melakukan scraping data hingga selesai pada
|
||||
|
||||
halaman terakhir.
|
||||
9. Setelah proses scraping selesai maka file tersebut
|
||||
telah tersimpan di folder dan siap untuk digunakan
|
||||
|
||||
sesuai kebutuhan pengguna
|
||||
|
||||
10. Pengguna dapat mengelola data hasil
|
||||
scraping tersebut untuk dibersihkan dan
|
||||
|
||||
diproses sesuai kebutuhan.
|
||||
PANDUAN PEMBERSIHAN DATA
|
||||
HASIL SCRAPING
|
||||
1. Pilih Menu “Scraper”
|
||||
2. Pengguna dapat membersihkan data dari file excel hasil
|
||||
|
||||
scraping dengan drag-and-drop(tarik dan lepas) file
|
||||
tersebut ke kotak upload tersebut atau bisa diupload
|
||||
|
||||
manual
|
||||
|
||||
3. Pengguna diharapkan menunggu proses
|
||||
pembersihan file hasil scraping oleh sistem.
|
||||
4. Pengguna bisa memilih opsi yang bertujuan
|
||||
di-visualisasikan ke grafik pada halaman dashboard
|
||||
|
||||
5. Pengguna bisa langsung menuju Dashboard
|
||||
untuk melihat hasil file excel dari scraping yang
|
||||
|
||||
telah dibersihkan ke dashboard dengan
|
||||
menekan tombol ini.
|
||||
6. File yang telah dibersihkan dapat terlihat di
|
||||
dashboard dan Pengguna dapat mengolah data
|
||||
|
||||
tersebut untuk kebutuhan selanjutnya
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 536 KiB |
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 z-50 flex justify-center items-center">
|
||||
<div class="bg-gray-800 border-2 border-gray-600 rounded-lg w-11/12 lg:w-4/5 p-6 relative">
|
||||
<div class="bg-gray-800 border-2 border-gray-600 rounded-lg w-[98vw] max-w-[1600px] p-6 relative">
|
||||
<button @click="$emit('close')" class="absolute top-2 right-2 text-white text-lg">×</button>
|
||||
<div class="mb-4">
|
||||
<slot name="header" />
|
||||
|
|
|
@ -1,17 +1,40 @@
|
|||
<template>
|
||||
<div class="fixed inset-0 w-full h-full flex bg-update-bg bg-cover bg-center">
|
||||
<!-- Sidebar -->
|
||||
<div class="absolute left-5 top-5 h-1/4 bg-gray-700 bg-opacity-90 border-r-4 border-gray-500 pixel-border shadow-lg w-16 flex flex-col items-center p-4">
|
||||
<nav class="flex flex-col space-y-6 text-center mt-4">
|
||||
<a href="/dashboard" class="text-white hover:text-yellow-400 transition">
|
||||
<LayoutDashboard class="w-8 h-8" />
|
||||
</a>
|
||||
<a href="/scraper" class="text-white hover:text-blue-400 transition">
|
||||
<Bot class="w-8 h-8" />
|
||||
</a>
|
||||
<div class="fixed inset-0 w-full h-full flex bg-gradient-to-br from-white via-white/70 to-gray-300 backdrop-blur-lg">
|
||||
<div class="absolute left-5 top-5 h-1/2 bg-gray-500 bg-opacity-90 border-r-4 border-gray-500 pixel-border shadow-lg w-16 flex flex-col items-center p-4">
|
||||
<nav class="flex flex-col space-y-6 mt-4">
|
||||
|
||||
<!-- Sidebar Item Component -->
|
||||
<div class="relative group flex justify-center">
|
||||
<a href="/dashboard" class="text-white hover:text-yellow-400 transition flex items-center justify-center w-10 h-10">
|
||||
<LayoutDashboard class="w-6 h-6" />
|
||||
</a>
|
||||
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
|
||||
Dashboard
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative group flex justify-center">
|
||||
<a href="/scraper" class="text-white hover:text-blue-400 transition flex items-center justify-center w-10 h-10">
|
||||
<Bot class="w-6 h-6" />
|
||||
</a>
|
||||
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
|
||||
Scraper
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative group flex justify-center">
|
||||
<button @click="openHelpModal" class="text-white hover:text-green-400 transition flex items-center justify-center w-10 h-10">
|
||||
<HelpCircle class="w-6 h-6" />
|
||||
</button>
|
||||
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
|
||||
Panduan
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 ml-20 overflow-y-auto p-4">
|
||||
<Modal v-if="isModalOpen" @close="isModalOpen = false">
|
||||
|
@ -19,33 +42,90 @@
|
|||
<h3 class="text-xl font-bold text-white">Edit Data {{ modalTitle }}</h3>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-sm text-white table-auto">
|
||||
<thead>
|
||||
<div class="overflow-x-auto max-h-[65vh] overflow-y-auto">
|
||||
<div class="flex items-center justify-between mb-3 px-2">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Cari data..."
|
||||
class="w-full md:w-1/3 px-4 py-2 rounded bg-gray-700 text-white placeholder-gray-400 border border-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-400"
|
||||
/>
|
||||
</div>
|
||||
<table class="min-w-[150%] text-sm text-white table-auto">
|
||||
<thead class="sticky top-0 bg-gray-800 z-10">
|
||||
<tr>
|
||||
<th v-for="(header, index) in tableHeaders" :key="index" class="px-4 py-2 border-b border-gray-500">{{ header }}</th>
|
||||
<th class="px-4 py-2 border-b border-gray-500">Aksi</th>
|
||||
<th class="px-4 py-2 border-b border-gray-500">
|
||||
<input type="checkbox" @change="toggleSelectAll($event)" class="accent-gray-300" />
|
||||
</th>
|
||||
<th v-for="(header, index) in tableHeaders" :key="index" class="px-4 py-2 border-b border-gray-500 min-w-[12rem]">
|
||||
{{ header }}
|
||||
</th>
|
||||
<th class="px-4 py-2 border-b border-gray-500 min-w-[8rem]">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, rowIndex) in editableData" :key="rowIndex">
|
||||
<td v-for="(header, colIndex) in tableHeaders" :key="colIndex" class="px-4 py-2 border-b border-gray-600">
|
||||
<input v-model="editableData[rowIndex][header]" class="bg-gray-700 text-white px-2 py-1 w-full rounded" />
|
||||
<tr v-for="(index, i) in filteredIndexes" :key="i">
|
||||
<td class="px-4 py-2 border-b border-gray-600 text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="index"
|
||||
v-model="selectedRows"
|
||||
class="accent-gray-300"
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
v-for="(header, colIndex) in tableHeaders"
|
||||
:key="colIndex"
|
||||
class="px-4 py-2 border-b border-gray-600 min-w-[12rem]"
|
||||
>
|
||||
<input
|
||||
v-model="editableData[index][header]"
|
||||
class="bg-gray-500 text-white px-2 py-3 w-full rounded"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2 border-b border-gray-600 text-center">
|
||||
<button @click="removeRow(rowIndex)" class="text-red-500 hover:underline">Hapus</button>
|
||||
<button @click="removeRow(index)" class="text-red-500 hover:underline">Hapus</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button @click="addRow" class="mt-4 bg-green-500 hover:bg-green-400 text-white px-4 py-2 rounded">Tambah Baris</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<button @click="saveChanges" class="bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded">Simpan Perubahan</button>
|
||||
<div class="flex justify-end gap-3 pt-4">
|
||||
<div class="flex flex-wrap gap-4 justify-between items-center text-white text-sm px-4 pb-4">
|
||||
<div class="flex items-center gap-2 bg-gray-700 rounded-lg px-4 py-2 shadow-sm border border-gray-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-yellow-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h4l3 9 4-18 3 9h4" />
|
||||
</svg>
|
||||
<span class="font-medium">Total Baris:</span>
|
||||
<span class="font-semibold text-white">{{ editableData.length }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 bg-gray-700 rounded-lg px-4 py-2 shadow-sm border border-gray-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.1 0-2 .9-2 2s.9 2 2 2m0 4v.01M12 12v-2m0 10c4.42 0 8-1.79 8-4V8c0-2.21-3.58-4-8-4S4 5.79 4 8v8c0 2.21 3.58 4 8 4z" />
|
||||
</svg>
|
||||
<span class="font-medium">Total Dana:</span>
|
||||
<span class="font-semibold text-white">{{ formattedTotalDana }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button @click="addRow" class="bg-green-500 hover:bg-green-400 text-white px-4 py-2 rounded">
|
||||
Tambah Baris
|
||||
</button>
|
||||
<button @click="removeSelectedRows" class="bg-red-600 hover:bg-red-500 text-white px-4 py-2 rounded" :disabled="selectedRows.length === 0">
|
||||
Hapus Terpilih ({{ selectedRows.length }})
|
||||
</button>
|
||||
<button @click="saveChanges" class="bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded">
|
||||
Simpan Perubahan
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
<div class="w-full bg-gray-700 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
|
||||
<div class="w-full bg-gray-500 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
|
||||
<h2 class="text-white text-xl font-bold mb-6">DASHBOARD</h2>
|
||||
<div class="flex items-center gap-4 mb-10">
|
||||
<button @click="refreshData" class="text-white bg-green-600 hover:bg-green-500 px-4 py-2 rounded-2xl">
|
||||
|
@ -78,7 +158,7 @@
|
|||
🔄
|
||||
</button>
|
||||
</div>
|
||||
<button @click="openModal('pengabdian')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
|
||||
<button @click="openModal('pengabdian')" class="text-sm text-yellow-400 hover:underline mb-10">Edit Data Grafik</button>
|
||||
<div
|
||||
@dragover.prevent
|
||||
@drop.prevent="handleDrop"
|
||||
|
@ -110,7 +190,7 @@
|
|||
🔄
|
||||
</button>
|
||||
</div>
|
||||
<button @click="openModal('hki')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
|
||||
<button @click="openModal('hki')" class="text-sm text-yellow-400 hover:underline mb-10">Edit Data Grafik</button>
|
||||
<div
|
||||
@dragover.prevent
|
||||
@drop.prevent="handleDropHKI"
|
||||
|
@ -142,7 +222,7 @@
|
|||
🔄
|
||||
</button>
|
||||
</div>
|
||||
<button @click="openModal('penelitian')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
|
||||
<button @click="openModal('penelitian')" class="text-sm text-yellow-400 hover:underline mb-10">Edit Data Grafik</button>
|
||||
<div
|
||||
@dragover.prevent
|
||||
@drop.prevent="handleDropPenelitian"
|
||||
|
@ -174,7 +254,7 @@
|
|||
🔄
|
||||
</button>
|
||||
</div>
|
||||
<button @click="openModal('scopus')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
|
||||
<button @click="openModal('scopus')" class="text-sm text-yellow-400 hover:underline mb-10">Edit Data Grafik</button>
|
||||
<div
|
||||
@dragover.prevent
|
||||
@drop.prevent="handleDropScopus"
|
||||
|
@ -204,7 +284,7 @@
|
|||
🔄
|
||||
</button>
|
||||
</div>
|
||||
<button @click="openModal('scholar')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
|
||||
<button @click="openModal('scholar')" class="text-sm text-yellow-400 hover:underline mb-10">Edit Data Grafik</button>
|
||||
<div
|
||||
@dragover.prevent
|
||||
@drop.prevent="handleDropScholar"
|
||||
|
@ -219,27 +299,54 @@
|
|||
</div>
|
||||
<div class="bg-gray-800 p-4 rounded-lg text-white w-full h-[500px] col-span-1 md:col-span-2 mt-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold">Kontribusi Luaran Publikasi Dosen per Tahun</h3>
|
||||
<select v-model="selectedYear" class="bg-gray-700 text-white p-2 rounded">
|
||||
<option v-for="year in availableYears" :key="year" :value="year">{{ year }}</option>
|
||||
</select>
|
||||
<h3 class="text-lg font-semibold">Kontribusi Data Luaran Publikasi Dosen per Tahun </h3>
|
||||
<div class="relative group inline-block">
|
||||
<select v-model="selectedYear" class="bg-gray-500 text-white p-2 rounded">
|
||||
<option v-for="year in availableYears" :key="year" :value="year">{{ year }}</option>
|
||||
</select>
|
||||
<div
|
||||
class="absolute bottom-full mb-1 left-1/2 -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
|
||||
Pilih Tahun
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="w-80 h-80 mx-auto">
|
||||
<canvas id="luaranPieChart" class="w-80 h-80" width="320" height="320"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full bg-gray-700 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
|
||||
<div class="w-full bg-gray-500 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="showHelpModal"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60"
|
||||
>
|
||||
<div class="bg-white rounded-lg overflow-hidden w-[85vw] h-[90vh] shadow-2xl relative">
|
||||
<button
|
||||
@click="showHelpModal = false"
|
||||
class="absolute top-2 right-2 bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded text-sm z-10"
|
||||
>
|
||||
✖
|
||||
</button>
|
||||
<iframe
|
||||
src="/panduan/Panduan-Dashboard.pdf"
|
||||
type="application/pdf"
|
||||
width="100%"
|
||||
height="100%"
|
||||
class="rounded-b-lg"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Modal from '@/components/Modal.vue'
|
||||
import axios from 'axios'
|
||||
import { LayoutDashboard, Bot, Settings, LogOut } from "lucide-vue-next"
|
||||
import { LayoutDashboard, Bot, HelpCircle, Settings, LogOut } from "lucide-vue-next"
|
||||
import { computed, defineComponent, h } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import * as XLSX from 'xlsx'
|
||||
|
@ -252,6 +359,8 @@ import {
|
|||
import { onMounted, watch} from 'vue'
|
||||
import Chart from 'chart.js/auto';
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
fetchCleanedFile('pengabdian', 'pengabdian_cleaned.xlsx', pengabdian)
|
||||
fetchCleanedFile('penelitian', 'penelitian_cleaned.xlsx', penelitian)
|
||||
|
@ -273,6 +382,36 @@ function refreshData() {
|
|||
|
||||
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
|
||||
|
||||
const selectedRows = ref([]);
|
||||
const searchQuery = ref('');
|
||||
const filteredEditableData = computed(() => {
|
||||
if (!searchQuery.value) return editableData.value;
|
||||
return editableData.value.filter((row) =>
|
||||
Object.values(row).some((value) =>
|
||||
String(value).toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const filteredIndexes = computed(() => {
|
||||
if (!searchQuery.value) return editableData.value.map((_, idx) => idx);
|
||||
return editableData.value
|
||||
.map((row, idx) => ({ row, idx }))
|
||||
.filter(({ row }) =>
|
||||
Object.values(row).some((value) =>
|
||||
String(value).toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
)
|
||||
)
|
||||
.map(({ idx }) => idx);
|
||||
});
|
||||
|
||||
const showHelpModal = ref(false);
|
||||
|
||||
function openHelpModal() {
|
||||
showHelpModal.value = true;
|
||||
}
|
||||
|
||||
|
||||
const pengabdian = ref([])
|
||||
const hki = ref([])
|
||||
const penelitian = ref([])
|
||||
|
@ -281,6 +420,27 @@ const scholar = ref([])
|
|||
const isModalOpen = ref(false)
|
||||
const modalTitle = ref('')
|
||||
const editableData = ref([])
|
||||
const totalDana = computed(() => {
|
||||
let total = 0;
|
||||
editableData.value.forEach((row) => {
|
||||
const value = row['Jumlah Dana'] || row['Dana'] || '';
|
||||
const cleanValue = parseInt(
|
||||
String(value).replace(/[^\d]/g, '') // Hilangkan Rp, titik, koma
|
||||
);
|
||||
if (!isNaN(cleanValue)) total += cleanValue;
|
||||
});
|
||||
return total;
|
||||
});
|
||||
|
||||
const formattedTotalDana = computed(() => {
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(totalDana.value);
|
||||
});
|
||||
|
||||
|
||||
const tableHeaders = ref([])
|
||||
const currentTargetArray = ref(null)
|
||||
|
||||
|
@ -288,6 +448,8 @@ const selectedYear = ref(new Date().getFullYear());
|
|||
const availableYears = ref([]);
|
||||
let pieChartInstance = null;
|
||||
|
||||
|
||||
|
||||
function clearCache() {
|
||||
pengabdian.value = []
|
||||
hki.value = []
|
||||
|
@ -311,6 +473,8 @@ function clearCache() {
|
|||
console.log("Cache data cleared")
|
||||
}
|
||||
|
||||
|
||||
|
||||
function refreshSingleData(type) {
|
||||
switch(type) {
|
||||
case 'pengabdian':
|
||||
|
@ -331,6 +495,22 @@ function refreshSingleData(type) {
|
|||
}
|
||||
}
|
||||
|
||||
function removeSelectedRows() {
|
||||
const rowsToRemove = [...selectedRows.value].sort((a, b) => b - a);
|
||||
rowsToRemove.forEach(index => {
|
||||
editableData.value.splice(index, 1);
|
||||
});
|
||||
selectedRows.value = [];
|
||||
}
|
||||
|
||||
function toggleSelectAll(event) {
|
||||
if (event.target.checked) {
|
||||
selectedRows.value = editableData.value.map((_, index) => index);
|
||||
} else {
|
||||
selectedRows.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getYearsFromData() {
|
||||
const allYears = new Set();
|
||||
|
@ -365,7 +545,7 @@ function renderPieChart() {
|
|||
data: {
|
||||
labels: ['HKI', 'Pengabdian', 'Penelitian'],
|
||||
datasets: [{
|
||||
label: 'Jumlah Luaran',
|
||||
label: 'Jumlah Data Luaran',
|
||||
data: [hkiCount, pengabdianCount, penelitianCount],
|
||||
backgroundColor: ['#38bdf8', '#facc15', '#34d399'],
|
||||
borderWidth: 1
|
||||
|
@ -456,22 +636,24 @@ async function saveChanges() {
|
|||
|
||||
async function fetchCleanedFile(folder, filename, targetArray) {
|
||||
try {
|
||||
const response = await axios.get(`http://localhost:8000/cleaned-files/${folder}/${filename}`, {
|
||||
const timestamp = new Date().getTime(); // Bypass cache
|
||||
const response = await axios.get(`http://localhost:8000/cleaned-files/${folder}/${filename}?t=${timestamp}`, {
|
||||
responseType: 'arraybuffer'
|
||||
})
|
||||
});
|
||||
|
||||
const data = new Uint8Array(response.data)
|
||||
const workbook = XLSX.read(data, { type: 'array' })
|
||||
const sheet = workbook.Sheets[workbook.SheetNames[0]]
|
||||
const json = XLSX.utils.sheet_to_json(sheet)
|
||||
const data = new Uint8Array(response.data);
|
||||
const workbook = XLSX.read(data, { type: 'array' });
|
||||
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
const json = XLSX.utils.sheet_to_json(sheet);
|
||||
|
||||
targetArray.value = json
|
||||
targetArray.value = json;
|
||||
} catch (error) {
|
||||
console.error(`Gagal mengambil file cleaned (${folder}/${filename}):`, error)
|
||||
console.error(`Gagal mengambil file cleaned (${folder}/${filename}):`, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function handleDrop(e) {
|
||||
const file = e.dataTransfer.files[0]
|
||||
if (file && file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
|
||||
|
@ -653,18 +835,14 @@ const BarChart = defineComponent({
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.font-pixel {
|
||||
font-family: 'Press Start 2P', cursive;
|
||||
}
|
||||
|
||||
.pixel-border {
|
||||
image-rendering: pixelated;
|
||||
position: relative;
|
||||
border-width: 4px;
|
||||
border-style: solid;
|
||||
border-radius: 8px;
|
||||
box-shadow:
|
||||
inset -2px -2px 0px rgba(0, 0, 0, 0.8),
|
||||
inset 2px 2px 0px rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.1);
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,78 +1,236 @@
|
|||
<template>
|
||||
<div class="fixed inset-0 w-full h-full flex bg-update-bg bg-cover bg-center">
|
||||
<div class="absolute left-5 top-5 h-1/4 bg-gray-700 bg-opacity-90 border-r-4 border-gray-500 pixel-border shadow-lg w-16 flex flex-col items-center p-4">
|
||||
<nav class="flex flex-col space-y-6 text-center mt-4">
|
||||
<a href="/dashboard" class="text-white hover:text-yellow-400 transition">
|
||||
<LayoutDashboard class="w-8 h-8" />
|
||||
</a>
|
||||
<a href="/scraper" class="text-white hover:text-blue-400 transition">
|
||||
<Bot class="w-8 h-8" />
|
||||
</a>
|
||||
<template>
|
||||
<div class="fixed inset-0 w-full h-full flex bg-gradient-to-br from-white via-white/60 to-gray-300 backdrop-blur-lg">
|
||||
<div class="absolute left-5 top-5 h-1/2 bg-gray-500 bg-opacity-90 border-r-4 border-gray-500 pixel-border shadow-lg w-16 flex flex-col items-center p-4">
|
||||
<nav class="flex flex-col space-y-6 mt-4">
|
||||
|
||||
<!-- Sidebar Item Component -->
|
||||
<div class="relative group flex justify-center">
|
||||
<a href="/dashboard" class="text-white hover:text-yellow-400 transition flex items-center justify-center w-10 h-10">
|
||||
<LayoutDashboard class="w-6 h-6" />
|
||||
</a>
|
||||
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
|
||||
Dashboard
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative group flex justify-center">
|
||||
<a href="/scraper" class="text-white hover:text-blue-400 transition flex items-center justify-center w-10 h-10">
|
||||
<Bot class="w-6 h-6" />
|
||||
</a>
|
||||
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
|
||||
Scraper
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative group flex justify-center">
|
||||
<button @click="openHelpModal" class="text-white hover:text-green-400 transition flex items-center justify-center w-10 h-10">
|
||||
<HelpCircle class="w-6 h-6" />
|
||||
</button>
|
||||
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
|
||||
Panduan
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex flex-col w-full max-w-full gap-6 ml-10 p-4">
|
||||
|
||||
<!-- Scraping Section -->
|
||||
<div class="w-full bg-gray-700 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
|
||||
<h2 class="text-center text-xl font-bold font-pixel text-white mb-4"></h2>
|
||||
<div class="w-full bg-gray-500 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
|
||||
<h2 class="text-center text-xl font-bold text-white mb-4">Silahkan melakukan scraping data data luaran sesuai tujuan dan kebutuhan anda</h2>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<button @click="startScrapeScopus" class="bg-blue-600 hover:bg-blue-700 text-white font-pixel px-4 py-2 rounded shadow">
|
||||
Scrape Scopus
|
||||
</button>
|
||||
<button @click="startScrapeHki" class="bg-green-600 hover:bg-green-700 text-white font-pixel px-4 py-2 rounded shadow">
|
||||
Scrape HKI
|
||||
</button>
|
||||
<button @click="startScrapeGoogleScholar" class="bg-purple-600 hover:bg-purple-700 text-white font-pixel px-4 py-2 rounded shadow">
|
||||
Scrape Google Scholar
|
||||
</button>
|
||||
<button @click="startScrapePenelitian" class="bg-red-600 hover:bg-red-700 text-white font-pixel px-4 py-2 rounded shadow">
|
||||
Scrape Penelitian
|
||||
</button>
|
||||
<button @click="startScrapePengabdian" class="bg-yellow-600 hover:bg-yellow-700 text-white font-pixel px-4 py-2 rounded shadow">
|
||||
Scrape Pengabdian
|
||||
</button>
|
||||
<!-- Scopus -->
|
||||
<div class="relative group">
|
||||
<button @click="showAffiliationModalScopus = true" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded shadow">
|
||||
Scrape Scopus
|
||||
</button>
|
||||
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
|
||||
Tombol untuk melakukan scraping data Scopus dosen dari portal SINTA
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <a v-if="latestFile" :href="`http://localhost:8000/scopus/download`" target="_blank" class="bg-green-600 hover:bg-green-700 text-white font-pixel px-4 py-2 rounded shadow">
|
||||
Download Data
|
||||
</a>
|
||||
<router-link v-if="latestFile" to="/data-cleaning" class="bg-purple-600 hover:bg-purple-700 text-white font-pixel px-4 py-2 rounded shadow">
|
||||
Open Cleaning
|
||||
</router-link> -->
|
||||
<!-- HKI -->
|
||||
<div class="relative group">
|
||||
<button @click="showAffiliationModalHki = true" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded shadow">
|
||||
Scrape HKI
|
||||
</button>
|
||||
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
|
||||
Tombol untuk melakukan scraping data Hak Kaya Intelektual dosen dari portal SINTA
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Google Scholar -->
|
||||
<div class="relative group">
|
||||
<button @click="startScrapeGoogleScholar" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded shadow">
|
||||
Scrape Google Scholar
|
||||
</button>
|
||||
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
|
||||
Tombol untuk melakukan scraping data luaran/publikasi dosen dari portal Google Scholar (Masih dalam lingkup Politeknik Negeri Jember Jurusan Teknologi Informasi)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Google Scholar lain-->
|
||||
<!-- <div class="relative group">
|
||||
<button @click="showModalGoogleScholar = true" class="bg-teal-600 hover:bg-teal-700 text-white px-4 py-2 rounded shadow">
|
||||
Scrape Google Scholar
|
||||
</button>
|
||||
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
|
||||
Tombol untuk melakukan scraping data luaran/publikasi dosen dari portal Google Scholar
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- Penelitian -->
|
||||
<div class="relative group">
|
||||
<button @click="showAffiliationModal = true" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded shadow">
|
||||
Scrape Penelitian
|
||||
</button>
|
||||
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
|
||||
Tombol untuk melakukan scraping data Penelitian dosen dari portal SINTA
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pengabdian -->
|
||||
<div class="relative group">
|
||||
<button @click="showAffiliationModalPengabdian = true" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded shadow">
|
||||
Scrape Pengabdian
|
||||
</button>
|
||||
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
|
||||
Tombol untuk melakukan scraping data Pengabdian pada Masyarakat oleh dari portal SINTA
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 text-sm font-pixel">
|
||||
<div class="mt-6 text-sm">
|
||||
<div v-if="loading" class="text-yellow-300 animate-pulse">🔄 Sedang memproses ...</div>
|
||||
<div v-if="statusMessage" class="text-green-400">{{ statusMessage }}</div>
|
||||
<div v-if="errorMessage" class="text-red-400">{{ errorMessage }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="w-full p-6 bg-gray-800 border-4 border-dashed border-gray-600 rounded-lg text-center text-white font-pixel"
|
||||
class="w-full p-6 bg-gray-500 border-4 border-dashed border-gray-400 rounded-lg text-center text-white"
|
||||
@dragover.prevent
|
||||
@drop.prevent="handleDrop"
|
||||
>
|
||||
<p>📂 Tarik dan lepas file Excel di sini atau klik untuk upload</p>
|
||||
<p>📂 Tarik dan lepas atau klik untuk upload file Excel hasil scraping data di sini untuk dibersihkan</p>
|
||||
<input type="file" accept=".xlsx" class="hidden" ref="fileInput" @change="handleFileUpload" />
|
||||
<button @click="$refs.fileInput.click()" class="mt-4 bg-blue-700 hover:bg-blue-800 px-4 py-2 rounded">
|
||||
Upload Manual
|
||||
Upload Manual File Excel
|
||||
</button>
|
||||
</div>
|
||||
<!-- Modal Konfirmasi -->
|
||||
<div v-if="showModal" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
|
||||
<div class="bg-gray-700 rounded-lg shadow-lg w-96 p-6">
|
||||
<div class="bg-gray-800 rounded-lg shadow-lg w-96 p-6">
|
||||
<h3 class="text-lg font-bold mb-4">Pilih Kategori Grafik</h3>
|
||||
<p class="mb-4 text-sm text-gray-700">File berhasil dibersihkan. Masukkan sebagai grafik apa?</p>
|
||||
<p class="mb-4 text-sm text-white">File hasil scraping berhasil dibersihkan. Anda ingin memasukkan file sebagai grafik apa?</p>
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<button v-for="option in chartOptions" :key="option" @click="submitCleanedFile(option)"
|
||||
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded font-pixel text-sm">
|
||||
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded text-sm">
|
||||
{{ option }}
|
||||
</button>
|
||||
</div>
|
||||
<button @click="showModal = false" class="text-gray-500 hover:text-red-500 text-sm">Batal</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Sukses Redirect -->
|
||||
<div v-if="successModal" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
|
||||
<div class="bg-gray-900 rounded-lg shadow-lg w-96 p-6 text-center text-white">
|
||||
<h3 class="text-lg font-bold mb-4">🎉 File Berhasil Dikirim</h3>
|
||||
<p class="mb-4 text-sm">File berhasil ditambahkan ke grafik. Lanjut ke dashboard untuk melihat visualisasi?</p>
|
||||
<div class="flex justify-center gap-4">
|
||||
<a href="/dashboard" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded text-white text-sm">Ke Dashboard</a>
|
||||
<button @click="successModal = false" class="text-gray-400 hover:text-red-500 text-sm">Tutup</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showAffiliationModal" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
|
||||
<div class="bg-gray-800 rounded-lg shadow-lg w-80 p-6 text-white">
|
||||
<h3 class="text-lg font-bold mb-4">Masukkan ID Afiliasi untuk scraping</h3>
|
||||
<input type="number" v-model="tempAffiliationId" placeholder="Contoh: 384"
|
||||
class="w-full text-black px-2 py-1 rounded border mb-4" />
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button @click="confirmScrapePenelitian"
|
||||
class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded">Scrape</button>
|
||||
<button @click="showAffiliationModal = false"
|
||||
class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded">Batal</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Modal Pengabdian -->
|
||||
<div v-if="showAffiliationModalPengabdian" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
|
||||
<div class="bg-gray-800 rounded-lg shadow-lg w-80 p-6 text-white">
|
||||
<h3 class="text-lg font-bold mb-4">Masukkan ID Afiliasi untuk scraping</h3>
|
||||
<input type="number" v-model="tempAffiliationIdPengabdian" placeholder="Contoh: 447" class="w-full text-black px-2 py-1 rounded border mb-4" />
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button @click="confirmScrapePengabdian" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded">Scrape</button>
|
||||
<button @click="showAffiliationModalPengabdian = false" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded">Batal</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showAffiliationModalScopus" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
|
||||
<div class="bg-gray-800 rounded-lg shadow-lg w-80 p-6 text-white">
|
||||
<h3 class="text-lg font-bold mb-4">Masukkan ID Afiliasi untuk scraping</h3>
|
||||
<input type="number" v-model="tempAffiliationIdScopus" placeholder="Contoh: 447" class="w-full text-black px-2 py-1 rounded border mb-4" />
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button @click="confirmScrapeScopus" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">Scrape</button>
|
||||
<button @click="showAffiliationModalScopus = false" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded">Batal</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showModalGoogleScholar" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
|
||||
<div class="bg-gray-800 rounded-lg shadow-lg w-1/2 p-6 text-white">
|
||||
<h3 class="text-lg font-bold mb-4">link</h3>
|
||||
<input
|
||||
type="text"
|
||||
v-model="tempLinkOneScholar"
|
||||
placeholder="contoh: https://scholar.google.com/citations?hl=id&user=syL17R9KKKs"
|
||||
class="w-full text-black px-2 py-1 rounded border mb-4"
|
||||
/>
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button @click="confirmScrapeOneScholar" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">Scrape</button>
|
||||
<button @click="showModalGoogleScholar = false" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded">Batal</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Modal HKI -->
|
||||
<div v-if="showAffiliationModalHki" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
|
||||
<div class="bg-gray-800 rounded-lg shadow-lg w-80 p-6 text-white">
|
||||
<h3 class="text-lg font-bold mb-4">Masukkan ID Afiliasi untuk scraping</h3>
|
||||
<input type="number" v-model="tempAffiliationIdHki" placeholder="Contoh: 447" class="w-full text-black px-2 py-1 rounded border mb-4" />
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button @click="confirmScrapeHki" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded">Scrape</button>
|
||||
<button @click="showAffiliationModalHki = false" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded">Batal</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="showHelpModal"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60"
|
||||
>
|
||||
<div class="bg-white rounded-lg overflow-hidden w-[85vw] h-[90vh] shadow-2xl relative">
|
||||
<button
|
||||
@click="showHelpModal = false"
|
||||
class="absolute top-2 right-2 bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded text-sm z-10"
|
||||
>
|
||||
✖
|
||||
</button>
|
||||
<iframe
|
||||
src="/panduan/Panduan-Scraper.pdf"
|
||||
type="application/pdf"
|
||||
width="100%"
|
||||
height="100%"
|
||||
class="rounded-b-lg"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -80,7 +238,9 @@
|
|||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { LayoutDashboard, Bot, Settings, LogOut } from "lucide-vue-next"
|
||||
import { LayoutDashboard, Bot, HelpCircle, Settings, LogOut } from "lucide-vue-next"
|
||||
|
||||
const successModal = ref(false);
|
||||
|
||||
const loading = ref(false)
|
||||
const statusMessage = ref("")
|
||||
|
@ -90,6 +250,33 @@
|
|||
const showModal = ref(false)
|
||||
const cleanedFile = ref(null)
|
||||
const chartOptions = ["HKI", "Penelitian", "Pengabdian", "Scopus", "Scholar"]
|
||||
const showAffiliationModal = ref(false) // Penelitian
|
||||
const tempAffiliationId = ref(null) // Penelitian
|
||||
const affiliationId = ref(null) // Penelitian
|
||||
|
||||
const showAffiliationModalPengabdian = ref(false)
|
||||
const tempAffiliationIdPengabdian = ref(null)
|
||||
const affiliationIdPengabdian = ref(null)
|
||||
|
||||
const showAffiliationModalHki = ref(false)
|
||||
const tempAffiliationIdHki = ref(null)
|
||||
const affiliationIdHki = ref(null)
|
||||
|
||||
const showAffiliationModalScopus = ref(false)
|
||||
const tempAffiliationIdScopus = ref(null)
|
||||
const affiliationIdScopus = ref(null)
|
||||
|
||||
const showModalGoogleScholar = ref(false)
|
||||
const tempLinkOneScholar = ref(null)
|
||||
const linkOneScholar = ref(null)
|
||||
|
||||
const showHelpModal = ref(false);
|
||||
|
||||
function openHelpModal() {
|
||||
showHelpModal.value = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const selectChart = (option) => {
|
||||
selectedChart.value = option
|
||||
|
@ -117,18 +304,30 @@
|
|||
statusMessage.value = ""
|
||||
errorMessage.value = ""
|
||||
try {
|
||||
await axios.get(`http://localhost:8000/scrape/scopus`)
|
||||
statusMessage.value = "✅ Scraping selesai!"
|
||||
await fetchLatestData()
|
||||
await axios.get(`http://localhost:8000/scrape/scopus`, {
|
||||
params: { affiliation_id: affiliationIdScopus.value }
|
||||
})
|
||||
statusMessage.value = "✅ Scraping Scopus selesai!"
|
||||
await fetchLatestData('scopus')
|
||||
if (latestFile.value) {
|
||||
triggerDownload(latestFile.value)
|
||||
}
|
||||
} catch (err) {
|
||||
errorMessage.value = "❌ Gagal scraping: " + err.message
|
||||
errorMessage.value = "❌ Gagal scraping Scopus: " + err.message
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
const confirmScrapeScopus = async () => {
|
||||
if (!tempAffiliationIdScopus.value || isNaN(tempAffiliationIdScopus.value)) {
|
||||
alert("❗ Mohon masukkan Affiliation ID yang valid.")
|
||||
return
|
||||
}
|
||||
affiliationIdScopus.value = tempAffiliationIdScopus.value
|
||||
showAffiliationModalScopus.value = false
|
||||
await startScrapeScopus()
|
||||
}
|
||||
|
||||
|
||||
const startScrapeGoogleScholar = async () => {
|
||||
loading.value = true
|
||||
|
@ -150,14 +349,51 @@
|
|||
}
|
||||
}
|
||||
|
||||
const startScrapeOneGoogleScholar = async () => {
|
||||
loading.value = true
|
||||
statusMessage.value = ""
|
||||
errorMessage.value = ""
|
||||
try {
|
||||
const response = await axios.get(`http://localhost:8000/scrape/onescholar`, {
|
||||
params: { link: linkOneScholar.value }
|
||||
})
|
||||
statusMessage.value = response.data.message || "✅ Scraping One Scholar selesai!"
|
||||
await fetchLatestData()
|
||||
if (latestFile.value) {
|
||||
triggerDownload(latestFile.value)
|
||||
}
|
||||
} catch (err) {
|
||||
errorMessage.value = "❌ Gagal scraping One Scholar: " + (err.response?.data?.error || err.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
const confirmScrapeOneScholar = async () => {
|
||||
if (
|
||||
!tempLinkOneScholar.value ||
|
||||
!tempLinkOneScholar.value.startsWith("https://scholar.google.com/citations")
|
||||
) {
|
||||
alert("❗ Mohon masukkan link Google Scholar dosen yang valid.")
|
||||
return
|
||||
}
|
||||
|
||||
linkOneScholar.value = tempLinkOneScholar.value
|
||||
showModalGoogleScholar.value = false
|
||||
await startScrapeOneGoogleScholar()
|
||||
}
|
||||
|
||||
|
||||
|
||||
const startScrapeHki = async () => {
|
||||
loading.value = true
|
||||
statusMessage.value = ""
|
||||
errorMessage.value = ""
|
||||
try {
|
||||
await axios.get(`http://localhost:8000/scrape/hki`)
|
||||
await axios.get(`http://localhost:8000/scrape/hki`, {
|
||||
params: { affiliation_id: affiliationIdHki.value }
|
||||
})
|
||||
statusMessage.value = "✅ Scraping HKI selesai!"
|
||||
await fetchLatestData()
|
||||
await fetchLatestData('hki')
|
||||
if (latestFile.value) {
|
||||
triggerDownload(latestFile.value)
|
||||
}
|
||||
|
@ -167,13 +403,26 @@
|
|||
loading.value = false
|
||||
}
|
||||
}
|
||||
const confirmScrapeHki = async () => {
|
||||
if (!tempAffiliationIdHki.value || isNaN(tempAffiliationIdHki.value)) {
|
||||
alert("❗ Mohon masukkan Affiliation ID yang valid.")
|
||||
return
|
||||
}
|
||||
affiliationIdHki.value = tempAffiliationIdHki.value
|
||||
showAffiliationModalHki.value = false
|
||||
await startScrapeHki()
|
||||
}
|
||||
|
||||
|
||||
const startScrapePenelitian = async () => {
|
||||
loading.value = true
|
||||
statusMessage.value = ""
|
||||
errorMessage.value = ""
|
||||
console.log("🔍 ID yang dikirim:", affiliationId.value) // Tambahkan ini
|
||||
try {
|
||||
await axios.get(`http://localhost:8000/scrape/penelitian`)
|
||||
await axios.get(`http://localhost:8000/scrape/penelitian`, {
|
||||
params: { affiliation_id: affiliationId.value }
|
||||
})
|
||||
statusMessage.value = "✅ Scraping Penelitian selesai!"
|
||||
await fetchLatestData()
|
||||
if (latestFile.value) {
|
||||
|
@ -185,13 +434,24 @@
|
|||
loading.value = false
|
||||
}
|
||||
}
|
||||
const confirmScrapePenelitian = async () => {
|
||||
if (!tempAffiliationId.value || isNaN(tempAffiliationId.value)) {
|
||||
alert("❗ Mohon masukkan Affiliation ID yang valid.")
|
||||
return
|
||||
}
|
||||
affiliationId.value = tempAffiliationId.value
|
||||
showAffiliationModal.value = false
|
||||
await startScrapePenelitian()
|
||||
}
|
||||
|
||||
const startScrapePengabdian = async () => {
|
||||
loading.value = true
|
||||
statusMessage.value = ""
|
||||
errorMessage.value = ""
|
||||
try {
|
||||
await axios.get(`http://localhost:8000/scrape/pengabdian`) // Ganti dengan endpoint scraping Pengabdian
|
||||
await axios.get(`http://localhost:8000/scrape/pengabdian`, {
|
||||
params: { affiliation_id: affiliationIdPengabdian.value }
|
||||
})
|
||||
statusMessage.value = "✅ Scraping Pengabdian selesai!"
|
||||
await fetchLatestData('pengabdian')
|
||||
if (latestFile.value) {
|
||||
|
@ -203,6 +463,16 @@
|
|||
loading.value = false
|
||||
}
|
||||
}
|
||||
const confirmScrapePengabdian = async () => {
|
||||
if (!tempAffiliationIdPengabdian.value || isNaN(tempAffiliationIdPengabdian.value)) {
|
||||
alert("❗ Mohon masukkan Affiliation ID yang valid.")
|
||||
return
|
||||
}
|
||||
affiliationIdPengabdian.value = tempAffiliationIdPengabdian.value
|
||||
showAffiliationModalPengabdian.value = false
|
||||
await startScrapePengabdian()
|
||||
}
|
||||
|
||||
|
||||
const fetchLatestData = async () => {
|
||||
try {
|
||||
|
@ -270,7 +540,7 @@
|
|||
}
|
||||
|
||||
const submitCleanedFile = async (chartTypeRaw) => {
|
||||
const chartType = chartTypeRaw.toLowerCase()
|
||||
const chartType = chartTypeRaw.toLowerCase();
|
||||
if (!cleanedFile.value) return;
|
||||
|
||||
const formData = new FormData();
|
||||
|
@ -281,6 +551,9 @@
|
|||
statusMessage.value = `✅ File berhasil dikirim ke grafik ${chartTypeRaw}`;
|
||||
showModal.value = false;
|
||||
cleanedFile.value = null;
|
||||
|
||||
// Munculkan modal sukses
|
||||
successModal.value = true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
errorMessage.value = `❌ Gagal mengirim ke grafik ${chartTypeRaw}: ` + (err.response?.data?.error || err.message);
|
||||
|
@ -289,21 +562,18 @@
|
|||
|
||||
|
||||
|
||||
|
||||
onMounted(fetchLatestData)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.font-pixel {
|
||||
font-family: 'Press Start 2P', cursive;
|
||||
}
|
||||
.pixel-border {
|
||||
image-rendering: pixelated;
|
||||
position: relative;
|
||||
border-width: 4px;
|
||||
border-style: solid;
|
||||
border-radius: 8px;
|
||||
box-shadow:
|
||||
inset -2px -2px 0px rgba(0, 0, 0, 0.8),
|
||||
inset 2px 2px 0px rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.1);
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -13,6 +13,7 @@ export default {
|
|||
'login-bg': "url('/bgta_n.png')",
|
||||
'dashboard-bg': "url('/bgpixel3.png')",
|
||||
'update-bg': "url('/update_bg.png')",
|
||||
'whites-bg': "url('/whitebg1.png')",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
49
main.py
49
main.py
|
@ -4,8 +4,9 @@ from fastapi.responses import FileResponse, JSONResponse
|
|||
from fastapi import FastAPI, File, UploadFile, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.responses import StreamingResponse
|
||||
from fastapi import Query, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict
|
||||
from typing import List, Dict, Optional
|
||||
import subprocess
|
||||
import glob
|
||||
import os
|
||||
|
@ -29,7 +30,7 @@ app.add_middleware(
|
|||
)
|
||||
|
||||
DATA_FOLDER = "D:\\lecturertask"
|
||||
BASE_FILENAME = "data_scopus"
|
||||
# BASE_FILENAME = "data_scopus"
|
||||
FILE_EXT = ".xlsx"
|
||||
|
||||
BASE_DIR = "D:/lecturertask/download_files_scraper"
|
||||
|
@ -126,7 +127,9 @@ def download_file(file_type: str):
|
|||
def get_cleaned_file(folder: str, filename: str):
|
||||
file_path = os.path.join(BASE_DIR, folder, filename)
|
||||
if os.path.exists(file_path):
|
||||
return FileResponse(file_path, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
response = FileResponse(file_path, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
response.headers["Cache-Control"] = "no-store"
|
||||
return response
|
||||
return {"error": "File not found"}
|
||||
|
||||
@app.get("/list-files/{category}")
|
||||
|
@ -244,47 +247,65 @@ async def upload_excel(file: UploadFile = File(...)):
|
|||
def scrape_scholar():
|
||||
return scrape_data("scraper_scholar.py", "Scraping Scholar selesai.")
|
||||
|
||||
@app.get("/scrape/onescholar")
|
||||
def scrape_one_scholar(link: str):
|
||||
return scrape_data("scraper_onescholar.py", "Scraping One Scholar selesai.", extra_args=[link])
|
||||
|
||||
|
||||
@app.get("/scholar/download")
|
||||
def download_latest_scholar():
|
||||
return download_file("data_scholar*.xlsx")
|
||||
|
||||
@app.get("/scrape/scopus")
|
||||
def scrape_scopus():
|
||||
return scrape_data("scraper_scopus.py", "Scraping Scopus selesai.")
|
||||
def scrape_scopus(affiliation_id: Optional[int] = Query(None)):
|
||||
if not affiliation_id:
|
||||
return JSONResponse(content={"error": "affiliation_id kosong"}, status_code=400)
|
||||
return scrape_data("scraper_scopus.py", "Scraping Scopus selesai.", extra_args=[str(affiliation_id)])
|
||||
|
||||
@app.get("/scopus/download")
|
||||
def download_latest_file():
|
||||
return download_file(f"{BASE_FILENAME}*{FILE_EXT}")
|
||||
|
||||
@app.get("/scrape/pengabdian")
|
||||
def scrape_pengabdian():
|
||||
return scrape_data("scraper_pengabdian.py", "Scraping Pengabdian selesai.")
|
||||
def scrape_pengabdian(affiliation_id: Optional[int] = Query(None)):
|
||||
if not affiliation_id:
|
||||
return JSONResponse(content={"error": "affiliation_id kosong"}, status_code=400)
|
||||
return scrape_data("scraper_pengabdian.py", "Scraping Pengabdian selesai.", extra_args=[str(affiliation_id)])
|
||||
|
||||
@app.get("/pengabdian/download")
|
||||
def download_latest_pengabdian():
|
||||
return download_file("data_pengabdian*.xlsx")
|
||||
|
||||
@app.get("/scrape/hki")
|
||||
def scrape_hki():
|
||||
return scrape_data("scraper_HKI.py", "Scraping HKI selesai.")
|
||||
def scrape_hki(affiliation_id: Optional[int] = Query(None)):
|
||||
if not affiliation_id:
|
||||
return JSONResponse(content={"error": "affiliation_id kosong"}, status_code=400)
|
||||
return scrape_data("scraper_HKI.py", "Scraping HKI selesai.", extra_args=[str(affiliation_id)])
|
||||
|
||||
@app.get("/hki/download")
|
||||
def download_latest_hki():
|
||||
return download_file("data_hki*.xlsx")
|
||||
|
||||
@app.get("/scrape/penelitian")
|
||||
def scrape_penelitian():
|
||||
return scrape_data("scraper_penelitian.py", "Scraping Penelitian selesai.")
|
||||
def scrape_penelitian(affiliation_id: Optional[int] = Query(None)):
|
||||
print("📩 Affiliation ID diterima di FastAPI:", affiliation_id)
|
||||
if not affiliation_id:
|
||||
return JSONResponse(content={"error": "affiliation_id kosong"}, status_code=400)
|
||||
return scrape_data("scraper_penelitian.py", "Scraping Penelitian selesai.", extra_args=[str(affiliation_id)])
|
||||
|
||||
@app.get("/penelitian/download")
|
||||
def download_latest_penelitian():
|
||||
return download_file("data_penelitian*.xlsx")
|
||||
|
||||
|
||||
# Generic function to scrape data
|
||||
def scrape_data(script_name: str, success_message: str):
|
||||
def scrape_data(script_name: str, success_message: str, extra_args: list = None):
|
||||
try:
|
||||
args = ["python", f"D:\\lecturertask\\scraper\\{script_name}"]
|
||||
if extra_args:
|
||||
args.extend(extra_args)
|
||||
|
||||
result = subprocess.run(
|
||||
["python", f"D:\\lecturertask\\scraper\\{script_name}"],
|
||||
args,
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import time
|
||||
import sys
|
||||
import pandas as pd
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
|
@ -23,6 +24,12 @@ chrome_options.add_argument("--window-size=1920x1080")
|
|||
service = Service(chrome_driver_path)
|
||||
browser = webdriver.Chrome(service=service, options=chrome_options)
|
||||
|
||||
# --- BACA AFFILIATION DARI ARGUMEN ---
|
||||
if len(sys.argv) > 1:
|
||||
affiliation_id = sys.argv[1]
|
||||
else:
|
||||
affiliation_id = "447"
|
||||
|
||||
all_hki = []
|
||||
|
||||
try:
|
||||
|
@ -42,7 +49,7 @@ try:
|
|||
else:
|
||||
print("\n[WARNING] Belum login setelah waktu tunggu, lanjut scraping saja...")
|
||||
|
||||
base_url = "https://sinta.kemdikbud.go.id/affiliations/profile/447/?view=iprs"
|
||||
base_url = f"https://sinta.kemdikbud.go.id/affiliations/profile/{affiliation_id}/?view=iprs"
|
||||
page = 1
|
||||
while True:
|
||||
print(f"\nScraping halaman {page}...")
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
# === scraper_onescholar.py ===
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import pandas as pd
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
# Validasi argumen
|
||||
if len(sys.argv) < 2:
|
||||
raise ValueError("Link profil Google Scholar harus disediakan sebagai argumen.")
|
||||
|
||||
scholar_link = sys.argv[1]
|
||||
print(f"[INFO] Link Google Scholar: {scholar_link}")
|
||||
|
||||
# Konfigurasi browser
|
||||
CHROME_DRIVER_PATH = "D:\\lecturertask\\chromedriver.exe"
|
||||
chrome_options = Options()
|
||||
chrome_options.add_argument("--start-maximized")
|
||||
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
|
||||
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, seperti Gecko) Chrome/110.0.0.0 Safari/537.36")
|
||||
|
||||
service = Service(CHROME_DRIVER_PATH)
|
||||
browser = webdriver.Chrome(service=service, options=chrome_options)
|
||||
|
||||
hasil_scraping = []
|
||||
|
||||
browser.get("https://scholar.google.com/")
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def scrape_teks(xpath, default="-"):
|
||||
try:
|
||||
return WebDriverWait(browser, 5).until(
|
||||
EC.presence_of_element_located((By.XPATH, xpath))
|
||||
).text.strip()
|
||||
except:
|
||||
return default
|
||||
|
||||
|
||||
def klik_tampilkan_lagi():
|
||||
while True:
|
||||
try:
|
||||
tombol = WebDriverWait(browser, 2).until(
|
||||
EC.element_to_be_clickable((By.ID, "gsc_bpf_more"))
|
||||
)
|
||||
tombol.click()
|
||||
print("Memuat lebih banyak...")
|
||||
time.sleep(random.uniform(1.2, 2))
|
||||
except:
|
||||
break
|
||||
|
||||
try:
|
||||
print(f"\nMembuka profil: {scholar_link}")
|
||||
browser.get(scholar_link)
|
||||
WebDriverWait(browser, 6).until(EC.presence_of_element_located((By.ID, "gsc_a_t")))
|
||||
klik_tampilkan_lagi()
|
||||
|
||||
elements = browser.find_elements(By.CSS_SELECTOR, "tr.gsc_a_tr td.gsc_a_t a.gsc_a_at")
|
||||
penelitian_links = [el.get_attribute("href") for el in elements]
|
||||
print(f"Total publikasi: {len(penelitian_links)}")
|
||||
|
||||
for link in penelitian_links:
|
||||
try:
|
||||
browser.get(link)
|
||||
WebDriverWait(browser, 5).until(EC.presence_of_element_located((By.ID, "gsc_oci_title")))
|
||||
|
||||
judul = scrape_teks("//div[@id='gsc_oci_title']")
|
||||
pengarang = scrape_teks("//div[@class='gsc_oci_field' and text()='Pengarang']/following-sibling::div")
|
||||
tahun = scrape_teks("//div[@class='gsc_oci_field' and text()='Tanggal terbit']/following-sibling::div")
|
||||
jurnal = scrape_teks("//div[@class='gsc_oci_field' and text()='Jurnal']/following-sibling::div")
|
||||
jilid = scrape_teks("//div[@class='gsc_oci_field' and text()='Jilid']/following-sibling::div")
|
||||
terbitan = scrape_teks("//div[@class='gsc_oci_field' and text()='Terbitan']/following-sibling::div")
|
||||
halaman = scrape_teks("//div[@class='gsc_oci_field' and text()='Halaman']/following-sibling::div")
|
||||
penerbit = scrape_teks("//div[@class='gsc_oci_field' and text()='Penerbit']/following-sibling::div")
|
||||
kutipan = scrape_teks("//div[@class='gsc_oci_field' and text()='Total kutipan']/following-sibling::div")
|
||||
|
||||
hasil_scraping.append([
|
||||
scholar_link, judul, pengarang, tahun, jurnal,
|
||||
jilid, terbitan, halaman, penerbit, kutipan
|
||||
])
|
||||
print(f"✓ {judul}")
|
||||
except Exception as e:
|
||||
print(f"Gagal ambil detail publikasi: {e}")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nScraping dihentikan oleh pengguna.")
|
||||
|
||||
finally:
|
||||
folder_path = "D:\\lecturertask"
|
||||
os.makedirs(folder_path, exist_ok=True)
|
||||
base_filename = "data_scholar"
|
||||
file_ext = ".xlsx"
|
||||
|
||||
existing_files = [f for f in os.listdir(folder_path) if f.startswith(base_filename) and f.endswith(file_ext)]
|
||||
max_num = max([int(f.replace(base_filename, "").replace(file_ext, "")) for f in existing_files if f.replace(base_filename, "").replace(file_ext, "").isdigit()] + [0])
|
||||
new_filename = f"{base_filename}{max_num + 1}{file_ext}"
|
||||
save_path = os.path.join(folder_path, new_filename)
|
||||
|
||||
if hasil_scraping:
|
||||
df = pd.DataFrame(hasil_scraping, columns=[
|
||||
"Link", "Judul", "Pengarang", "Tahun", "Jurnal",
|
||||
"Jilid", "Terbitan", "Halaman", "Penerbit", "Total Kutipan"
|
||||
])
|
||||
df.to_excel(save_path, index=False, engine="openpyxl")
|
||||
print(f"\nScraping selesai/dihentikan! Total {len(hasil_scraping)} data disimpan di '{save_path}'")
|
||||
else:
|
||||
print("\nScraping gagal/tidak mendapatkan data. File tidak dibuat.")
|
||||
|
||||
browser.quit()
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import time
|
||||
import sys
|
||||
import pandas as pd
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
|
@ -24,6 +25,10 @@ service = Service(chrome_driver_path)
|
|||
browser = webdriver.Chrome(service=service, options=chrome_options)
|
||||
|
||||
all_research = []
|
||||
if len(sys.argv) < 2:
|
||||
raise ValueError("Affiliation ID harus disediakan sebagai argumen.")
|
||||
affiliation_id = sys.argv[1]
|
||||
|
||||
|
||||
try:
|
||||
browser.get("https://sinta.kemdikbud.go.id/logins")
|
||||
|
@ -42,7 +47,8 @@ try:
|
|||
else:
|
||||
print("\n[WARNING] Belum login setelah 3 menit, lanjut scraping saja...")
|
||||
|
||||
browser.get("https://sinta.kemdikbud.go.id/affiliations/profile/447/?view=researches")
|
||||
url = f"https://sinta.kemdikbud.go.id/affiliations/profile/{affiliation_id}/?view=researches"
|
||||
browser.get(url)
|
||||
time.sleep(3)
|
||||
|
||||
page = 1
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import time
|
||||
import sys
|
||||
import pandas as pd
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
|
@ -24,6 +25,13 @@ chrome_options.add_argument("--window-size=1920x1080")
|
|||
service = Service(chrome_driver_path)
|
||||
browser = webdriver.Chrome(service=service, options=chrome_options)
|
||||
|
||||
# --- BACA AFFILIATION DARI ARGUMEN ---
|
||||
if len(sys.argv) < 2:
|
||||
raise ValueError("Affiliation ID harus disediakan sebagai argumen.")
|
||||
|
||||
affiliation_id = sys.argv[1]
|
||||
print(f"[INFO] Menggunakan Affiliation ID: {affiliation_id}")
|
||||
|
||||
# --- LOGIN ---
|
||||
browser.get("https://sinta.kemdikbud.go.id/logins")
|
||||
browser.implicitly_wait(10)
|
||||
|
@ -42,10 +50,12 @@ else:
|
|||
print("\n[WARNING] Belum login setelah 3 menit, lanjut scraping saja...")
|
||||
|
||||
# --- SCRAPING PENGABDIAN ---
|
||||
browser.get("https://sinta.kemdikbud.go.id/affiliations/profile/447/?view=services")
|
||||
browser.get(f"https://sinta.kemdikbud.go.id/affiliations/profile/{affiliation_id}/?view=services")
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
all_services = []
|
||||
|
||||
page = 1
|
||||
|
||||
while True:
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
import sys
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import pandas as pd
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
import time, random
|
||||
import pandas as pd
|
||||
import os
|
||||
|
||||
# Ambil Affiliation ID dari argumen
|
||||
if len(sys.argv) < 2:
|
||||
raise ValueError("Affiliation ID harus disediakan sebagai argumen.")
|
||||
affiliation_id = sys.argv[1]
|
||||
print(f"[INFO] Menggunakan Affiliation ID: {affiliation_id}")
|
||||
|
||||
# Konfigurasi Chrome
|
||||
chrome_driver_path = "D:\\lecturertask\\chromedriver.exe"
|
||||
chrome_options = Options()
|
||||
chrome_options.add_argument("--start-maximized")
|
||||
|
@ -15,87 +24,88 @@ chrome_options.add_argument("--no-sandbox")
|
|||
service = Service(chrome_driver_path)
|
||||
browser = webdriver.Chrome(service=service, options=chrome_options)
|
||||
|
||||
# Login manual
|
||||
browser.get("https://sinta.kemdikbud.go.id/logins")
|
||||
print("[INFO] Silakan login di browser yang terbuka...")
|
||||
|
||||
login_wait_timeout = 180
|
||||
elapsed = 0
|
||||
|
||||
while elapsed < login_wait_timeout:
|
||||
if "profile" in browser.current_url:
|
||||
print("\n[SUCCESS] Login sukses, lanjut scraping...")
|
||||
break
|
||||
else:
|
||||
print(f"[WAIT] Menunggu login... ({login_wait_timeout - elapsed} detik tersisa)", end='\r')
|
||||
time.sleep(2)
|
||||
elapsed += 2
|
||||
print(f"[WAIT] Menunggu login... ({login_wait_timeout - elapsed} detik tersisa)", end='\r')
|
||||
time.sleep(2)
|
||||
elapsed += 2
|
||||
else:
|
||||
print("\n[WARNING] Belum login setelah 3 menit, lanjut scraping saja...")
|
||||
|
||||
base_url = "https://sinta.kemdikbud.go.id/affiliations/profile/447?page={}&view=scopus"
|
||||
|
||||
# Scraping Scopus
|
||||
base_url = f"https://sinta.kemdikbud.go.id/affiliations/profile/{affiliation_id}?page={{}}&view=scopus"
|
||||
all_scopus = []
|
||||
page = 1
|
||||
|
||||
while True:
|
||||
browser.get(base_url.format(page))
|
||||
time.sleep(random.uniform(3, 5))
|
||||
print(f"Scraping halaman {page}...")
|
||||
try:
|
||||
while True:
|
||||
browser.get(base_url.format(page))
|
||||
time.sleep(random.uniform(3, 5))
|
||||
print(f"Scraping halaman {page}...")
|
||||
|
||||
items = browser.find_elements(By.CSS_SELECTOR, "div.ar-list-item.mb-5")
|
||||
if not items:
|
||||
print("Tidak ada data lagi. Selesai.")
|
||||
break
|
||||
items = browser.find_elements(By.CSS_SELECTOR, "div.ar-list-item.mb-5")
|
||||
if not items:
|
||||
print("Tidak ada data lagi. Selesai.")
|
||||
break
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
title = item.find_element(By.CSS_SELECTOR, "div.ar-title a").text.strip()
|
||||
quartile = item.find_element(By.CSS_SELECTOR, "a.ar-quartile").text.strip()
|
||||
journal = item.find_element(By.CSS_SELECTOR, "a.ar-pub").text.strip()
|
||||
for item in items:
|
||||
try:
|
||||
title = item.find_element(By.CSS_SELECTOR, "div.ar-title a").text.strip()
|
||||
quartile = item.find_element(By.CSS_SELECTOR, "a.ar-quartile").text.strip()
|
||||
journal = item.find_element(By.CSS_SELECTOR, "a.ar-pub").text.strip()
|
||||
|
||||
metas = item.find_elements(By.CSS_SELECTOR, "div.ar-meta")
|
||||
creator = "-"
|
||||
for meta in metas:
|
||||
anchor_tags = meta.find_elements(By.TAG_NAME, "a")
|
||||
for a in anchor_tags:
|
||||
if "Creator :" in a.text:
|
||||
creator = a.text.replace("Creator :", "").strip()
|
||||
break
|
||||
if creator != "-":
|
||||
break
|
||||
metas = item.find_elements(By.CSS_SELECTOR, "div.ar-meta")
|
||||
creator = "-"
|
||||
for meta in metas:
|
||||
anchor_tags = meta.find_elements(By.TAG_NAME, "a")
|
||||
for a in anchor_tags:
|
||||
if "Creator :" in a.text:
|
||||
creator = a.text.replace("Creator :", "").strip()
|
||||
break
|
||||
|
||||
year = item.find_element(By.CSS_SELECTOR, "a.ar-year").text.strip()
|
||||
cited = item.find_element(By.CSS_SELECTOR, "a.ar-cited").text.strip()
|
||||
year = item.find_element(By.CSS_SELECTOR, "a.ar-year").text.strip()
|
||||
cited = item.find_element(By.CSS_SELECTOR, "a.ar-cited").text.strip()
|
||||
|
||||
all_scopus.append([title, quartile, journal, creator, year, cited])
|
||||
print(f"{title} | {creator} ({year})")
|
||||
except Exception as e:
|
||||
print(f"Error mengambil data item: {e}")
|
||||
all_scopus.append([title, quartile, journal, creator, year, cited])
|
||||
print(f"{title} | {creator} ({year})")
|
||||
except Exception as e:
|
||||
print(f"Error mengambil data item: {e}")
|
||||
|
||||
page += 1
|
||||
time.sleep(random.uniform(2.5, 5))
|
||||
page += 1
|
||||
time.sleep(random.uniform(2.5, 5))
|
||||
|
||||
folder_path = "D:\\lecturertask"
|
||||
base_filename = "data_scopus"
|
||||
file_ext = ".xlsx"
|
||||
except KeyboardInterrupt:
|
||||
print("\n[INTERRUPTED] Scraping dihentikan oleh pengguna (Ctrl+C atau tutup Chrome).")
|
||||
|
||||
existing_files = [f for f in os.listdir(folder_path) if f.startswith(base_filename) and f.endswith(file_ext)]
|
||||
finally:
|
||||
print("\n[MENYIMPAN DATA] Menyimpan hasil sementara ke Excel...")
|
||||
|
||||
folder_path = "D:\\lecturertask"
|
||||
base_filename = "data_scopus"
|
||||
file_ext = ".xlsx"
|
||||
|
||||
existing_files = [f for f in os.listdir(folder_path) if f.startswith(base_filename) and f.endswith(file_ext)]
|
||||
max_num = max([int(f.replace(base_filename, "").replace(file_ext, "")) for f in existing_files if f.replace(base_filename, "").replace(file_ext, "").isdigit()] + [0])
|
||||
next_num = max_num + 1
|
||||
new_filename = f"{base_filename}{next_num}{file_ext}"
|
||||
save_path = os.path.join(folder_path, new_filename)
|
||||
|
||||
if all_scopus:
|
||||
df = pd.DataFrame(all_scopus, columns=["Judul", "Quartile", "Jurnal", "Creator", "Tahun", "Sitasi"])
|
||||
df.to_excel(save_path, index=False, engine="openpyxl")
|
||||
print(f"[SAVED] Data disimpan di '{save_path}' ({len(all_scopus)} item)")
|
||||
else:
|
||||
print("[INFO] Tidak ada data untuk disimpan.")
|
||||
|
||||
max_num = 0
|
||||
for filename in existing_files:
|
||||
try:
|
||||
num = int(filename.replace(base_filename, "").replace(file_ext, ""))
|
||||
if num > max_num:
|
||||
max_num = num
|
||||
browser.quit()
|
||||
except:
|
||||
pass
|
||||
|
||||
next_num = max_num + 1
|
||||
new_filename = f"{base_filename}{next_num}{file_ext}"
|
||||
save_path = os.path.join(folder_path, new_filename)
|
||||
|
||||
df = pd.DataFrame(all_scopus, columns=["Judul", "Quartile", "Jurnal", "Creator", "Tahun", "Sitasi"])
|
||||
df.to_excel(save_path, index=False, engine="openpyxl")
|
||||
print(f"\n[SUCCESS] Scraping selesai! {len(all_scopus)} data berhasil disimpan di '{save_path}'")
|
||||
|
||||
browser.quit()
|
||||
|
|
Loading…
Reference in New Issue