AIGC标识 高德地图批量抓取北京库迪咖啡门店及营业执照完整指南(营业执照抓取失败)

一、 准备工作:申请高德开放平台 Key

  1. 登录 高德开放平台 (lbs.amap.com) 并完成个人开发者认证。
  2. 进入 控制台 -> 应用管理 -> 我的应用 -> 创建新应用。
  3. 在创建好的应用右侧点击 添加 Key:
    • Key 名称:自定义(如 python_key)
    • 服务平台:必须勾选【Web服务】(切勿选择 Web端 JS API)
    • IP 白名单:保持留空
  4. 提交后复制生成的 32 位 Key 字符串。

二、 环境依赖安装

为防止虚拟环境(.venv)移动文件夹后导致 pip.exe 路径失效报错,统一使用 python -m 方式安装所需依赖:

python -m pip install requests pandas openpyxl playwright
python -m playwright install chromium

三、 第一步:批量抓取北京 16 个区全量门店信息(含分店名提取)

核心设计说明

  • 突破 200 条限制:高德 POI 搜索接口单次最多返回约 200 条记录。北京市库迪咖啡门店数量庞大,因此通过遍历北京市 16 个行政区分区抓取,并在最后按 门店ID 去重,确保全量覆盖。
  • 防并发超限 (CUQPS_HAS_EXCEEDED_THE_LIMIT):个人免费版 Key 的 QPS(每秒并发请求数)上限为 3 次。代码内置了 time.sleep(0.6) 固定限速及触发限流后的 1.5秒 自动重试机制。
  • 自动提取分店名:利用正则表达式自动提取 库迪咖啡(xxx店) 括号内的文字(兼容中英文括号),单独生成 【分店名】 列。

完整 Python 脚本 (step1_amap_beijing_stores.py)

import os
import re
import time
import requests
import pandas as pd

# =====================================================================
# 【必填】将此处替换为你申请的 32 位高德【Web服务】Key
AMAP_KEY = "填入你的高德Key"
# =====================================================================

KEYWORD = "库迪咖啡"
EXCEL_FILE = "1_高德_北京库迪全量门店.xlsx"

# 北京市 16 个行政区,分区抓取突破单次搜索条数上限
DISTRICTS = [
    "东城区", "西城区", "朝阳区", "丰台区", "石景山区", "海淀区",
    "门头沟区", "房山区", "通州区", "顺义区", "昌平区", "大兴区",
    "怀柔区", "平谷区", "密云区", "延庆区"
]

def extract_branch_name(name):
    """提取店名括号里的分店名称,兼容中英文括号 () 和 ()"""
    if pd.isna(name):
        return ""
    match = re.search(r"[\((]([^\))]+)[\))]", str(name))
    if match:
        return match.group(1).strip()
    return re.sub(r"^库迪咖啡\s*", "", str(name)).strip()

