google map scraper

google map result

目前為 2025-05-25 提交的版本,檢視 最新版本

// ==UserScript==
// @name         google map scraper
// @namespace    http://google.com/
// @version      2025-05-22
// @description  google map result
// @author       Web Automation Lover
// @match        https://www.google.com/maps/search/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=xiaohongshu.com
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.9/xlsx.full.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    window.jsonArr = [];

    const button = document.createElement('button');
    button.innerText = 'Click to Export (0)';
    button.style.marginLeft = '10px';

    button.style.backgroundColor = '#f0f0f0';
    button.style.border = '1px solid #ccc';
    button.style.borderRadius = '5px';
    button.style.padding = '5px 10px';
    button.style.fontSize = '14px';
    button.style.cursor = 'pointer';
    button.style.transition = 'background-color 0.3s';

    button.addEventListener('mouseenter', () => {
        button.style.backgroundColor = '#e0e0e0';
    });

    button.addEventListener('mouseleave', () => {
        button.style.backgroundColor = '#f0f0f0';
    });

    button.addEventListener('click', function() {
        const ws = XLSX.utils.json_to_sheet(window.jsonArr);

        const wb = XLSX.utils.book_new();
        XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');

        const wbout = XLSX.write(wb, { type: 'binary', bookType: 'xlsx' });
        const s2ab = function(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;
        };
        const blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = 'data.xlsx';
        a.click();

    });

    function updateButtonText() {
        button.innerText = 'Click to Export (' + window.jsonArr.length + ')';
    }

    const injectButton = () => {
        const targetDiv = document.querySelectorAll('#assistive-chips > div > div > div > div > div > div > div > div > div')[1];
        if (targetDiv && !document.querySelector('#my-custom-button')) {
            button.id = 'my-custom-button';
            targetDiv.appendChild(button);
        }
    };

    setInterval(injectButton, 1000);

    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;

    XMLHttpRequest.prototype.open = function(method, url) {
        this._url = url;
        return originalOpen.apply(this, arguments);
    };

    XMLHttpRequest.prototype.send = function() {
        this.addEventListener('load', function() {
            if (this._url.includes('/search?tbm=map')) {

                var rspJson = JSON.parse(this.responseText.replace(`/*""*/`,""));
                var e = rspJson.d;
                var cleanedData = e.replace(`)]}'`, "");

                let parsedData = JSON.parse(cleanedData);

                let dataList = parsedData[0][1];

                let filteredData = dataList.filter(item => {
                    return item?.[14] !== undefined;
                });

                if (!filteredData || filteredData.length < 1) {
                    filteredData = parsedData[64];
                }

                if (filteredData) {
                    var formatedData = formatAllData(filteredData)
                    window.jsonArr.push(...formatedData)
                    console.log('song jsonArr:' + window.jsonArr.length)
                    updateButtonText()
                }
            }
        });

        return originalSend.apply(this, arguments);
    };


function formatAllData(allDataList) {
    return allDataList.map(d => formatDataItem(d)).filter(d => d.name)
}

function formatDataItem(item) {
    const fieldConfig = {
        fullAddress: [39],
        placeId: [78],
        kgmid: [89],
        categories: [13],
        feature: [32, 0, 1],
        cid: [10],
        featuredImage: [37, 0, 0, 6, 0],
        phones: [],
        icon: [122, 0, 1],
        name: [11],
        latitude: [9, 2],
        longitude: [9, 3],
        reviewCount: [4, 8],
        reviewURL: [4, 3, 0],
        averageRating: [4, 7],
        street: [183, 0, 0, 1, 1],
        municipality: [183, 1, 3],
        openingHours: [],
        website: [7, 0],
        domain: [7, 1]
    }

    const resultData = {}
    Object.keys(fieldConfig).forEach(key => {
        resultData[key] = handleSingleField(fieldConfig[key])
    })
    resultData.phones = handleSingleField([178, 0, 1])?.map(d => d?.[0])
    resultData.openingHours = handleSingleField([34, 1])?.map(d => [`${d[0]}:[${d[1]}]`])?.join(', ')
    resultData.googleMapsURL = "https://www.google.com/maps?cid=".concat(resultData.cid)
    resultData.googleKnowledgeURL = "https://www.google.com/search?kgmid=".concat(resultData.kgmid, "&kponly")

    resultData.phones = resultData.phones?.join?.(', ')
    resultData.categories = resultData.categories?.join?.(', ')
    resultData.street = resultData.street?.join?.(', ')

    function handleSingleField(config) {
        const itemData = item[1]
        if (!itemData) {
            return
        }
        if (!config || !config.length) {
            return
        }
        let currentData = itemData
        for (let i = 0; i < config.length; i++) {
            currentData = currentData?.[config[i]]
        }
        return currentData
    }

    return resultData
}

})();