在DRF(Django Rest Framework)视图中调用Scrapy爬虫的最佳方法
DRF 调用 Scrapy 爬虫最优实现方案
方案选型结论
你现有的第一种基于 Scrapyd 的实现方向是正确的,第二种通过 os.system 直接启动爬虫脚本的方案不建议在生产环境使用,存在严重的稳定性、并发问题。
现有实现的问题梳理
废弃 spiding 实现的原因
os.system是同步阻塞调用,接口请求会一直等待爬虫执行完成才返回,并发量稍高就会占满 Django 所有 worker 进程,直接导致服务不可用- 通过系统环境变量传递爬取 URL 的方式存在并发冲突,多个请求同时发起时,后设置的 URL 会覆盖之前的参数,导致爬取目标完全错乱
- 没有任务状态查询、异常处理机制,调用方完全无法感知爬虫执行结果
现有 Scrapyd 实现的可优化点
- 目前用的是 Django 原生 FBV 写法,改成 DRF 标准的 CBV/序列化器校验逻辑,更符合项目规范,也能减少手动校验参数的冗余代码
- 爬取结果查询逻辑可以加缓存,不用每次都查库,降低数据库压力
- 缺少权限控制、接口限流逻辑,生产环境容易被恶意调用
最优实现核心逻辑
业内通用的方案就是基于 Scrapyd 做爬虫任务调度,核心优势如下:
- 完全异步非阻塞,调用接口后直接返回任务ID,不会阻塞 Django 请求
- 原生支持多爬虫并发调度、任务队列管理、爬虫版本管理,生产环境可以水平扩容爬虫节点
- 自带任务状态查询、日志查询能力,不需要自己实现任务管理逻辑
DRF 适配后的参考代码
from uuid import uuid4 from urllib.parse import urlparse from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from scrapyd_api import ScrapydAPI from .serializers import CrawlRequestSerializer, CrawlStatusQuerySerializer from scrapy_app.models import QuoteItem scrapyd = ScrapydAPI('http://localhost:6800') class CrawlView(APIView): # 生产环境可打开权限控制 # permission_classes = [IsAuthenticated] def post(self, request): serializer = CrawlRequestSerializer(data=request.data) serializer.is_valid(raise_exception=True) url = serializer.validated_data['url'] domain = urlparse(url).netloc unique_id = str(uuid4()) settings = { 'unique_id': unique_id, 'USER_AGENT': 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' } task_id = scrapyd.schedule('default', 'icrawler', settings=settings, url=url, domain=domain) return Response({ 'task_id': task_id, 'unique_id': unique_id, 'status': 'started' }, status=status.HTTP_201_CREATED) def get(self, request): serializer = CrawlStatusQuerySerializer(data=request.query_params) serializer.is_valid(raise_exception=True) task_id = serializer.validated_data['task_id'] unique_id = serializer.validated_data['unique_id'] task_status = scrapyd.job_status('default', task_id) if task_status == 'finished': try: item = QuoteItem.objects.get(unique_id=unique_id) return Response({'data': item.to_dict()}) except QuoteItem.DoesNotExist: return Response({'error': '爬取结果不存在'}, status=status.HTTP_404_NOT_FOUND) return Response({'status': task_status})
对应的序列化器代码:
from rest_framework import serializers class CrawlRequestSerializer(serializers.Serializer): url = serializers.URLField(required=True, help_text="爬取目标URL") class CrawlStatusQuerySerializer(serializers.Serializer): task_id = serializers.CharField(required=True, help_text="爬虫任务ID") unique_id = serializers.CharField(required=True, help_text="结果关联唯一标识")
部署注意事项
- 提前部署启动 Scrapyd 服务,将你的 Scrapy 项目打包部署到 Scrapyd 节点
- Scrapy 爬虫要做参数适配,支持接收传入的
url作为爬取入口,unique_id作为结果关联字段存入数据库 - 生产环境建议给 Scrapyd 加身份认证,接口增加限流规则,避免被滥用
内容的提问来源于stack exchange,提问作者Sadegh-khan
相关产品推荐
相关产品推荐

