本文介绍如何通过分析 yapo.cl 前端 api 接口,完全规避 selenium,使用纯 scrapy 高效抓取房源列表及详情(含标题、url、经纬度),大幅提升稳定性与速度。
本文介绍如何通过分析 yapo.cl 前端 api 接口,完全规避 selenium,使用纯 scrapy 高效抓取房源列表及详情(含标题、url、经纬度),大幅提升稳定性与速度。
Yapo.cl 并非完全依赖前端 JavaScript 渲染房源列表——其真实数据由两个关键 API 接口提供:一个用于分页获取房源摘要(/buyers/search),另一个用于根据 listId 获取单条房源完整信息(/buyers/items/{listId})。这使得我们无需启动浏览器、滚动页面或等待 DOM 加载,即可精准、高效地采集结构化数据。
✅ 核心优势
- 零浏览器依赖:彻底摆脱 Selenium 的资源开销与维护成本;
- 高稳定性:避免因页面结构微调、JS 加载失败或反爬策略导致的解析中断;
- 原生支持地理坐标:geoLocation.geoPosition 字段直接返回 [latitude, longitude] 数组,无需额外逆地理编码;
- 可扩展性强:接口参数清晰(如 regionId, estateType, orders),便于按区域、类型、排序规则定制抓取。
? 实现要点说明
-
API 地址构造
初始请求 URL 中的 query 参数是 JSON 对象经 URI 编码的结果,例如筛选“大区:首都大区(ID=15)、房产类型:公寓/住宅([1,2])、分类:出租房产(1240)”:query = {"estateType": [1,2], "regionId": 15, "category": [1240]} # 编码后为 %7B%22estateType%22:[1,2],%22regionId%22:15,%22category%22:[1240]%7D 必要请求头(Headers)
Yapo 的 API 要求严格的身份标识。虽然 x-txref 等部分字段具有时效性,但实测中即使固定值(如 "fcbbbb37-6f67-4cc0-a39a-367769e7eb3b")仍可长期有效。务必保留 origin、referer、user-agent 及所有 x-* 自定义头,否则将返回 403。广告 URL 构建逻辑
官网展示链接格式为 https://www.yapo.cl/inmuebles/{cleaned-title}_{listId}。需对 subject 字段做标准化处理:转小写、移除特殊符号、空格替换为短横线(-),确保 URL 合法且符合前端路由规则。-
经纬度安全提取
data["header"]["geoLocation"]["geoPosition"] 可能为 null 或空数组(如示例输出中部分条目为 [""])。建议在 parse_dir_contents 中添加健壮性判断:lat_long = data["header"].get("geoLocation", {}).get("geoPosition", []) latitude = lat_long[0] if len(lat_long) > 0 else None longitude = lat_long[1] if len(lat_long) > 1 else None
? 完整可运行代码(已优化)
import scrapy
import json
import re
class YapoSpider(scrapy.Spider):
name = "yapo"
allowed_domains = ["yapo.cl"]
start_urls = [
"https://public-api.yapo.cl/buyers/search?page=0&limit=47&query=%7B%22estateType%22:[1,2],%22regionId%22:15,%22category%22:[1240]%7D&orders=%7B%22orderBy%22:%22listTime%22,%22typeOrder%22:%22desc%22%7D"
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.headers = {
"accept": "application/json, text/plain, */*",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"origin": "https://www.yapo.cl",
"referer": "https://www.yapo.cl/",
"sec-ch-ua": '"Chromium";v="123", "Not:A-Brand";v="8"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Linux"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
"x-chref": "WEB",
"x-cmref": "client",
"x-commerce": "Yapo",
"x-country": "CL",
"x-domain": "Buyer",
"x-rhsref": "www.yapo.cl",
"x-txref": "fcbbbb37-6f67-4cc0-a39a-367769e7eb3b",
}
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, headers=self.headers, callback=self.parse)
def parse(self, response):
try:
data = json.loads(response.text)
ads_list = data.get("ads", [])
except (json.JSONDecodeError, KeyError):
self.logger.error(f"Failed to parse API response from {response.url}")
return
for ad in ads_list:
list_id = ad.get("listId")
if not list_id:
continue
api_url = f"https://public-api.yapo.cl/buyers/items/{list_id}"
yield scrapy.Request(
api_url,
headers=self.headers,
callback=self.parse_dir_contents,
cb_kwargs={"list_id": list_id}
)
def parse_dir_contents(self, response, list_id):
try:
data = json.loads(response.text)
except json.JSONDecodeError:
self.logger.error(f"Invalid JSON in item detail: {response.url}")
return
header = data.get("header", {})
title = header.get("subject", "").strip()
geo = header.get("geoLocation", {})
lat_long = geo.get("geoPosition", [])
# 构建标准化 URL
cleaned_title = re.sub(r"[^a-zA-Z0-9\s]", "", title.lower())
cleaned_title = re.sub(r"\s+", "-", cleaned_title.strip())
url = f"https://www.yapo.cl/inmuebles/{cleaned_title}_{list_id}" if cleaned_title else None
yield {
"url": url,
"title": title,
"latitude": lat_long[0] if len(lat_long) > 0 else None,
"longitude": lat_long[1] if len(lat_long) > 1 else None,
"list_id": list_id,
}
⚠️ 注意事项
- 分页处理:当前示例仅抓取第一页(page=0)。如需全量数据,可在 parse() 中循环生成后续页请求(page=1, page=2…),注意检查 data["pagination"]["hasNext"]。
- 请求频率控制:API 有隐式限流,建议在 settings.py 中设置 DOWNLOAD_DELAY = 1 避免被封 IP。
- 字段容错:data 结构可能随版本更新微调,所有 .get() 访问均应添加默认值或异常捕获。
- 法律与合规:请遵守 robots.txt(https://www.yapo.cl/robots.txt)及服务条款,仅用于个人学习或授权用途。
通过直连后端 API,你不仅能稳定获取经纬度等关键字段,还能将爬虫性能提升数倍。告别 Selenium 的不确定性,拥抱结构化、可维护、可监控的数据采集新范式。










