// ==UserScript==
// 📕备份- 👉实用工具👉压缩包导出 备份预设,仅导出js请自行更新js数据
// @name 网页区域文本提取导出工具 V4.9(无日志)
// @namespace http://tampermonkey.net/
// @version 4.90
// @license MIT
// @description 抓取指定元素文字,过滤特定内容并导出为TXT - 支持源码内置预设
// @author 能用就行
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// @downloadURL https://update.greasyfork.icu/scripts/572884/%E7%BD%91%E9%A1%B5%E5%8C%BA%E5%9F%9F%E6%96%87%E6%9C%AC%E6%8F%90%E5%8F%96%E5%AF%BC%E5%87%BA%E5%B7%A5%E5%85%B7%20V49%EF%BC%88%E6%97%A0%E6%97%A5%E5%BF%97%EF%BC%89.user.js
// @updateURL https://update.greasyfork.icu/scripts/572884/%E7%BD%91%E9%A1%B5%E5%8C%BA%E5%9F%9F%E6%96%87%E6%9C%AC%E6%8F%90%E5%8F%96%E5%AF%BC%E5%87%BA%E5%B7%A5%E5%85%B7%20V49%EF%BC%88%E6%97%A0%E6%97%A5%E5%BF%97%EF%BC%89.meta.js
// ==/UserScript==
(function() {
'use strict';
// 🔹🔹🔹 【用户编辑区】预设网站配置 🔹🔹🔹
// 格式: '纯域名': { title: '显示名称', countSelector: '统计选择器(可留空)', targetSelector: '抓取选择器', blacklist: ['词1', '词2'] }
const SITE_PRESETS = {
'example.com': {
title: '示例',
countSelector: '1',
targetSelector: '1',
blacklist: ['1', '2']
},
// 在此下方继续添加您的预设网站...
// 'another-site.net': { title: '另一个站', countSelector: '', targetSelector: 'h2.title', blacklist: ['VIP', '付费'] }
};
// 🔹🔹🔹 预设配置结束(无需修改下方代码)🔹🔹🔹
const STORAGE_KEY_BL = 'scraper_blacklist_rules_v4';
const STORAGE_KEY_SEL = 'scraper_selector_rules_v4';
const STORAGE_KEY_INC = 'scraper_include_filter_rules_v4';
const STORAGE_KEY_INC_MATCH = 'scraper_include_match_rules_v4';
let CONFIG = {
filterKeyword: '',
exportFileName: 'scraped_data.txt',
blacklistRules: { common: [], sites: {} },
selectorRules: {},
includeFilterRules: { common: [], sites: {} },
includeMatchRules: { common: [], sites: {} }
};
function loadAllConfigs() {
try {
const bl = GM_getValue(STORAGE_KEY_BL, null);
if (bl && typeof bl === 'object') {
CONFIG.blacklistRules.common = Array.isArray(bl.common) ? bl.common : [];
CONFIG.blacklistRules.sites = (bl.sites && typeof bl.sites === 'object') ? bl.sites : {};
}
const sel = GM_getValue(STORAGE_KEY_SEL, null);
if (sel && typeof sel === 'object') {
for (const host in sel) {
if (typeof sel[host] === 'string') {
sel[host] = { title: host, countSelector: '', targetSelector: sel[host] };
}
}
CONFIG.selectorRules = sel;
}
const inc = GM_getValue(STORAGE_KEY_INC, null);
if (inc && typeof inc === 'object') {
CONFIG.includeFilterRules.common = Array.isArray(inc.common) ? inc.common : [];
CONFIG.includeFilterRules.sites = (inc.sites && typeof inc.sites === 'object') ? inc.sites : {};
}
const incMatch = GM_getValue(STORAGE_KEY_INC_MATCH, null);
if (incMatch && typeof incMatch === 'object') {
CONFIG.includeMatchRules.common = Array.isArray(incMatch.common) ? incMatch.common : [];
CONFIG.includeMatchRules.sites = (incMatch.sites && typeof incMatch.sites === 'object') ? incMatch.sites : {};
}
} catch (e) { }
}
function saveBlacklistConfig() {
try { GM_setValue(STORAGE_KEY_BL, CONFIG.blacklistRules); } catch(e) {}
}
function saveSelectorConfig() {
try { GM_setValue(STORAGE_KEY_SEL, CONFIG.selectorRules); } catch(e) {}
}
function saveIncludeFilterConfig() {
try { GM_setValue(STORAGE_KEY_INC, CONFIG.includeFilterRules); } catch(e) {}
}
function saveIncludeMatchConfig() {
try { GM_setValue(STORAGE_KEY_INC_MATCH, CONFIG.includeMatchRules); } catch(e) {}
}
function mergePresets() {
for (const [host, preset] of Object.entries(SITE_PRESETS)) {
if (!CONFIG.selectorRules[host]) {
CONFIG.selectorRules[host] = {
title: preset.title || host,
countSelector: preset.countSelector || '',
targetSelector: preset.targetSelector || ''
};
}
if (preset.blacklist && Array.isArray(preset.blacklist)) {
if (!CONFIG.blacklistRules.sites[host]) CONFIG.blacklistRules.sites[host] = [];
const existing = CONFIG.blacklistRules.sites[host];
preset.blacklist.forEach(kw => { if (!existing.includes(kw)) existing.push(kw); });
}
if (preset.includeFilter && Array.isArray(preset.includeFilter)) {
if (!CONFIG.includeFilterRules.sites[host]) CONFIG.includeFilterRules.sites[host] = [];
const existing = CONFIG.includeFilterRules.sites[host];
preset.includeFilter.forEach(kw => { if (!existing.includes(kw)) existing.push(kw); });
}
if (preset.includeMatch && Array.isArray(preset.includeMatch)) {
if (!CONFIG.includeMatchRules.sites[host]) CONFIG.includeMatchRules.sites[host] = [];
const existing = CONFIG.includeMatchRules.sites[host];
preset.includeMatch.forEach(kw => { if (!existing.includes(kw)) existing.push(kw); });
}
}
}
loadAllConfigs();
mergePresets();
function getSiteConfig() {
const host = window.location.hostname;
if (!CONFIG.selectorRules[host]) {
CONFIG.selectorRules[host] = { title: document.title || host, countSelector: '', targetSelector: '' };
}
return CONFIG.selectorRules[host];
}
function getSiteBlacklist() {
const host = window.location.hostname;
const siteKws = CONFIG.blacklistRules.sites[host] || [];
const commonKws = CONFIG.blacklistRules.common || [];
return [...new Set([...siteKws, ...commonKws])];
}
function getSiteIncludeFilter() {
const host = window.location.hostname;
const siteKws = CONFIG.includeFilterRules.sites[host] || [];
const commonKws = CONFIG.includeFilterRules.common || [];
return [...new Set([...siteKws, ...commonKws])];
}
let panelRef = null, toggleBtnRef = null;
let currentPickerTargetId = null, pickerMode = false, highlightEl = null, infoEl = null;
let selectorPanelRef = null, currentSelectorLevels = [], previewHighlights = [], scrollUpdateTimer = null;
function createControlPanel() {
const globalStyle = document.createElement('style');
globalStyle.id = 'scraper-global-style';
globalStyle.textContent = `#scraper-panel { position: fixed; top: 20px; right: 20px; background: linear-gradient(145deg, #1e293b 0%, #0f172a 100%); padding: 20px; border-radius: 16px; box-shadow: 0 25px 80px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.08), inset 0 1px 0 rgba(255,255,255,0.1); z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: white; min-width: 320px; max-width: 380px; max-height: calc(100vh - 40px); overflow-y: auto; overflow-x: hidden; } #scraper-panel.draggable { cursor: default; } #scraper-panel.dragging { opacity: 0.9; } #scraper-panel::-webkit-scrollbar { width: 6px; } #scraper-panel::-webkit-scrollbar-track { background: rgba(255,255,255,0.05); border-radius: 3px; } #scraper-panel::-webkit-scrollbar-thumb { background: linear-gradient(135deg, #a78bfa 0%, #f472b6 100%); border-radius: 3px; } #scraper-panel h3 { margin:0 0 16px 0; font-size:18px; font-weight:700; background:linear-gradient(135deg,#a78bfa 0%,#f472b6 100%); -webkit-background-clip:text; -webkit-text-fill-color:transparent; display:flex; align-items:center; gap:8px; cursor:move; user-select:none; } #scraper-panel h3::before { content:'🔍'; -webkit-text-fill-color:initial; } #scraper-panel .config-row { margin-bottom:14px; display:flex; align-items:center; gap:8px; } #scraper-panel .config-row label { font-size:13px; color:#cbd5e1; white-space:nowrap; min-width:70px; font-weight:500; flex-shrink:0; } #scraper-panel input[type="text"] { flex:1; padding:10px 14px; background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.1); border-radius:10px; font-size:13px; color:white; box-sizing:border-box; min-width:0; } #scraper-panel input[type="text"]:focus { outline:none; background:rgba(255,255,255,0.1); border-color:#a78bfa; box-shadow:0 0 0 4px rgba(167,139,250,0.15); } #scraper-panel input[type="text"]::placeholder { color:#64748b; } #scraper-panel .btn-row { display:flex; align-items:center; gap:8px; margin-bottom:10px; } #scraper-panel .collapse-btn { flex:1; background:linear-gradient(135deg,rgba(167,139,250,0.2) 0%,rgba(244,114,182,0.2) 100%); color:#e2e8f0; border:1px solid rgba(167,139,250,0.3); border-radius:8px; padding:8px 14px; font-size:12px; font-weight:500; cursor:pointer; display:flex; align-items:center; justify-content:space-between; } #scraper-panel .collapse-btn .arrow { transition:transform 0.3s; } #scraper-panel .collapse-btn.expanded .arrow { transform:rotate(180deg); } #scraper-panel .btn-blacklist { background:rgba(251,191,36,0.15); color:#fbbf24; border:1px solid rgba(251,191,36,0.3); border-radius:8px; padding:8px 14px; font-size:12px; font-weight:500; cursor:pointer; white-space:nowrap; } #scraper-panel .collapse-content { display:none; padding:12px; background:rgba(0,0,0,0.2); border-radius:10px; margin-bottom:14px; border:1px solid rgba(255,255,255,0.05); } #scraper-panel .collapse-content.show { display:block; } #scraper-panel .btn-picker { background:linear-gradient(135deg,#10b981 0%,#059669 100%); color:white; border:none; border-radius:6px; padding:8px 10px; font-size:14px; cursor:pointer; flex-shrink:0; } #scraper-panel .btn-picker:hover { transform:scale(1.1); box-shadow:0 4px 12px rgba(16,185,129,0.4); } #scraper-panel .btn-picker.active { background:linear-gradient(135deg,#f59e0b 0%,#d97706 100%); } #scraper-element-highlight { position:fixed; pointer-events:none; z-index:2147483646; border:2px solid #f59e0b; background:rgba(245,158,11,0.1); transition:all 0.1s ease; } #scraper-element-info { position:fixed; background:rgba(15,23,42,0.95); color:white; padding:8px 12px; border-radius:6px; font-size:12px; font-family:monospace; z-index:2147483647; pointer-events:none; box-shadow:0 4px 12px rgba(0,0,0,0.3); } #scraper-selector-panel { position:fixed; top:20px; right:20px; background:linear-gradient(145deg,#1e293b 0%,#0f172a 100%); padding:16px; border-radius:12px; box-shadow:0 20px 60px rgba(0,0,0,0.5); z-index:2147483647; color:white; min-width:340px; max-width:400px; border:1px solid rgba(255,255,255,0.1); } #scraper-selector-panel .panel-header { display:flex; align-items:center; gap:8px; margin-bottom:12px; padding-bottom:10px; border-bottom:1px solid rgba(255,255,255,0.1); cursor:move; user-select:none; } #scraper-selector-panel .btn-close { background:rgba(248,113,113,0.2); color:#f87171; border:none; border-radius:6px; width:28px; height:28px; cursor:pointer; display:flex; align-items:center; justify-content:center; } #scraper-selector-panel .rule-list { max-height:300px; overflow-y:auto; margin-bottom:12px; } #scraper-selector-panel .rule-item { display:flex; align-items:center; gap:8px; padding:8px; margin-bottom:4px; background:rgba(255,255,255,0.05); border-radius:6px; cursor:pointer; font-size:12px; font-family:monospace; } #scraper-selector-panel .rule-item.unchecked { opacity:0.4; text-decoration:line-through; } #scraper-selector-panel .rule-checkbox { width:16px; height:16px; border:2px solid rgba(255,255,255,0.3); border-radius:3px; display:flex; align-items:center; justify-content:center; } #scraper-selector-panel .rule-item:not(.unchecked) .rule-checkbox { background:linear-gradient(135deg,#10b981 0%,#059669 100%); border-color:transparent; } #scraper-selector-panel .rule-checkbox::after { content:'✓'; color:white; font-size:10px; } #scraper-selector-panel .rule-item.unchecked .rule-checkbox::after { display:none; } #scraper-selector-panel .rule-text { flex:1; word-break:break-all; } #scraper-selector-panel .panel-buttons { display:flex; gap:8px; } #scraper-selector-panel .btn-action { flex:1; padding:10px; border:none; border-radius:8px; cursor:pointer; } #scraper-selector-panel .btn-select { background:rgba(255,255,255,0.1); color:white; } #scraper-selector-panel .btn-create { background:linear-gradient(135deg,#10b981 0%,#059669 100%); color:white; } #scraper-panel .options-row { display:flex; align-items:center; gap:6px; margin-bottom:10px; } #scraper-panel .filter-toggle { background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.1); border-radius:8px; padding:6px 8px; font-size:11px; color:#cbd5e1; cursor:pointer; display:flex; align-items:center; gap:5px; } #scraper-panel .filter-toggle .badge { background:rgba(251,191,36,0.2); color:#fbbf24; padding:1px 5px; border-radius:4px; font-size:9px; } #scraper-panel .filter-content { display:none; margin-top:8px; margin-bottom:10px; } #scraper-panel .filter-content.show { display:block; } #scraper-panel .keywords-list { font-size:12px; color:#fbbf24; margin-bottom:10px; padding:12px; background:linear-gradient(135deg,rgba(251,191,36,0.1) 0%,rgba(245,158,11,0.1) 100%); border:1px solid rgba(251,191,36,0.2); border-radius:10px; display:none; } #scraper-panel .keywords-list.show { display:block; } #scraper-panel .blacklist-section { background:rgba(0,0,0,0.15); padding:10px; border-radius:8px; margin-bottom:10px; } #scraper-panel .section-title { font-weight:600; color:#fbbf24; font-size:12px; } #scraper-panel .section-info { font-size:10px; color:#fcd34d; opacity:0.8; } #scraper-panel .blacklist-tags { display:flex; flex-wrap:wrap; gap:4px; margin-bottom:8px; min-height:24px; } #scraper-panel .empty-hint { color:#64748b; font-size:11px; font-style:italic; } #scraper-panel .blacklist-add-row { display:flex; gap:6px; align-items:center; margin-top:8px; } #scraper-panel .blacklist-input { flex:1; padding:6px 10px; background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.1); border-radius:6px; font-size:11px; color:white; } #scraper-panel .blacklist-input:focus { outline:none; border-color:#fbbf24; } #scraper-panel .blacklist-input::placeholder { color:#64748b; } #scraper-panel .blacklist-add-btn { padding:6px 12px; background:linear-gradient(135deg,#fbbf24 0%,#f59e0b 100%); border:none; border-radius:6px; font-size:11px; font-weight:500; color:#0f172a; cursor:pointer; white-space:nowrap; } #scraper-panel .keyword-tag { display:inline-block; margin:3px 5px 3px 0; padding:4px 8px; background:rgba(251,191,36,0.15); border:1px solid rgba(251,191,36,0.3); border-radius:4px; font-size:11px; color:#fbbf24; cursor:pointer; } #scraper-panel .keyword-tag.deletable { position:relative; padding-right:20px; } #scraper-panel .keyword-tag.deletable::after { content:'×'; position:absolute; right:6px; top:50%; transform:translateY(-50%); font-size:14px; font-weight:bold; } #scraper-panel .btn-delete-site { background:linear-gradient(135deg,#ef4444 0%,#dc2626 100%); color:white; border:none; border-radius:6px; padding:4px 8px; font-size:11px; cursor:pointer; } #scraper-panel .other-site-header { display:flex; justify-content:space-between; align-items:center; padding:6px 10px; cursor:pointer; user-select:none; border-radius:6px; background:rgba(255,255,255,0.03); margin-top:8px; } #scraper-panel .other-site-header:hover { background:rgba(255,255,255,0.06); } #scraper-panel .expand-icon { transition:transform 0.3s; display:inline-block; font-size:10px; margin-right:6px; } #scraper-panel .expand-icon.rotated { transform:rotate(90deg); } #scraper-panel .toggle-item { display:flex; align-items:center; gap:5px; font-size:11px; color:#cbd5e1; cursor:pointer; padding:6px 8px; background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.1); border-radius:8px; } #scraper-panel .toggle-switch { position:relative; width:28px; height:16px; background:rgba(255,255,255,0.2); border-radius:8px; } #scraper-panel .toggle-switch::after { content:''; position:absolute; top:2px; left:2px; width:12px; height:12px; background:white; border-radius:50%; } #scraper-panel .toggle-item.active .toggle-switch { background:linear-gradient(135deg,#a78bfa 0%,#f472b6 100%); } #scraper-panel .toggle-item.active .toggle-switch::after { transform:translateX(12px); } #scraper-panel .btn-group { display:flex; gap:10px; margin-top:16px; } #scraper-panel .btn-group button { flex:1; padding:12px 16px; border:none; border-radius:10px; font-size:14px; font-weight:600; cursor:pointer; display:flex; align-items:center; justify-content:center; gap:6px; } #scraper-panel .btn-scrape { background:linear-gradient(135deg,#10b981 0%,#059669 100%); color:white; box-shadow:0 4px 15px rgba(16,185,129,0.3); } #scraper-panel .btn-export { background:linear-gradient(135deg,#3b82f6 0%,#2563eb 100%); color:white; box-shadow:0 4px 15px rgba(59,130,246,0.3); } #scraper-panel .btn-close { background:rgba(255,255,255,0.06); color:#cbd5e1; margin-top:12px; width:100%; padding:10px; border:1px solid rgba(255,255,255,0.1); border-radius:10px; font-size:13px; font-weight:500; cursor:pointer; } #scraper-panel .result-area { max-height:160px; overflow-y:auto; background:rgba(0,0,0,0.3); border-radius:10px; padding:12px; margin-top:12px; font-size:11px; font-family:monospace; } #scraper-panel .stats { font-size:13px; margin-top:12px; padding:14px 16px; background:linear-gradient(135deg,rgba(167,139,250,0.1) 0%,rgba(244,114,182,0.1) 100%); border:1px solid rgba(167,139,250,0.2); border-radius:12px; line-height:1.6; } #scraper-panel .stats-row { display:flex; justify-content:space-between; align-items:center; margin-bottom:8px; } #scraper-panel .stats-row:last-child { margin-bottom:0; } #scraper-panel .stats-main { display:flex; gap:20px; align-items:center; } #scraper-panel .stats-item { display:flex; align-items:center; gap:6px; } #scraper-panel .stats-label { opacity:0.9; font-weight:500; } #scraper-panel .stats-value { font-weight:700; font-size:15px; } #scraper-panel .stats-detail { margin-top:10px; padding:12px; background:rgba(0,0,0,0.2); border-radius:8px; display:none; border:1px solid rgba(255,255,255,0.05); } #scraper-panel .stats-detail.show { display:block; } #scraper-panel .stats-detail-row { display:flex; align-items:center; gap:10px; margin-bottom:6px; font-size:12px; padding:4px 0; } #scraper-panel .btn-stats-detail { background:linear-gradient(135deg,rgba(251,191,36,0.2) 0%,rgba(245,158,11,0.2) 100%); color:#fbbf24; border:1px solid rgba(251,191,36,0.3); border-radius:6px; padding:4px 12px; font-size:11px; font-weight:500; cursor:pointer; margin-left:8px; } #scraper-toggle { position:fixed; top:20px; right:20px; background:linear-gradient(135deg,#a78bfa 0%,#f472b6 100%); color:white; border:none; border-radius:50%; width:56px; height:56px; font-size:24px; cursor:move; box-shadow:0 8px 30px rgba(167,139,250,0.4); z-index:2147483646; user-select:none; } #scraper-toggle.dragging { cursor: grabbing; box-shadow:0 16px 50px rgba(167,139,250,0.6); transform:scale(1.05); } #scraper-panel .sel-config-item { display:flex; align-items:center; justify-content:space-between; padding:8px; background:rgba(255,255,255,0.04); border-radius:6px; margin-bottom:6px; font-size:11px; border:1px solid rgba(255,255,255,0.05); } #scraper-panel .sel-config-item:hover { background:rgba(255,255,255,0.08); } #scraper-panel .sel-title { color:#a78bfa; font-weight:500; max-width:100px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } #scraper-panel .sel-host { color:#94a3b8; flex:1; text-align:center; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin:0 4px; } #scraper-panel .sel-divider { color:#64748b; } #scraper-panel .bl-current-header { display:flex; align-items:center; gap:6px; margin-bottom: 6px; } #scraper-panel .bl-current-count { color:#fbbf24; font-weight:500; font-size:12px; white-space:nowrap; } #scraper-panel .bl-current-input { flex:1; padding:6px 10px; background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.1); border-radius:6px; font-size:11px; color:white; } #scraper-panel .bl-current-input:focus { outline:none; border-color:#fbbf24; } #scraper-panel .bl-add-btn { padding:6px 10px; background:linear-gradient(135deg,#fbbf24 0%,#f59e0b 100%); border:none; border-radius:6px; font-size:11px; font-weight:500; color:#0f172a; cursor:pointer; } #scraper-panel .bl-toggle { color:#94a3b8; font-size:11px; cursor:pointer; user-select:none; margin-left:4px; } #scraper-panel .bl-toggle:hover { color:#fbbf24; } #scraper-panel .bl-keywords { display:none; padding:6px 0 0 0; } #scraper-panel .bl-keywords.show { display:block; } #scraper-panel .bl-common-header { display:flex; align-items:center; gap:6px; padding:6px 0; cursor:pointer; user-select:none; } #scraper-panel .bl-common-header:hover { opacity:0.9; } #scraper-panel .bl-common-count { color:#fbbf24; font-size:11px; cursor: pointer; } #scraper-panel .bl-common-sep { color:#64748b; font-size:11px; } #scraper-panel .bl-other-link { color:#60a5fa; font-size:11px; cursor:pointer; } #scraper-panel .bl-other-link:hover { text-decoration:underline; } #scraper-panel .bl-common-content { display:none; padding:6px 0 0 0; } #scraper-panel .bl-common-content.show { display:block; } #scraper-panel .bl-other-list { display:none; padding:6px 0 0 0; } #scraper-panel .bl-other-list.show { display:block; } #scraper-panel .bl-other-item { display:flex; align-items:center; justify-content:space-between; padding:6px 8px; background:rgba(255,255,255,0.03); border-radius:6px; margin-bottom:4px; font-size:11px; } #scraper-panel .bl-other-item:hover { background:rgba(255,255,255,0.06); } #scraper-panel .bl-other-title { color:#60a5fa; font-weight:500; max-width:80px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } #scraper-panel .bl-other-host { color:#94a3b8; flex:1; text-align:center; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin:0 4px; } #scraper-panel .other-preset-item { background:rgba(255,255,255,0.03); border-radius:6px; margin-bottom:8px; overflow:hidden; } #scraper-panel .other-preset-header { display:flex; align-items:center; justify-content:space-between; padding:8px 10px; cursor:pointer; user-select:none; } #scraper-panel .other-preset-header:hover { background:rgba(255,255,255,0.06); } #scraper-panel .other-preset-title { color:#60a5fa; font-weight:500; max-width:100px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } #scraper-panel .other-preset-divider { color:#64748b; font-size:10px; margin:0 4px; } #scraper-panel .other-preset-host { color:#94a3b8; flex:1; text-align:center; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } #scraper-panel .other-preset-arrow { color:#94a3b8; font-size:10px; margin-left:8px; transition:transform 0.3s; } #scraper-panel .other-preset-content { display:none; padding:8px 10px; border-top:1px solid rgba(255,255,255,0.05); } #scraper-panel .other-preset-content.show { display:block; } #scraper-panel .other-preset-detail { display:flex; gap:6px; margin-bottom:4px; font-size:11px; } #scraper-panel .other-preset-detail:last-child { margin-bottom:0; } #scraper-panel .bl-add-row { display:flex; gap:6px; align-items:center; margin-top:6px; } #scraper-panel .blacklist-input { flex:1; padding:6px 10px; background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.1); border-radius:6px; font-size:11px; color:white; } #scraper-panel .blacklist-input:focus { outline:none; border-color:#fbbf24; } #scraper-panel .blacklist-input::placeholder { color:#64748b; } #scraper-panel .blacklist-add-btn { padding:6px 12px; background:linear-gradient(135deg,#fbbf24 0%,#f59e0b 100%); border:none; border-radius:6px; font-size:11px; font-weight:500; color:#0f172a; cursor:pointer; white-space:nowrap; } #scraper-panel .bl-add-btn { padding:6px 12px; background:linear-gradient(135deg,#fbbf24 0%,#f59e0b 100%); border:none; border-radius:6px; font-size:11px; font-weight:500; color:#0f172a; cursor:pointer; white-space:nowrap; } #scraper-panel .preset-row { display:flex; align-items:center; gap:8px; margin-bottom:10px; } #scraper-panel .preset-row label { font-size:12px; color:#cbd5e1; min-width:60px; } #scraper-panel .preset-stats { font-size:10px; color:#64748b; text-align:center; margin-top:6px; } @keyframes successPulse { 0%,100% { opacity:1; } 50% { opacity:0.8; } }`;
document.head.appendChild(globalStyle);
const siteCfg = getSiteConfig();
const panel = document.createElement('div');
panel.id = 'scraper-panel';
panel.style.display = 'none';
panelRef = panel;
panel.innerHTML = `
元素抓取工具
已预设: 0 个站点
等待抓取...
`;
document.body.appendChild(panel);
initPanelDrag(panel);
const toggleBtn = document.createElement('button');
toggleBtn.id = 'scraper-toggle';
toggleBtn.innerHTML = '🔍';
toggleBtn.title = '打开抓取工具';
toggleBtnRef = toggleBtn;
document.body.appendChild(toggleBtn);
document.getElementById('btn-scrape').addEventListener('click', scrapeElements);
document.getElementById('btn-export').addEventListener('click', exportToTXT);
document.getElementById('btn-close').addEventListener('click', hidePanel);
document.getElementById('btn-blacklist').addEventListener('click', toggleKeywordsList);
initToggleDrag(showPanel);
document.getElementById('btn-selector-toggle').addEventListener('click', function() {
const content = document.getElementById('selector-content');
const isExpanded = content.classList.contains('show');
if (!isExpanded) {
const bl = document.getElementById('keywords-list');
const blBtn = document.getElementById('btn-blacklist');
const ps = document.getElementById('preset-content');
const psBtn = document.getElementById('btn-preset-toggle');
if (bl?.classList.contains('show')) { bl.classList.remove('show'); if(blBtn) blBtn.textContent = '🚫 黑名单'; }
if (ps?.classList.contains('show')) { ps.classList.remove('show'); if(psBtn) psBtn.textContent = '🌐 站点预设'; }
}
content.classList.toggle('show');
this.classList.toggle('expanded');
if (content.classList.contains('show')) updateSelectorUI();
});
document.getElementById('btn-preset-toggle').addEventListener('click', function() {
const content = document.getElementById('preset-content');
const isExpanded = content.classList.contains('show');
if (!isExpanded) {
const bl = document.getElementById('keywords-list');
const blBtn = document.getElementById('btn-blacklist');
const sel = document.getElementById('selector-content');
const selBtn = document.getElementById('btn-selector-toggle');
if (bl?.classList.contains('show')) { bl.classList.remove('show'); if(blBtn) blBtn.textContent = '🚫 黑名单'; }
if (sel?.classList.contains('show')) { sel.classList.remove('show'); if(selBtn) { selBtn.classList.remove('expanded'); const a = selBtn.querySelector('.arrow'); if(a) a.style.transform = ''; } }
}
content.classList.toggle('show');
this.textContent = isExpanded ? '🌐 站点预设' : '✕ 收起预设';
updatePresetStats();
});
document.getElementById('count-selector').addEventListener('input', function() {
getSiteConfig().countSelector = this.value;
saveSelectorConfig();
});
document.getElementById('target-selector').addEventListener('input', function() {
getSiteConfig().targetSelector = this.value;
saveSelectorConfig();
});
document.getElementById('toggle-dedup').addEventListener('click', function() { this.classList.toggle('active'); });
document.getElementById('toggle-include-filter').addEventListener('click', function() { this.classList.toggle('active'); });
document.getElementById('toggle-exact-match').addEventListener('click', function() {
this.classList.toggle('active');
const incMatch = document.getElementById('toggle-include-match');
if (this.classList.contains('active') && incMatch.classList.contains('active')) {
incMatch.classList.remove('active');
}
});
document.getElementById('toggle-include-match').addEventListener('click', function () {
this.classList.toggle('active');
const exactMatch = document.getElementById('toggle-exact-match');
if (this.classList.contains('active') && exactMatch.classList.contains('active')) {
exactMatch.classList.remove('active');
}
});
document.querySelectorAll('.btn-picker').forEach(btn => {
btn.addEventListener('click', function() {
currentPickerTargetId = this.dataset.target;
startElementPicker();
});
});
initPresetLogic();
}
function toggleKeywordsList() {
const list = document.getElementById('keywords-list');
const btn = document.getElementById('btn-blacklist');
if (!list || !btn) return;
if (list.classList.contains('show')) {
list.classList.remove('show');
btn.textContent = '🚫 黑名单';
} else {
const selContent = document.getElementById('selector-content');
const selBtn = document.getElementById('btn-selector-toggle');
const psContent = document.getElementById('preset-content');
const psBtn = document.getElementById('btn-preset-toggle');
if (selContent?.classList.contains('show')) { selContent.classList.remove('show'); if(selBtn) { selBtn.classList.remove('expanded'); const arrow = selBtn.querySelector('.arrow'); if (arrow) arrow.style.transform = ''; } }
if (psContent?.classList.contains('show')) { psContent.classList.remove('show'); if(psBtn) psBtn.textContent = '🌐 站点预设'; }
updateBlacklistUI();
list.classList.add('show');
btn.textContent = '✕ 收起';
}
}
function initPresetLogic() {
const domainInput = document.getElementById('preset-domain');
const countInput = document.getElementById('preset-count-sel');
const targetInput = document.getElementById('preset-target-sel');
const blInput = document.getElementById('preset-blacklist');
const incInput = document.getElementById('preset-include-filter');
const incMatchInput = document.getElementById('preset-include-match');
const loadBtn = document.getElementById('btn-preset-load');
const saveBtn = document.getElementById('btn-preset-save');
const exportBtn = document.getElementById('btn-preset-export');
const otherBtn = document.getElementById('btn-preset-other');
if (!domainInput || !loadBtn) return;
const parseDomain = (d) => d.trim().replace(/^https?:\/\//, '').split('/')[0].split('?')[0];
loadBtn.addEventListener('click', () => {
const host = parseDomain(domainInput.value);
if (!host) { alert('请输入有效的域名'); return; }
domainInput.value = host;
const selCfg = CONFIG.selectorRules[host] || { countSelector: '', targetSelector: '' };
countInput.value = selCfg.countSelector || '';
targetInput.value = selCfg.targetSelector || '';
const siteBl = CONFIG.blacklistRules.sites[host] || [];
blInput.value = siteBl.join(', ');
const siteInc = CONFIG.includeFilterRules.sites[host] || [];
incInput.value = siteInc.join(', ');
const siteIncMatch = CONFIG.includeMatchRules.sites[host] || [];
incMatchInput.value = siteIncMatch.join(', ');
});
saveBtn.addEventListener('click', () => {
const host = parseDomain(domainInput.value);
if (!host) { alert('请先输入目标域名'); return; }
if (!CONFIG.selectorRules[host]) CONFIG.selectorRules[host] = { title: host, countSelector: '', targetSelector: '' };
CONFIG.selectorRules[host].countSelector = countInput.value.trim();
CONFIG.selectorRules[host].targetSelector = targetInput.value.trim();
const kws = blInput.value.split(/[,,]/).map(k => k.trim()).filter(k => k);
CONFIG.blacklistRules.sites[host] = [...new Set(kws)];
const incKws = incInput.value.split(/[,,]/).map(k => k.trim()).filter(k => k);
CONFIG.includeFilterRules.sites[host] = [...new Set(incKws)];
const incMatchKws = incMatchInput.value.split(/[,,]/).map(k => k.trim()).filter(k => k);
CONFIG.includeMatchRules.sites[host] = [...new Set(incMatchKws)];
saveSelectorConfig();
saveBlacklistConfig();
saveIncludeFilterConfig();
saveIncludeMatchConfig();
updatePresetStats();
alert(`✅ 站点 [${host}] 预设已保存!`);
});
exportBtn.addEventListener('click', () => {
const exportText = generatePresetExport();
const blob = new Blob(['\uFEFF' + exportText], { type: 'text/plain;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'site_presets.txt';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
});
otherBtn.addEventListener('click', () => {
const content = document.getElementById('preset-other-content');
content.classList.toggle('show');
otherBtn.textContent = content.classList.contains('show') ? '✕ 收起' : '📋 其他配置';
if (content.classList.contains('show')) {
renderOtherPresets();
}
});
}
function generatePresetExport() {
let text = '// 🔹🔹🔹 【用户编辑区】预设网站配置 🔹🔹🔹\n';
text += '// 格式: \'纯域名\': { title: \'显示名称\', countSelector: \'统计选择器(可留空)\', targetSelector: \'抓取选择器\', blacklist: [\'词1\', \'词2\'], includeFilter: [\'词1\', \'词2\'], includeMatch: [\'词1\', \'词2\'] }\n\n';
text += 'const SITE_PRESETS = {\n';
const allHosts = Object.keys(CONFIG.selectorRules).filter(h => CONFIG.selectorRules[h]?.targetSelector);
allHosts.forEach((host, index) => {
const cfg = CONFIG.selectorRules[host] || {};
const bl = CONFIG.blacklistRules.sites[host] || [];
const inc = CONFIG.includeFilterRules.sites[host] || [];
const incMatch = CONFIG.includeMatchRules.sites[host] || [];
const title = cfg.title || host;
const countSel = cfg.countSelector || '';
const targetSel = cfg.targetSelector || '';
text += ` '${host}': {\n`;
text += ` title: '${title.replace(/'/g, "\\'")}',\n`;
text += ` countSelector: '${countSel.replace(/'/g, "\\'")}',\n`;
text += ` targetSelector: '${targetSel.replace(/'/g, "\\'")}'`;
if (bl.length > 0) {
text += `,\n blacklist: [${bl.map(k => `'${k.replace(/'/g, "\\'")}'`).join(', ')}]`;
}
if (inc.length > 0) {
text += `,\n includeFilter: [${inc.map(k => `'${k.replace(/'/g, "\\'")}'`).join(', ')}]`;
}
if (incMatch.length > 0) {
text += `,\n includeMatch: [${incMatch.map(k => `'${k.replace(/'/g, "\\'")}'`).join(', ')}]`;
}
text += index < allHosts.length - 1 ? '\n },\n' : '\n }';
});
text += '\n};\n// 🔹🔹🔹 预设配置结束(无需修改下方代码)🔹🔹🔹\n';
return text;
}
function checkSiteInJS(host) {
for (const [presetHost, preset] of Object.entries(SITE_PRESETS)) {
if (presetHost === host) return true;
}
return false;
}
function renderOtherPresets() {
const container = document.getElementById('preset-other-content');
if (!container) return;
const currentHost = window.location.hostname;
const allHosts = Object.keys(CONFIG.selectorRules).filter(h => h !== currentHost && CONFIG.selectorRules[h]?.targetSelector);
if (allHosts.length === 0) {
container.innerHTML = '暂无其他站点预设
';
return;
}
container.innerHTML = allHosts.map(host => {
const cfg = CONFIG.selectorRules[host] || {};
const bl = CONFIG.blacklistRules.sites[host] || [];
const inc = CONFIG.includeFilterRules.sites[host] || [];
const incMatch = CONFIG.includeMatchRules.sites[host] || [];
const title = cfg.title || host;
const inJS = checkSiteInJS(host);
return `
统计:
${cfg.countSelector || '(空)'}
抓取:
${cfg.targetSelector}
黑名单${bl.length}条:
${bl.length ? bl.join(', ') : '(空)'}
包含过滤${inc.length}条:
${inc.length ? inc.join(', ') : '(空)'}
包含匹配${incMatch.length}条:
${incMatch.length ? incMatch.join(', ') : '(空)'}
`;
}).join('');
container.querySelectorAll('.other-preset-header').forEach(header => {
header.addEventListener('click', function(e) {
if (e.target.classList.contains('btn-delete-site')) return;
const host = this.closest('.other-preset-item').dataset.host;
const content = container.querySelector(`.other-preset-content[data-host="${host}"]`);
const arrow = this.querySelector('.other-preset-arrow');
if (content) {
const isShow = content.classList.toggle('show');
arrow.textContent = isShow ? '▲' : '▼';
}
});
});
container.querySelectorAll('[data-del-preset]').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const host = this.dataset.delPreset;
if (confirm(`确定删除 ${host} 的所有预设配置吗?`)) {
delete CONFIG.selectorRules[host];
delete CONFIG.blacklistRules.sites[host];
delete CONFIG.includeFilterRules.sites[host];
delete CONFIG.includeMatchRules.sites[host];
saveSelectorConfig();
saveBlacklistConfig();
saveIncludeFilterConfig();
saveIncludeMatchConfig();
updatePresetStats();
renderOtherPresets();
}
});
});
}
function updatePresetStats() {
const el = document.getElementById('preset-count-info');
if (el) {
const count = Object.keys(CONFIG.selectorRules).filter(h => CONFIG.selectorRules[h]?.targetSelector).length;
el.textContent = `已预设: ${count} 个站点`;
}
}
function updateBlacklistUI() {
const host = window.location.hostname;
const blacklists = CONFIG.blacklistRules.sites || {};
const includeFilters = CONFIG.includeFilterRules.sites || {};
const includeMatches = CONFIG.includeMatchRules.sites || {};
const keywordsContent = document.getElementById('keywords-content');
if (!keywordsContent) return;
const currentBL = blacklists[host] || [];
const currentINC = includeFilters[host] || [];
const currentMATCH = includeMatches[host] || [];
const commonBL = CONFIG.blacklistRules.common || [];
const commonINC = CONFIG.includeFilterRules.common || [];
const commonMATCH = CONFIG.includeMatchRules.common || [];
keywordsContent.innerHTML = `
`;
const batchAddCurrent = () => {
const blInput = document.getElementById('bl-current-input');
const incInput = document.getElementById('inc-current-input');
const matchInput = document.getElementById('match-current-input');
if (!CONFIG.blacklistRules.sites[host]) CONFIG.blacklistRules.sites[host] = [];
if (!CONFIG.includeFilterRules.sites[host]) CONFIG.includeFilterRules.sites[host] = [];
if (!CONFIG.includeMatchRules.sites[host]) CONFIG.includeMatchRules.sites[host] = [];
let added = false;
if (blInput?.value.trim()) {
const keywords = blInput.value.split(/[,,]/).map(k => k.trim()).filter(k => k);
if (keywords.length > 0) CONFIG.blacklistRules.sites[host].push(...keywords);
blInput.value = '';
added = true;
}
if (incInput?.value.trim()) {
const keywords = incInput.value.split(/[,,]/).map(k => k.trim()).filter(k => k);
if (keywords.length > 0) CONFIG.includeFilterRules.sites[host].push(...keywords);
incInput.value = '';
added = true;
}
if (matchInput?.value.trim()) {
const keywords = matchInput.value.split(/[,,]/).map(k => k.trim()).filter(k => k);
if (keywords.length > 0) CONFIG.includeMatchRules.sites[host].push(...keywords);
matchInput.value = '';
added = true;
}
if (added) {
saveBlacklistConfig();
saveIncludeFilterConfig();
saveIncludeMatchConfig();
updateKeywordsDisplay();
}
};
const batchAddCommon = () => {
const blInput = document.getElementById('bl-common-input');
const incInput = document.getElementById('inc-common-input');
const matchInput = document.getElementById('match-common-input');
let added = false;
if (blInput?.value.trim()) {
const keywords = blInput.value.split(/[,,]/).map(k => k.trim()).filter(k => k);
if (keywords.length > 0) CONFIG.blacklistRules.common.push(...keywords);
blInput.value = '';
added = true;
}
if (incInput?.value.trim()) {
const keywords = incInput.value.split(/[,,]/).map(k => k.trim()).filter(k => k);
if (keywords.length > 0) CONFIG.includeFilterRules.common.push(...keywords);
incInput.value = '';
added = true;
}
if (matchInput?.value.trim()) {
const keywords = matchInput.value.split(/[,,]/).map(k => k.trim()).filter(k => k);
if (keywords.length > 0) CONFIG.includeMatchRules.common.push(...keywords);
matchInput.value = '';
added = true;
}
if (added) {
saveBlacklistConfig();
saveIncludeFilterConfig();
saveIncludeMatchConfig();
updateKeywordsDisplay();
}
};
document.getElementById('bl-add-current')?.addEventListener('click', batchAddCurrent);
document.getElementById('inc-add-current')?.addEventListener('click', batchAddCurrent);
document.getElementById('match-add-current')?.addEventListener('click', batchAddCurrent);
document.getElementById('bl-add-common')?.addEventListener('click', batchAddCommon);
document.getElementById('inc-add-common')?.addEventListener('click', batchAddCommon);
document.getElementById('match-add-common')?.addEventListener('click', batchAddCommon);
const commonHeader = document.getElementById('bl-common-header');
const commonContent = document.getElementById('bl-common-content');
if (commonHeader) {
commonHeader.addEventListener('click', function() {
commonContent.classList.toggle('show');
commonContent.style.display = commonContent.classList.contains('show') ? 'block' : 'none';
});
}
keywordsContent.querySelectorAll('.keyword-tag.deletable').forEach(tag => {
tag.addEventListener('click', function() {
const type = this.dataset.type;
const idx = parseInt(this.dataset.index);
if (type === 'current-bl') { if (CONFIG.blacklistRules.sites[host]?.[idx] !== undefined) CONFIG.blacklistRules.sites[host].splice(idx, 1); }
else if (type === 'current-inc') { if (CONFIG.includeFilterRules.sites[host]?.[idx] !== undefined) CONFIG.includeFilterRules.sites[host].splice(idx, 1); }
else if (type === 'current-match') { if (CONFIG.includeMatchRules.sites[host]?.[idx] !== undefined) CONFIG.includeMatchRules.sites[host].splice(idx, 1); }
else if (type === 'common-bl') { if (CONFIG.blacklistRules.common?.[idx] !== undefined) CONFIG.blacklistRules.common.splice(idx, 1); }
else if (type === 'common-inc') { if (CONFIG.includeFilterRules.common?.[idx] !== undefined) CONFIG.includeFilterRules.common.splice(idx, 1); }
else if (type === 'common-match') { if (CONFIG.includeMatchRules.common?.[idx] !== undefined) CONFIG.includeMatchRules.common.splice(idx, 1); }
saveBlacklistConfig();
saveIncludeFilterConfig();
saveIncludeMatchConfig();
updateKeywordsDisplay();
});
});
}
function updateKeywordsDisplay() {
const host = window.location.hostname;
const currentBL = CONFIG.blacklistRules.sites[host] || [];
const currentINC = CONFIG.includeFilterRules.sites[host] || [];
const currentMATCH = CONFIG.includeMatchRules.sites[host] || [];
const commonBL = CONFIG.blacklistRules.common || [];
const commonINC = CONFIG.includeFilterRules.common || [];
const commonMATCH = CONFIG.includeMatchRules.common || [];
const blContainer = document.querySelector('#bl-current-input').parentElement.nextElementSibling;
if (blContainer) {
blContainer.innerHTML = currentBL.length === 0
? '暂无精准过滤关键词'
: currentBL.map((k, i) => `${k}`).join('');
bindDeleteEvents(blContainer, host);
}
const incContainer = document.querySelector('#inc-current-input').parentElement.nextElementSibling;
if (incContainer) {
incContainer.innerHTML = currentINC.length === 0
? '暂无包含过滤关键词'
: currentINC.map((k, i) => `${k}`).join('');
bindDeleteEvents(incContainer, host);
}
const matchContainer = document.querySelector('#match-current-input').parentElement.nextElementSibling;
if (matchContainer) {
matchContainer.innerHTML = currentMATCH.length === 0
? '暂无包含匹配关键词'
: currentMATCH.map((k, i) => `${k}`).join('');
bindDeleteEvents(matchContainer, host);
}
const commonContent = document.getElementById('bl-common-content');
if (commonContent) {
const commonBlContainer = commonContent.querySelectorAll(':scope > div')[1];
if (commonBlContainer) {
commonBlContainer.innerHTML = commonBL.map((k, i) => `${k}`).join('') + (commonBL.length === 0 ? '暂无' : '');
bindDeleteEvents(commonBlContainer, host);
}
const commonIncContainer = commonContent.querySelectorAll(':scope > div')[3];
if (commonIncContainer) {
commonIncContainer.innerHTML = commonINC.map((k, i) => `${k}`).join('') + (commonINC.length === 0 ? '暂无' : '');
bindDeleteEvents(commonIncContainer, host);
}
const commonMatchContainer = commonContent.querySelectorAll(':scope > div')[5];
if (commonMatchContainer) {
commonMatchContainer.innerHTML = commonMATCH.map((k, i) => `${k}`).join('') + (commonMATCH.length === 0 ? '暂无' : '');
bindDeleteEvents(commonMatchContainer, host);
}
const commonHeader = document.getElementById('bl-common-header');
if (commonHeader) {
commonHeader.querySelector('span').textContent = `公用共 ${commonBL.length + commonINC.length + commonMATCH.length} 个`;
}
}
}
function bindDeleteEvents(container, host) {
container.querySelectorAll('.keyword-tag.deletable').forEach(tag => {
tag.addEventListener('click', function() {
const type = this.dataset.type;
const idx = parseInt(this.dataset.index);
if (type === 'current-bl') { if (CONFIG.blacklistRules.sites[host]?.[idx] !== undefined) CONFIG.blacklistRules.sites[host].splice(idx, 1); }
else if (type === 'current-inc') { if (CONFIG.includeFilterRules.sites[host]?.[idx] !== undefined) CONFIG.includeFilterRules.sites[host].splice(idx, 1); }
else if (type === 'current-match') { if (CONFIG.includeMatchRules.sites[host]?.[idx] !== undefined) CONFIG.includeMatchRules.sites[host].splice(idx, 1); }
else if (type === 'common-bl') { if (CONFIG.blacklistRules.common?.[idx] !== undefined) CONFIG.blacklistRules.common.splice(idx, 1); }
else if (type === 'common-inc') { if (CONFIG.includeFilterRules.common?.[idx] !== undefined) CONFIG.includeFilterRules.common.splice(idx, 1); }
else if (type === 'common-match') { if (CONFIG.includeMatchRules.common?.[idx] !== undefined) CONFIG.includeMatchRules.common.splice(idx, 1); }
saveBlacklistConfig();
saveIncludeFilterConfig();
saveIncludeMatchConfig();
updateKeywordsDisplay();
});
});
}
function updateSelectorUI() {
const host = window.location.hostname;
const cfg = getSiteConfig();
document.getElementById('count-selector').value = cfg.countSelector || '';
document.getElementById('target-selector').value = cfg.targetSelector || '';
}
function addBlacklistKeyword(type, keyword, host = null) {
if (type === 'common') { if (!CONFIG.blacklistRules.common) CONFIG.blacklistRules.common = []; CONFIG.blacklistRules.common.push(keyword); }
else { const targetHost = (type === 'current-site') ? window.location.hostname : host; if (!CONFIG.blacklistRules.sites[targetHost]) CONFIG.blacklistRules.sites[targetHost] = []; CONFIG.blacklistRules.sites[targetHost].push(keyword); }
saveBlacklistConfig();
updateKeywordsDisplay();
}
function deleteBlacklistKeyword(type, host = null, index = 0) {
const targetHost = host || window.location.hostname;
if (type === 'common') { if (CONFIG.blacklistRules.common?.[index] !== undefined) CONFIG.blacklistRules.common.splice(index, 1); }
else { if (CONFIG.blacklistRules.sites?.[targetHost]?.[index] !== undefined) CONFIG.blacklistRules.sites[targetHost].splice(index, 1); }
saveBlacklistConfig();
updateKeywordsDisplay();
}
function showPanel() { if (panelRef) panelRef.style.display = 'block'; if (toggleBtnRef) toggleBtnRef.style.display = 'none'; }
function hidePanel() { if (panelRef) panelRef.style.display = 'none'; if (toggleBtnRef) toggleBtnRef.style.display = 'block'; }
function initToggleDrag(showPanelCallback) {
if (!toggleBtnRef) return;
let isDragging = false, hasMoved = false, startX, startY, buttonX, buttonY;
toggleBtnRef.addEventListener('mousedown', e => { if(e.button===0){ isDragging=true; hasMoved=false; startX=e.clientX; startY=e.clientY; const r=toggleBtnRef.getBoundingClientRect(); buttonX=r.left; buttonY=r.top; toggleBtnRef.classList.add('dragging'); e.preventDefault(); }});
document.addEventListener('mousemove', e => { if(!isDragging)return; if(Math.abs(e.clientX-startX)>5||Math.abs(e.clientY-startY)>5)hasMoved=true; toggleBtnRef.style.left=Math.max(0,Math.min(buttonX+e.clientX-startX,window.innerWidth-toggleBtnRef.offsetWidth))+'px'; toggleBtnRef.style.top=Math.max(0,Math.min(buttonY+e.clientY-startY,window.innerHeight-toggleBtnRef.offsetHeight))+'px'; toggleBtnRef.style.right='auto'; toggleBtnRef.style.bottom='auto'; e.preventDefault(); });
document.addEventListener('mouseup', () => { if(isDragging){ isDragging=false; toggleBtnRef.classList.remove('dragging'); const r=toggleBtnRef.getBoundingClientRect(); buttonX=r.left; buttonY=r.top; }});
toggleBtnRef.addEventListener('click', e => { if(hasMoved){e.stopPropagation();e.preventDefault();} else if(typeof showPanelCallback==='function') showPanelCallback(); });
}
function initPanelDrag(panel) {
if(!panel)return; let isDragging=false, startX, startY, panelX, panelY;
const handle = panel.querySelector('.panel-drag-handle');
if(!handle)return; panel.classList.add('draggable');
handle.addEventListener('mousedown', e => { if(e.button===0){ isDragging=true; startX=e.clientX; startY=e.clientY; const r=panel.getBoundingClientRect(); panelX=r.left; panelY=r.top; panel.classList.add('dragging'); e.preventDefault(); }});
document.addEventListener('mousemove', e => { if(!isDragging)return; panel.style.left=Math.max(0,Math.min(panelX+e.clientX-startX,window.innerWidth-panel.offsetWidth))+'px'; panel.style.top=Math.max(0,Math.min(panelY+e.clientY-startY,window.innerHeight-panel.offsetHeight))+'px'; panel.style.right='auto'; panel.style.bottom='auto'; e.preventDefault(); });
document.addEventListener('mouseup', () => { if(isDragging){ isDragging=false; panel.classList.remove('dragging'); const r=panel.getBoundingClientRect(); panelX=r.left; panelY=r.top; }});
}
function startElementPicker() {
if (pickerMode) return; pickerMode = true;
if (panelRef) panelRef.style.display = 'none';
if (!highlightEl) { highlightEl = document.createElement('div'); highlightEl.id = 'scraper-element-highlight'; document.body.appendChild(highlightEl); }
if (!infoEl) { infoEl = document.createElement('div'); infoEl.id = 'scraper-element-info'; document.body.appendChild(infoEl); }
infoEl.textContent = '点击元素获取选择器 | ESC 退出'; infoEl.style.cssText = 'top:10px;left:50%;transform:translateX(-50%);display:block;';
document.addEventListener('mouseover', handlePickerMouseOver, true);
document.addEventListener('mouseout', handlePickerMouseOut, true);
document.addEventListener('click', handlePickerClick, true);
document.addEventListener('keydown', handlePickerKeyDown, true);
}
function stopElementPicker() {
pickerMode = false;
if (highlightEl) highlightEl.style.display = 'none';
if (infoEl) infoEl.style.display = 'none';
document.removeEventListener('mouseover', handlePickerMouseOver, true);
document.removeEventListener('mouseout', handlePickerMouseOut, true);
document.removeEventListener('click', handlePickerClick, true);
document.removeEventListener('keydown', handlePickerKeyDown, true);
}
function handlePickerMouseOver(e) {
if (!pickerMode || e.target.closest('#scraper-panel,#scraper-toggle,.scraper-preview-highlight')) return;
e.stopPropagation(); const rect = e.target.getBoundingClientRect();
highlightEl.style.cssText = `display:block;top:${rect.top}px;left:${rect.left}px;width:${rect.width}px;height:${rect.height}px;`;
}
function handlePickerMouseOut(e) { if (pickerMode) { highlightEl.style.display = 'none'; infoEl.style.display = 'none'; } }
function handlePickerClick(e) {
if (!pickerMode || e.target.closest('#scraper-panel,#scraper-toggle,.scraper-preview-highlight')) return;
e.preventDefault(); e.stopPropagation();
showSelectorPanel(generateSelectorLevels(e.target));
}
function handlePickerKeyDown(e) { if (pickerMode && e.key === 'Escape') { e.preventDefault(); stopElementPicker(); closeSelectorPanel(); } }
function generateSelectorLevels(el) {
const levels = []; let current = el;
while (current && current !== document.body) { levels.unshift({ selector: generateSingleLevelSelector(current), checked: true }); current = current.parentElement; }
return levels;
}
function generateSingleLevelSelector(el) {
if (el.id) return '#' + el.id;
if (el.className && typeof el.className === 'string') {
const classes = el.className.trim().split(/\s+/).filter(c => c);
for (const cls of classes) if (document.querySelectorAll('.' + cls).length === 1) return '.' + cls;
const cs = '.' + classes.join('.'); if (document.querySelectorAll(cs).length === 1) return cs;
}
const parent = el.parentElement;
if (parent) {
const siblings = Array.from(parent.children), idx = siblings.indexOf(el) + 1;
if (siblings.filter(s => s.tagName === el.tagName).length === 1) return el.tagName.toLowerCase();
return el.tagName.toLowerCase() + ':nth-child(' + idx + ')';
}
return el.tagName.toLowerCase();
}
function showSelectorPanel(levels) {
currentSelectorLevels = levels; closeSelectorPanel();
const panel = document.createElement('div'); panel.id = 'scraper-selector-panel'; selectorPanelRef = panel;
panel.innerHTML = `${levels.map((l, i) => `
`).join('')}
`;
document.body.appendChild(panel); initSelectorPanelDrag(panel);
document.getElementById('btn-close-panel').addEventListener('click', () => { clearPreviewHighlights(); closeSelectorPanel(); if(panelRef) panelRef.style.display='block'; });
document.getElementById('btn-reselect').addEventListener('click', () => { clearPreviewHighlights(); closeSelectorPanel(); startElementPicker(); });
document.getElementById('btn-create').addEventListener('click', function() {
const sel = buildSelector(currentSelectorLevels);
if (sel && currentPickerTargetId) { const input = document.getElementById(currentPickerTargetId); if (input) { input.value = sel; input.dispatchEvent(new Event('input', { bubbles: true })); } }
clearPreviewHighlights(); closeSelectorPanel(); if(panelRef) panelRef.style.display='block';
});
panel.querySelectorAll('.rule-item').forEach(item => {
item.addEventListener('click', function() {
const idx = parseInt(this.dataset.index);
currentSelectorLevels[idx].checked = !currentSelectorLevels[idx].checked;
this.classList.toggle('unchecked');
panel.querySelector('.panel-count').textContent = countMatchedElements(currentSelectorLevels) + ' 个匹配';
updatePreviewHighlights();
});
});
stopElementPicker(); updatePreviewHighlights();
window.addEventListener('scroll', handleScroll, true); window.addEventListener('resize', handleScroll, true);
}
function handleScroll() { if (scrollUpdateTimer) cancelAnimationFrame(scrollUpdateTimer); scrollUpdateTimer = requestAnimationFrame(updatePreviewHighlights); }
function updatePreviewHighlights() {
clearPreviewHighlights();
const sel = buildSelector(currentSelectorLevels);
if (!sel) return;
try { document.querySelectorAll(sel).forEach(el => {
const rect = el.getBoundingClientRect();
const h = document.createElement('div'); h.className = 'scraper-preview-highlight';
h.style.cssText = `position:fixed;top:${rect.top}px;left:${rect.left}px;width:${rect.width}px;height:${rect.height}px;border:2px solid #3b82f6;background:rgba(59,130,246,0.1);pointer-events:none;z-index:2147483646;`;
document.body.appendChild(h); previewHighlights.push(h);
}); } catch(e){}
}
function clearPreviewHighlights() { previewHighlights.forEach(el => el.remove()); previewHighlights = []; }
function closeSelectorPanel() {
window.removeEventListener('scroll', handleScroll, true); window.removeEventListener('resize', handleScroll, true);
if (scrollUpdateTimer) { cancelAnimationFrame(scrollUpdateTimer); scrollUpdateTimer = null; }
if (selectorPanelRef) { selectorPanelRef.remove(); selectorPanelRef = null; }
clearPreviewHighlights();
}
function countMatchedElements(levels) { const s = buildSelector(levels); return s ? document.querySelectorAll(s).length : 0; }
function buildSelector(levels) { return levels.filter(l => l.checked).map(l => l.selector).join(' > '); }
function initSelectorPanelDrag(panel) {
if (!panel) return; let drag = false, sx, sy, px, py;
const hd = panel.querySelector('.panel-header'); hd.style.cursor = 'move'; panel.classList.add('draggable');
const mv = e => { if(!drag) return; panel.style.left=Math.max(0,Math.min(px+e.clientX-sx,window.innerWidth-panel.offsetWidth))+'px'; panel.style.top=Math.max(0,Math.min(py+e.clientY-sy,window.innerHeight-panel.offsetHeight))+'px'; panel.style.right='auto'; panel.style.bottom='auto'; };
const up = () => { if(!drag) return; drag=false; panel.classList.remove('dragging'); const r=panel.getBoundingClientRect(); px=r.left; py=r.top; };
hd.addEventListener('mousedown', e => { if(e.button!==0||e.target.closest('.btn-close')) return; drag=true; sx=e.clientX; sy=e.clientY; const r=panel.getBoundingClientRect(); px=r.left; py=r.top; panel.classList.add('dragging'); e.preventDefault(); });
document.addEventListener('mousemove', mv); document.addEventListener('mouseup', up);
}
let scrapedData = [];
function scrapeElements() {
const cfg = getSiteConfig();
const selector = cfg.targetSelector?.trim() || '';
const countSelector = cfg.countSelector?.trim() || '';
if (!selector) {
alert('请先配置当前网站的选择器!');
return;
}
const includeMatchKeywords = getSiteIncludeMatch();
const includeFilterKeywords = getSiteIncludeFilter();
const useIncludeMatch = document.getElementById('toggle-include-match').classList.contains('active');
const useIncludeFilter = document.getElementById('toggle-include-filter').classList.contains('active');
const useExactMatch = document.getElementById('toggle-exact-match').classList.contains('active');
const useDedup = document.getElementById('toggle-dedup').classList.contains('active');
const exactKeywords = getSiteBlacklist();
try {
const elements = document.querySelectorAll(selector);
if (elements.length === 0) {
document.getElementById('scraper-stats').innerHTML = `❌ 未找到匹配元素`;
return;
}
scrapedData = []; const valid = [], filtered = [], seen = new Set();
let incF=0, exactF=0, matchF=0, dupF=0, vIdx=0;
elements.forEach((el, i) => {
const txt = el.textContent.trim(); if(!txt) return;
let reason = '', filt = false;
if (useIncludeMatch) {
const matched = includeMatchKeywords.some(k => txt.includes(k));
if (!matched) {
reason = '未包含匹配关键词';
filt = true;
matchF++;
filtered.push(`[${i+1}] ${escapeHtml(txt.substring(0,35))}${txt.length >35?'...':''} (${reason})
`);
} else {
if (useExactMatch) {
const exactMatched = exactKeywords.some(k => txt === k);
if (exactMatched) {
reason = `完整匹配 "${exactKeywords.find(k => txt === k)}"`;
filt = true;
exactF++;
filtered.push(`[${i+1}] ${escapeHtml(txt.substring(0,35))}${txt.length >35?'...':''} (${reason})
`);
}
}
if (useIncludeFilter && !filt && includeFilterKeywords.length > 0) {
const filteredOut = includeFilterKeywords.some(k => txt.includes(k));
if (filteredOut) {
reason = `包含 "${includeFilterKeywords.find(k => txt.includes(k))}"`;
filt = true;
incF++;
filtered.push(`[${i+1}] ${escapeHtml(txt.substring(0,35))}${txt.length >35?'...':''} (${reason})
`);
}
}
}
} else {
if (useExactMatch) {
const exactMatched = exactKeywords.some(k => txt === k);
if (exactMatched) {
reason = `完整匹配 "${exactKeywords.find(k => txt === k)}"`;
filt = true;
exactF++;
filtered.push(`[${i+1}] ${escapeHtml(txt.substring(0,35))}${txt.length >35?'...':''} (${reason})
`);
}
}
if (useIncludeFilter && !filt && includeFilterKeywords.length > 0) {
const filteredOut = includeFilterKeywords.some(k => txt.includes(k));
if (filteredOut) {
reason = `包含 "${includeFilterKeywords.find(k => txt.includes(k))}"`;
filt = true;
incF++;
filtered.push(`[${i+1}] ${escapeHtml(txt.substring(0,35))}${txt.length >35?'...':''} (${reason})
`);
}
}
}
if (!filt) {
if (useDedup && seen.has(txt)) {
dupF++;
filtered.push(`[${i+1}] ${escapeHtml(txt.substring(0,35))}${txt.length >35?'...':''} (重复)
`);
} else {
if (useDedup) seen.add(txt);
vIdx++;
scrapedData.push({index:vIdx, text:txt});
valid.push(`[${vIdx}] ${escapeHtml(txt.substring(0,35))}${txt.length >35?'...':''}
`);
}
}
});
let cnt = 0; if(countSelector) try { cnt = document.querySelectorAll(countSelector).length; } catch(e){}
document.getElementById('scraper-stats').innerHTML = `✅ 找到${elements.length}个
${cnt >0?`元素 ${cnt} 个`:'无元素'}
📥 有效${scrapedData.length}条
🚫 过滤${incF+exactF+matchF+dupF}条
${matchF >0?`
● 未匹配:${matchF} 条
`:''}${dupF >0?`
● 重复:${dupF} 条
`:''}${exactF >0?`
● 精准过滤:${exactF} 条
`:''}${incF >0?`
● 包含过滤:${incF} 条
`:''}${incF+exactF+matchF+dupF===0?'
暂无过滤数据
':''}
`;
const db = document.getElementById('btn-filter-detail'), dc = document.getElementById('filter-detail-content');
if(db && dc) db.addEventListener('click', function() { const ex=this.dataset.expanded==='true'; dc.classList.toggle('show'); this.textContent=ex?'详情':'收起'; this.dataset.expanded=(!ex).toString(); });
document.getElementById('scraper-results').innerHTML = [...valid, ...filtered].join('');
} catch (e) {
document.getElementById('scraper-stats').innerHTML = `❌ 错误: ${e.message}`;
}
}
function getSiteIncludeMatch() {
const host = window.location.hostname;
const siteKws = CONFIG.includeMatchRules.sites[host] || [];
const commonKws = CONFIG.includeMatchRules.common || [];
return [...new Set([...siteKws, ...commonKws])];
}
function exportToTXT() {
if (scrapedData.length === 0) { alert('没有数据可导出!请先抓取元素。'); return; }
const blob = new Blob(['\uFEFF' + scrapedData.map(d=>d.text).join('\n')], { type: 'text/plain;charset=utf-8;' });
const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = CONFIG.exportFileName;
document.body.appendChild(link); link.click(); document.body.removeChild(link);
document.getElementById('scraper-stats').innerHTML = `✅ 已导出 ${scrapedData.length} 条数据!`;
}
function escapeHtml(t) { const d=document.createElement('div'); d.textContent=t; return d.innerHTML; }
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', createControlPanel);
else createControlPanel();
})();