您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
1.在亚马逊搜索结果页上定位ASIN, 获取排名 2.代码重构————dom操作->fetch+DOMParser 3.结果面板 4.批量导入excel关键词表,返回关键词排名表.xlsx
// ==UserScript== // @name 亚马逊关键词排名-Amazon keywords Positioning by Asin // @namespace http://tampermonkey.net/ // @version 3.5.0 // @description 1.在亚马逊搜索结果页上定位ASIN, 获取排名 2.代码重构————dom操作->fetch+DOMParser 3.结果面板 4.批量导入excel关键词表,返回关键词排名表.xlsx // @author You // @match https://www.amazon.com/* // @match https://www.amazon.co.uk/* // @match https://www.amazon.ca/* // @icon https://www.amazon.com/favicon.ico // @license MIT // @grant none // ==/UserScript== (async function () { 'use strict'; // —— 配置区 —— const DEFAULT_MAX_PAGES = 2; // 默认最多搜索页数 const STYLE = ` /* 容器 */ #tm-asin-container { position: fixed; top: 60px; left: 0; right: 0; padding: 6px 12px; background: #fff; box-shadow: 0 2px 12px rgba(0,0,0,0.1); font-family: "Helvetica Neue", Arial, sans-serif; z-index: 9999; display: flex; align-items: center; } /* tag-wrapper-css */ #tm-asin-container #tag-wrapper { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-right: 6px; } .tag-item { display: inline-flex; align-items: center; height: 28px; padding: 0 8px; font-size: 14px; background: #ecf5ff; color: #409eff; border: 1px solid #b3d8ff; border-radius: 4px; } .tag-item .tag-close { display: inline-block; margin-left: 4px; font-style: normal; cursor: pointer; color: #409eff; font-weight: bold; } .tag-item .tag-close:hover { color: #66b1ff; } .tag-add-btn { display: inline-flex; align-items: center; height: 32px; padding: 0 12px; font-size: 14px; color: #409eff; background: #fff; border: 1px solid #409eff; border-radius: 4px; cursor: pointer; transition: background-color .2s; } .tag-add-btn:hover { background-color: #ecf5ff; } /* 临时输入框 */ .tag-input { flex: 1; min-width: 100px; height: 28px; padding: 0 6px; font-size: 14px; border: 1px solid #dcdfe6; border-radius: 4px; outline: none; } /* input错误提示 */ .input-error { border-color: red; outline: none; box-shadow: 0 0 5px red; } /* ASIN 和页数输入框 */ #tm-asin-container input[type="number"] { margin-right: 14px; font-size: 16px; border: 1px solid #dcdfe6; border-radius: 4px; color: #606266; outline: none; transition: border-color .2s, box-shadow .2s; width: 200px; box-sizing: border-box; } #tm-asin-container input:focus { border-color: #409eff; box-shadow: 0 0 2px rgba(64,158,255,0.2); } /* 文件上传按钮 追加 ElementUI Button 样式 */ .el-button { display: inline-block; line-height: 1.5; white-space: nowrap; font-size: 14px; font-weight: 500; padding: 6px 12px; border: 1px solid #dcdfe6; border-radius: 4px; cursor: pointer; user-select: none; background-color: #fff; color: #606266; transition: background-color .2s, border-color .2s, color .2s; margin-right: 12px; } .el-button--primary { background-color: #409eff; border-color: #409eff; color: #fff; } .el-button--primary:hover { background-color: #66b1ff; border-color: #66b1ff; } /* 按钮 */ #tm-asin-container button { margin-right: 12px; padding: 5px 10px; font-size: 14px; font-weight: 500; color: #fff; background-color: #409eff; border: 1px solid #409eff; border-radius: 4px; cursor: pointer; transition: background-color .2s, border-color .2s; } #tm-asin-container button:hover:not([disabled]) { background-color: #66b1ff; border-color: #66b1ff; } #tm-asin-container span { font-size: 16px; } /* 状态文字:紧跟按钮后面 */ #tm-asin-container span#tm-status { margin-left: 12px; margin-right: 12px; font-size: 16px; color:rgb(110, 111, 111); } /* 面板button样式 */ #batch-results-panel .rp-jump-btn { margin-top: 2px; margin-left: 6px; margin-bottom: 3.5px; line-height: 12px; color: #5ba7f4; background-color: #ecf5ff; border: 1px solid; border-radius: 5px; padding-top: 2px; cursor: pointer; transition: background-color .2s, color .2s, border-color .2s; } #batch-results-panel .rp-jump-btn:hover { background-color: #5ba7f4; color: #ffffff; border-color: #5ba7f4; } #batch-results-panel .dw-jump-btn { style='width: 0px; background-color: #ffffff; border: 0px; line-height: 12px; margin-top: -3px; font-size: 18px; padding: 0px; cursor: pointer;' transition: font-size .2s; } #batch-results-panel .dw-jump-btn:hover { font-size: 20px; } `; // —— 状态 —— let results = {}; let maxPages = DEFAULT_MAX_PAGES; let keywords = []; //tag-wrapper-2 初始化数据 let tagAsins = [] const maxTags = 3 // —— 注入样式 & UI —— const styleEl = document.createElement('style'); styleEl.textContent = STYLE; document.head.appendChild(styleEl); // container框 const container = document.createElement('div'); container.id = 'tm-asin-container'; // tag-wrapper-1 const tagWrapper = document.createElement('div'); tagWrapper.id = 'tag-wrapper' container.insertBefore(tagWrapper, container.firstChild); // Max🔎Pages const maxPageText = document.createElement('span'); maxPageText.textContent = 'Max🔎Pages:'; // maxpage input const inputPages = document.createElement('input'); inputPages.type = 'number'; inputPages.min = '1'; inputPages.max = '9' inputPages.value = DEFAULT_MAX_PAGES; inputPages.style.width = '60px'; // search button const btnSearch = document.createElement('button'); btnSearch.textContent = '搜索排名'; // clear storage const btnClearCache = document.createElement('button'); btnClearCache.textContent = '清除缓存'; // upload button const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.xlsx,.xls'; fileInput.style.display = 'none'; // status的div的元素 const status = document.createElement('span'); status.setAttribute("id", "tm-status"); status.textContent = '请填写 ASIN, 点击"搜索排名"'; // 创建一个 ElementUI 风格的标签按钮 const uploadLabel = document.createElement('label'); uploadLabel.className = 'el-button el-button--primary'; uploadLabel.textContent = '⬆上传关键词'; uploadLabel.appendChild(fileInput); // 把 fileInput 内嵌到 label // 批量搜索按钮 const batchSearchBtn = document.createElement('button'); batchSearchBtn.className = 'el-button el-button--primary'; batchSearchBtn.textContent = '批量搜索🔍'; // 下载按钮 const downloadBtn = document.createElement('button'); downloadBtn.className = 'el-button el-button--primary'; downloadBtn.textContent = '下载结果表'; /* 动画过渡——container栏的伸缩 */ container.style.transition = 'top 0.4s ease'; let ticking = false; let lastScrollY = window.scrollY; window.addEventListener('scroll', e => { if (!ticking) { window.requestAnimationFrame(() => { container.style.top = window.scrollY > lastScrollY ? '0' : '55px'; lastScrollY = window.scrollY; ticking = false; }); ticking = true; } }, { passive: true }); // —— 初始化时尝试读取缓存 —— const storedTags = sessionStorage.getItem('tm_tagAsins'); if (storedTags) { try { tagAsins = JSON.parse(storedTags); } catch { } } const storedResults = sessionStorage.getItem('tm_results'); if (storedResults) { try { results = JSON.parse(storedResults); } catch { } } batchSearchBtn.disabled = true const keywordResult = sessionStorage.getItem('tm_keywords'); if (keywordResult) { batchSearchBtn.disabled = false console.log(`已有缓存keywords 可以直接批量搜索`); try { keywords = JSON.parse(keywordResult); } catch { } } const storedBatch = sessionStorage.getItem('tm_batch_table'); if (storedBatch) { try { const table = JSON.parse(storedBatch); renderResultsPanelFromTable(table); } catch { } } // tag-wrapper-3 渲染 function renderTags() { tagWrapper.innerHTML = ''; // 渲染每个 tag tagAsins.forEach((tag, idx) => { const span = document.createElement('span'); span.className = 'tag-item'; span.textContent = tag; // close按钮 const close = document.createElement('i'); close.className = 'tag-close'; close.textContent = '×'; close.addEventListener('click', () => { tagAsins.splice(idx, 1); renderTags(); // 可在此触发“close”事件回调 }); span.appendChild(close); tagWrapper.appendChild(span); }); // 渲染"+ New Asin"按钮 const btnAdd = document.createElement('button'); btnAdd.className = 'tag-add-btn'; btnAdd.textContent = '+ New Asin'; btnAdd.addEventListener('click', showInput); tagWrapper.appendChild(btnAdd); } // tag-wrapper-4 显示输入框新增 function showInput() { // 如果已经有输入框,直接聚焦 const existingInput = tagWrapper.querySelector('input.tag-input'); if (existingInput) { existingInput.focus(); return; } const input = document.createElement('input'); input.className = 'tag-input'; input.placeholder = 'Enter ASIN'; // Asin检验格式 const asinRegex = /^B0[A-Z0-9]{8}$/; // 插到按钮前 tagWrapper.insertBefore(input, tagWrapper.querySelector('.tag-add-btn')); input.focus(); // 只保留一个 confirmInput,接收事件对象 const confirmInput = (e) => { const v = input.value.trim().replace(/,$/, ''); // —— 1. 如果是 blur 触发,只处理“空值移除”或“合法新值添加” if (e.type === 'blur') { if (!v) { input.remove(); renderTags(); } else if (!tagAsins.includes(v) && tagAsins.length < maxTags && /^B0[A-Z0-9]{8}$/.test(v)) { tagAsins.push(v); input.remove(); renderTags(); } // 其它情况(重复/不合法/超限)都不 alert,也不移除,让用户继续改 return; } // —— 2. 如果是 keydown 且回车,做完整校验 if (e.type === 'keydown' && e.key === 'Enter') { e.preventDefault(); // 空值 —— 直接移除 if (!v) { input.remove(); renderTags(); return; } // 格式不对 if (!/^B0[A-Z0-9]{8}$/.test(v)) { input.classList.add('input-error'); alert(`ASIN "${v}" 格式不正确!`); input.focus(); return; } // 重复 if (tagAsins.includes(v)) { input.classList.add('input-error'); alert(`ASIN "${v}" 已存在!`); input.focus(); return; } // 超限 if (tagAsins.length >= maxTags) { input.classList.add('input-error'); alert(`最多只能添加 ${maxTags} 个 ASIN!`); input.focus(); return; } // 全部通过——添加并移除 tagAsins.push(v); input.remove(); renderTags(); } } input.addEventListener('keydown', confirmInput); input.addEventListener('blur', confirmInput); } // tag-wrapper-5 初次渲染 renderTags(); // 如果有旧的 results 就直接渲染面板 if (Object.keys(results).length) { renderResultsPanel(results); } [maxPageText, inputPages, btnSearch, btnClearCache, status, uploadLabel, batchSearchBtn, downloadBtn].forEach(el => container.appendChild(el)); document.body.appendChild(container); // —— 状态更新 —— const updateStatus = txt => { status.textContent = txt; }; // —— 主搜索逻辑 —— btnSearch.addEventListener('click', async () => { // 搜索前清楚存储 sessionStorage.removeItem('tm_results'); // search-1 参数 maxPages = parseInt(inputPages.value, 10) || DEFAULT_MAX_PAGES; if (!tagAsins.length) return alert('请先添加至少一个 ASIN!'); // search-2 初始化结果存储 results = {}; tagAsins.forEach(a => results[a] = { found: false }); // search-3 删除原有 page 参数 const baseUrl = new URL(location.href); baseUrl.searchParams.delete('page'); // search-4 顺序翻页 updateStatus(`🔎 开始搜索 ${tagAsins.length}个 ASIN,最多 ${maxPages} 页......`); for (let page = 1; page <= maxPages; page++) { updateStatus(`🔎 正在搜索第 ${page} 页…`); const url = new URL(baseUrl); // 重新设置 page 参数 url.searchParams.set('page', page); // search-4.1 拉取解析HTML // fetch获取html字符串 DOMParser转成document对象 再搜索 let doc try { // 表示跨域请求时会带上 cookie(登录(不可用)态) const resp = await fetch(url.href, { credentials: 'include' }); const html = await resp.text(); doc = new DOMParser().parseFromString(html, 'text/html'); } catch (e) { console.error(e); updateStatus('❌ 网络请求出错,请重试'); return; } // search-4.2 扫描当前页 统计所有未找到的 ASIN const items = doc.querySelectorAll('div[data-asin]') let nat = 0, sp = 0; for (const node of items) { // 带有购物车按钮的才算有效位置 if (!node.querySelector('button.a-button-text, a.a-button-text')) continue; const asin = node.getAttribute('data-asin'); // 广告位 const isAd = !!node.querySelector('a.puis-label-popover.puis-sponsored-label-text'); if (isAd) { sp++; continue; } nat++; // 如果ASIN在tagAsins列表里&切还未找到&不是广告位 if (tagAsins.includes(asin) && !results[asin].found && !isAd) { results[asin] = { found: true, page, position: isAd ? sp : nat, isAd }; } } // search-4.3 如果所有 ASIN 都已找到,则提前退出翻页 const unfinished = tagAsins.filter(asin => !results[asin].found); if (unfinished.length === 0) { updateStatus(`✅ 全部 ASIN 在 ${page} 页内找到`); break; } } // search-5 更新最终状态 & (可选) 渲染结果 const notFound = tagAsins.filter(asin => !results[asin].found); if (notFound.length) { updateStatus(`❌ 未找到:${notFound.join(', ')}`); } else { updateStatus(`✅ 全部 ASIN 已定位`); } // session存储 sessionStorage.setItem('tm_tagAsins', JSON.stringify(tagAsins)); sessionStorage.setItem('tm_results', JSON.stringify(results)); renderResultsPanel(results) }); // —— 搜索结果面板 —— function renderResultsPanel(results) { let panel = document.getElementById('results-panel'); if (!panel) { panel = document.createElement('div'); panel.id = 'results-panel'; Object.assign(panel.style, { position: 'fixed', top: '100px', // 根据你的 tm-asin-container 高度适当调整 left: '5px', background: 'rgba(255,255,255,0.95)', border: '1px solid #ddd', borderRadius: '4px', padding: '5px', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', zIndex: '9999', fontSize: '14px', width: '290px', lineHeight: '1.4' }); document.body.appendChild(panel); } // 1. 构造 HTML:带一个 drag-handle 和每行的“跳转”按钮 const lines = Object.entries(results).map(([asin, r]) => { let text; if (r.found) { const totalRank = (r.page - 1) * 48 + r.position; text = `第${r.page}页${r.position}位-排名${totalRank}`; } else { text = `<span style="color:#f56c6c;">❌未找到</span>`; } // 如果找到了,就显示一个按钮,点击后跳转到该 ASIN 所在页 const btn = r.found ? ` <button class="rp-jump-btn" data-page="${r.page}" style=" margin-top: 2px; margin-left: 6px; margin-bottom: 3.5px; line-height: 12px; color: #5ba7f4; background-color: #ecf5ff; border: 1px solid; border-radius: 5px; padding-top: 2px; cursor: pointer;" onmouseover="this.style.backgroundColor='#5ba7f4'; this.style.color='#ffffff'; this.style.borderColor='#5ba7f4';" onmouseout="this.style.backgroundColor='#ecf5ff'; this.style.color='#5ba7f4'; this.style.borderColor='initial';" >➡</button> <button class="dw-jump-btn" data-asin="${asin}" style='width: 0px; background-color: #ffffff; border: 0px; line-height: 12px; margin-top: -3px; font-size: 18px; padding: 0px; cursor: pointer;' onmouseover="this.style.fontSize='20px';" onmouseout="this.style.fontSize='18px';" >📍</button>` : ''; return `<li style="margin-top:4px;list-style:none;"> <strong>${asin}</strong>:${text}${btn} </li>`; }).join(''); panel.innerHTML = ` <div class="drag-handle" style=" cursor: move; background:#f5f5f5; padding:6px; border-bottom:1px solid #ddd; font-weight:500; font-size: 16px; ">查询结果</div> <ul style="padding:4px;margin:0;">${lines}</ul> `; // 2. 拖拽:只绑定到 .drag-handle const handle = panel.querySelector('.drag-handle'); handle.onmousedown = e => { const rect = panel.getBoundingClientRect(); const shiftX = e.clientX - rect.left; const shiftY = e.clientY - rect.top; function onMouseMove(e) { panel.style.left = (e.clientX - shiftX) + 'px'; panel.style.top = (e.clientY - shiftY) + 'px'; } document.addEventListener('mousemove', onMouseMove); document.onmouseup = () => { document.removeEventListener('mousemove', onMouseMove); document.onmouseup = null; }; e.preventDefault(); }; handle.ondragstart = () => false; // 3. 点击 “跳转” 按钮 的事件委托 panel.onclick = e => { if (e.target.matches('.rp-jump-btn')) { const page = parseInt(e.target.dataset.page, 10); // 当前页page const currentUrl = new URL(location.href); const currentPage = parseInt(currentUrl.searchParams.get('page'), 10) || 1; // 如果要跳转的页就是当前页 if (page === currentPage) { console.log(`Already on page ${page}, no navigation.`); return; } // 跳转 const gotoUrl = currentUrl; gotoUrl.searchParams.delete('page'); if (page > 1) gotoUrl.searchParams.set('page', page); location.href = gotoUrl.href; } if (e.target.matches('.dw-jump-btn')) { // 需要高亮的asin const highLightAsin = e.target.dataset.asin; // 只在 Amazon 搜索结果区查找&非广告位 const candidates = document.querySelectorAll(`.s-main-slot > [data-asin="${highLightAsin}"]`); const elem = Array.from(candidates).find(node => !node.querySelector('a.puis-label-popover.puis-sponsored-label-text') ); if (!elem) { return alert(`未能在当前页面找到ASIN-${highLightAsin}, 点击前方页面跳转按钮`); } // 4. 高亮 & 滚动到视图 elem.style.border = '2px solid red'; elem.style.padding = '5px'; elem.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } }; // —— 缓存清除 —— btnClearCache.addEventListener('click', () => { sessionStorage.removeItem('tm_tagAsins'); sessionStorage.removeItem('tm_results'); sessionStorage.removeItem('tm_keywords'); sessionStorage.removeItem('tm_batch_table'); // 请填写 ASIN, 点击"搜索排名 updateStatus('请填写 ASIN, 点击"搜索排名') tagAsins = []; results = {}; keywords = []; renderTags(); renderResultsPanelFromTable() const panel = document.getElementById('results-panel'); if (panel) panel.remove(); const batchPanel = document.getElementById('batch-results-panel'); if (batchPanel) batchPanel.remove(); }); // 动态加载 SheetJS(xlsx.full.min.js),确保全局有 XLSX async function loadSheetJSLib() { return new Promise((resolve, reject) => { if (window.XLSX) return resolve(); const s = document.createElement('script'); s.src = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/xlsx.full.min.js'; s.onload = () => resolve(); s.onerror = () => reject(new Error('加载 XLSX 库失败')); document.head.appendChild(s); }); } // 然后在设置 fileInput 监听之前,先调用它 await loadSheetJSLib(); // —— excel文件解析 —— fileInput.addEventListener('change', async e => { const file = e.target.files[0]; if (!file) alert('未选择文件'); // 校验文件大小:不超过 1MB const maxSize = 1 * 1024 * 1024; // 1MB if (file.size > maxSize) { alert('Excel 文件不能大于 1MB,请选择更小的文件。'); fileInput.value = ''; // 清空选中文件 return; } // 读取并解析 const data = await file.arrayBuffer(); const wb = XLSX.read(data, { type: 'array' }); const sheet = wb.Sheets[wb.SheetNames[0]]; const rows = XLSX.utils.sheet_to_json(sheet, { header: 1 }); // 只取每行第一列,过滤空值并 trim keywords = rows .map(row => row[0]) .filter(cell => typeof cell === 'string' && cell.trim().length > 0) .map(cell => cell.trim()); sessionStorage.setItem('tm_keywords', JSON.stringify(keywords)); batchSearchBtn.disabled = false; alert(`已导入并缓存 ${keywords.length} 条关键词`); console.log('keywords keywords keywords', keywords); }); // 工具-睡眠和随机数 function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function randomBetween(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } // 包装后的 fetch 函数,包含随机延迟 & 错误退避 async function fetchAsinWithDelay(keyword, asin, maxPages) { // 在真正请求之前,先等待 1-3 秒(随机) await sleep(randomBetween(3000, 5000)); try { return await fetchAsinPosition(keyword, asin, maxPages); } catch (err) { console.warn(`Request failed for ${keyword} / ${asin}:`, err); // 碰到错误(网络、429等),退避 30–60 秒再重试一次 await sleep(randomBetween(30000, 60000)); return fetchAsinPosition(keyword, asin, maxPages); } } // 批量搜索fetch函数 async function fetchAsinPosition(keyword, asin, maxPages) { const base = new URL(location.href); base.searchParams.set('k', keyword); base.searchParams.delete('page'); for (let page = 1; page <= maxPages; page++) { base.searchParams.set('page', page); const html = await fetch(base.href, { credentials: 'include' }) .then(r => r.text()); const doc = new DOMParser().parseFromString(html, 'text/html'); let nat = 0; for (const node of doc.querySelectorAll('div[data-asin]')) { if (!node.querySelector('button.a-button-text, a.a-button-text')) continue; if (node.querySelector('.puis-sponsored-label-text')) continue; // 只加自然位 nat++; if (node.getAttribute('data-asin') === asin) { return { page, position: nat }; } } } // 确保不返回undefined return { page: null, position: null }; } // 渲染批量结果面板 function renderResultsPanelFromTable(table) { let panel = document.getElementById('batch-results-panel'); if (!panel) { panel = document.createElement('div'); panel.id = 'batch-results-panel'; Object.assign(panel.style, { position: 'fixed', top: '100px', left: '10px', background: 'rgba(255,255,255,0.95)', border: '1px solid #ddd', borderRadius: '4px', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', zIndex: '9999', width: '320px', fontSize: '14px', lineHeight: '1.4', overflow: 'hidden' }); document.body.appendChild(panel); const header = document.createElement('div'); header.id = 'results-header'; header.textContent = '查询结果'; Object.assign(header.style, { cursor: 'move', background: '#f5f5f5', padding: '6px 8px', borderBottom: '1px solid #ddd', fontWeight: '600', fontSize: '16px' }); panel.appendChild(header); header.addEventListener('mousedown', e => { const rect = panel.getBoundingClientRect(); const dx = e.clientX - rect.left; const dy = e.clientY - rect.top; function mm(ev) { panel.style.left = ev.clientX - dx + 'px'; panel.style.top = ev.clientY - dy + 'px'; } document.addEventListener('mousemove', mm); document.addEventListener('mouseup', () => { document.removeEventListener('mousemove', mm); }, { once: true }); e.preventDefault(); }); } else { panel.innerHTML = ''; const header = document.createElement('div'); header.id = 'batch-results-header'; header.textContent = '查询结果'; Object.assign(header.style, { cursor: 'move', background: '#f5f5f5', padding: '6px 8px', borderBottom: '1px solid #ddd', fontWeight: '600', fontSize: '16px' }); panel.appendChild(header); header.addEventListener('mousedown', e => { const rect = panel.getBoundingClientRect(); const dx = e.clientX - rect.left; const dy = e.clientY - rect.top; function mm(ev) { panel.style.left = ev.clientX - dx + 'px'; panel.style.top = ev.clientY - dy + 'px'; } document.addEventListener('mousemove', mm); document.addEventListener('mouseup', () => { document.removeEventListener('mousemove', mm); }, { once: true }); e.preventDefault(); }); } const ul = document.createElement('ul'); ul.style.listStyle = 'none'; ul.style.padding = '8px'; ul.style.margin = '0'; table.forEach(({ keyword, asin, page, position }) => { const li = document.createElement('li'); li.style.marginBottom = '6px'; const text = document.createElement('span'); const totalRank = (page - 1) * 48 + position; text.innerHTML = `<strong>${keyword}</strong> | ASIN: ${asin} | ` + (page ? `第${page}页 第${position}位 总排名${totalRank}` : `<span style="color:#f56c6c;">未找到</span>`); li.appendChild(text); if (page) { const btnJump = document.createElement('button'); btnJump.className = 'rp-jump-btn'; btnJump.dataset.page = page; btnJump.dataset.keyword = keyword btnJump.textContent = '➡'; btnJump.style.marginLeft = '8px'; li.appendChild(btnJump); const btnLoc = document.createElement('button'); btnLoc.className = 'dw-jump-btn'; btnLoc.dataset.asin = asin; btnLoc.textContent = '📍'; btnLoc.style.marginLeft = '4px'; li.appendChild(btnLoc); } ul.appendChild(li); }); panel.appendChild(ul); panel.onclick = e => { const jump = e.target.closest('.rp-jump-btn'); if (jump) { const page = +jump.dataset.page; const keyword = jump.dataset.keyword; // 构造新的搜索 URL:带上 k=keyword 和 page=page(page>1 时) const url = new URL(window.location.origin + '/s'); url.searchParams.set('k', keyword); if (page > 1) url.searchParams.set('page', page); location.href = url.href; return; } const loc = e.target.closest('.dw-jump-btn'); if (loc) { const a = loc.dataset.asin; const nodes = Array.from(document.querySelectorAll(`.s-main-slot > [data-asin="${a}"]`)); const el = nodes.find(n => !n.querySelector('.s-sponsored-label, .puis-sponsored-label-text')); if (el) { el.style.border = '2px solid red'; el.style.padding = '5px'; el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } else { alert(`当前页未找到 ASIN:${a}`); } } }; } // excel导出函数 async function exportToExcel(data) { // data: [ { keyword, asin ,page, position }, … ] await loadSheetJSLib(); // 2. 预处理数据:page/position 为空替换为 "-" const processed = data.map(({ asin, keyword, page, position, totalRank }) => ({ asin, keyword, page: page == null ? "-" : page, position: position == null ? "-" : position, totalRank: totalRank == null ? "-" : totalRank })); // 把 JSON 转为工作表 const ws = XLSX.utils.json_to_sheet(data, { header: ['关键词', 'ASIN', '页数', '位置', '总排名'] }); // 4. 给表头加粗且居中 const range = XLSX.utils.decode_range(ws['!ref']); for (let C = range.s.c; C <= range.e.c; ++C) { const cellAddress = XLSX.utils.encode_cell({ r: 0, c: C }); const cell = ws[cellAddress]; if (!cell) continue; cell.s = { font: { bold: true }, alignment: { horizontal: 'center' } }; } // 新建工作簿并追加工作表 const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, '排名结果'); // 6. 生成文件名:YYYY/M/D-站点-AsinKwRank const host = window.location.host; const siteMap = { 'www.amazon.com': 'US', 'www.amazon.co.uk': 'UK', 'www.amazon.ca': 'CA', 'www.amazon.de': 'DE', 'www.amazon.fr': 'FR', 'www.amazon.es': 'ES', 'www.amazon.it': 'IT' }; const siteCode = siteMap[host] || host; const now = new Date(); const fileName = `${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}-${siteCode}-AsinKwRank.xlsx`; // 生成二进制数组 const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); // 创建 Blob 并触发下载 const blob = new Blob([wbout], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'asin_keyword_rankings.xlsx'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // —— 批量搜索按钮 —— batchSearchBtn.addEventListener('click', async () => { if (!keywords.length) { return alert('请先导入关键词文件'); } if (!tagAsins.length) { return alert('请先添加至少一个 ASIN'); } const tasks = []; for (const keyword of keywords) { for (const asin of tagAsins) { // {'winter gloves men', B0xxxxxxxx} tasks.push({ keyword, asin }); } } // 循环执行,每次查询一个"关键词 × ASIN" const table = []; updateStatus(`🔎 开始批量查询:${tasks.length} 次`); for (const { keyword, asin } of tasks) { updateStatus(`🔎 查询 "${keyword}" 下 ASIN-${asin}`); // fetchAsinWithDelay 返回 { page, position } const { page, position } = await fetchAsinWithDelay(keyword, asin, maxPages); const totalRank = (page - 1) * 48 + position; table.push({ keyword, asin, page, position, totalRank }); } console.log('结果table 结果table 结果table', table); sessionStorage.setItem('tm_batch_table', JSON.stringify(table)); sessionStorage.setItem('tm_tagAsins', JSON.stringify(tagAsins)); alert('搜索完成,共 ' + table.length + ' 条记录'); renderResultsPanelFromTable(table); }) // 点击时,从 sessionStorage 取出缓存的 table,并调用 exportToExcel downloadBtn.addEventListener('click', async () => { const raw = sessionStorage.getItem('tm_batch_table'); if (!raw) { return alert('当前没有可下载的查询结果,请先执行批量搜索。'); } let table; try { table = JSON.parse(raw); } catch { return alert('结果数据解析失败。'); } // 调用之前定义的导出函数 await exportToExcel(table); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址