def fetch_beijing_cotti_stores():
    if AMAP_KEY == "填入你的高德Key" or len(AMAP_KEY) < 20:
        print("❌ 错误:请先在代码第 10 行填入正确的 32 位高德 Key!")
        return

    print("🚀 开始从高德地图拉取北京市 16 个行政区的库迪咖啡门店...")
    all_stores = []

    for district in DISTRICTS:
        page = 1
        while True:
            url = "[https://restapi.amap.com/v3/place/text](https://restapi.amap.com/v3/place/text)"
            params = {
                "key": AMAP_KEY,
                "keywords": KEYWORD,
                "city": district,
                "citylimit": "true",
                "offset": 20,
                "page": page,
                "extensions": "all"
            }
            
            # 固定间隔 0.6 秒,避开免费版 QPS 并发限制
            time.sleep(0.6)
            res = requests.get(url, params=params).json()

            # 若偶发触发并发超限,自动休息 1.5 秒后原地重试
            if res.get("info") == "CUQPS_HAS_EXCEEDED_THE_LIMIT":
                print(f"   ⏳ 触发并发限流,休息 1.5 秒后重试【{district}】第 {page} 页...")
                time.sleep(1.5)
                continue

            if res.get("status") == "0":
                print(f"❌ 高德 API 报错:{res.get('info')}")
                return

            pois = res.get("pois", [])
            if not pois:
                break

            for poi in pois:
                full_name = poi.get("name", "")
                if "库迪" in full_name:
                    all_stores.append({
                        "门店ID": poi.get("id"),
                        "门店名称": full_name,
                        "分店名": extract_branch_name(full_name),
                        "所属区域": poi.get("adname"),
                        "高德地址": str(poi.get("address", "")),
                        "门店电话": poi.get("tel") if poi.get("tel") else "",
                        "经纬度": poi.get("location", "")
                    })
            page += 1
        print(f"   -> 【{district}】抓取完毕,当前累计获取 {len(all_stores)} 条记录")

    # 按门店ID去重并导出 Excel
    df = pd.DataFrame(all_stores).drop_duplicates(subset=["门店ID"])
    df.to_excel(EXCEL_FILE, index=False)
    print(f"\n🎉 抓取完成!去重后共获取 {len(df)} 家北京库迪咖啡门店。")
    print(f"📁 文件保存路径:{os.path.abspath(EXCEL_FILE)}")

if __name__ == "__main__":
    fetch_beijing_cotti_stores()

四、 第二步:接管真实浏览器获取门店营业执照(避开滑块报错)

核心原理

高德 API 不直接返回营业执照图片,而直接用 Playwright 启动的内置浏览器带有底层自动化协议特征,在滑动阿里“霸下”验证码时会报 验证失败,点击框体重试[cite: 3, 5]。
通过 Windows 原生命令行开启 Edge 调试端口 + connect_over_cdp 接管真浏览器,即可保证 100% 真人浏览器指纹,滑块一滑即过。

操作步骤

1. 启动真实 Edge 浏览器

先关闭电脑上所有已打开的 Edge 窗口,在终端运行以下命令:

Start-Process "msedge.exe" -ArgumentList "--remote-debugging-port=9222"

2. 运行接管与存图脚本 (step2_amap_beijing_licenses.py)

import os
import re
import requests
import pandas as pd
from playwright.sync_api import sync_playwright

EXCEL_FILE = "1_高德_北京库迪全量门店.xlsx"

