抓点亚马逊和速卖通的搜索数据

抓取亚马逊和速卖通商品数据并导出到Excel,支持多页抓取和自定义页面范围

// ==UserScript==
// @name         抓点亚马逊和速卖通的搜索数据
// @namespace    http://tampermonkey.net/
// @version      1.0.2
// @description  抓取亚马逊和速卖通商品数据并导出到Excel,支持多页抓取和自定义页面范围
// @author       Meng
// @license      MIT
// @homepage     https://github.com/yourusername/amazon-ali-scraper
// @supportURL   https://github.com/yourusername/amazon-ali-scraper/issues
// @match        https://www.amazon.com/s*
// @match        https://www.aliexpress.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @icon         https://www.google.com/s2/favicons?domain=amazon.com
// ==/UserScript==

/*
 * 使用说明:
 * 1. 安装要求:
 *    - 安装Tampermonkey浏览器扩展
 *    - 支持Chrome、Firefox、Edge等主流浏览器
 * 
 * 2. 功能特点:
 *    - 支持亚马逊和速卖通商品数据抓取
 *    - 可自定义抓取页面范围
 *    - 自动导出Excel文件
 *    - 支持多页连续抓取
 * 
 * 3. 使用方法:
 *    - 在亚马逊或速卖通搜索页面运行脚本
 *    - 输入要抓取的起始页码和结束页码
 *    - 点击对应的导出按钮
 *    - 等待抓取完成,自动下载Excel文件
 * 
 * 4. 注意事项:
 *    - 请合理设置抓取间隔,避免对网站造成压力
 *    - 建议每次抓取不超过50页
 *    - 如遇到问题,请查看浏览器控制台日志
 * 
 * 5. 更新日志:
 *    v1.0.0 (2024-03-31)
 *    - 首次发布
 *    - 支持亚马逊和速卖通数据抓取
 *    - 支持自定义页面范围
 *    - 自动导出Excel文件
 */

