Google Maps Reviews Scraper & Exporter

Scrapes reviews from Google Maps with a floating interface and improved functionality

  1. // ==UserScript==
  2. // @name Google Maps Reviews Scraper & Exporter
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.13
  5. // @description Scrapes reviews from Google Maps with a floating interface and improved functionality
  6. // @author sharmanhall
  7. // @match https://www.google.com/maps/place/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
  9. // @grant GM_addStyle
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // Add styles for floating panel
  17. GM_addStyle(`
  18. #review-scraper-panel {
  19. position: fixed;
  20. bottom: 20px;
  21. right: 20px;
  22. background-color: #fff;
  23. border: 1px solid #ccc;
  24. border-radius: 8px;
  25. padding: 10px;
  26. box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  27. z-index: 1000;
  28. font-family: Arial, sans-serif;
  29. }
  30. #review-scraper-panel button {
  31. background-color: #4285f4;
  32. color: white;
  33. border: none;
  34. padding: 8px 12px;
  35. margin: 5px;
  36. border-radius: 4px;
  37. cursor: pointer;
  38. font-size: 14px;
  39. }
  40. #review-scraper-panel button:hover {
  41. background-color: #3367d6;
  42. }
  43. #scraper-status {
  44. margin-top: 10px;
  45. font-size: 14px;
  46. }
  47. `);
  48.  
  49. // Create floating panel
  50. const panel = document.createElement('div');
  51. panel.id = 'review-scraper-panel';
  52. panel.innerHTML = `
  53. <button id="scrape-reviews">Scrape Reviews</button>
  54. <button id="copy-to-clipboard">Copy to Clipboard</button>
  55. <div id="scraper-status"></div>
  56. `;
  57. document.body.appendChild(panel);
  58.  
  59. const setStatus = (message) => {
  60. document.getElementById('scraper-status').textContent = message;
  61. };
  62.  
  63. const autoScroll = async () => {
  64. setStatus('Scrolling to load reviews...');
  65. let lastScrollHeight = 0, currentScrollHeight = document.documentElement.scrollHeight;
  66. do {
  67. lastScrollHeight = currentScrollHeight;
  68. window.scrollTo(0, currentScrollHeight);
  69. await new Promise(r => setTimeout(r, 2000));
  70. currentScrollHeight = document.documentElement.scrollHeight;
  71. } while(currentScrollHeight > lastScrollHeight);
  72. window.scrollTo(0, 0);
  73. };
  74.  
  75. const expandReviews = async () => {
  76. setStatus('Expanding truncated reviews...');
  77. const moreButtons = document.querySelectorAll("button.w8nwRe.kyuRq");
  78. for (const button of moreButtons) {
  79. button.click();
  80. await new Promise(r => setTimeout(r, 500));
  81. }
  82. };
  83.  
  84. const scrapeReviews = async () => {
  85. await autoScroll();
  86. await expandReviews();
  87.  
  88. setStatus('Scraping reviews...');
  89. const reviewDivs = document.querySelectorAll("div[data-review-id]");
  90. const reviews = [];
  91. const scrapedReviewIds = new Set();
  92.  
  93. for (const reviewDiv of reviewDivs) {
  94. const reviewId = reviewDiv.getAttribute("data-review-id");
  95. if (scrapedReviewIds.has(reviewId)) continue;
  96.  
  97. const review = {
  98. reviewer_name: reviewDiv.querySelector("div.d4r55")?.textContent.trim() || '',
  99. img_url: reviewDiv.querySelector("img.NBa7we")?.src.replace('=w36-h36-p-rp-mo-br100', '=s0') || '',
  100. review_date: reviewDiv.querySelector("span.rsqaWe")?.textContent.trim() || '',
  101. star_rating: parseInt(reviewDiv.querySelector("span.kvMYJc[role='img']")?.getAttribute("aria-label").match(/(\d+)/)?.[0] || '0', 10),
  102. review_url: reviewDiv.querySelector("button[data-href]")?.getAttribute("data-href") || '',
  103. review_content: reviewDiv.querySelector("span.wiI7pd")?.textContent.trim() || ''
  104. };
  105.  
  106. scrapedReviewIds.add(reviewId);
  107. reviews.push(review);
  108. }
  109.  
  110. setStatus(`Scraped ${reviews.length} reviews.`);
  111. return reviews;
  112. };
  113.  
  114. const copyToClipboard = async () => {
  115. const reviews = await scrapeReviews();
  116. const contentToCopy = JSON.stringify(reviews, null, 2);
  117. navigator.clipboard.writeText(contentToCopy).then(() => {
  118. setStatus('Content copied to clipboard!');
  119. }).catch(err => {
  120. setStatus('Error copying to clipboard. See console for details.');
  121. console.error("Could not copy content to clipboard: ", err);
  122. });
  123. };
  124.  
  125. document.getElementById('scrape-reviews').addEventListener('click', scrapeReviews);
  126. document.getElementById('copy-to-clipboard').addEventListener('click', copyToClipboard);
  127. })();

QingJ © 2025

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