
Scrapy 解析新楼盘列表页时返回 None,主因是目标网站使用动态生成的 CSS 类名(如 NewBuildingItem__MainTitle-sc-o36w9y-6 KYYzh),导致硬编码类名的选择器极易失效;本文提供基于属性前缀/包含关系的稳定 CSS 选择器方案,并给出完整可运行的 Scrapy 爬虫示例。
scrapy 解析新楼盘列表页时返回 none,主因是目标网站使用动态生成的 css 类名(如 `newbuildingitem__maintitle-sc-o36w9y-6 kyyzh`),导致硬编码类名的选择器极易失效;本文提供基于属性前缀/包含关系的稳定 css 选择器方案,并给出完整可运行的 scrapy 爬虫示例。
在使用 Scrapy 抓取现代前端渲染的房产类网站(如俄罗斯联邦统一房地产服务平台)时,常见问题并非网络请求失败或反爬拦截,而是 CSS 选择器始终匹配为空(返回 None)。根本原因在于:该类网站普遍采用 CSS-in-JS 方案(如 Styled Components),自动生成带哈希后缀的动态类名(例如 NewBuildingItem__MainTitle-sc-o36w9y-6 KYYzh 中的 sc-o36w9y-6 和 KYYzh 每次构建都会变化)。一旦页面重构或服务端重新部署,原选择器立即失效。
因此,必须摒弃对完整类名的强依赖,转而采用语义化、结构化、抗干扰的选择策略。以下是针对您提供的 HTML 片段优化后的稳健解析方案:
✅ 推荐的稳健 CSS 选择器(Scrapy 兼容)
| 字段 | 稳健选择器 | 说明 |
|---|---|---|
| 单栋楼盘容器 | div[class^="NewBuildingItem__Wrapper"] |
利用 ^= 匹配以固定前缀开头的类名,忽略随机后缀 |
| 详情链接 | a[class^="NewBuildingItem__ImageWrapper"]::attr(href) |
同上,定位图片外层链接 |
| 楼盘标题 | a[class^="NewBuildingItem__MainTitle"]::text |
避免依赖多级嵌套或变动 class |
| 地址 | p[class^="NewBuildingItem__Text"]:not([class*="ObjectID"])::text |
使用 :not() 排除 ID 段落,精准捕获地址 |
| ID 编号 | p[class*="ObjectID"]::text |
利用 *= 匹配包含关键词的类名,更灵活 |
| 入住时间 & 开发商 | p[class^="NewBuildingItem__InfoValue"]::text |
二者同属该类,按顺序取 [0] 和 [1]
|
✅ 完整可运行的 Scrapy Spider 示例
import scrapy
class BuildingSpider(scrapy.Spider):
name = "building"
allowed_domains = ["xn--80az8a.xn--d1aqf.xn--p1ai"]
# 注意:URL 中 & 符号需转义为 &(Scrapy 对 URL 解析严格)
start_urls = [
"https://xn--80az8a.xn--d1aqf.xn--p1ai/%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81%D1%8B/"
"%D0%BA%D0%B0%D1%82%D0%B0%D0%BB%D0%BE%D0%B3-%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BA/"
"%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BE%D0%B2/"
"%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA?place=0-44&objStatus=0"
]
def parse(self, response):
# 使用前缀匹配定位所有楼盘项(抗类名变动)
building_items = response.css('div[class^="NewBuildingItem__Wrapper"]')
if not building_items:
self.logger.warning("⚠️ 未找到任何楼盘容器,请检查页面结构或是否被 JavaScript 渲染")
return
for idx, item in enumerate(building_items):
try:
# 稳健提取字段(全部使用 get() 防止 None 报错)
link = item.css('a[class^="NewBuildingItem__ImageWrapper"]::attr(href)').get()
title = item.css('a[class^="NewBuildingItem__MainTitle"]::text').get(default='').strip()
# 地址:排除含 ObjectID 的 p 标签
address_p = item.css('p[class^="NewBuildingItem__Text"]:not([class*="ObjectID"])::text').get()
address = address_p.strip() if address_p else ''
# ID:利用包含匹配
id_p = item.css('p[class*="ObjectID"]::text').get()
building_id = id_p.replace('ID: ', '').strip() if id_p else ''
# 入住时间 & 开发商:同属 InfoValue 类,按顺序索引
info_values = item.css('p[class^="NewBuildingItem__InfoValue"]::text').getall()
commissioning = info_values[0].strip() if len(info_values) > 0 else ''
developer = info_values[1].strip() if len(info_values) > 1 else ''
yield {
'link': link,
'title': title,
'address': address,
'id': building_id,
'commissioning': commissioning,
'developer': developer,
'item_index': idx # 调试辅助字段
}
except Exception as e:
self.logger.error(f"❌ 解析第 {idx} 个楼盘时异常: {e}")
continue⚠️ 关键注意事项
-
URL 特殊字符处理:Scrapy 中
&必须写为&,否则会被截断(您原始代码中start_urls的&未转义,是潜在失败原因之一)。 -
动态渲染风险:若页面内容由 JavaScript 异步加载(如通过 API 请求填充),Scrapy 原生无法执行 JS。此时需改用
scrapy-splash或切换至 Playwright/Selenium,但本例 HTML 片段显示内容已静态存在,优先验证是否为服务端渲染(SSR)。 -
调试技巧:
- 在
parse中添加self.logger.info(f"HTML snippet: {response.text[:500]}")查看实际响应; - 使用
scrapy shell "URL"进入交互式环境,手动测试response.css(...).getall(); - 启用
ROBOTSTXT_OBEY = False并检查robots.txt是否禁止爬取。
- 在
-
扩展建议:为提升鲁棒性,可进一步结合 XPath(如
//div[starts-with(@class, "NewBuildingItem__Wrapper")])或正则提取(re=r'ID: (.*?)),但 CSS 前缀/包含匹配已覆盖 95% 动态类名场景。
通过以上方案,您将不再受困于“选择器总返回 None”的循环调试,真正实现面向语义而非样式名称的可持续爬取。

















