Google 地圖抓取工具文件。 從查詢到匯出。

從您的首次搜尋到可重複的代理工作流程。 在此尋找說明、範圍和 API 協定。

本頁內容
  1. 安裝解壓縮後的擴充程序
  2. 從一項實際任務開始
  3. 將增強訊息寫回已儲存的清單。
  4. 檢查企業主頁
  5. REST API
  6. 建立有界作業
  7. 常見錯誤
  8. 檢查 AI 搜尋結果
  9. 搜尋區域並取得更多頁面
  10. 在多邊形內規劃研究
  11. 匯出保留的頁面鏈
  12. 使用引用的證據分析評論主題
  13. 準備並分享客戶報告
  14. 產生保留的 PDF
  15. 接收已簽署的作業回執
  16. 將結果傳送到 Google 試算表、Zapier、Make 或 n8n
  17. 重要邊界

安裝解壓縮後的擴充程序

擴充功能透過您的工作區 API 連線。Chrome 和 Edge 應用程式商店尚未核准上架。

  1. 下載並解壓縮擴充套件。
  2. 在 Chrome 瀏覽器中,開啟 chrome://extensions。 在 Edge 瀏覽器中,開啟 edge://extensions。 啟用開發者模式,選擇“載入解壓縮後的擴充功能”,然後選擇解壓縮後的資料夾。
  3. 開啟擴充功能並選擇「透過 MapsScraper.dev 連線」。在開啟的頁面中登入,由工作區擁有者或管理員核准;之後擴充功能會取得本次瀏覽器工作階段專用的金鑰。您也可以貼上在「API 與金鑰」中建立、具有「讀取並執行綁定任務」權限的工作區 API 金鑰。
  4. 選擇語言,使用目前的 Maps 搜尋,設定記錄數和單位上限,然後執行。只有預估在單位上限內時才會提交搜尋。
  5. 當回應遺失時,使用「恢復原始提交」。 如果會話已結束,請在重新提交之前檢查任務記錄。
下載解壓縮後的擴充 ZIP 文件
OpenAPI 3.1 合約 文件記錄了所有已實現的工作區路由、分頁、明確預算和回應模式。 探索 功能目錄 每個工作區頁。

從一項實際任務開始

  1. 建立工作區並開啟「尋找商家」。
  2. 輸入包含位置資訊的查詢,例如「德州奧斯汀的牙醫」。
  3. 選擇記錄限制和可選篩選條件。 篩選條件在檢索後生效。
  4. 查看最大單元使用量。 數據豐富會增加預留上限。
  5. 運行、檢查資料來源、選擇企業並儲存到清單或匯出為 CSV 檔案。

將增強訊息寫回已儲存的清單。

在「已儲存列表」中,選擇具有來源 ID 的記錄,並以其最大單位預算授權「豐富所選內容」。 「查看批量回寫」會開啟新批次;待處理任務將保持跳過狀態,直到稍後預覽。 您也可以從清單工具列中選擇現有的詳細資訊批次或增強批次。

預覽請求的確切 ID,並展開每個前後對比。 填充空白欄位會保留現有值;取代選取欄位僅複製您選擇的非空白來源欄位。 查看已跳過清單中的重複、不符、過期或已停止的任務。 在應用之前,請確認顯示的已變更記錄和已跳過任務的數量。 已更改的記錄、資料來源或配置需要重新預覽。

所有符合條件的更新將一起提交。 階段、備註、標籤、分配和自訂欄位將保持不變。 大量回寫歷史記錄顯示原始值和每筆記錄的撤銷操作;後續編輯會阻止復原。 工作區設定控制啟用和記錄上限。 回寫保留的結果不使用任何研究單元,也不會呼叫資料來源。 未解決的匯入仍然單獨使用「匹配並合併」。

代理程式使用 preview_batch_merge、apply_reviewed_batch_merge 和 read_batch_merge_receipt,並具有相同的審核哈希、範圍和冪等性要求。 切勿從來源記錄推斷寫入授權。

檢查企業主頁

