手把手教你自建Yandex地图解析器:无封禁采集企业信息、评论和电话
引言:你能获得什么,这份教程适合谁
Yandex地图早已不只是导航工具,而是俄罗斯最完整的企业目录。企业卡片里藏着营销人员、套利者和企业主都需要的东西:精确的地址、电话、网站、营业时间、评分以及成千上万条真实的客户评论。问题在于,手动浏览一个城市里五百家牙科诊所的卡片根本不可能,而现成的服务要么太贵,要么数据过时。
在这份指南中,我们将一起用Python编写自己的Yandex地图解析器。不需要付费软件,不需要别人的服务器。读完这份教程,你将拥有:
- 一个可用的脚本,能按任意关键词和城市采集企业卡片。
- 一份Excel表格,包含名称、类别、地址、电话、网站、评分和营业时间。
- 一份单独的评论表格:作者、评分、日期、文本。
- 理解如何通过移动代理分散负载,从而避免验证码和封禁。
本文的重要边界:我们只讲解Yandex地图内部的企业卡片。普通Yandex搜索结果的爬取、反检测浏览器以及移动代理的基础配置在博客的其他文章中有详细介绍,这里不再赘述,只作为基础引用。
这份指南适合谁
- 营销人员和企业主,需要一份包含真实电话和评分的本地竞争对手或合作伙伴数据库。
- 套利者,收集线下细分市场和本地offer以供后续分析。
- 开发者,被指派做地图数据采集,又不想从零开始。
- 分析师,研究评论以评估细分市场的服务质量。
需要提前知道什么
没什么复杂的。只要会安装软件、打开命令行、复制文本就够了。不需要编程经验:所有代码都是现成的,你只需要替换自己的参数。如果你曾经运行过Python脚本,那就更轻松了。在进阶部分我们会涉及异步和网络响应处理,但可以跳过。
需要多少时间
- 环境准备:30-40分钟。
- 分步编写和调试解析器:1.5-2小时。
- 首次完整采集300-500张卡片:1-3小时的挂机时间,你可以同时做其他事。
也就是说,半个工作日你就能获得一个工具,之后它将为你节省数周的手工劳动。
前期准备:工具、权限和要求
在写代码之前,先把所有需要的东西准备好。如果你已经安装了Python并且有移动代理,可以跳过这一节。
系统要求
- Windows 10或11、macOS 12及以上,或者任何现代Linux。
- 至少8GB内存:我们要运行真实的Chromium浏览器。
- 3-4GB可用磁盘空间,用于Python、浏览器和结果文件。
- 稳定的网络。速度不重要,重要的是不要断线。
需要安装什么
- Python 3.11或3.12。从python.org官网下载安装包。在Windows上安装时,务必勾选安装程序第一个窗口底部的Add Python to PATH,否则命令在控制台里无法运行。
- 代码编辑器。Visual Studio Code、PyCharm Community甚至Notepad++都可以。我们以VS Code为例,但对我们任务来说没有区别。
- Python库:playwright用于浏览器控制,pandas和openpyxl用于表格,requests用于检查代理。在第一步安装。
- Playwright的Chromium浏览器。单独命令下载,约150MB。
移动代理权限
这是"无封禁"部分的关键。Yandex地图对同一地址的请求频率很敏感。移动代理提供真实移动运营商的IP地址,这些地址同时有成千上万普通用户在使用,所以Yandex对它们非常宽容。在mobileproxy.space个人后台购买代理后,你需要四个值:
- 主机(服务器地址)和端口,用于HTTP连接。
- 登录名和密码,用于认证。
- 换IP链接:一个特殊URL,访问它后代理会从运营商获取新地址。把它复制到单独文件,第五步会用到。
建议:选择与你采集城市同一地区或至少同一国家的代理。地图会根据地理位置调整结果,其他国家的代理可能显示不完整的企业列表或其他语言的界面。
备份和工作文件夹
在磁盘上创建一个文件夹,比如maps_parser。脚本和结果都放在里面。所有中间数据我们会在每张卡片后保存到文件,所以即使脚本在第300家企业崩溃,前299家也不会丢。每次重新运行前最好备份结果文件:只需在旧文件名后加上日期重命名即可。
检查:打开命令行(Windows按Win+R,输入cmd回车),输入命令python --version。如果看到类似Python 3.12.x的行,准备完成。如果报错,请重新安装Python并勾选Add to PATH。
基础概念:地图是如何构成的,什么是卡片爬取
在写代码之前,先弄清楚术语。它们很简单,但不了解的话后面就看不懂为什么我们要这样做。
关键术语通俗解释
- 爬取(抓取)——自动从网站页面收集信息。程序像人一样打开页面,从中提取需要的片段。
- 企业卡片——地图中的独立页面,地址形如yandex.ru/maps/org/名称/数字ID/。电话、地址、评论等数据都在上面。左侧的企业列表只是展示窗口,我们只从中获取卡片链接。
- 选择器——页面内元素的"地址"。例如,类business-contacts-view__address指向地址块。脚本通过选择器找到需要的文本。
- 无头浏览器——由程序控制的真实浏览器。可以有窗口(你能看到发生了什么),也可以没有。
- Playwright——从Python控制浏览器的库。我们选择它是因为地图完全基于JavaScript构建,简单的HTML请求会返回没有数据的空页面。
- 移动代理——你的电脑和网站之间的中间人,拥有移动网络的IP地址。网站看到的是运营商地址,而不是你的地址。
- IP轮换——定期更换代理地址,避免负载集中在一个IP上。
- 验证码——"请确认你不是机器人"的验证页面。对我们来说是信号,说明我们太急了。我们不会用第三方服务破解它,而是停下来,换IP,降低速度。
Yandex地图解析器的基本原理
逻辑很简单,分三个阶段。首先获取所需企业卡片的链接列表。然后依次打开每张卡片提取数据。最后全部汇总到表格。在这些操作之间,我们做随机时长的暂停,并时不时通过换地址链接更换IP。
关于合法性和道德的重要说明
注意:只收集企业的公开数据并用于分析。公司电话和地址不是个人数据,但评论作者的名字根据152-FZ可能被视为个人数据。不要无必要地存储它们,也不要在未经同意的情况下使用收集的电话进行群发,这违反广告法和Yandex规则。另外记住:Yandex用户协议限制自动化采集,所以保持最低负载,对于大容量的商业项目,请考虑使用Yandex官方的企业搜索API。
第1步:配置环境并连接移动代理
本阶段目标:安装所有库,下载浏览器,确认请求通过移动代理而不是直连。
安装库
- 打开命令行或终端。
- 用cd命令和文件夹路径进入工作目录。例如:Windows中cd C:\maps_parser,macOS和Linux中cd ~/maps_parser。
- 创建虚拟环境,避免库干扰其他项目:python -m venv venv
- 激活它。Windows:venv\Scripts\activate。macOS和Linux:source venv/bin/activate。终端行首会出现(venv)字样。
- 用一条命令安装库:
pip install playwright pandas openpyxl requests
playwright install chromium第二条命令会下载Chromium。根据网速需要2-5分钟。等终端重新出现输入提示符。
检查代理
在编辑器中创建文件check_proxy.py,粘贴代码,将ЛОГИН、ПАРОЛЬ、ХОСТ和ПОРТ替换为个人后台的值:
import requests
proxy = 'http://ЛОГИН:ПАРОЛЬ@ХОСТ:ПОРТ'
proxies = {'http': proxy, 'https': proxy}
direct = requests.get('https://api.ipify.org?format=json', timeout=30).json()
via_proxy = requests.get('https://api.ipify.org?format=json', proxies=proxies, timeout=30).json()
print('Ваш прямой IP:', direct['ip'])
print('IP через прокси:', via_proxy['ip'])用命令python check_proxy.py运行文件。你应该看到两个不同的地址。如果地址相同或出现连接错误,代理不工作,继续下去没有意义。
检查换IP
在普通浏览器中打开个人后台的换IP链接。通常它会返回简短的换IP成功响应。等15-30秒,再次运行check_proxy.py。通过代理的地址应该改变。记住实际需要多少秒:这个值我们会在第五步放入脚本。
建议:把代理设置保存到单独的config.py文件,包含变量PROXY_HOST、PROXY_PORT、PROXY_LOGIN、PROXY_PASSWORD和CHANGE_IP_URL。这样其他脚本只需写from config import *,不用反复重写密码。
检查:命令playwright --version输出版本号,check_proxy.py显示与家庭地址不同的移动运营商IP,访问换地址链接后地址改变。
可能的问题
- 错误"pip不是内部命令"。Python安装时没有添加到PATH。重新安装勾选选项,或用py -m pip代替pip。
- 错误407 Proxy Authentication Required。登录名或密码错误。检查复制时有没有多余空格。
- 通过代理请求超时。端口不正确或代理正在换IP。等半分钟重试。
第2步:收集企业卡片链接
本阶段目标:获得links.json文件,包含所需城市和关键词的所有企业卡片地址列表。
这里我们只一次性查看地图结果列表,从中获取链接。数据本身我们只从卡片获取,所以列表只是目录。
企业列表的构成
当你在Yandex地图输入"стоматология Казань"这样的关键词时,左侧出现可滚动的摘要面板。每个摘要包含卡片链接。列表按滚动逐步加载,所以脚本会用鼠标滚轮滚动面板,每次滚动后收集链接,直到不再出现新的。
编写链接收集脚本
创建文件collect_links.py:
from playwright.sync_api import sync_playwright
import time, random, json
from config import *
PROXY = {'server': f'http://{PROXY_HOST}:{PROXY_PORT}', 'username': PROXY_LOGIN, 'password': PROXY_PASSWORD}
QUERY = 'стоматология Казань'
MAX_SCROLLS = 40
with sync_playwright() as p:
browser = p.chromium.launch(headless=False, proxy=PROXY)
context = browser.new_context(locale='ru-RU', viewport={'width': 1400, 'height': 900})
page = context.new_page()
page.goto('https://yandex.ru/maps/?text=' + QUERY, wait_until='domcontentloaded')
time.sleep(random.uniform(5, 8))
page.mouse.move(350, 500)
links = set()
stale = 0
for i in range(MAX_SCROLLS):
before = len(links)
page.mouse.wheel(0, random.randint(1200, 2000))
time.sleep(random.uniform(1.5, 3.5))
for a in page.query_selector_all('a[href*="/maps/org/"]'):
href = a.get_attribute('href') or ''
clean = href.split('?')[0]
if clean.startswith('/'):
clean = 'https://yandex.ru' + clean
links.add(clean)
stale = stale + 1 if len(links) == before else 0
print(f'Прокрутка {i+1}: ссылок {len(links)}')
if stale >= 4:
break
with open('links.json', 'w', encoding='utf-8') as f:
json.dump(sorted(links), f, ensure_ascii=False, indent=2)
print('Итого собрано:', len(links))
browser.close()解析发生了什么
- 行headless=False打开带窗口的浏览器。调试阶段这是必须的:你能亲眼看到地图是否加载,有没有验证码。
- page.mouse.move(350, 500)把光标放在左侧面板上。没有这一步,鼠标滚轮会移动地图而不是列表。
- 选择器a[href*="/maps/org/"]查找所有包含/maps/org/片段的链接。它比具体类名更稳定,Yandex每隔几个月就改。
- 计数器stale在连续四次滚动没有新链接时停止循环。这说明列表结束了。
- 链接会去掉问号后的参数,避免同一家企业重复出现。
运行脚本:python collect_links.py。会打开浏览器窗口,加载带结果的地图,面板开始滚动。终端里链接计数器会跑动。中等城市一个关键词通常2-4分钟能收集100到400张卡片。
建议:地图很少在一次查询中返回超过500个结果。如果你需要大都市的整个细分市场,按区域拆分关键词:"стоматология Вахитовский район Казань"、"стоматология Советский район Казань"。不同查询的链接通过set合并,像上面代码那样,重复会自动消失。
检查:文件夹中出现links.json文件,内含形如https://yandex.ru/maps/org/名称/1234567890/的地址列表。在普通浏览器中手动打开两三个地址,确认是所需企业的卡片。
可能的问题
- 收集到零个链接。很可能页面没加载完或光标不在列表上。把第一次暂停增加到10秒,检查鼠标坐标:面板应该在光标下方。
- 列表不滚动,地图在动。根据你窗口中面板的宽度,增大或减小mouse.move中的X坐标。
- 立即出现验证码。通过链接换IP,等一分钟再运行。如果重复,试试另一个代理通道。
第3步:解析企业卡片——名称、地址、电话、网站
本阶段目标:编写一个函数,打开一张卡片并返回包含企业主要数据的字典,然后对links.json中的所有链接运行它。
如何自己查找选择器
Yandex会定期重命名页面类名。所以重要的是能自己找到它们,而不是只依赖现成代码。方法如下:
- 在普通Chrome浏览器中打开任意企业卡片。
- 右键点击企业电话,选择查看代码(或按F12并用选择工具点击元素)。
- 在打开的面板中你会看到高亮的带class属性的标签。例如class="card-phones-view__phone-number"。这就是选择器:在代码中以点开头。
- 对名称、地址、网站、评分和营业时间重复此操作。把类名记在记事本里。
类名可能包含多个由空格分隔的词。取第一个,最"能说明问题"的,带双下划线的那个。本教程编写时,下面是有效的选择器,但运行前请检查。
编写卡片解析函数
创建文件parse_cards.py:
from playwright.sync_api import sync_playwright
import time, random, json, csv, os
from config import *
PROXY = {'server': f'http://{PROXY_HOST}:{PROXY_PORT}', 'username': PROXY_LOGIN, 'password': PROXY_PASSWORD}
OUT = 'orgs.csv'
FIELDS = ['url', 'name', 'category', 'address', 'phones', 'site', 'rating', 'reviews_count', 'hours']
def grab(page, selector):
el = page.query_selector(selector)
return el.inner_text().strip() if el else ''
def parse_card(page, url):
page.goto(url, wait_until='domcontentloaded')
time.sleep(random.uniform(3, 6))
more = page.query_selector('.card-phones-view__more')
if more:
more.click()
time.sleep(random.uniform(1, 2))
phones = [e.inner_text().strip() for e in page.query_selector_all('.card-phones-view__phone-number')]
return {
'url': url,
'name': grab(page, 'h1.orgpage-header-view__header'),
'category': grab(page, '.orgpage-categories-info-view'),
'address': grab(page, '.business-contacts-view__address'),
'phones': '; '.join(dict.fromkeys(phones)),
'site': grab(page, '.business-urls-view__text'),
'rating': grab(page, '.business-rating-badge-view__rating-text'),
'reviews_count': grab(page, '.business-header-rating-view__text'),
'hours': grab(page, '.business-working-status-view'),
}
def load_done():
if not os.path.exists(OUT):
return set()
with open(OUT, encoding='utf-8') as f:
return {row['url'] for row in csv.DictReader(f)}
links = json.load(open('links.json', encoding='utf-8'))
done = load_done()
todo = [u for u in links if u not in done]
print(f'Всего {len(links)}, осталось {len(todo)}')
with sync_playwright() as p:
browser = p.chromium.launch(headless=False, proxy=PROXY)
page = browser.new_context(locale='ru-RU').new_page()
new_file = not os.path.exists(OUT)
with open(OUT, 'a', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=FIELDS)
if new_file:
writer.writeheader()
for n, url in enumerate(todo, 1):
try:
row = parse_card(page, url)
writer.writerow(row)
f.flush()
print(f'{n}/{len(todo)} {row["name"]} | {row["phones"]}')
except Exception as e:
print('Ошибка на', url, e)
time.sleep(random.uniform(4, 9))
browser.close()这里重要的是什么
- "显示电话"按钮。部分卡片的完整号码隐藏在按钮后面。我们通过类card-phones-view__more查找它并点击。之后才收集号码。
- 每张卡片后写入。函数f.flush()强制将数据刷到磁盘。如果脚本崩溃,收集的内容会留在orgs.csv中。
- 恢复工作。函数load_done读取已收集的链接,重新运行时脚本从停止处继续,而不是从头开始。
- 卡片间暂停4-9秒。这是阅读信息的细心人的节奏。首次运行时不要缩短它。
运行python parse_cards.py,在浏览器窗口中观察前五到十张卡片。你应该看到页面打开,必要时电话展开,终端出现名称和号码。
注意:如果连续五张卡片返回空名称,立即用Ctrl+C停止脚本。几乎可以肯定Yandex改了页面,选择器过时了。按上面的说明找新的并更新代码。继续收集空行没有意义,只会增加代理负载。
建议:除了文本,保存企业ID也很有用:这是URL最后的数字部分。用它很容易在多次采集之间核对数据库,跟踪已关闭的公司。在字典中添加字段'org_id': url.rstrip('/').split('/')[-1]。
检查:文件orgs.csv在Excel中打开,name、address、phones列在至少90%的行中已填写。电话显示为+7 (843) 000-00-00格式。评分看起来像带逗号的数字,例如4,7。
可能的问题
- 电话为空,虽然网站上有。"显示电话"按钮有不同的类。通过F12找到它并替换代码中的。
- CSV中的西里尔字母显示为乱码。Excel没识别编码。通过菜单数据、从文本/CSV打开并选择UTF-8,或者等第6步我们转换为xlsx。
- 脚本卡在一张卡片上。在page.goto中添加参数timeout=45000,这样45秒后会抛出异常,循环继续。
第4步:从卡片收集评论
本阶段目标:为每家企业获取带评分、日期和文本的评论列表,保存到单独的reviews.csv文件。
评论在哪里
每张卡片都有"评论"标签,地址形如https://yandex.ru/maps/org/名称/标识符/reviews/。评论像第二步的企业列表一样按滚动逐步加载。默认按相关度排序,最新评论可以通过切换到"按最新"排序获得。
编写评论收集函数
创建文件parse_reviews.py。基础与上一步相同,所以只给出函数和循环:
def parse_reviews(page, url, max_scrolls=15):
page.goto(url.rstrip('/') + '/reviews/', wait_until='domcontentloaded')
time.sleep(random.uniform(3, 6))
page.mouse.move(350, 600)
seen = 0
for _ in range(max_scrolls):
page.mouse.wheel(0, random.randint(1500, 2500))
time.sleep(random.uniform(1.5, 3))
cards = page.query_selector_all('.business-review-view')
if len(cards) == seen:
break
seen = len(cards)
result = []
for c in page.query_selector_all('.business-review-view'):
def sub(sel):
el = c.query_selector(sel)
return el.inner_text().strip() if el else ''
stars = c.query_selector_all('.business-rating-badge-view__star._full')
result.append({
'org_url': url,
'author': sub('.business-review-view__author-name'),
'date': sub('.business-review-view__date'),
'stars': len(stars),
'text': sub('.business-review-view__body-text'),
})
return result在链接循环中调用parse_reviews而不是parse_card,把列表的每个元素作为单独行写入reviews.csv,字段为org_url、author、date、stars、text。恢复逻辑和每家企业后写入保持不变。
解析细节
- 限制max_scrolls=15。热门商家可能有两三千条评论。全部收集很少需要,而且消耗大量时间和请求。十五次滚动通常够100-150条最新评论。
- 通过星星评分。评论中的分数不是文字,而是画出来的星星。我们计算带_full修饰符的元素,即填充的星星。
- 长评论。部分文本被折叠,需要点击"更多"。如果需要完整文本,收集前点击卡片内所有类为business-review-view__expand的按钮。
注意:评论作者的名字是用户数据,不是企业数据。如果你的任务是情感分析或寻找典型投诉,不需要author字段。不要无目的地收集它,这样你可以免除152-FZ的额外问题。如果确实需要该字段,请将文件保存在本地,不要传给第三方。
建议:不要对所有企业收集评论,而是对筛选后的列表:例如只有评分低于4.0的,或只有直接竞争对手的。这样请求量减少数倍,数据价值不减。
检查:reviews.csv中每家企业有20到150行,stars列包含1到5的数字,text中有有意义的俄语文本。日期形如"15 января"或"3 марта 2026"。
可能的问题
- 找到零条评论。检查URL是否以/reviews/结尾,滚动时评论面板是否在光标下。
- 所有评论的stars都是0。填充星星的类变了。通过F12点击星星找到它。
- 评论重复。面板没完全滚动,一个块被算了两次。在第6步按author加text的配对去重。
第5步:配置IP轮换和防封保护
本阶段目标:教解析器表现得像细心的用户:按计划换IP,识别验证码并自动降低速度,而不是硬撞封禁。
为什么会出现封禁
Yandex不禁止查看卡片,但会监控异常:一个地址每分钟数百页、请求间隔相同、没有鼠标移动、空cookie。当怀疑累积时,出现SmartCaptcha页面,继续坚持则对IP临时限制。我们的策略不是"突破"防御,而是不给它开启的理由。
Yandex地图解析器的三条规则
- 人的节奏。一个IP每分钟不超过8-12张卡片。随机暂停,而非固定。
- 定期换IP。每25-40张卡片或每10-15分钟访问换地址链接。移动代理几秒内获得新的运营商IP,请求历史从网站角度看"归零"。
- 遇到验证码立即回退。看到验证就暂停2-3分钟,换IP,在新的浏览器上下文中从同一张卡片继续。
在代码中添加轮换
把以下函数插入parse_cards.py和parse_reviews.py,并在主循环中调用:
import requests
def change_ip():
try:
r = requests.get(CHANGE_IP_URL, timeout=30)
print('Смена IP:', r.status_code)
except Exception as e:
print('Не удалось сменить IP:', e)
time.sleep(IP_CHANGE_WAIT)
def is_captcha(page):
if 'showcaptcha' in page.url or 'checkcaptcha' in page.url:
return True
return page.query_selector('.CheckboxCaptcha') is not None
def new_page(browser):
ctx = browser.new_context(locale='ru-RU', viewport={'width': random.choice([1366, 1440, 1536]), 'height': 900})
return ctx.new_page()主循环变成这样:
page = new_page(browser)
since_change = 0
for n, url in enumerate(todo, 1):
if since_change >= random.randint(25, 40):
page.context.close()
change_ip()
page = new_page(browser)
since_change = 0
row = parse_card(page, url)
if is_captcha(page):
print('Капча! Пауза и смена IP')
page.context.close()
time.sleep(random.uniform(120, 180))
change_ip()
page = new_page(browser)
row = parse_card(page, url)
writer.writerow(row)
f.flush()
since_change += 1
time.sleep(random.uniform(4, 9))重要理解
- 新上下文随新IP一起。关闭上下文会重置cookie和本地存储。换地址不重置cookie没意义:网站会继续通过会话认出你。
- config.py中的变量IP_CHANGE_WAIT是第一步测出的时间,通常15-30秒。不能设得更短:浏览器会通过旧地址打开页面。
- 随机窗口大小为浏览器指纹增加多样性。这是温和措施,但免费。
- 通过链接轮换的移动代理比其他方式方便:你不用手动在数十个地址间切换,只需访问一个URL。
建议:维护简单日志:把时间、卡片编号和事件(成功、验证码、换IP)写入文件。一周后从日志你能看出什么节奏下验证码完全不出现,并根据你的代理通道调整暂停。
检查:一小时内脚本收集400-600张卡片,终端不超过一两条验证码消息,每次之后采集自动继续。通过代理的IP至少变了十次(从"Смена IP: 200"行可见)。
可能的问题
- 每10张卡片就出现验证码。对你的通道来说节奏太高。把暂停增加到8-15秒,每15张卡片换一次IP。
- 换IP后页面不加载。增加IP_CHANGE_WAIT:运营商还没分配新地址。
- 换IP链接返回访问过频错误。大多数套餐有最小换IP间隔,通常一到两分钟。不要更频繁换IP。
第6步:清洗数据并保存到Excel
本阶段目标:把原始CSV变成整洁的Excel表格,无重复,电话规范化,评分数值化。
编写清洗脚本
创建文件clean_export.py:
import pandas as pd
def norm_phone(value):
out = []
for raw in str(value).split(';'):
digits = ''.join(ch for ch in raw if ch.isdigit())
if len(digits) == 11 and digits[0] in '78':
out.append('+7' + digits[1:])
elif len(digits) == 10:
out.append('+7' + digits)
return '; '.join(dict.fromkeys(out))
orgs = pd.read_csv('orgs.csv', encoding='utf-8')
orgs = orgs.drop_duplicates(subset='url')
orgs['phones'] = orgs['phones'].fillna('').apply(norm_phone)
orgs['rating'] = pd.to_numeric(orgs['rating'].astype(str).str.replace(',', '.'), errors='coerce')
orgs['reviews_count'] = pd.to_numeric(orgs['reviews_count'].astype(str).str.extract('(\d+)')[0], errors='coerce')
orgs = orgs.sort_values('rating', ascending=False)
reviews = pd.read_csv('reviews.csv', encoding='utf-8')
reviews = reviews.drop_duplicates(subset=['org_url', 'author', 'text'])
with pd.ExcelWriter('yandex_maps_result.xlsx', engine='openpyxl') as w:
orgs.to_excel(w, sheet_name='Организации', index=False)
reviews.to_excel(w, sheet_name='Отзывы', index=False)
print('Организаций:', len(orgs), 'Отзывов:', len(reviews))提取评论数的行使用了由反斜杠加括号中的字母d组成的正则表达式:它从"312 отзывов"这样的文本中取出第一个数字。请仔细复制。
脚本做什么
- 按URL去重企业。
- 把所有电话规范为+7XXXXXXXXXX格式,去掉括号、空格和连字符。这种格式对CRM和数据库核对很方便。
- 把评分中的逗号换成点,让Excel识别为数字并允许排序。
- 从文本字符串中提取评论数。
- 去重评论,将两个工作表写入一个xlsx文件。
运行python clean_export.py并打开yandex_maps_result.xlsx。第一个工作表企业按评分排序,电话统一,第二个工作表是按org_url列关联企业的评论。
建议:在脚本中添加"采集日期"列,填当前日期。一个月后重复采集并用pandas的merge函数比较表格:你会看到新企业、关闭的店铺和竞争对手评分变化。这已经是完整的市场监控。
检查:xlsx文件打开无警告,西里尔字母正常,rating列按数字排序,phones列无括号和空格,"Организации"工作表行数等于links.json中唯一链接数减去错误数。
结果检查:就绪解析器清单
过一遍清单。如果每一项都回答"是",Yandex地图解析器就准备好定期工作了。
清单
- check_proxy.py显示与家庭地址不同的移动运营商IP。
- collect_links.py对中等查询收集超过50个链接并自动停止。
- parse_cards.py在至少90%的卡片中填写名称、地址和电话。
- "显示电话"按钮自动展开。
- parse_reviews.py返回评分1到5的评论。
- 遇到验证码时脚本暂停、换IP并在无人干预下继续。
- 事故停止后重新运行从断点继续。
- clean_export.py创建带两个工作表和规范化电话的xlsx。
如何完整测试
- 选一个小查询,城市里只有30-60家企业,例如某县城里的"шиномонтаж"。
- 依次运行所有脚本并计时。50张带评论的卡片应该花15-25分钟。
- 从表格中随机选五家企业,手动核对电话和地址与卡片。
- 在parse_cards.py进行到一半时用Ctrl+C停止,再次运行。确认"剩余"计数减少而不是重置。
成功执行的指标
- 手动核对时数据准确度:电话和地址100%匹配。
- 空名称比例:低于3%。
- 验证码频率:每200-300张卡片不超过一次。
- 速度:安全节奏下一个代理通道每小时400-600张卡片。
常见错误及解决方法
我们收集了几乎所有第一次写Yandex地图解析器的人都会遇到的问题及解决办法。
大多数卡片字段为空
原因:Yandex更新了页面,元素类变了。解决方法:在Chrome中打开卡片,按F12,找到新的类并在parse_card函数中替换它们。把选择器保存在文件开头的一个字典中,只需修改一处。
第一页就出现验证码
原因:代理IP已经被之前活动"用累了",或浏览器启动时参数可疑。解决方法:启动前换IP,确认设置了locale='ru-RU',首次运行不要用无头模式:它会给出更多自动化信号。
滚动列表时地图在动而不是面板
原因:鼠标光标在左面板外。解决方法:根据你的窗口大小调整page.mouse.move的坐标。宽度1400像素时,面板大约占前450像素。
总是收集同样的20家企业
原因:面板没滚到底,stale计数器过早触发。解决方法:把滚动后的暂停增加到3-4秒,stale阈值增加到6,地图有时下一批加载很慢。
浏览器中能看到电话,但表格中为空
原因:号码只在点击后出现,而脚本在其完成前收集数据,或按钮有不同的类。解决方法:把点击后的暂停增加到2-3秒,检查按钮的类。
错误Target closed或Browser has been closed
原因:换IP时你关闭了上下文,但继续使用旧的page对象。解决方法:确认每次page.context.close()后,page变量通过new_page(browser)重新赋值,如第5步示例。
换IP后页面加载不完
原因:运营商还没分配新地址,代理处于过渡状态。解决方法:把IP_CHANGE_WAIT增加到30-40秒,在page.goto中添加timeout,避免挂起阻塞循环。
Excel打开CSV显示乱码
原因:Excel没识别没有BOM标记的UTF-8。解决方法:使用clean_export.py并处理xlsx,或写CSV时指定encoding='utf-8-sig'。
进阶功能
基础解析器已能解决90%的任务。如果想要更多速度和数据,以下是发展方向。
拦截网络响应而非解析页面
地图以JSON格式通过单独请求加载卡片数据。Playwright可以通过page.on('response', handler)订阅它们。在处理函数内检查response.url是否包含/maps/api/片段,并保存response.json()。方法优点:数据结构化,不依赖页面类名。缺点:内部地址无预警变化,解析嵌套JSON比读取页面文本复杂。这个方式适合与主要方式结合:如果JSON响应来了就用它,否则回退到HTML解析。
多代理通道并行工作
一个移动代理在安全节奏下每小时提供400-600张卡片。如果需要更快,买两三个通道,每个通道运行单独进程,把links.json分成等份。不要通过一个通道启动多个浏览器:IP上的总节奏会增加,验证码会回来。编排可以用简单的subprocess启动脚本,或用asyncio配合async_playwright,每个worker在new_context的proxy参数中获得自己的上下文和代理。
监控变化
把每次采集结果保存到带日期的单独文件,按org_id比较。出现的标识符是市场新玩家,消失的是关闭的,rating和reviews_count的变化是声誉动态。通过Windows任务计划程序或cron设置每两周运行一次,你就有了细分市场的活地图。
扩展字段
卡片中还有其他有用块:带价格的服务列表、社交媒体链接、"已验证企业"标志、照片数量、最近地铁站。每个字段按同样方式添加:通过F12找到类,在字典中添加grab。价格块对评估细分市场平均客单价的套利者特别有价值。
评论分析
收集的文本很适合简单分析:统计一到两星评论中的词频,得到客户对竞争对手的主要投诉列表。用pandas和标准库的Counter就够了。进阶方案是用语言模型对文本按主题分类:价格、质量、服务、等待。
官方API作为替代
对于大型商业项目,考虑Yandex的企业搜索API。它以结构化形式返回名称、地址、电话和类别,完全没有封禁风险。里面没有评论,限额付费,但对于收集联系数据库这是最干净的路径。这种情况下卡片解析器仍是获取评论和API中缺失字段的工具。
FAQ:Yandex地图解析常见问题
从Yandex地图收集企业电话合法吗?
企业联系方式由组织自己公开发布,不是个人数据。为自身分析收集是允许的。但在未经同意的情况下用这些号码群呼和群发违反广告法。另外记住Yandex用户协议对自动化采集的限制,保持最低负载。
为什么不能只用requests请求,不用浏览器?
地图完全由JavaScript代码绘制。服务器返回几乎空的HTML,数据随后加载。requests会得到没有电话和地址的框架。所以需要带真实Chromium的Playwright。
必须用移动代理吗,能用家庭IP解析吗?
技术上前50-100张卡片也能收集。但之后会出现验证码,家庭IP会被限制数小时,你无法正常使用Yandex。移动代理通过两种方式解决问题:移动运营商地址本身就更有信任度,通过链接轮换可以在不停采集的情况下在地址间分散负载。
一个代理每天能收集多少张卡片?
安全节奏下每分钟8-12张卡片,带暂停和每30张换IP——连续工作一昼夜约5000-8000张。加上评论,量会减少两到三倍,因为每个评论页都需要滚动。
如果Yandex改了页面,解析器坏了怎么办?
这是正常情况,一年发生几次。打开卡片,按F12,找到所需元素的新类并在代码中替换。花10-15分钟。为简化过程,把所有选择器保存在文件开头的一个字典中。
如何收集整个地区而不仅一个城市的企业?
列出居民点列表,循环运行collect_links.py,把名称代入QUERY。把链接合并到一个set。大城市还要按区域拆分,因为一次查询很少返回超过500个结果。
能在没有浏览器窗口的后台运行解析器吗?
可以,设置headless=True。但只有在调试好选择器并确认不出现验证码后才这样做。无窗口模式更难发现问题,一些自动化迹象表现更强。折中方案:headless=True加上每次错误时通过page.screenshot(path='error.png')截图。
不看屏幕怎么知道脚本遇到了验证码?
第5步的is_captcha函数检查页面地址和验证块的存在。添加给自己发通知,例如写日志文件或通过bot发消息到即时通讯,你就能立即知道问题。
评论只收集到最后一百条,怎么获取更多?
把parse_reviews中的max_scrolls增加到50-100。注意每次滚动都是对服务器的额外请求,所以对于有数千条评论的企业,采集会花几分钟并需要更频繁换IP。
这个方法比现成的解析服务好在哪里?
你完全控制字段、节奏和数据新鲜度,不用为每行付费,不依赖别人的更新计划。现成服务对几百张卡片的一次性任务很方便,而自己的Yandex地图解析器第二次采集就回本了。
结语
让我们总结。你安装了Python和Playwright,连接了移动代理并检查了换IP。按关键词和城市收集了企业卡片链接。编写了打开每张卡片、展开隐藏电话并获取名称、地址、网站、评分和营业时间的函数。添加了带评分的评论收集。教解析器按计划换IP并正确应对验证码。最后清洗数据并获得带两个工作表的整洁Excel。
最值得记住的是:Yandex地图解析器的稳定性不靠技巧,而靠三件事——人的节奏、通过移动代理定期轮换地址,以及第一个信号出现时愿意停下来。尊重资源的脚本能数月无人干预地工作。
接下来做什么
- 按你的细分市场进行第一次完整采集,手动检查五到十张卡片。
- 设置每两周采集一次,开始积累变化历史。
- 尝试进阶部分中的JSON响应拦截,减少对页面的依赖。
- 如果量增长,添加第二个代理通道并并行工作。
发展方向
下一个合理步骤是自动分析收集的评论,并把表格与你的CRM或广告账户关联。如果你与多个平台合作,同样的Playwright和移动代理方法可以迁移到任何动态加载的网站。原则相同,只是选择器变化。祝你采集顺利,数据干净!