You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scrapy+Playwright爬虫漏爬产品且提前终止,请求顺序异常排查

Scrapy+Playwright爬虫漏爬、提前终止及页面顺序问题排查与修复

问题背景

核心需求是爬取某零售品牌网站的完整数据链路:列表页→产品页→颜色选项→对应颜色图片。当前遇到两个关键问题:

  1. 大量产品漏爬,爬虫无预警提前终止
  2. 未完成当前列表页的产品爬取,就开始处理下一页,违背页面顺序遍历的预期

已排查点:

  • 已设置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 11:27:07