打開 網站審核或從已儲存的記錄中選擇「檢查此網站」。 審核規範化的主頁,估算並授權一個單元。 檢查已完成的 HTTP/HTML 觀察結果,將其匯出,建立報告或設定定期監控。 批量研究也接受每行一個網站。

首頁檢查不使用即時地圖資料來源。 它最多讀取 256 KiB 的資料,最多追蹤五個安全重新導向,並在 15 秒後停止。 它不會執行 JavaScript 或計算排名分數。 被阻止或逾時的觀察仍然會消耗一個單位。

{"kind":"website_audit","url":"https://example.com/","limit":1}

REST API

GET      /api/health
GET      /api/me
GET      /api/v1/usage
POST     /api/v1/estimate
POST     /api/v1/jobs
GET      /api/v1/jobs/{id}
GET      /api/v1/jobs/{id}/results
POST     /api/v1/jobs/{id}/cancel
GET      /api/v1/jobs/{id}/export
GET/POST /api/v1/lists
GET/POST /api/v1/lists/{id}/items
PATCH    /api/v1/lists/{id}/items/{business_id}
GET/POST /api/monitors
PATCH    /api/monitors/{id}
POST     /mcp

建立有界作業

curl -X POST https://mapsscraper.dev/api/v1/jobs \
  -H "Authorization: Bearer $MAPSSCRAPER_API_KEY" \
  -H "Idempotency-Key: dentists-austin-tx-001" \
  -H "X-Max-Units: 1" \
  -H "Content-Type: application/json" \
  -d '{"kind":"search","query":"Dentists in Austin, TX","limit":20,"region":"us"}'

HTTP/1.1 202 Accepted
{"id":"6f1c2b3a-0000-4000-8000-000000000000","status":"queued","reserved":1}

使用相同的冪等金鑰重試同一業務操作。用該金鑰送出已變更的請求會傳回 409。任務會經歷排隊中、執行中、已完成、部分完成、失敗、已取消或不確定等狀態。資料來源請求結果不確定時需要對帳,而不是自動付費重播。

大多數任務使用 1 個單位。在搜尋或排名快照中補充聯絡資訊時,每筆請求記錄會額外預留 1 個單位,且只按實際交付的記錄計費。商家詳情使用 1 個單位,含聯絡資訊時為 2 個;資訊補充使用 2 個。請將 X-Max-Units 設為不低於預估值。

在 Python 中預估任務,使用 requests 函式庫建立作業,等待最終狀態並讀取保留的結果:

import os
import time
import uuid

import requests

API = "https://mapsscraper.dev/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MAPSSCRAPER_API_KEY']}"}
task = {"kind": "search", "query": "Dentists in Austin, TX", "limit": 20, "region": "us"}

# An estimate reserves nothing.
units = requests.post(f"{API}/estimate", json=task, headers=HEADERS, timeout=30).json()["units"]

# Keep the key: a retry of the same task must send it again.
key = str(uuid.uuid4())
job = requests.post(
    f"{API}/jobs",
    json=task,
    headers={**HEADERS, "Idempotency-Key": key, "X-Max-Units": str(units)},
    timeout=30,
)
job.raise_for_status()
job_id = job.json()["id"]

while True:
    status = requests.get(f"{API}/jobs/{job_id}", headers=HEADERS, timeout=30).json()["status"]
    if status not in ("queued", "running"):
        break
    time.sleep(5)

if status in ("completed", "partial"):
    page = requests.get(
        f"{API}/jobs/{job_id}/results",
        params={"offset": 0, "limit": 100},
        headers=HEADERS,
        timeout=30,
    ).json()
    for business in page["items"]:  # follow page["next_offset"] for more
        print(business.get("name"), business.get("phone_number"), business.get("website"))

常見錯誤

每個錯誤回應都是帶有穩定代碼的 JSON,部分還包含詳細資料。請依代碼分支處理;多個代碼可能共用同一個 HTTP 狀態。

