
本文详解 scrapy 解析动态生成或类名随机化的网页时 selector 失效的常见原因,提供基于属性前缀/内容的稳健 css 选择器写法、调试技巧及完整可运行代码。
本文详解 scrapy 解析动态生成或类名随机化的网页时 selector 失效的常见原因,提供基于属性前缀/内容的稳健 css 选择器写法、调试技巧及完整可运行代码。
在使用 Scrapy 爬取俄罗斯房地产平台(如 xn--80az8a.xn--d1aqf.xn--p1ai)的新建楼盘列表页时,开发者常遇到 response.css(...).get() 持续返回 None 的问题——即使 HTML 结构清晰、元素可见,CSS 选择器却“失灵”。这并非 Scrapy 本身缺陷,而是现代前端框架(如 React + Styled Components)生成的类名具有高度随机性(如 NewBuildingItem__MainTitle-sc-o36w9y-6 KYYzh 中的 o36w9y-6 和 KYYzh 每次构建可能变化),导致硬编码完整类名的选择器极易失效。
✅ 根本原因:类名不可靠,非语义化
原始代码中使用的 CSS 选择器:
response.css('div.Newbuildings__NewBuildingList-sc-1bou0u4-14.huMfny')依赖了完整且固定的类名组合。但实际页面中,Newbuildings__NewBuildingList-sc-1bou0u4-14 中的哈希后缀(如 1bou0u4-14)会随前端构建版本变更;而 huMfny 是随机生成的 BEM-style 修饰符,不具备稳定性。
✅ 推荐方案:使用语义化、前缀驱动的选择器
应放弃匹配完整类名,转而利用类名前缀(^=)、类名包含(*=)或结构关系+文本特征进行定位。以下是针对您提供的 HTML 片段优化后的稳健选择器(已验证有效):
| 目标字段 | 推荐 Scrapy CSS 选择器 | 说明 |
|---|---|---|
| 楼盘容器 | div[class^='NewBuildingItem__Wrapper'] |
匹配以 NewBuildingItem__Wrapper 开头的 class |
| 详情链接 | a[class^='NewBuildingItem__ImageWrapper']::attr(href) |
利用功能语义前缀,避免依赖随机后缀 |
| 楼盘标题 | a[class^='NewBuildingItem__MainTitle']::text |
同上,精准定位主标题链接文本 |
| 地址信息 | p[class^='NewBuildingItem__Text']:not([class*='InfoLabel']):not([class*='InfoValue'])::text |
排除标签类,保留纯文本描述段落 |
| ID 编号 | p[class*='ObjectID']::text |
使用 *= 匹配含关键词的类名,更鲁棒 |
| 入住时间 | p[class^='NewBuildingItem__InfoValue']:nth-child(1)::text |
结合顺序定位(首个 InfoValue) |
| 开发商 | p[class^='NewBuildingItem__InfoValue']:nth-child(2)::text |
第二个 InfoValue,对应开发商字段 |
? 小技巧:在 Scrapy Shell 中快速验证选择器是否生效:
scrapy shell "https://xn--80az8a.xn--d1aqf.xn--p1ai/..." >>> response.css("div[class^='NewBuildingItem__Wrapper']").getall()[:2]
✅ 完整修复版 Spider 代码
import scrapy
class BuildingSpider(scrapy.Spider):
name = "building"
allowed_domains = ["xn--80az8a.xn--d1aqf.xn--p1ai"]
# 注意:URL 中 & 需转义为 &(HTML 实体),但 Scrapy 发起请求时应使用原始 &,否则可能被服务端拒绝
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_wrappers = response.css("div[class^='NewBuildingItem__Wrapper']")
if not building_wrappers:
self.logger.warning("⚠️ 未找到任何楼盘容器,请检查页面结构或是否被反爬拦截")
return
self.logger.info(f"✅ 成功定位 {len(building_wrappers)} 个楼盘条目")
for idx, building in enumerate(building_wrappers):
try:
link = building.css("a[class^='NewBuildingItem__ImageWrapper']::attr(href)").get()
title = building.css("a[class^='NewBuildingItem__MainTitle']::text").get(default="").strip()
address = building.css(
"p[class^='NewBuildingItem__Text']:not([class*='InfoLabel']):not([class*='InfoValue'])::text"
).get(default="").strip()
building_id = building.css("p[class*='ObjectID']::text").re_first(r'ID:\s*(\d+)') or ""
# 提取两个 InfoValue:按 DOM 顺序,第一个是“入伙时间”,第二个是“开发商”
info_values = building.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': response.urljoin(link) if link else None,
'title': title,
'address': address,
'id': building_id,
'commissioning': commissioning,
'developer': developer,
'scraped_index': idx
}
except Exception as e:
self.logger.error(f"❌ 解析第 {idx} 个楼盘时出错: {e}")
continue
# 可选:自动翻页(若存在分页按钮)
next_page = response.css("a[aria-label='Следующая страница']::attr(href)").get()
if next_page:
yield response.follow(next_page, self.parse)⚠️ 关键注意事项
-
URL 编码与实体转义:
start_urls中的&应保持原始字符(非&),否则 Scrapy 请求将发送错误参数。 -
动态渲染风险:该网站可能依赖 JavaScript 渲染列表。若
response.body中无目标 HTML,需改用scrapy-splash或playwright集成方案。 -
反爬机制:添加
USER_AGENT和合理DOWNLOAD_DELAY,避免被封 IP:custom_settings = { 'USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'DOWNLOAD_DELAY': 1.5, 'RANDOMIZE_DOWNLOAD_DELAY': True, } -
数据清洗:
ID: 60491中含 HTML 空格实体,推荐用.re_first(r'ID:\s*(\d+)')提取纯数字。
✅ 总结
当 Scrapy 返回 None 时,首要排查方向不是“XPath 写错了”,而是页面是否真实返回了预期 HTML(用 response.text[:500] 查看原始响应)以及选择器是否过度依赖不稳定类名。坚持使用 [class^='Prefix']、[class*='Keyword'] 和结构逻辑(如 :nth-child()、:not())替代完整类名,可大幅提升爬虫健壮性。对于重度 JS 渲染站点,应果断引入浏览器自动化方案,而非强行用静态解析“硬刚”。
通过以上策略,您将不再被随机类名困扰,真正实现稳定、可维护的新房数据采集流程。

















