如何实现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
相关产品推荐
相关产品推荐