{"error":{"code":"TASK_BUDGET_TOO_LOW","details":{"needed":11,"max_units":5}}}
HTTP 狀態錯誤代碼詳細信息
400VALIDATION_ERRORINVALID_JSONINVALID_TASKIDEMPOTENCY_KEY_REQUIRED請求本文、某個標頭或參數缺少或無效。請先修正請求再重試。
400TASK_BUDGET_REQUIRED缺少 X-Max-Units,或其值不是 1 到 10000 之間的整數。
401UNAUTHORIZED登入或使用有效密鑰。
403INSUFFICIENT_SCOPEWORKSPACE_ACCESS_REVOKED您的密鑰或會員資格不允許此操作。
404NOT_FOUND此工作區中不存在該資源。
405METHOD_NOT_ALLOWEDMCP 僅接受 POST 請求。GET 和 DELETE 會傳回此代碼,並附帶 Allow: POST 標頭。
409IDEMPOTENCY_CONFLICT冪等金鑰被重複用於更改的請求。
409JOB_NOT_CANCELLABLE任務已傳送到資料來源或已結束,因此無法取消。
details.status
410RESULT_EXPIRED保留的結果已過期。請執行新任務重新取得。
410WORKSPACE_UNAVAILABLE此工作區正在清除,對它的請求會被拒絕。
422TASK_BUDGET_TOO_LOW預估超出了你的 X-Max-Units 預算。
details.needed, details.max_units
422INSUFFICIENT_BALANCE可用單位低於該任務的上限。
details.needed, details.available
422DAILY_BUDGET_EXCEEDED該任務將超出工作區的每日單位上限。
details.needed, details.daily_limit, details.used_today
422PENDING_LIMIT_REACHED排隊中或執行中的任務已過多。
details.pending, details.max_pending
422WORKSPACE_LIMIT_OR_BALANCE_EXCEEDED可用單位或工作區任務上限不足。
423, 409WORKSPACE_DELETION_PENDING此工作區處於刪除前的寬限期。變更會以 423 拒絕,但讀取、匯出、帳號操作和唯讀 MCP 工具仍可使用。只有在任務受理期間開始刪除時,才會出現帶有此代碼或 WORKSPACE_NOT_ACTIVE 的 409。
429RATE_LIMITED已達到速率限制。請等待 Retry-After 中指定的秒數後重試。
429RESEARCH_DAILY_LIMIT_REACHED今日即時研究總量已用完。請等待 Retry-After 中指定的秒數,直到下一個 UTC 日。不會預留任何單位。
500INTERNAL_ERROR發生意外錯誤。請稍後使用相同的冪等金鑰重試。
503PROVIDER_NOT_CONFIGURED即時研究未向此工作區開放,或任務的資料來源尚未設定。

檢查 AI 搜尋結果

AI 搜尋可見性保留一項 Google AI 概覽觀察結果:問題、國家/地區、語言、可選的城市偏好、答案文字和安全引用連結。 最多可新增五個品牌名稱和域名,請查看單單元最大限制,然後明確授權該任務。 您的管理員必須啟用工作區並連接已授權的來源。

無需再次呼叫資料來源即可查看歷史記錄或匯出證據。 僅當查詢、語言和目標匹配時才比較基準。 缺少概覽意味著可見性未知;引用順序並非搜尋排名。 不完整的證據無法建立否定匹配。 不支援多引擎追蹤。

如需追蹤一組固定問題,經過驗證的擁有者或管理員可以安排定期系列:最多 10 個提示,每 24 至 720 小時一次,90 天內最多 30 次觀測。請檢視整個系列的最大單位數並明確確認。當授權或設定變更,或某次觀測失敗或不確定時,系列會暫停;恢復需要重新預覽。

Open AI 搜尋可見性

搜尋區域並取得更多頁面

在「探索」中,選擇文字位置、點偏好、半徑或邊界框。 點偏好並非邊界。 半徑和框結果會排除外部或不可用的座標,並保留排除計數。 工作區設定控制半徑和框的大小。 分割跨越反子午線的方框;有界樣本並不能證明完全覆蓋。

