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

如何实现Python Scrapy按顺序请求网站及对应站内链接?

解决Scrapy按域名顺序爬取的问题

要实现逐个域名爬取(完成一个站点所有请求后再处理下一个),核心是让调度器按域名分组管理请求队列,而非默认的全局队列。以下是具体实现步骤:

1. 实现自定义域名分组队列

创建按域名隔离的队列类,确保同一个域名的请求全部处理完毕后,才切换到下一个域名:

from scrapy.queues import PriorityQueue

class DomainPriorityQueue:
    def __init__(self):
        # 存储每个域名对应的请求队列
        self.domain_queues = {}
        # 当前正在处理的域名
        self.current_domain = None
        # 待处理的域名顺序(严格遵循初始请求的顺序)
        self.pending_domains = []

    def push(self, request):
        # 从meta或URL中提取域名
        domain = request.meta.get('domain') or request.url.split('/')[2]
        # 首次遇到该域名时,初始化队列并加入待处理列表
        if domain not in self.domain_queues:
            self.domain_queues[domain] = PriorityQueue()
            if domain not in self.pending_domains:
                self.pending_domains.append(domain)
        # 将请求加入对应域名的队列
        self.domain_queues[domain].push(request)

    def pop(self):
        # 优先处理当前域名的剩余请求
        if self.current_domain and not self.domain_queues[self.current_domain].empty():
            return self.domain_queues[self.current_domain].pop()
        # 当前域名处理完后,切换到下一个待处理域名
        while self.pending_domains:
            next_domain = self.pending_domains[0]
            if not self.domain_queues[next_domain].empty():
                self.current_domain = next_domain
                return self.domain_queues[next_domain].pop()
            else:
                # 该域名无剩余请求,从待处理列表移除
                self.pending_domains.pop(0)
                self.current_domain = None
        # 所有队列均为空时返回None
        return None

    def empty(self):
        return all(q.empty() for q in self.domain_queues.values())

2. 实现自定义调度器

继承Scrapy默认调度器,替换请求队列为上述自定义队列:

from scrapy.core.scheduler import Scheduler

class DomainOrderedScheduler(Scheduler):
    def __init__(self, dupefilter, jobdir=None, dqclass=None, mqclass=None, logunser=False, stats=None, pqclass=None, crawler=None):
        super().__init__(dupefilter, jobdir, dqclass, mqclass, logunser, stats, pqclass, crawler)
        # 替换默认优先级队列为自定义域名分组队列
        self.pq = DomainPriorityQueue()

3. 配置Scrapy项目设置

在settings.py中指定使用自定义调度器,并锁定并发规则:

# 全局并发请求设为1,避免同时处理多个请求
CONCURRENT_REQUESTS = 1
# 单域名并发请求设为1,强化顺序控制
CONCURRENT_REQUESTS_PER_DOMAIN = 1
# 指定自定义调度器路径(替换为你的项目模块路径)
SCHEDULER = 'your_project.schedulers.DomainOrderedScheduler'

4. 爬虫代码中标记域名

在初始请求和回调生成的站内请求中,统一标记域名,确保队列分组准确:

import scrapy
from urllib.parse import urlparse

class OrderedSpider(scrapy.Spider):
    name = 'ordered_spider'
    # 按期望顺序排列的初始站点列表
    start_urls = ['https://domain1.com', 'https://domain2.com', 'https://domain3.com']

    def start_requests(self):
        for url in self.start_urls:
            domain = urlparse(url).netloc
            yield scrapy.Request(
                url,
                callback=self.parse,
                meta={'domain': domain}
            )

    def parse(self, response):
        # 仅提取当前域名下的站内链接
        for link in response.css('a::attr(href)').getall():
            absolute_url = response.urljoin(link)
            link_domain = urlparse(absolute_url).netloc
            if link_domain == response.meta['domain']:
                yield scrapy.Request(
                    absolute_url,
                    callback=self.parse_detail,
                    meta={'domain': response.meta['domain']}
                )

    def parse_detail(self, response):
        # 处理详情页逻辑,在此处同步更新谷歌表格
        domain = response.meta['domain']
        # 你的谷歌表格更新代码写在这里
        pass

关键说明

  • 自定义队列会严格遵循start_urls的顺序处理域名,每个域名的所有请求完成后才会切换到下一个。
  • 回调中需过滤跨域名链接,避免其他域名的请求混入当前队列。
  • 并发设置是辅助约束,核心逻辑由自定义调度器和队列实现。

内容的提问来源于stack exchange,提问作者Sahil Dua

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 14:23:19