当Scrapy遇见Django:打造带管理和可视化界面的爬虫平台
在数据采集领域,Scrapy 是 Python 生态中无可争议的 “爬虫利器”—— 它自带高效的异步下载、灵活的解析规则和完善的中间件机制,能轻松应对各类网站的数据抓取需求。但 Scrapy 的短板也同样明显:它默认依赖命令行启动,缺乏直观的管理界面,爬取进度、任务状态和结果数据都难以可视化监控;而 Django 作为 “全能型 Web 框架”,凭借自带的 Admin 后台、ORM 数据模型和灵活的视图系统,恰好能弥补这些不足。
当 Scrapy 的 “数据采集能力” 与 Django 的 “Web 管理能力” 相结合,我们就能打造一个集 “爬虫配置 - 任务调度 - 数据存储 - 可视化监控” 于一体的完整爬虫平台。本文将从环境搭建到功能落地,手把手教你实现这一方案。
一、技术栈与核心思路
在动手前,我们先明确技术栈选型和整体架构,避免后续开发 “无章可循”。
1. 核心技术栈
| 工具 / 框架 | 作用 |
|---|---|
| Python 3.8+ | 基础开发语言(确保兼容 Scrapy 和 Django 的最新稳定版) |
| Scrapy 2.8+ | 负责核心数据爬取(异步下载、页面解析、数据提取) |
| Django 4.2+ | 提供 Web 管理界面(Admin 后台)、数据模型(ORM)和视图接口 |
| Celery 5.3+ | 处理异步任务(避免爬虫阻塞 Web 请求,支持定时调度) |
| Redis 6.0+ | 作为 Celery 的消息队列(存储爬虫任务)和缓存(临时存储爬取进度) |
| ECharts 5.4+ | 实现数据可视化(任务状态饼图、爬取量柱状图等) |
| MySQL 8.0+ | 持久化存储爬虫配置、任务记录和爬取结果(替代 Django 默认的 SQLite) |
2. 整体架构思路
我们将整个平台拆分为 3 个核心模块,各司其职又相互联动:
- Django 层:负责 “管理” 和 “展示”—— 通过 Admin 后台配置爬虫规则、发起爬取任务;通过视图和模板展示任务状态、爬取结果的可视化图表。
- Scrapy 层:负责 “采集”—— 接收 Django 传递的任务参数(如目标 URL、解析规则),执行爬取逻辑,将结果回传给 Django 的数据库。
- Celery 层:负责 “调度”—— 作为中间件,接收 Django 发起的爬虫任务,异步触发 Scrapy 爬取,避免 Web 请求阻塞;同时支持定时任务(如每天凌晨爬取数据)。
二、分步实现:从环境到功能
1. 第一步:搭建基础环境
(1)创建虚拟环境(避免依赖冲突)
bash
# 安装虚拟环境工具(若未安装)
pip install virtualenv
# 创建虚拟环境
virtualenv scrapy_django_env
# 激活虚拟环境(Windows)
scrapy_django_env\Scripts\activate
# 激活虚拟环境(Mac/Linux)
source scrapy_django_env/bin/activate
(2)安装依赖库
bash
# 安装Django、Scrapy、Celery等核心库
pip install django==4.2.7 scrapy==2.11.0 celery==5.3.6 redis==4.6.0 pymysql==1.1.0 django-celery-beat==2.5.0
(3)初始化项目结构
我们采用 “Django 为主项目,Scrapy 作为子模块” 的结构,确保 Django 能直接调用 Scrapy:
bash
# 1. 创建Django主项目
django-admin startproject scrapy_platform
cd scrapy_platform
# 2. 创建Django App(负责爬虫管理和可视化)
python manage.py startapp spider_manager
# 3. 在spider_manager目录下创建Scrapy项目(作为子模块)
cd spider_manager
scrapy startproject spider_core
cd .. && cd ..
最终项目结构如下(关键目录标注):
plaintext
scrapy_platform/ # Django主项目
├── scrapy_platform/ # Django项目配置
│ ├── settings.py # 全局配置(数据库、Celery等)
│ └── urls.py # 主路由
├── spider_manager/ # 核心App(爬虫管理)
│ ├── models.py # 数据模型(任务、结果、配置)
│ ├── admin.py # Admin后台配置
│ ├── views.py # 视图(可视化、任务触发)
│ ├── templates/ # 模板(可视化页面)
│ └── spider_core/ # Scrapy子模块
│ ├── spiders/ # 爬虫脚本
│ ├── items.py # 爬取数据结构
│ └── pipelines.py # 数据管道(对接Django ORM)
└── manage.py # Django命令行工具
2. 第二步:设计 Django 数据模型(ORM)
数据模型是平台的 “骨架”,我们需要存储 3 类核心数据:爬虫配置(爬什么、怎么爬)、爬取任务(任务状态、时间)、爬取结果(具体数据)。
打开spider_manager/models.py,定义如下模型:
python
from django.db import models
from django.utils import timezone
# 1. 爬虫配置表(存储爬虫的固定规则)
class SpiderConfig(models.Model):
SPIDER_TYPE_CHOICES = (
('article', '文章爬取'),
('product', '商品爬取'),
('comment', '评论爬取'),
)
name = models.CharField(max_length=100, verbose_name="爬虫名称") # 如“知乎文章爬虫”
spider_type = models.CharField(max_length=20, choices=SPIDER_TYPE_CHOICES, verbose_name="爬虫类型")
start_url = models.URLField(verbose_name="起始URL") # 爬虫入口
parse_rule = models.JSONField(verbose_name="解析规则") # 存储CSS/XPath规则(如{"title": "h1::text"})
is_active = models.BooleanField(default=True, verbose_name="是否启用")
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
class Meta:
verbose_name = "爬虫配置"
verbose_name_plural = "爬虫配置"
def __str__(self):
return self.name
# 2. 爬取任务表(存储每次爬取的状态和日志)
class SpiderTask(models.Model):
TASK_STATUS_CHOICES = (
('pending', '等待中'),
('running', '运行中'),
('success', '成功'),
('failed', '失败'),
)
config = models.ForeignKey(SpiderConfig, on_delete=models.CASCADE, related_name="tasks", verbose_name="关联配置")
task_id = models.CharField(max_length=50, unique=True, verbose_name="Celery任务ID") # 关联Celery任务
status = models.CharField(max_length=20, choices=TASK_STATUS_CHOICES, default='pending', verbose_name="任务状态")
start_time = models.DateTimeField(null=True, blank=True, verbose_name="开始时间")
end_time = models.DateTimeField(null=True, blank=True, verbose_name="结束时间")
log = models.TextField(null=True, blank=True, verbose_name="爬取日志") # 存储错误日志或进度
class Meta:
verbose_name = "爬取任务"
verbose_name_plural = "爬取任务"
ordering = ['-start_time']
def __str__(self):
return f"{self.config.name} - {self.status}"
# 3. 爬取结果表(存储具体的爬取数据)
class SpiderResult(models.Model):
task = models.ForeignKey(SpiderTask, on_delete=models.CASCADE, related_name="results", verbose_name="关联任务")
data = models.JSONField(verbose_name="爬取数据") # 存储结构化数据(如{"title": "xxx", "content": "xxx"})
crawl_time = models.DateTimeField(default=timezone.now, verbose_name="爬取时间")
url = models.URLField(null=True, blank=True, verbose_name="数据来源URL")
class Meta:
verbose_name = "爬取结果"
verbose_name_plural = "爬取结果"
ordering = ['-crawl_time']
def __str__(self):
return f"任务{self.task.id} - {self.url[:50]}"
定义完成后,执行数据库迁移,生成表结构:
bash
# 生成迁移文件
python manage.py makemigrations
# 执行迁移(创建表)
python manage.py migrate
3. 第三步:Scrapy 与 Django 的核心整合
这是整个平台的 “关键链路”—— 需要实现两个核心目标:Django 触发 Scrapy 爬取、Scrapy 将结果存入 Django 数据库。
(1)配置 Scrapy 对接 Django ORM
Scrapy 默认通过pipelines.py处理爬取结果,我们需要自定义一个 Pipeline,将 Scrapy 的 Item 数据存入 Django 的SpiderResult模型。
首先,在 Scrapy 的settings.py(路径:spider_manager/spider_core/spider_core/settings.py)中添加 Django 环境变量,确保 Scrapy 能找到 Django 模型:
python
# 配置Django环境(关键!否则Scrapy无法导入Django模型)
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'scrapy_platform.settings')
django.setup()
# 启用自定义Pipeline(用于对接Django ORM)
ITEM_PIPELINES = {
'spider_manager.spider_core.spider_core.pipelines.DjangoORM pipeline': 300,
}
然后,修改pipelines.py,实现数据写入逻辑:
python
from spider_manager.models import SpiderTask, SpiderResult
class DjangoORMPipeline:
def __init__(self, task_id):
self.task_id = task_id # 接收Celery传递的任务ID,关联到具体任务
@classmethod
def from_crawler(cls, crawler):
# 从Scrapy的配置中获取任务ID(后续由Celery触发时传入)
task_id = crawler.settings.get('TASK_ID')
return cls(task_id)
def process_item(self, item, spider):
"""将Scrapy Item存入Django的SpiderResult表"""
try:
# 找到当前任务(通过task_id)
task = SpiderTask.objects.get(task_id=self.task_id)
# 创建爬取结果
SpiderResult.objects.create(
task=task,
data=dict(item), # 将Scrapy Item转为字典
url=item.get('url', '')
)
return item
except Exception as e:
# 若出错,将错误日志写入任务表
task.log += f"\n{str(e)}"
task.status = 'failed'
task.save()
raise e
(2)用 Celery 实现异步任务触发
如果直接在 Django 视图中调用 Scrapy,会导致 Web 请求阻塞(爬取可能耗时几分钟甚至几小时)。因此,我们用 Celery 将爬虫任务转为异步执行。
首先,在 Django 主项目(scrapy_platform)下创建celery.py,配置 Celery:
python
import os
from celery import Celery
# 设置Django环境
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'scrapy_platform.settings')
# 初始化Celery
app = Celery('scrapy_platform')
# 从Django settings中读取Celery配置
app.config_from_object('django.conf:settings', namespace='CELERY')
# 自动发现所有Django App中的tasks.py
app.autodiscover_tasks()
然后,在spider_manager目录下创建tasks.py,定义 Celery 任务(触发 Scrapy 爬取):
python
from celery import shared_task
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
from spider_manager.spider_core.spider_core.spiders.generic_spider import GenericSpider # 自定义的通用爬虫
from spider_manager.models import SpiderTask, SpiderConfig
from datetime import datetime
@shared_task(bind=True, name="run_spider_task")
def run_spider_task(self, config_id):
"""
异步执行爬虫任务
:param config_id: 爬虫配置ID(从Django Admin传递)
:return: 任务结果
"""
try:
# 1. 获取爬虫配置
config = SpiderConfig.objects.get(id=config_id, is_active=True)
# 2. 创建任务记录(状态设为“运行中”)
task = SpiderTask.objects.create(
config=config,
task_id=self.request.id, # Celery任务ID
status='running',
start_time=datetime.now()
)
# 3. 配置Scrapy,传入任务ID和爬取规则
scrapy_settings = get_project_settings()
scrapy_settings.set('TASK_ID', self.request.id) # 传递任务ID给Pipeline
scrapy_settings.set('PARSE_RULE', config.parse_rule) # 传递解析规则给爬虫
# 4. 启动Scrapy爬虫
process = CrawlerProcess(scrapy_settings)
process.crawl(GenericSpider, start_url=config.start_url) # 传入起始URL
process.start() # 阻塞直到爬取完成
# 5. 更新任务状态为“成功”
task.status = 'success'
task.end_time = datetime.now()
task.log += "\n爬取完成!"
task.save()
return f"Spider {config.name} finished successfully"
except Exception as e:
# 6. 若出错,更新任务状态为“失败”并记录日志
task.status = 'failed'
task.end_time = datetime.now()
task.log += f"\nError: {str(e)}"
task.save()
raise e
(3)编写通用 Scrapy 爬虫
为了适配不同的爬取需求(如文章、商品),我们编写一个GenericSpider(通用爬虫),通过 Django 传递的PARSE_RULE动态解析页面。
在spider_manager/spider_core/spider_core/spiders/目录下创建generic_spider.py:
python
import scrapy
from scrapy.settings import settings
class GenericSpider(scrapy.Spider):
name = "generic_spider" # 爬虫名称(固定)
def __init__(self, start_url, *args, **kwargs):
super().__init__(*args, **kwargs)
self.start_urls = [start_url] # 从Celery传递的起始URL
self.parse_rule = settings.get('PARSE_RULE') # 从Scrapy配置获取解析规则
def parse(self, response):
"""动态解析页面(根据PARSE_RULE提取数据)"""
# 示例:解析规则为{"title": "h1::text", "content": ".content::text", "url": "url"}
item = {}
for field, rule in self.parse_rule.items():
if field == 'url':
item[field] = response.url # 直接获取当前页面URL
else:
# 支持CSS选择器(如"h1::text")或XPath(如"//h1/text()")
if rule.startswith('//'):
item[field] = response.xpath(rule).get(default='').strip()
else:
item[field] = response.css(rule).get(default='').strip()
yield item # 传递给Pipeline,存入Django数据库
# 可选:自动跟进页面中的链接(如分页)
next_page = response.css('.next-page::attr(href)').get()
if next_page:
yield response.follow(next_page, self.parse)
4. 第四步:定制 Django Admin 管理界面
Django 自带的 Admin 后台是 “开箱即用的管理工具”,我们只需注册模型并自定义界面,就能实现 “爬虫配置管理” 和 “任务触发”。
打开spider_manager/admin.py,编写如下代码:
python
from django.contrib import admin
from .models import SpiderConfig, SpiderTask, SpiderResult
from .tasks import run_spider_task
# 1. 注册SpiderConfig(爬虫配置)
@admin.register(SpiderConfig)
class SpiderConfigAdmin(admin.ModelAdmin):
list_display = ('name', 'spider_type', 'start_url', 'is_active', 'create_time') # 列表页显示字段
search_fields = ('name', 'start_url') # 搜索字段
list_filter = ('spider_type', 'is_active') # 过滤字段
fieldsets = (
('基本信息', {
'fields': ('name', 'spider_type', 'is_active')
}),
('爬取规则', {
'fields': ('start_url', 'parse_rule'),
'description': '解析规则格式:{"字段名": "CSS/XPath规则"},如{"title": "h1::text"}'
}),
) # 编辑页字段分组
# 2. 注册SpiderTask(爬取任务),并添加“触发爬虫”的自定义动作
@admin.register(SpiderTask)
class SpiderTaskAdmin(admin.ModelAdmin):
list_display = ('config', 'task_id', 'status', 'start_time', 'end_time')
search_fields = ('config__name', 'task_id')
list_filter = ('status', 'start_time')
readonly_fields = ('task_id', 'start_time', 'end_time', 'log') # 只读字段(不允许编辑)
# 自定义动作:批量触发爬虫任务
actions = ['trigger_spider']
def trigger_spider(self, request, queryset):
"""触发选中配置的爬虫任务"""
for config in queryset:
# 调用Celery异步任务
run_spider_task.delay(config_id=config.id)
self.message_user(request, f"已触发{queryset.count()}个爬虫任务,可在任务列表查看进度!")
trigger_spider.short_description = "触发选中配置的爬虫任务" # 动作名称
# 3. 注册SpiderResult(爬取结果)
@admin.register(SpiderResult)
class SpiderResultAdmin(admin.ModelAdmin):
list_display = ('task', 'url', 'crawl_time')
search_fields = ('url', 'data__title') # 支持JSON字段的搜索(Django 4.0+)
list_filter = ('crawl_time',)
readonly_fields = ('task', 'data', 'url', 'crawl_time')
# 优化JSON字段显示(用更友好的格式展示)
def formatted_data(self, obj):
import json
return json.dumps(obj.data, ensure_ascii=False, indent=2)
formatted_data.short_description = "爬取数据(格式化)"
fieldsets = (
('关联信息', {
'fields': ('task', 'url', 'crawl_time')
}),
('爬取数据', {
'fields': (formatted_data,)
}),
)
此时,启动 Django 服务器,访问http://127.0.0.1:8000/admin(需先创建超级用户:python manage.py createsuperuser),就能看到完整的管理界面:
- 在 “爬虫配置” 中添加规则(如 “知乎文章爬虫”,解析规则为
{"title": "h1.Post-Title::text", "content": ".Post-Content::text", "url": "url"}); - 选中配置,点击 “触发选中配置的爬虫任务”,即可通过 Celery 异步启动 Scrapy;
- 在 “爬取任务” 中查看任务状态,在 “爬取结果” 中查看具体数据。
5. 第五步:实现数据可视化(ECharts)
管理界面解决了 “功能” 问题,可视化则解决 “直观性” 问题。我们用 ECharts 绘制两个核心图表:任务状态分布饼图、每日爬取量柱状图。
(1)编写可视化视图
打开spider_manager/views.py,定义视图函数,传递统计数据给模板:
python
from django.shortcuts import render
from django.db.models import Count, DateField
from django.db.models.functions import TruncDate
from .models import SpiderTask, SpiderResult
def dashboard(request):
"""可视化仪表盘视图"""
# 1. 统计任务状态分布(饼图数据)
task_status_data = SpiderTask.objects.values('status').annotate(count=Count('id'))
status_labels = [item['status'] for item in task_status_data]
status_counts = [item['count'] for item in task_status_data]
# 2. 统计每日爬取量(柱状图数据)
daily_result_data = SpiderResult.objects.annotate(
crawl_date=TruncDate('crawl_time') # 按日期分组
).values('crawl_date').annotate(count=Count('id')).order_by('crawl_date')
date_labels = [item['crawl_date'].strftime('%Y-%m-%d') for item in daily_result_data]
result_counts = [item['count'] for item in daily_result_data]
return render(request, 'dashboard.html', {
'status_labels': status_labels,
'status_counts': status_counts,
'date_labels': date_labels,
'result_counts': result_counts,
})
(2)创建可视化模板
在spider_manager目录下创建templates文件夹,再创建dashboard.html:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>爬虫平台可视化仪表盘</title>
<!-- 引入ECharts CDN -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
.chart-container {
width: 80%;
height: 400px;
margin: 20px auto;
border: 1px solid #eee;
padding: 10px;
}
</style>
</head>
<body>
<h1 style="text-align: center;">爬虫平台可视化仪表盘</h1>
<!-- 1. 任务状态饼图 -->
<div class="chart-container" id="taskStatusChart"></div>
<!-- 2. 每日爬取量柱状图 -->
<div class="chart-container" id="dailyResultChart"></div>
<script>
// 初始化饼图(任务状态)
var statusChart = echarts.init(document.getElementById('taskStatusChart'));
statusChart.setOption({
title: { text: '爬取任务状态分布', left: 'center' },
tooltip: { trigger: 'item' },
legend: { orient: 'vertical', left: 'left' },
series: [
{
name: '任务数量',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: { show: false, position: 'center' },
emphasis: {
label: { show: true, fontSize: 20, fontWeight: 'bold' }
},
labelLine: { show: false },
data: [
{% for i in status_labels %}
{ value: {{ status_counts.forloop.counter0 }}, name: '{{ i }}' },
{% endfor %}
]
}
]
});
// 初始化柱状图(每日爬取量)
var dailyChart = echarts.init(document.getElementById('dailyResultChart'));
dailyChart.setOption({
title: { text: '每日爬取量统计', left: 'center' },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: { type: 'category', data: {{ date_labels|safe }} },
yAxis: { type: 'value', name: '爬取数量' },
series: [
{
name: '爬取结果数',
type: 'bar',
data: {{ result_counts|safe }},
itemStyle: { color: '#5470c6' }
}
]
});
// 窗口大小变化时,自动调整图表尺寸
window.addEventListener('resize', function() {
statusChart.resize();
dailyChart.resize();
});
</script>
</body>
</html>
(3)配置 URL 路由
打开scrapy_platform/urls.py,添加可视化仪表盘的路由:
python
from django.contrib import admin
from django.urls import path
from spider_manager.views import dashboard
urlpatterns = [
path('admin/', admin.site.urls),
path('dashboard/', dashboard, name='dashboard'), # 可视化仪表盘路由
]
此时,访问http://127.0.0.1:8000/dashboard/,就能看到实时的任务状态和爬取量图表。
三、部署与扩展:从本地到生产
1. 本地测试与验证
在启动平台前,需确保各组件正常运行:
- 启动 Redis(作为 Celery 消息队列):
redis-server(Windows 需先安装 Redis); - 启动 Celery Worker(处理异步任务):在 Django 项目根目录执行
celery -A scrapy_platform worker --loglevel=info; - 启动 Django 服务器:
python manage.py runserver; - 流程验证:Admin 添加爬虫配置 → 触发任务 → 查看任务状态 → 查看可视化图表。
2. 生产环境部署
本地测试通过后,可按以下步骤部署到生产环境:
- Web 服务器:用 Gunicorn 替代 Django 内置服务器(
gunicorn scrapy_platform.wsgi:application --bind 0.0.0.0:8000),并配置 Nginx 作为反向代理(处理静态文件、负载均衡); - Celery 部署:用
supervisord管理 Celery Worker 进程(确保进程后台运行、崩溃自动重启); - 数据库:使用 MySQL(而非 SQLite),并配置定期备份;
- 静态文件:用 Django 的
collectstatic命令收集静态文件,由 Nginx 提供访问。
3. 功能扩展方向
该平台具备良好的可扩展性,可根据需求添加以下功能:
- 权限管理:用 Django 的
django-guardian实现细粒度权限(如不同用户只能管理自己的爬虫); - 定时任务:用
celery-beat添加定时爬取(如每天凌晨 3 点爬取数据); - 邮件通知:任务失败或完成时,通过 Django 的
django.core.mail发送邮件通知; - 数据导出:在 Admin 中添加 “导出 Excel/CSV” 功能(用
django-import-export库); - 爬虫监控:添加实时日志流(用
django-channels实现 WebSocket 实时刷新日志)。
四、总结
当 Scrapy 的 “爬虫能力” 遇上 Django 的 “Web 能力”,我们不再需要在命令行中反复输入scrapy crawl,也不再需要手动分析爬取结果 —— 通过一个统一的平台,就能实现 “配置 - 触发 - 监控 - 分析” 的全流程管理。
这种 “爬虫框架 + Web 框架” 的组合,不仅提升了开发效率,更降低了爬虫的使用门槛(非技术人员也能通过 Admin 操作)。无论是企业内部的数据采集平台,还是个人的爬虫管理工具,这一方案都具备极高的实用价值。
更多推荐
所有评论(0)