Scrapy+Playwright爬虫漏爬产品且提前终止,请求顺序异常排查
Scrapy+Playwright爬虫漏爬、提前终止及页面顺序问题排查与修复
问题背景
核心需求是爬取某零售品牌网站的完整数据链路:列表页→产品页→颜色选项→对应颜色图片。当前遇到两个关键问题:
- 大量产品漏爬,爬虫无预警提前终止
- 未完成当前列表页的产品爬取,就开始处理下一页,违背页面顺序遍历的预期
已排查点:
- 已设置
CONCURRENT_REQUESTS = 1,排除并发数导致的混乱 - CPU、内存资源充足,排除硬件瓶颈
- 仅保留第一页请求时,爬虫可正常完成所有产品、颜色、图片的爬取
- 尝试通过点击下一页按钮遍历页面,未解决问题
核心代码
import scrapy from connect_spider_db import DbSpider class MySpider(DbSpider): name = "website" first_page = "https://www.website.fr/men/tshirts/" start_dicts = [{"url": first_page}] for k in range(2,150): start_dicts.append({"url": first_page + "?p=" + str(k),}) def __init__(self, name=None, **kwargs): super().__init__(name, **kwargs) self.website = self.start_dicts[0]["url"].split("/")[2] # extracting 'www.website.fr' self.init() def start_requests(self): for start_dict in self.start_dicts: yield scrapy.Request( url=start_dict["url"], callback=self.parse, meta=dict( playwright=True, playwright_include_page=False ), ) def parse(self, response): css_selector = response.css('...').get() main_links = [] for product in response.css(css_selector): link = product.css('a').attrib["href"] main_links.append(link) for product_link in main_links: yield response.follow( product_link, meta={ "playwright": True, "playwright_include_page": True, }, callback=self.parse_product, ) async def parse_product(self, response): page = response.meta["playwright_page"] links_list = [] for product in response.css('...'): # in all cases, the images of the current color must be selected image_url = product.xpath('@src').get() yield {'url': image_url} button = page.locator('button...') if await button.is_visible(): await button.click() li_list = await page.locator('...Available colors...').all() for link in li_list: link_url = await link.get_attribute("href") links_list.append(link_url) for color_link in links_list: yield response.follow( color_link, callback=self.parse_color ) await page.close() def parse_color(self, response): for product in ...: yield {...}
问题定位
1. 预生成所有列表页请求导致顺序混乱
在类定义阶段直接循环生成1到149页的start_dicts,Scrapy启动后会一次性将所有列表页请求加入调度队列。即使设置了CONCURRENT_REQUESTS=1,调度器也会在当前页的产品请求还未全部处理完成时,就调度下一页的请求——因为调度逻辑是只要队列中有请求就会执行,并发数限制只是同时运行的请求数,无法保证页面的顺序依赖。
2. Playwright操作未等待页面渲染完成
在parse_product中,点击"显示更多颜色"按钮后,立刻获取颜色列表元素:
- 页面点击后需要时间渲染新增的颜色选项,直接获取会导致部分颜色链接未被抓取,进而漏爬对应图片
- 未对获取到的
href做判空处理,无效链接会导致后续请求失败,甚至触发爬虫终止逻辑
3. 无效列表页请求触发异常
直接循环到150页,若网站实际不存在这么多页,无效请求会返回错误响应,可能触发反爬机制或导致爬虫提前终止。
修复方案
1. 改为动态翻页,保证页面顺序
取消预生成所有列表页URL的逻辑,改为从第一页开始,处理完当前页所有产品后,再生成下一页请求:
def start_requests(self): # 仅生成第一页请求 yield scrapy.Request( url=self.first_page, callback=self.parse, meta=dict( playwright=True, playwright_include_page=True # 保留page实例用于翻页操作 ), ) async def parse(self, response): page = response.meta["playwright_page"] # 提取当前页产品链接 css_selector = '...' # 替换为实际产品容器选择器 main_links = [] for product in response.css(css_selector): link = product.css('a::attr(href)').get() if link: main_links.append(link) # 生成产品页请求 for product_link in main_links: yield response.follow( product_link, meta={ "playwright": True, "playwright_include_page": True, }, callback=self.parse_product, ) # 动态处理下一页 next_button = page.locator('...下一页按钮选择器...') if await next_button.is_enabled() and await next_button.is_visible(): await next_button.click() # 等待下一页加载完成(根据网站情况调整wait_until参数) await page.wait_for_url(f"{self.first_page}?p=*", wait_until="networkidle") next_url = page.url # 生成下一页请求 yield scrapy.Request( url=next_url, callback=self.parse, meta=dict( playwright=True, playwright_include_page=True ), ) await page.close()
2. 完善Playwright操作的等待与判空
在parse_product中,添加页面渲染等待,并对提取的链接做判空:
async def parse_product(self, response): page = response.meta["playwright_page"] links_list = [] # 提取当前颜色图片(增加判空) for product in response.css('...'): image_url = product.xpath('@src').get() if image_url: yield {'url': image_url} # 点击显示更多颜色并等待加载 button = page.locator('button...') if await button.is_visible(): await button.click() # 等待颜色列表完全渲染 await page.wait_for_selector('...Available colors...', state='visible') # 获取颜色链接(增加判空) li_list = await page.locator('...Available colors...').all() for link in li_list: link_url = await link.get_attribute("href") if link_url: links_list.append(link_url) # 生成颜色页请求 for color_link in links_list: yield response.follow( color_link, callback=self.parse_color, meta={"playwright": True} # 若颜色页需要JS渲染则保留 ) await page.close()
3. 优化起始请求逻辑
删除类定义阶段的start_dicts批量生成代码,避免无效请求,减少被反爬的风险。
内容的提问来源于stack exchange,提问作者paulbouu
相关产品推荐
相关产品推荐

