您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically parse Shopee reviews into JSON across pages with UI & dark mode
// ==UserScript== // @name 🦖 SREX – Shopee Review Extractor // @namespace http://tampermonkey.net/ // @version 1.5 // @description Automatically parse Shopee reviews into JSON across pages with UI & dark mode // @author Ryu-Sena // @match https://shopee.co.id/* // @grant GM_setClipboard // @grant GM_addStyle // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; const STORAGE_KEY = 'shopee_reviews_json'; let reviews = []; try { reviews = JSON.parse(sessionStorage.getItem(STORAGE_KEY)) || []; } catch (e) { reviews = []; } let autoMode = false; let stopFlag = false; GM_addStyle(` #review-parser-ui { position: fixed; top: 10%; right: 10px; width: 350px; max-height: 80%; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); font-family: sans-serif; display: none; flex-direction: column; z-index: 9999; } #review-parser-ui.dark { --bg: #1e1e1e; --fg: #ddd; --border: #333; } #review-parser-ui.light { --bg: #fff; --fg: #333; --border: #ccc; } #review-parser-ui header { padding: 8px; font-size: 1.1em; font-weight: bold; display: flex; justify-content: space-between; align-items: center; background: var(--border); } #review-parser-ui header button { background: transparent; border: none; color: var(--fg); cursor: pointer; font-size: 1em; } #review-parser-ui .controls { padding: 8px; display: flex; flex-wrap: wrap; gap: 4px; } #review-parser-ui .controls button { flex: 1 0 48%; padding: 6px; border: none; border-radius: 4px; cursor: pointer; background: var(--fg); color: var(--bg); font-weight: bold; } #review-parser-ui .status { padding: 4px 8px; font-size: 0.9em; text-align: center; } #review-parser-ui textarea { flex: 1; margin: 0 8px 8px; width: calc(100% - 16px); resize: vertical; font-family: monospace; font-size: 0.9em; background: var(--bg); color: var(--fg); border: 1px solid var(--border); } #review-parser-toggle { position: fixed; top: 50%; right: 10px; transform: translateY(-50%); background: #007bff; color: #fff; border: none; border-radius: 50%; width: 40px; height: 40px; cursor: pointer; font-size: 1.2em; z-index: 9999; } `); const ui = document.createElement('div'); ui.id = 'review-parser-ui'; ui.classList.add(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); document.body.appendChild(ui); ui.innerHTML = ` <header> <span>Review Parser</span> <button id="ui-close">✖️</button> </header> <div class="controls"> <button id="btn-parse">Parse</button> <button id="btn-clear">Clear</button> <button id="btn-copy">Copy</button> <button id="btn-start">Auto Start</button> <button id="btn-stop">Stop</button> </div> <div class="status" id="parser-status">Idle</div> <textarea id="json-output" rows="10" readonly placeholder="No data"></textarea> `; function toggleUI(show) { ui.style.display = show ? 'flex' : 'none'; } document.getElementById('ui-close').addEventListener('click', () => toggleUI(false)); const toggleBtn = document.createElement('button'); toggleBtn.id = 'review-parser-toggle'; toggleBtn.textContent = '➤'; document.body.appendChild(toggleBtn); toggleBtn.addEventListener('click', () => toggleUI(true)); function updateOutput() { const ta = document.getElementById('json-output'); ta.value = reviews.length ? JSON.stringify(reviews, null, 2) : ''; ta.placeholder = reviews.length ? '' : 'No data'; } function updateStatus(text) { document.getElementById('parser-status').textContent = text; } function parseReviews() { document.querySelectorAll('div.A7MThp').forEach(item => { if (item.dataset.parsed) return; const username = item.querySelector('a.InK5kS')?.textContent.trim() || ''; const date = item.querySelector('div.XYk98l')?.textContent.trim() || ''; const rating = item.querySelectorAll('svg.icon-rating-solid').length; const content = item.querySelector('div.YNedDV')?.textContent.trim() || ''; reviews.push({ username, date, rating, content }); item.dataset.parsed = 'true'; }); sessionStorage.setItem(STORAGE_KEY, JSON.stringify(reviews)); updateOutput(); } function clickPageButton(pageNum) { const buttons = Array.from(document.querySelectorAll('nav.shopee-page-controller button')); const pageButton = buttons.find(btn => btn.textContent.trim() === pageNum.toString()); if (pageButton) pageButton.click(); } async function autoParsePages(maxPages = 5) { stopFlag = false; updateStatus('Waiting 15s...'); await new Promise(res => setTimeout(res, 15000)); for (let i = 1; i <= maxPages && !stopFlag; i++) { updateStatus(`Parsing page ${i}...`); if (i !== 1) clickPageButton(i); await new Promise(res => setTimeout(res, 4000)); parseReviews(); await new Promise(res => setTimeout(res, 2000)); } updateStatus(stopFlag ? 'Stopped by user' : 'Finished auto parsing'); } updateOutput(); document.getElementById('btn-parse').addEventListener('click', parseReviews); document.getElementById('btn-clear').addEventListener('click', () => { reviews = []; sessionStorage.removeItem(STORAGE_KEY); document.querySelectorAll('div.A7MThp').forEach(item => delete item.dataset.parsed); updateOutput(); updateStatus('Cleared'); }); document.getElementById('btn-copy').addEventListener('click', () => { GM_setClipboard(document.getElementById('json-output').value); alert('JSON copied'); }); document.getElementById('btn-start').addEventListener('click', () => autoParsePages()); document.getElementById('btn-stop').addEventListener('click', () => { stopFlag = true; updateStatus('Stopping...'); }); window.addEventListener('keydown', e => { if (e.ctrlKey && e.key === '1') toggleUI(ui.style.display === 'none'); }); })();