Greasy Fork 还支持 简体中文。

google map scraper

google map result

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

  1. // ==UserScript==
  2. // @name google map scraper
  3. // @namespace http://google.com/
  4. // @version 2025-05-22
  5. // @description google map result
  6. // @author Web Automation Lover
  7. // @match https://www.google.com/maps/search/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=xiaohongshu.com
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.9/xlsx.full.min.js
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
  11. // @grant none
  12. // @run-at document-end
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. window.jsonArr = [];
  19.  
  20. const button = document.createElement('button');
  21. button.innerText = 'Click to Export (0)';
  22. button.style.marginLeft = '10px';
  23.  
  24. button.style.backgroundColor = '#f0f0f0';
  25. button.style.border = '1px solid #ccc';
  26. button.style.borderRadius = '5px';
  27. button.style.padding = '5px 10px';
  28. button.style.fontSize = '14px';
  29. button.style.cursor = 'pointer';
  30. button.style.transition = 'background-color 0.3s';
  31.  
  32. button.addEventListener('mouseenter', () => {
  33. button.style.backgroundColor = '#e0e0e0';
  34. });
  35.  
  36. button.addEventListener('mouseleave', () => {
  37. button.style.backgroundColor = '#f0f0f0';
  38. });
  39.  
  40. button.addEventListener('click', function() {
  41. const ws = XLSX.utils.json_to_sheet(window.jsonArr);
  42.  
  43. const wb = XLSX.utils.book_new();
  44. XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
  45.  
  46. const wbout = XLSX.write(wb, { type: 'binary', bookType: 'xlsx' });
  47. const s2ab = function(s) {
  48. const buf = new ArrayBuffer(s.length);
  49. const view = new Uint8Array(buf);
  50. for (let i = 0; i < s.length; i++) view[i] = s.charCodeAt(i) & 0xFF;
  51. return buf;
  52. };
  53. const blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' });
  54. const a = document.createElement('a');
  55. a.href = URL.createObjectURL(blob);
  56. a.download = 'data.xlsx';
  57. a.click();
  58.  
  59. });
  60.  
  61. function updateButtonText() {
  62. button.innerText = 'Click to Export (' + window.jsonArr.length + ')';
  63. }
  64.  
  65. const injectButton = () => {
  66. const targetDiv = document.querySelectorAll('#assistive-chips > div > div > div > div > div > div > div > div > div')[1];
  67. if (targetDiv && !document.querySelector('#my-custom-button')) {
  68. button.id = 'my-custom-button';
  69. targetDiv.appendChild(button);
  70. }
  71. };
  72.  
  73. setInterval(injectButton, 1000);
  74.  
  75. const originalOpen = XMLHttpRequest.prototype.open;
  76. const originalSend = XMLHttpRequest.prototype.send;
  77.  
  78. XMLHttpRequest.prototype.open = function(method, url) {
  79. this._url = url;
  80. return originalOpen.apply(this, arguments);
  81. };
  82.  
  83. XMLHttpRequest.prototype.send = function() {
  84. this.addEventListener('load', function() {
  85. if (this._url.includes('/search?tbm=map')) {
  86.  
  87. var rspJson = JSON.parse(this.responseText.replace(`/*""*/`,""));
  88. var e = rspJson.d;
  89. var cleanedData = e.replace(`)]}'`, "");
  90.  
  91. let parsedData = JSON.parse(cleanedData);
  92.  
  93. let dataList = parsedData[0][1];
  94.  
  95. let filteredData = dataList.filter(item => {
  96. return item?.[14] !== undefined;
  97. });
  98.  
  99. if (!filteredData || filteredData.length < 1) {
  100. filteredData = parsedData[64];
  101. }
  102.  
  103. if (filteredData) {
  104. var formatedData = formatAllData(filteredData)
  105. window.jsonArr.push(...formatedData)
  106. console.log('song jsonArr:' + window.jsonArr.length)
  107. updateButtonText()
  108. }
  109. }
  110. });
  111.  
  112. return originalSend.apply(this, arguments);
  113. };
  114.  
  115.  
  116. function formatAllData(allDataList) {
  117. return allDataList.map(d => formatDataItem(d)).filter(d => d.name)
  118. }
  119.  
  120. function formatDataItem(item) {
  121. const fieldConfig = {
  122. fullAddress: [39],
  123. placeId: [78],
  124. kgmid: [89],
  125. categories: [13],
  126. feature: [32, 0, 1],
  127. cid: [10],
  128. featuredImage: [37, 0, 0, 6, 0],
  129. phones: [],
  130. icon: [122, 0, 1],
  131. name: [11],
  132. latitude: [9, 2],
  133. longitude: [9, 3],
  134. reviewCount: [4, 8],
  135. reviewURL: [4, 3, 0],
  136. averageRating: [4, 7],
  137. street: [183, 0, 0, 1, 1],
  138. municipality: [183, 1, 3],
  139. openingHours: [],
  140. website: [7, 0],
  141. domain: [7, 1]
  142. }
  143.  
  144. const resultData = {}
  145. Object.keys(fieldConfig).forEach(key => {
  146. resultData[key] = handleSingleField(fieldConfig[key])
  147. })
  148. resultData.phones = handleSingleField([178, 0, 1])?.map(d => d?.[0])
  149. resultData.openingHours = handleSingleField([34, 1])?.map(d => [`${d[0]}:[${d[1]}]`])?.join(', ')
  150. resultData.googleMapsURL = "https://www.google.com/maps?cid=".concat(resultData.cid)
  151. resultData.googleKnowledgeURL = "https://www.google.com/search?kgmid=".concat(resultData.kgmid, "&kponly")
  152.  
  153. resultData.phones = resultData.phones?.join?.(', ')
  154. resultData.categories = resultData.categories?.join?.(', ')
  155. resultData.street = resultData.street?.join?.(', ')
  156.  
  157. function handleSingleField(config) {
  158. const itemData = item[1]
  159. if (!itemData) {
  160. return
  161. }
  162. if (!config || !config.length) {
  163. return
  164. }
  165. let currentData = itemData
  166. for (let i = 0; i < config.length; i++) {
  167. currentData = currentData?.[config[i]]
  168. }
  169. return currentData
  170. }
  171.  
  172. return resultData
  173. }
  174.  
  175. })();