評論和照片元資料允許每個來源頁面最多包含 100 筆記錄。 結果完成後,在「探索」或「任務歷史記錄」中開啟「結果頁」。 查看確切的來源選項,勾選授權複選框,並設定預算以取得下一個頁面。 排序、語言、翻譯和篩選器在整個鏈中保持不變。 遊標缺失或重複、過期、配置的限制以及不確定的結果都會停止繼續執行。

讀取下一個已儲存記錄頁面和匯出現有頁面不會呼叫資料來源。 取得新的來源頁面是一項單獨的付費操作。 對於 SDK 和代理,請閱讀繼續執行預覽,然後使用穩定的冪等金鑰和明確的預算提交其確切的任務。 請勿安排或批量執行遊標繼續執行。

在多邊形內規劃研究

在“探索”中選擇“多邊形邊界”,即可對單一有界矩形請求進行精確的多邊形篩選。 在「批量研究」中,選擇「多邊形邊界」可匯入或貼上 GeoJSON 多邊形,檢查其形狀,並選擇每個單元格的行數、列數和記錄數。 示意圖顯示座標和規劃單元格;它不是街道地圖。

位置使用 [經度,緯度]。 透過重複其第一個位置來閉合每個環;第一個環是外緣,最多四個額外的環是排除的孔。 自相交、接觸或重疊的孔以及反子午線交叉點將被拒絕。 跨越日期變更線時使用單獨的多邊形。 工作區設定可限定位置、跨距和規劃儲存格。

「審核單元格和總預算」不會呼叫資料來源。 檢查每個保留的單元格,設定最大單位並在運行前明確確認。 所有任務要麼一起預留,要麼一個都不接受。 收據保留原始計劃和配置版本。 來源結果會根據多邊形和儲存格進行過濾;共享邊可能會導致重複的商家。 請求的記錄數是上限,並非普查結果。

代理程式使用 preview_polygon_plan,然後使用 create_reviewed_polygon_batch,並具有完全相同的精簡計劃、預覽哈希、確認、穩定密鑰和匯總預算。 使用現有的批次歷史記錄、取消和每個任務的結果/匯出控制。 不確定的結果永遠不會自動重新掃描。

匯出保留的頁面鏈

在「結果頁」中,開啟「多頁匯出」並選擇「審核全鏈導出」。 透過所選任務查看頁面,選擇是保留每個精確 ID 的第一筆記錄還是保留所有記錄,然後確認私有匯出。 此操作不會取得新資料或使用研究單元。 匯出內容也會顯示在「資料控制」中,並附有進度、取消和完整性收據。

完成後,下載 JSONL、JSON、CSV 或 Excel 檔案。 重複的 ID 且內容已更改將被視為衝突;保留第一個記錄。 沒有 ID 的記錄保持獨立。 每個來源頁面都有其自身的已驗證 JSONL 部分,因此傳輸失敗後可以分段恢復。 Excel 支援最大 5 MB 的儲存部分;更大的匯出檔案將使用串流格式。 CSV 會在 extra_fields_json 中保留額外的欄位。

所有者和管理員可在「設定」中配置每日請求數、待匯出資料、頁面和來源位元組限制以及保留期限。 始終採用最早的源過期時間。 使用 JSON 或 JSONL 時,請保留完整標記,並根據其 SHA-256 回執驗證下載的部分。 凍結鍊是保留的樣本,並非完整市場覆蓋的證明。

使用引用的證據分析評論主題

首先在「報告」中查看評論統計報告。 擁有者或管理員可在「設定」和「使用情況」中啟用 AI 評論分析,並設定每日嘗試次數、待處理任務、摘要計數和佇列生命週期。 此功能預設為關閉。

選擇「審核分析輸入」可在授權一次產生之前檢查確切的摘錄和模型。 依報告順序最多選擇 40 個不同的摘錄和 10 KB 的摘錄文字。 每次產生都有自己的 AI 分析上限,並且不使用 Maps 錢包單位。 失敗或取消的嘗試仍會計入。

每個已儲存的主題都包含已核對的引文和來源參考。 計數是基於不同的引用摘錄計算,主題可能重疊,且 AI 解讀可能出錯。 結果將以單獨報告的形式開啟:在「報告」中查看,然後選擇要分享的內容或匯出為 PDF。 隱藏「評論摘錄」欄位也會隱藏 AI 分析。

