Scrapy爬取Etsy搜索页与分类页产品数量差异问题排查
Etsy爬取产品数量不足的问题分析与解决方案
问题原因
CSS选择器匹配不全
你用的div.js-merch-stash-check-listing仅匹配了部分产品容器,Etsy搜索页和分类页的DOM结构存在差异:分类页部分产品用这个类,但搜索页的产品容器类名不同(比如可能是div.v2-listing-card),导致大量产品被遗漏。动态加载时机未适配
Etsy产品采用滚动加载机制,仅靠wait:2的固定等待时间不足以让单页所有产品加载完成。尤其是搜索页加载策略更严格,需触发滚动事件才会加载后续产品,单纯等待无法触发加载逻辑。反爬机制限制
Splash默认请求头(如User-Agent)可能被Etsy识别为非真实浏览器,导致返回内容被截断,仅展示少量产品;同时未维护会话Cookie也可能导致页面内容不完整。
可行解决方案
1. 修正CSS选择器,覆盖所有产品容器
放弃依赖特定类名,改用产品节点的通用属性匹配——所有产品都带有data-listing-id属性,用这个定位更可靠:
div[data-listing-id]
同时检查每个字段的选择器兼容性,比如搜索页的产品名称可能用h2.wt-text-body而非h3.wt-text-caption,需做兼容处理。
2. 优化Splash配置,模拟滚动加载
用Lua脚本模拟页面滚动,触发所有产品的加载逻辑,替换单纯的wait参数。示例代码如下:
def start_requests(self): lua_script = """ function main(splash, args) splash:set_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36") splash:go(args.url) splash:wait(2) -- 模拟滚动3次,每次滚动到底部并等待加载 for i=1,3 do splash:runjs("window.scrollTo(0, document.body.scrollHeight);") splash:wait(2) end return splash:html() end """ for url in self.start_urls: yield SplashRequest( url, self.parse, endpoint='execute', args={'lua_source': lua_script}, meta={'splash': {'resource_timeout': 30}} )
3. 配置真实请求头与会话
在Splash请求中添加真实浏览器的User-Agent,避免被反爬识别;同时启用Splash的Cookie管理,保持会话一致性,确保页面返回完整内容。
4. 调试验证方法
- 直接访问Splash渲染页面:
http://localhost:8050/execute?lua_source=[你的Lua脚本]&url=[目标URL],查看渲染后的HTML是否包含所有产品,以此判断是加载问题还是选择器问题。 - 在Spider中打印
response.text长度或保存为HTML文件,对比手动浏览的页面内容,定位差异点。
修改后的示例Spider代码
import scrapy from scrapy_splash import SplashRequest import logging class EtsySpider(scrapy.Spider): name = 'etsy' # 同时测试分类页和搜索页 start_urls = [ 'https://www.etsy.com/uk/c/electronics-and-accessories/gadgets?ref=catnav-11049', 'https://www.etsy.com/uk/search?q=gadgets&ref=search_bar' ] item_id = 0 def start_requests(self): lua_script = """ function main(splash, args) splash:set_user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36") splash:go(args.url) splash:wait(2) for i=1,3 do splash:runjs("window.scrollTo(0, document.body.scrollHeight);") splash:wait(2) end return splash:html() end """ for url in self.start_urls: yield SplashRequest( url, self.parse, endpoint='execute', args={'lua_source': lua_script}, meta={'splash': {'resource_timeout': 30}} ) self.log(f"Splash request sent: {url}", level=logging.INFO) def parse(self, response): # 用通用属性定位所有产品 for product in response.css('div[data-listing-id]'): # 兼容不同页面的名称选择器 name = product.css('h3.wt-text-caption::text, h2.wt-text-body::text').get() if not name: continue # 兼容分类页和搜索页的category获取 if 'search' in response.url: category = response.css('#global-enhancements-search-query::attr(value)').get() else: category = response.css('span.wt-breadcrumb__text::text').getall()[-1] yield { 'id': self.item_id, 'name': name.strip(), 'category': category.strip() if category else 'Unknown', 'price': product.css('span.currency-value::text').get(), 'item_link': product.css('a.listing-link::attr(href)').get(), 'img_link': product.css('img.wt-width-full::attr(src)').get(), 'listing_id': product.css('::attr(data-listing-id)').get() } self.item_id += 1
内容的提问来源于stack exchange,提问作者Olly Entwistle
相关产品推荐
相关产品推荐

