Amazon keywords Positioning by Asin

1.在亚马逊搜索结果页上定位ASIN, 获取排名 2.代码重构————dom操作->fetch+DOMParser 3.结果面板

// ==UserScript==
// @name         Amazon keywords Positioning by Asin
// @namespace    http://tampermonkey.net/
// @version      2.3.1
// @description  1.在亚马逊搜索结果页上定位ASIN, 获取排名 2.代码重构————dom操作->fetch+DOMParser 3.结果面板
// @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==


(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;
        padding: 6px 12px
        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);
    }

    /* 按钮 */
    #tm-asin-container button {
        margin-right: 12px;
        padding: 5px 10px;
        font-size: 14px;
        font-weight: 600;
        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[disabled] {
        background-color: #c0c4cc;
        border-color: #c0c4cc;
        cursor: not-allowed;
    }
    #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;
        font-size: 16px;
        color:rgb(110, 111, 111);
    }
    `;

    // —— 状态 ——  
    let targetASIN = '';
    let maxPages = DEFAULT_MAX_PAGES;

    // —— 注入样式 & 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.value = DEFAULT_MAX_PAGES;
    inputPages.style.width = '60px';
    // search button
    const btnSearch = document.createElement('button');
    btnSearch.textContent = '搜索排名';
    // result goto
    const btnGoto = document.createElement('button');
    btnGoto.textContent = '跳转到结果页';
    btnGoto.disabled = true;
    // status的div的元素
    const status = document.createElement('span');
    status.setAttribute("id", "tm-status");
    status.textContent = '请填写 ASIN,点击“搜索排名”';
    /* 动画过渡——container栏的伸缩 */
    container.style.transition = 'top 0.4s ease';
    // scroll事件-待优化
    let isScrolling;
    let lastScrollY = window.scrollY;
    window.addEventListener("scroll", () => {
        // 清除之前的计时器,避免频繁触发
        window.cancelAnimationFrame(isScrolling);

        // 用 requestAnimationFrame 优化性能
        isScrolling = window.requestAnimationFrame(() => {
            const currentScrollY = window.scrollY;
            const isScrollingDown = currentScrollY > lastScrollY;

            container.style.top = isScrollingDown ? "0px" : "55px";
            lastScrollY = currentScrollY;
        });
    });

    //tag-wrapper-2 初始化数据
    let tagAsins = []
    const maxTags = 3

    // 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,接收事件对象     
        function 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();

    [maxPageText, inputPages, btnSearch, btnGoto, status].forEach(el => container.appendChild(el));
    document.body.appendChild(container);

    // —— 状态更新 ——  
    const updateStatus = txt => { status.textContent = txt; };

    // —— 主搜索逻辑 ——  
    btnSearch.addEventListener('click', async () => {
        // search-1 参数
        maxPages = parseInt(inputPages.value, 10) || DEFAULT_MAX_PAGES;
        btnGoto.disabled = true;
        if (!tagAsins.length) return alert('请先添加至少一个 ASIN!');
        // search-2 初始化结果存储
        const 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');
                isAd ? sp++ : nat++;
                // 如果这个 ASIN 在 tagAsins 列表里,且还没找到,就记录它
                if (tagAsins.includes(asin) && !results[asin].found) {
                    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 已定位`);
            btnGoto.disabled = false;
            // 跳转到最后一页查看详情
            btnGoto.onclick = () => location.href =
                new URL(baseUrl).searchParams.set('page', results[tagAsins[tatagAsinsgs.length - 1]].page) && baseUrl.href;
        }
        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: '10px',
                background: 'rgba(255,255,255,0.95)',
                padding: '10px',
                border: '1px solid #ddd',
                borderRadius: '4px',
                boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
                zIndex: '9999',
                fontSize: '14px',
                maxWidth: '270px',
                lineHeight: '1.4'
            });
            document.body.appendChild(panel);
        }

        let html = '<h4 style="margin:0 0 8px 0;font-size:16px;">查询结果</h4>';
        html += '<ul style="list-style:none;padding:0;margin:0;">';
        for (const asin in results) {
            const r = results[asin];
            html += '<li  style="margin-bottom:4px;">';
            html += `<strong>${asin}</strong>:`;
            if (r.found) {
                html += `第${r.page}页,第${r.position}位`;
                if (r.isAd) html += '(广告位)';
            } else {
                html += `<span style="color:#f56c6c;list-style-type: none !important;">未找到</span>`;
            }
            html += '</li>';
        }
        html += '</ul>';

        panel.innerHTML = html;

        // 可拖拽逻辑开始
        panel.style.cursor = 'move'; // 显示拖拽手型
        panel.onmousedown = function (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 = function () {
                document.removeEventListener('mousemove', onMouseMove);
                document.onmouseup = null;
            };

            // 阻止默认拖拽行为
            e.preventDefault();
        };
        panel.ondragstart = () => false;
        // 可拖拽逻辑结束
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址