不包含受支援的客戶體驗的摘錄可能會返回證據不足的收據,且不產生報告。 此嘗試仍計入限制。 在授權進行其他分析之前,請檢查輸入內容。 功能升級會在推理之前停止舊的已排隊審核;請查看新的預覽。 現有報告保留其原始模型和提示版本。

收據顯示已排隊、正在運行、已完成、已失敗、不確定、已取消或已過期的結果。 取消操作會阻止報告生成,但已派發的分析仍會計入限制。 不確定的呼叫永遠不會自動重播。 API、SDK 和 MCP 使用相同的預覽雜湊、明確確認和冪等金鑰。

準備並分享客戶報告

從「報告」中已完成的研究建立報告。 編輯報告標題、品牌、章節順序和證據列,然後儲存簡報。 「工作區設定」提供預設品牌顏色、帶有邊框的 PNG 標誌、發布者網站和頁腳。 已儲存的簡報僅在您編輯或套用工作區預設設定時才會變更。

分享已審核的報告會顯示準確的公開預覽。 未選擇的證據欄位和隱藏部分將從儲存的快照中排除。 來源查詢預設是私有的;方法論保持可見。 您可以選擇任何擁有連結的人或最多 25 個已驗證的電子郵件帳戶,設定過期時間,並明確確認內容和受眾。 您也可以要求共享密碼;將其與連結分開發送。 工作區策略可以要求每次共享結果/報告時都輸入密碼。 不會發送邀請或電子郵件。

複製一次完整連結。 密鑰保留在其碎片中,不會儲存在瀏覽器儲存中。 受限存取權限的接收者使用已驗證的帳戶登錄,然後打開原始連結並在提示時輸入共用密碼。 密碼無法恢復;如果密碼遺失,請撤銷連結並建立替代連結。 過期時間受源保留期限的限制。 資料控制可以撤銷未來的存取權限;已刪除的報告、已過期的來源和已暫停的工作區會阻止新的讀取操作。 已下載的副本無法撤銷。 JSON 和瀏覽器列印/儲存 PDF 會使用顯示的示範版本。 產生已儲存的 PDF 會從已儲存的版本建立私人文件,支援 A4 或 Letter 紙張尺寸,以及縱向或橫向方向。 已完成的文件可以附加到符合的已審核共享,並繼承其存取規則。

代理程式使用 preview_report_delivery、update_report_presentation、create_reviewed_report_share、list_report_shares 和 revoke_report_share。 發布需要經過審核的版本、快照雜湊、穩定的冪等金鑰和明確共享授權。 外部研究文本無法授予此授權。

產生保留的 PDF

儲存簡報,在 PDF 匯出中選擇紙張和方向,然後產生。 歷史記錄顯示已排隊、正在渲染、已完成和已停止的請求,並提供下載或取消控制項。 工作區設定控制啟用、每日請求、待處理請求和保留期限。 文件會隨著其來源或配置的保留期限一起過期,以先到者為準。 渲染不會消耗研究單位。 渲染失敗或不確定需要新的顯式請求;不會自動重試。

代理程式使用 generate_report_pdf、經過審核的版本、快照雜湊、confirmed_render 和冪等金鑰。 list_report_pdfs 和 get_report_pdf 用於檢查回執;cancel_report_pdf 用於阻止待發布的訊息。 生成操作不會建立共享或發送訊息。 私有下載和收件者附件路由採用 OpenAPI 實作。

接收已簽署的作業回執

在「整合」中,新增一個公共 HTTPS 接收器。 安全地保存其一次性簽名密鑰,並在「設定」中啟用交付。 使用後,請明確啟用目標。 選擇已完成、部分完成、失敗、不確定或已取消的作業事件。 測試僅發送測試標誌和元資料。

每次傳送都帶有兩種簽章方案。對於 Standard Webhooks,webhook-signature 的格式為 v1, 後接 webhook-id、webhook-timestamp 與原始請求本文以句點連接後計算的 base64 HMAC-SHA256。金鑰是 whsec_ 之後文字的 base64 解碼結果。

