Scrapy爬虫抓取Houzz电话号码仅返回“点击呼叫”的问题
抓不到号码的核心原因
你当前的抓取逻辑永远拿不到正确电话号码,问题根本不在CSS选择器写得对不对:
- 现在用的选择器
span.hz-pro-search-result__contact-info__cover对应的节点,就是页面上默认显示“Click to call”的引导占位文本,这个节点在初始返回的HTML里,永远只会显示这行引导字 - Houzz的电话号码不是简单的用CSS属性隐藏在DOM里的,是用户点击呼叫按钮后,前端JS发异步请求拉取到号码,再动态渲染到页面上的。所以不管你补多少针对展示态号码节点的CSS类规则,都抓不到——初始返回的HTML里根本没有这些内容
- 不要一上来就换Selenium模拟点击,Houzz对无头浏览器的反爬拦截阈值很高,大部分模拟请求会直接弹人机验证,后期维护成本极高
两个低维护成本的可落地方案
从页面内嵌全局JSON提取数据
Houzz的搜索结果页在初始加载时,会把全量商家的结构化信息(包括电话号码)直接存在页面的前端全局变量里,根本不需要触发交互:
- 跳过逐DOM节点爬取的逻辑,直接从页面的script标签里提取内嵌的
__INITIAL_STATE__全局对象,解析成字典后遍历每个商家条目,直接取对应的电话号码字段即可 - 这种方法的抓取效率比逐节点爬DOM高30%以上,也不受前端交互逻辑变动的影响
定位号码查询的异步接口
如果全局JSON里确实没有存电话号码,就直接抓包找点击呼叫按钮时触发的XHR接口:
- 从静态渲染的DOM节点里提取每个商家的Pro ID(这个字段直接存在节点的data属性里,不需要交互就能拿到)
- 构造请求调用号码查询接口,带齐和搜索页一致的User-Agent、Cookie、Referer头,就能直接拿到JSON格式的电话号码
- 最后把接口返回的号码和之前爬的商家名称、地址等基础信息做匹配拼接,就能生成完整的采集结果
优化后的爬虫代码
删掉原代码里无意义的重复异常捕获逻辑,换成更稳健的写法:
import json import scrapy class houzzSpider(scrapy.Spider): name = 'houzz' start_urls = ['https://www.houzz.com/professionals/interior-designer'] def parse(self, response): # 提取页面内嵌的全局状态数据 state_script = response.xpath('//script[contains(text(), "__INITIAL_STATE__")]/text()').get() if state_script: # 切割提取JSON字符串 raw_json = state_script.split('window.__INITIAL_STATE__ = ')[1].split(';')[0] # 根据实际页面结构调整JSON遍历路径 pro_list = json.loads(raw_json)['searchResult']['professionals'] for pro in pro_list: yield { 'Name': pro.get('displayName', ''), 'Call': pro.get('phoneNumber', '号码未公开') } # 优化翻页逻辑,避免节点缺失时报KeyError next_page = response.css('a.hz-pagination-link--next::attr(href)').get() if next_page: yield response.follow(next_page, callback=self.parse)
代码里的JSON路径需要根据实际页面的结构做小幅调整,不要直接硬抄。原代码里的try/except块两个分支的抓取逻辑完全一致,属于无效冗余代码,直接删除即可。
内容的提问来源于stack exchange,提问作者Arif Fadhillah
相关产品推荐
相关产品推荐