def scrape_licenses_with_real_browser():
    if not os.path.exists(EXCEL_FILE):
        print(f"❌ 找不到 {EXCEL_FILE},请先运行第一步脚本!")
        return

    df = pd.read_excel(EXCEL_FILE)
    os.makedirs("高德_北京营业执照原图", exist_ok=True)
    os.makedirs("高德_北京营业执照截图", exist_ok=True)
    license_img_urls = []

    with sync_playwright() as p:
        try:
            # 接管刚才通过 9222 端口打开的真实 Edge 浏览器
            browser = p.chromium.connect_over_cdp("http://localhost:9222")
        except Exception:
            print("❌ 连接失败!请先关闭所有 Edge 窗口,并在终端运行:")
            print('   Start-Process "msedge.exe" -ArgumentList "--remote-debugging-port=9222"')
            return

        context = browser.contexts[0]
        page = context.pages[0] if context.pages else context.new_page()

        # 监听网络请求,捕获大于图标尺寸的证照原图
        def handle_response(response):
            url = response.url
            if response.request.resource_type == "image":
                if not any(w in url.lower() for w in ["icon", "logo", "marker", "avatar", "tile", ".svg", ".gif"]):
                    if url not in license_img_urls:
                        license_img_urls.append(url)

        page.on("response", handle_response)

        print("✅ 已成功接管真实 Edge 浏览器!")
        input("👉 准备好后,按【回车键】打开第 1 家北京门店...")

        results = []
        # 若需断点续传(如从第 50 家开始),可将 df.iterrows() 改为 df.iloc[49:].iterrows()
        for idx, row in df.iterrows():
            poi_id = str(row["门店ID"]).strip()
            store_name = str(row["门店名称"]).strip()
            branch_name = str(row.get("分店名", "")).strip()
            district = str(row["所属区域"]).strip()
            safe_name = re.sub(r'[\\/:*?"<>|]', '_', f"{idx+1}_{district}_{branch_name}")

            detail_url = f"[https://m.amap.com/detail/index/poiid=](https://m.amap.com/detail/index/poiid=){poi_id}"
            print(f"\n[{idx+1}/{len(df)}] 正在打开:{store_name}")
            license_img_urls.clear()

            try:
                # 使用 domcontentloaded 防止地图图层后台持续加载导致 30 秒超时报错
                page.goto(detail_url, wait_until="domcontentloaded", timeout=15000)
            except Exception:
                pass

            cmd = input("   👉 请在浏览器手动点开【营业详细 -> 服务须知及信息公示】,看到执照后按【回车】保存(跳过输 s,退出输 q):").strip().lower()
            if cmd == 'q':
                break
            if cmd == 's':
                continue

            # 1. 保存当前屏幕截图
            shot_path = f"高德_北京营业执照截图/{safe_name}.png"
            try:
                page.screenshot(path=shot_path)
                print(f"   📸 截图已存:{shot_path}")
            except Exception as e:
                print(f"   ⚠️ 截图失败: {e}")

            # 2. 下载嗅探到的最新大图(过滤小于 10KB 的小杂图)
            saved_paths = []
            for i, img_url in enumerate(license_img_urls[-4:]):
                try:
                    img_data = requests.get(img_url, timeout=8).content
                    if len(img_data) > 10240:
                        raw_path = f"高德_北京营业执照原图/{safe_name}_执照{i+1}.jpg"
                        with open(raw_path, "wb") as f:
                            f.write(img_data)
                        saved_paths.append(raw_path)
                        print(f"   📥 原图已下:{raw_path}")
                except Exception:
                    pass

            results.append({
                "门店ID": poi_id,
                "门店名称": store_name,
                "分店名": branch_name,
                "所属区域": district,
                "高德地址": row.get("高德地址", ""),
                "门店电话": row.get("门店电话", ""),
                "营业执照截图": shot_path,
                "营业执照原图路径": "; ".join(saved_paths)
            })
            pd.DataFrame(results).to_excel("2_高德_北京营业执照抓取汇总.xlsx", index=False)

        print("\n✅ 采集已保存并退出!")

if __name__ == "__main__":
    scrape_licenses_with_real_browser()

五、 常见报错与排坑速查表

报错信息 / 现象 原因分析 解决方案
Unable to create process using ... pip.exe 项目文件夹移动或重命名后,.venv 虚拟环境中的固定路径失效 改用 python -m pip install ... 执行安装
CUQPS_HAS_EXCEEDED_THE_LIMIT 每秒请求次数超过高德个人免费版上限(3次/秒) 每次请求增加 time.sleep(0.6),并加入捕获限流后暂停 1.5秒 重试的逻辑
'latin-1' codec can't encode characters 手动复制的 Cookie 中包含中文字符或换行符 使用 cookie.encode('utf-8').decode('latin-1') 转码并去除换行符
Timeout 30000ms exceeded (waiting until "load") 高德后台持续加载地图瓦片,导致 load 状态一直不结束 将 page.goto() 的等待策略改为 wait_until="domcontentloaded"
验证失败,点击框体重试[cite: 3, 5] Playwright 直接启动的浏览器带有自动化特征,被阿里霸下风控拦截 用系统命令行开启 --remote-debugging-port=9222,再用 connect_over_cdp 接管真实浏览器
posted @ 2026-10-22 14:19  qishazi  阅读(2)  评论(0)    收藏  举报