(function() {
    'use strict';

    // 添加导出按钮
    function addExportButton() {
        const buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 9999;
            display: flex;
            flex-direction: column;
            gap: 10px;
            background-color: white;
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        `;

        // 添加页面范围输入框
        const inputContainer = document.createElement('div');
        inputContainer.style.cssText = `
            display: flex;
            flex-direction: row;
            gap: 5px;
            margin-bottom: 10px;
        `;

        const startPageInput = document.createElement('input');
        startPageInput.type = 'number';
        startPageInput.placeholder = '起始页码';
        startPageInput.min = '1';
        startPageInput.value = '1';
        startPageInput.style.cssText = `
            padding: 5px;
            border: 1px solid #ccc;
            border-radius: 4px;
            width: 100px;
        `;

        const endPageInput = document.createElement('input');
        endPageInput.type = 'number';
        endPageInput.placeholder = '结束页码';
        endPageInput.min = '1';
        endPageInput.style.cssText = `
            padding: 5px;
            border: 1px solid #ccc;
            border-radius: 4px;
            width: 100px;
        `;

        inputContainer.appendChild(startPageInput);
        inputContainer.appendChild(endPageInput);
        buttonContainer.appendChild(inputContainer);

        // 判断当前网站
        const isAmazon = window.location.hostname.includes('amazon');
        const isAliExpress = window.location.hostname.includes('aliexpress');

        if (isAmazon) {
            // Amazon按钮
            const amazonButton = document.createElement('button');
            amazonButton.innerHTML = '导出Amazon商品数据';
            amazonButton.style.cssText = `
                padding: 10px 20px;
                background-color: #FF9900;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
            `;
            amazonButton.onclick = () => {
                const startPage = parseInt(startPageInput.value) || 1;
                const endPage = parseInt(endPageInput.value);
                if (endPage && endPage < startPage) {
                    alert('结束页码不能小于起始页码!');
                    return;
                }
                scrapeAndExport(startPage, endPage);
            };
            buttonContainer.appendChild(amazonButton);
        }

        if (isAliExpress) {
            // AliExpress按钮
            const aliButton = document.createElement('button');
            aliButton.innerHTML = '导出AliExpress商品数据';
            aliButton.style.cssText = `
                padding: 10px 20px;
                background-color: #E62E04;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
            `;
            aliButton.onclick = () => {
                const startPage = parseInt(startPageInput.value) || 1;
                const endPage = parseInt(endPageInput.value);
                if (endPage && endPage < startPage) {
                    alert('结束页码不能小于起始页码!');
                    return;
                }
                scrapeAliExpress(startPage, endPage);
            };
            buttonContainer.appendChild(aliButton);
        }

        document.body.appendChild(buttonContainer);
    }

    // 添加加载提示
    function showLoading(message) {
        let loadingDiv = document.getElementById('amazon-scraper-loading');
        if (!loadingDiv) {
            loadingDiv = document.createElement('div');
            loadingDiv.id = 'amazon-scraper-loading';
            loadingDiv.style.cssText = `
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background-color: rgba(0, 0, 0, 0.8);
                color: white;
                padding: 20px;
                border-radius: 8px;
                z-index: 10000;
                text-align: center;
            `;
            document.body.appendChild(loadingDiv);
        }
        loadingDiv.innerHTML = message;
    }

    // 隐藏加载提示
    function hideLoading() {
        const loadingDiv = document.getElementById('amazon-scraper-loading');
        if (loadingDiv) {
            loadingDiv.remove();
        }
    }

    // 获取商品数据
    async function getProductData(startPage = 1, endPage = null) {
        const products = [];
        let currentPage = startPage;
        let hasNextPage = true;

        // 如果当前不在起始页,需要先跳转到起始页
        if (currentPage > 1) {
            showLoading(`正在跳转到第 ${currentPage} 页...`);
            // 构建URL跳转到指定页面
            const currentUrl = new URL(window.location.href);
            currentUrl.searchParams.set('page', currentPage);
            window.location.href = currentUrl.toString();
            return products; // 页面会刷新,所以这里直接返回
        }
        
        while (hasNextPage) {
            showLoading(`正在抓取第 ${currentPage} 页数据...`);
            
            const productElements = Array.from(document.querySelectorAll('div[data-asin][data-component-type="s-search-result"]'));
            console.log(`当前页面找到 ${productElements.length} 个商品`);
            
            for (const product of productElements) {
                const asin = product.getAttribute('data-asin');
                if (!asin) continue;

                // 获取标题
                let title = '';
                try {
                    const h2WithAriaLabel = product.querySelector('h2[aria-label]');
                    if (h2WithAriaLabel) {
                        title = h2WithAriaLabel.getAttribute('aria-label');
                    } else {
                        title = product.querySelector('h2 span').textContent.trim();
                    }
                } catch (e) {
                    title = '无标题';
                }

                // 获取价格
                let price = '';
                try {
                    const priceElement = product.querySelector('.a-price .a-offscreen');
                    if (priceElement) {
                        price = priceElement.textContent.trim();
                    } else {
                        const listPriceElement = product.querySelector('.a-text-price .a-offscreen');
                        if (listPriceElement) {
                            price = listPriceElement.textContent.trim();
                        }
                    }
                } catch (e) {
                    price = '无价格';
                }

                products.push({
                    title: title,
                    price: price
                });
            }

            // 检查是否达到结束页
            if (endPage && currentPage >= endPage) {
                console.log(`已达到指定的结束页 ${endPage},停止抓取`);
                break;
            }

            // 检查是否有下一页
            const nextButton = document.querySelector('a.s-pagination-next:not(.s-pagination-disabled)');
            if (nextButton) {
                console.log('找到下一页按钮,准备翻页');
                nextButton.click();
                await new Promise(resolve => setTimeout(resolve, 3000));
                currentPage++;
            } else {
                console.log('没有找到下一页按钮,停止抓取');
                hasNextPage = false;
            }
        }

        console.log(`总共抓取了 ${products.length} 条数据`);
        return products;
    }

    // 导出到Excel
    function exportToExcel(products, startPage, endPage) {
        // 创建工作簿
        const wb = XLSX.utils.book_new();
        
        // 准备数据
        const data = [
            ['标题', '价格'] // 表头
        ];
        
        // 添加商品数据
        products.forEach(product => {
            data.push([
                product.title,
                product.price
            ]);
        });

        // 创建工作表
        const ws = XLSX.utils.aoa_to_sheet(data);
        
        // 将工作表添加到工作簿
        XLSX.utils.book_append_sheet(wb, ws, '商品信息');

        // 生成Excel文件
        const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'binary' });

        // 转换二进制数据为Blob
        const blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' });

        // 下载文件
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `amazon_products_${startPage}-${endPage || 'end'}.xlsx`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }

    // 辅助函数:字符串转ArrayBuffer
    function s2ab(s) {
        const buf = new ArrayBuffer(s.length);
        const view = new Uint8Array(buf);
        for (let i = 0; i < s.length; i++) {
            view[i] = s.charCodeAt(i) & 0xFF;
        }
        return buf;
    }

    // 主函数:抓取数据并导出
    async function scrapeAndExport(startPage = 1, endPage = null) {
        try {
            showLoading('开始导出数据...');
            const products = await getProductData(startPage, endPage);
            showLoading(`共抓取 ${products.length} 条数据,正在生成Excel文件...`);
            exportToExcel(products, startPage, endPage);
            hideLoading();
            alert(`数据导出完成!共导出 ${products.length} 条数据`);
        } catch (error) {
            console.error('导出失败:', error);
            alert('导出失败,请查看控制台获取详细信息');
            hideLoading();
        }
    }

    // 获取速卖通商品数据
    async function getAliExpressData(startPage = 1, endPage = null) {
        const products = [];
        let currentPage = startPage;
        let hasNextPage = true;
        
        // 如果当前不在起始页,需要先跳转到起始页
        if (currentPage > 1) {
            showLoading(`正在跳转到第 ${currentPage} 页...`);
            // 这里需要实现跳转到指定页面的逻辑
            // 由于速卖通可能没有直接的页面跳转,我们可能需要多次点击下一页
            for (let i = 1; i < currentPage; i++) {
                const nextButton = findNextButton();
                if (nextButton) {
                    nextButton.click();
                    await new Promise(resolve => setTimeout(resolve, 3000));
                } else {
                    alert(`无法跳转到第 ${currentPage} 页,请确保页面存在!`);
                    return products;
                }
            }
        }
        
        while (hasNextPage) {
            showLoading(`正在抓取第 ${currentPage} 页数据...`);
            
            // 获取当前页面的商品
            const productElements = document.querySelectorAll('div.lj_z');
            console.log(`当前页面找到 ${productElements.length} 个商品`);
            
            for (const product of productElements) {
                // 获取标题
                let title = '';
                try {
                    const titleElement = product.querySelector('h3.lj_jz');
                    if (titleElement) {
                        title = titleElement.textContent.trim();
                    }
                } catch (e) {
                    title = '无标题';
                }

                // 获取价格
                let price = '';
                try {
                    const priceElement = product.querySelector('div.lj_k1');
                    if (priceElement) {
                        price = priceElement.textContent.trim();
                    }
                } catch (e) {
                    price = '无价格';
                }

                products.push({
                    title: title,
                    price: price
                });
            }

            // 检查是否达到结束页
            if (endPage && currentPage >= endPage) {
                console.log(`已达到指定的结束页 ${endPage},停止抓取`);
                break;
            }

            // 检查是否有下一页
            const nextButton = findNextButton();
            if (nextButton) {
                console.log('找到下一页按钮,准备翻页');
                nextButton.click();
                await new Promise(resolve => setTimeout(resolve, 3000));
                currentPage++;
            } else {
                console.log('没有找到下一页按钮,停止抓取');
                hasNextPage = false;
            }
        }

        console.log(`总共抓取了 ${products.length} 条数据`);
        return products;
    }

    // 查找下一页按钮
    function findNextButton() {
        const nextButtonSelectors = [
            'button.comet-pagination-item-link[aria-label="Next"]',
            'button.comet-pagination-item-link:not([disabled])',
            'button.comet-pagination-item-link',
            'a.comet-pagination-item-link',
            'a[aria-label="Next"]',
            'a[rel="next"]'
        ];

        for (const selector of nextButtonSelectors) {
            const button = document.querySelector(selector);
            if (button && !button.disabled) {
                return button;
            }
        }
        return null;
    }

    // 导出速卖通数据
    async function scrapeAliExpress(startPage = 1, endPage = null) {
        try {
            showLoading('开始导出速卖通数据...');
            const products = await getAliExpressData(startPage, endPage);
            showLoading(`共抓取 ${products.length} 条数据,正在生成Excel文件...`);
            
            // 创建工作簿
            const wb = XLSX.utils.book_new();
            
            // 准备数据
            const data = [
                ['标题', '价格'] // 表头
            ];
            
            // 添加商品数据
            products.forEach(product => {
                data.push([
                    product.title,
                    product.price
                ]);
            });

            // 创建工作表
            const ws = XLSX.utils.aoa_to_sheet(data);
            
            // 将工作表添加到工作簿
            XLSX.utils.book_append_sheet(wb, ws, '速卖通商品信息');

            // 生成Excel文件
            const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'binary' });

            // 转换二进制数据为Blob
            const blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' });

            // 下载文件
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `aliexpress_products_${startPage}-${endPage || 'end'}.xlsx`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            
            hideLoading();
            alert(`速卖通数据导出完成!共导出 ${products.length} 条数据`);
        } catch (error) {
            console.error('导出失败:', error);
            alert('导出失败,请查看控制台获取详细信息');
            hideLoading();
        }
    }

    // 页面加载完成后添加导出按钮
    window.addEventListener('load', () => {
        addExportButton();
    });
})();