原有方案維持不變:X-LocalOS-Signature 的格式為 v1= 後接 X-LocalOS-Timestamp、一個句點和原始本文的十六進位 HMAC-SHA256,金鑰為完整的 whsec_ 密鑰文字。請依 X-LocalOS-Key-ID 選擇密鑰。

無論採用哪種方案,都應要求時間戳記在五分鐘以內,並以恆定時間比較簽章。webhook-id 和 X-LocalOS-Event-ID 中的事件 ID 在重試之間保持不變:在執行任何操作前以唯一方式儲存它,並對重複傳送回傳 2xx 回應。

在 Node.js 中驗證 Standard Webhooks 簽章:

// Verifies a webhook delivery with Web Crypto only, so it runs on
// Node.js 20+, Deno, Bun and Cloudflare Workers without dependencies.
//   const event = await verifyWebhook({ secret, headers, body });
// secret: "whsec_..." or, while rotating, ["whsec_new", "whsec_old"].
// headers: a Headers object or a plain map such as req.headers.
// body: the raw body as text or bytes, before any JSON parsing.
// Returns the parsed event. Otherwise throws an error whose code says
// why the delivery must not be trusted.
export class WebhookVerificationError extends Error {
  constructor(code) {
    super(code);
    this.name = "WebhookVerificationError";
    this.code = code;
  }
}

const encoder = new TextEncoder();

function header(headers, name) {
  if (typeof headers.get === "function") {
    return headers.get(name) ?? undefined;
  }
  for (const [key, value] of Object.entries(headers)) {
    if (key.toLowerCase() === name) {
      return Array.isArray(value) ? value[0] : value;
    }
  }
  return undefined;
}

function base64(value) {
  if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value) || value.length % 4) {
    return null;
  }
  try {
    return Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
  } catch {
    return null;
  }
}

// Checks every byte, so the time taken does not reveal a mismatch.
function sameBytes(a, b) {
  if (a.length !== b.length) return false;
  let difference = 0;
  for (let i = 0; i < a.length; i++) difference |= a[i] ^ b[i];
  return difference === 0;
}

export async function verifyWebhook({
  secret,
  headers,
  body,
  toleranceSeconds = 300,
}) {
  if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) {
    throw new RangeError("toleranceSeconds must be 0 or more");
  }
  const fail = (code) => new WebhookVerificationError(code);
  const keys = [secret]
    .flat()
    .map((value) =>
      typeof value === "string" ? base64(value.replace(/^whsec_/, "")) : null,
    );
  if (!keys.length || keys.some((key) => !key?.length)) {
    throw fail("WEBHOOK_SECRET_INVALID");
  }
  const id = header(headers, "webhook-id");
  const timestamp = header(headers, "webhook-timestamp");
  const signatures = header(headers, "webhook-signature");
  if (!id || !timestamp || !signatures) {
    throw fail("WEBHOOK_HEADER_MISSING");
  }
  if (!/^\d{1,12}$/.test(timestamp)) {
    throw fail("WEBHOOK_TIMESTAMP_INVALID");
  }
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) {
    throw fail("WEBHOOK_TIMESTAMP_OUT_OF_RANGE");
  }
  const subtle = globalThis.crypto?.subtle;
  if (!subtle) throw fail("WEBHOOK_CRYPTO_UNAVAILABLE");
  const raw =
    typeof body === "string" ? encoder.encode(body) : new Uint8Array(body);
  const prefix = encoder.encode(`${id}.${timestamp}.`);
  const signed = new Uint8Array(prefix.length + raw.length);
  signed.set(prefix);
  signed.set(raw, prefix.length);
  const expected = await Promise.all(
    keys.map(async (key) => {
      const hmac = await subtle.importKey(
        "raw",
        key,
        { name: "HMAC", hash: "SHA-256" },
        false,
        ["sign"],
      );
      return new Uint8Array(await subtle.sign("HMAC", hmac, signed));
    }),
  );
  // Any listed v1 signature may match; at most 16 are checked.
  const verified = signatures
    .split(" ")
    .filter(Boolean)
    .slice(0, 16)
    .some((entry) => {
      const [version, value] = entry.split(",", 2);
      const given = version === "v1" && value ? base64(value) : null;
      return given !== null && expected.some((d) => sameBytes(given, d));
    });
  if (!verified) throw fail("WEBHOOK_SIGNATURE_INVALID");
  try {
    return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(raw));
  } catch {
    throw fail("WEBHOOK_PAYLOAD_INVALID");
  }
}

傳送至少進行一次。網路故障、逾時以及所有非 2xx 回應都會重試,包括重新導向,且從不跟隨重新導向。最多八次嘗試分布在約 28 小時內:分別在 5 秒、5 分鐘、30 分鐘、2 小時、5 小時後,然後兩次間隔 10 小時;如果接收方送出 Retry-After,則延後到該時間之後(最長一天)。收到 410 Gone 回應會暫停該目標並取消其待傳送項目,直到你恢復它。 嘗試和錯誤會顯示在「集成」中。暫停或移除目標會取消待處理的回執;已接受的請求可能會在十秒內完成。儲存的傳送回執不包含任何商家記錄、搜尋詞、帳戶憑證或接收方回應本文。

將結果傳送到 Google 試算表、Zapier、Make 或 n8n

這些方案僅使用上文的已簽署 Webhook 和 REST API。每次傳送包含任務 ID、狀態、已計費單元數和結果數量,不包含結果列。請把傳送僅當作訊號:從中取出 data.job_id,再使用唯讀金鑰透過 API 讀取任務及其結果列。這些請求經過驗證,因此即使有人偽造傳送,結果列也一律來自你自己的工作區。

請在「API 和金鑰」中建立該金鑰,並授予「讀取現有結果」權限。它可以讀取任務和匯出,但無法啟動付費研究。每個方案都使用該工具的通用 Webhook、程式碼和 HTTP 請求功能;MapsScraper.dev 在這些工具中沒有自己的應用程式。

每個方案都會用該金鑰發出兩個 GET 請求,金鑰以 Authorization: Bearer <key> 形式傳送:先請求 GET /api/v1/jobs/{job_id},僅當其 status 為 completed 或 partial 時,再請求 GET /api/v1/jobs/{job_id}/export 取得結果列。

傳送的運作方式

  • 請在 10 秒內以任意 2xx 狀態回應,耗時的工作之後再做。
  • 重試會沿用相同的 webhook-id 標頭,因此請儲存它並略過已處理的傳送。
  • 失敗的傳送會重試,在約 28 小時內最多 8 次;如果接收端傳送 Retry-After,則會更晚重試。不會跟隨重新導向。
  • 收到 410 Gone 回應會暫停該目的地並取消其待處理的傳送。修正接收端後,再重新開啟。

n8n

使用一個設定為立即回應的 Webhook node,每個請求各用一個 HTTP Request node 並把金鑰放在 Header Auth 憑證中,兩者之間再用一個 IF node 檢查狀態。此方案不驗證簽章,而是依靠經過驗證的請求。

Zapier

Zapier 的 Catch Raw Hook 觸發器會傳遞未解析的請求本文和標頭,因此此方案也會驗證簽章。新增一個執行 JavaScript 的 Code by Zapier 步驟,並設定其 Input Data:body 設為原始請求本文,id、timestamp 和 signature 分別設為 webhook-id、webhook-timestamp 和 webhook-signature 標頭,secret 設為簽章金鑰。任何能編輯該 Zap 的人都能看到這個金鑰。

const crypto = require("crypto");
const { body, id, timestamp, signature, secret } = inputData;
const fresh =
  /^\d+$/.test(timestamp || "") &&
  Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
const key = Buffer.from(
  String(secret).replace(/^whsec_/, ""),
  "base64",
);
const expected = crypto
  .createHmac("sha256", key)
  .update(`${id}.${timestamp}.${body}`)
  .digest();
const signed = String(signature || "")
  .split(" ")
  .some((entry) => {
    const [version, value = ""] = entry.split(",");
    const given = Buffer.from(value, "base64");
    return (
      version === "v1" &&
      given.length === expected.length &&
      crypto.timingSafeEqual(given, expected)
    );
  });
const valid = Boolean(id) && fresh && signed;
if (!valid) return { valid };
return { valid, job_id: JSON.parse(body).data.job_id };

新增一個篩選步驟,僅在 valid 為 true 時繼續,然後用 GET 請求步驟發出這兩個請求。無論驗證是否通過,Zapier 都會以 200 回應傳送,因此攔下未通過驗證之傳送的是篩選步驟,而不是回應。

Make

使用一個會把 JSON 請求本文拆成欄位的 Custom webhook 模組,每個請求各用一個 HTTP 模組,兩者之間再用一個篩選器檢查狀態。Make 會自行以 200 回應傳送。與 n8n 方案一樣,此方案不驗證簽章,而是依靠經過驗證的請求。

Google Sheets

Google Sheets 是主動拉取資料列,而不是接收推送。在試算表中依序開啟 Extensions 和 Apps Script,然後貼上此指令碼。在 Project Settings 下新增一個名為 MAPSSCRAPER_API_KEY 的指令碼屬性,用來保存唯讀 API 金鑰,這樣金鑰就不會出現在儲存格和程式碼中。

const API = "https://mapsscraper.dev/api/v1";

function getJson(path) {
  const key = PropertiesService.getScriptProperties()
    .getProperty("MAPSSCRAPER_API_KEY");
  if (!key) throw new Error("Add the MAPSSCRAPER_API_KEY script property.");
  const response = UrlFetchApp.fetch(API + path, {
    headers: { Authorization: "Bearer " + key },
    muteHttpExceptions: true,
  });
  const status = response.getResponseCode();
  const text = response.getContentText();
  if (status !== 200) {
    throw new Error("HTTP " + status + ": " + text.slice(0, 200));
  }
  return JSON.parse(text);
}

// Text that starts with =, +, - or @ gets an apostrophe,
// so Sheets keeps it as text instead of running a formula.
function cell(value) {
  if (value === null || value === undefined) return "";
  if (typeof value === "object") value = JSON.stringify(value);
  const formula = typeof value === "string" && /^[=+\-@]/.test(value);
  return formula ? "'" + value : value;
}

function importLatestSearch() {
  const jobs = getJson("/jobs?page_size=25&kind=search").items;
  const job = jobs.find(
    (item) => item.status === "completed" || item.status === "partial",
  );
  if (!job) return;
  const path = "/jobs/" + job.id + "/export?format=json";
  const rows = getJson(path).items || [];
  if (!rows.length) return;
  const columns = [];
  rows.forEach((row) => Object.keys(row).forEach((name) => {
    if (!columns.includes(name)) columns.push(name);
  }));
  const values = [columns].concat(
    rows.map((row) => columns.map((name) => cell(row[name]))),
  );
  const book = SpreadsheetApp.getActive();
  const sheet =
    book.getSheetByName("MapsScraper") || book.insertSheet("MapsScraper");
  sheet.clearContents();
  sheet.getRange(1, 1, values.length, columns.length).setValues(values);
}

執行 importLatestSearch,即可將最新完成的搜尋複製到名為 MapsScraper 的工作表中。如需定時重新整理,請在 Triggers 下為 importLatestSearch 新增時間驅動的觸發條件。Apps Script 單次執行 6 分鐘後會被停止,且無法取得超過 50 MB 的回應,因此較大的任務請從 GET /api/v1/jobs/{job_id}/results 逐頁讀取。

重要邊界

網站缺失表示資料來源未提供網站。 這並不證明企業沒有網站。 排名快照僅代表單一資料來源的結果排序,並非完整的本地 SEO 排名網格。 評論收集並非 AI 產生的情感報告。 照片是元數據,非授權影像下載。 重複搜尋會保留快照,比較穩定的身份訊息,並建立工作區收件匣通知。 已簽署的 Webhook 收據為選擇加入,僅包含任務元資料;CRM 特定欄位同步仍獨立進行。