抓取任务的权限隔离与多租户(SaaS)平台设计要点

36 阅读5分钟

一、项目背景

很多人做采集做到中后期,都会遇到一个绕不开的问题——“多用户共用平台怎么隔离权限?”

一开始也许只是想做个简单的管理后台,大家都能登录提交爬取任务。但慢慢你会发现:

  • 不同客户的数据混在一起,查的时候要手动过滤;
  • 某个团队的代理IP被封了,影响到了别人;
  • 某个任务出错竟然把别的租户任务也卡死了……

这就是典型的 权限隔离和多租户(SaaS)问题

于是,我就做了这样一个实战项目:构建一个支持多租户、任务隔离、代理独立的采集平台。让每个租户(公司或部门)都能在自己的空间里提交任务、查看结果、用自己的代理池,不干扰别人。

二、数据目标

我们以一个具体案例来展开。

假设有几家汽车经销商客户,他们都想抓取 Autohome 上的汽车品牌、型号和价格数据,用于市场分析。

要求很明确:

  • 每个客户能自定义关键词(比如“新能源SUV”、“丰田”等);
  • 每个客户的数据独立保存;
  • 抓取任务之间互不影响;
  • 每个客户使用自己的代理身份,避免被封号或数据串线。

换句话说,这个平台要实现一个带有“隐形边界”的多租户采集系统,每个租户既能自由爬,又互不打扰。

三、技术选型

为了实现这样的系统,我选用了这套技术组合:

  • FastAPI:轻量、快、天然适合做多租户API层;
  • Celery + Redis:分布式任务调度系统,用来分发不同租户的抓取任务;
  • PostgreSQL:多 schema 架构,每个租户单独一个 schema 实现逻辑隔离;
  • Requests + 代理IP(亿牛云):为每个租户配置独立的代理身份;
  • JWT + RBAC:用户身份认证 + 角色权限控制;
  • Docker Compose:快速部署和环境隔离。

目标很清晰:前端提交任务 → 后端认证租户 → Celery 分发任务 → 采集模块带上独立代理执行 → 数据落入对应 schema。

四、模块实现

1. 用户与租户模型

每个用户都属于某个租户。登录后系统会根据 JWT 自动识别所属租户,从而决定他能访问的数据范围。

# models.py
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from database import Base

class Tenant(Base):
    __tablename__ = "tenants"
    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True)
    users = relationship("User", back_populates="tenant")

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    username = Column(String)
    password = Column(String)
    tenant_id = Column(Integer, ForeignKey("tenants.id"))
    tenant = relationship("Tenant", back_populates="users")

这一层主要负责身份绑定——系统永远知道请求是谁的。


2. 任务调度与权限隔离

接着是任务调度。Celery 负责异步任务的分发,每个租户的任务通过独立参数区分,避免冲突。

# tasks.py
from celery import Celery
from crawler import fetch_car_data

celery = Celery("crawler", broker="redis://localhost:6379/0")

@celery.task
def run_crawl_task(tenant_id, keyword):
    """
    每个租户独立运行的任务
    """
    data = fetch_car_data(keyword, tenant_id)
    save_to_tenant_schema(tenant_id, data)

不同租户的任务在执行层面上是逻辑隔离的,比如租户A在抓汽车,租户B在抓价格,他们都走不同的 Celery 任务通道。

3. 抓取核心模块

这是整个系统的灵魂。每个租户都会使用独立的代理凭证,从网络层就实现隔离。这里用的是 爬虫代理 举例。

# crawler.py
import requests
from fake_useragent import UserAgent

def fetch_car_data(keyword, tenant_id):
    """
    抓取汽车品牌与价格信息
    每个租户使用独立的代理IP和UA
    """
    ua = UserAgent()
    headers = {
        "User-Agent": ua.random,
        "Cookie": "your_cookie_here",
    }

    # 亿牛云代理示例配置 www.16yun.cn
    proxy_host = "proxy.16yun.cn"
    proxy_port = "3100"
    proxy_user = f"tenant_{tenant_id}_user"
    proxy_pass = "tenant_specific_password"

    proxy_meta = f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}"
    proxies = {
        "http": proxy_meta,
        "https": proxy_meta,
    }

    url = f"https://www.autohome.com.cn/{keyword}/"
    response = requests.get(url, headers=headers, proxies=proxies, timeout=10)
    
    if response.status_code == 200:
        # 模拟解析逻辑
        return {"keyword": keyword, "price": "12.5万", "brand": "Toyota"}
    else:
        return {"error": f"Request failed: {response.status_code}"}

这里最关键的是:不同租户的代理身份是分开的,不仅能防止封禁波及,也保证了任务独立。

4. 数据隔离(多 Schema)

数据库层我采用了 PostgreSQL 的多 schema 架构。每个租户独立一个 schema,这样数据上就彻底隔离了。

# database.py
from sqlalchemy import create_engine

def get_engine(tenant_id):
    """
    根据租户ID连接对应schema
    """
    db_url = f"postgresql://user:pass@localhost:5432/crawler"
    engine = create_engine(db_url, connect_args={"options": f"-c search_path=tenant_{tenant_id}"})
    return engine

这样做的好处是显而易见的——哪怕某个租户的表炸了,其他租户的数据完全不受影响。

5. API 层实现(FastAPI)

API 层是整个系统的入口。用户带着 JWT 令牌请求任务接口,后端自动判断租户身份并分发任务。

# main.py
from fastapi import FastAPI, Depends
from auth import get_current_user
from tasks import run_crawl_task

app = FastAPI()

@app.post("/crawl")
def crawl(keyword: str, user=Depends(get_current_user)):
    """
    发起抓取任务(基于当前用户租户)
    """
    tenant_id = user.tenant_id
    run_crawl_task.delay(tenant_id, keyword)
    return {"msg": f"爬取任务已提交(租户 {tenant_id})"}

这样,一个租户发的任务永远不会被另一个租户干扰,整个过程对用户是透明的。

五、总结

多租户采集平台最难的地方,其实不在“爬”,而在“分”。

要解决的是——

  • 不同租户之间的 任务独立
  • 不同网络层的 代理隔离
  • 不同存储层的 数据分区
  • 不同访问层的 权限控制

简单回顾一下本项目的分层方案:

  • 网络层:独立代理(爬虫代理)
  • 存储层:PostgreSQL 多 schema
  • 应用层:JWT + 权限验证
  • 任务层:Celery 异步队列

进一步扩展的话,还可以:

  • 加上限速和优先级控制;
  • 用 Kubernetes 给不同租户分配资源;
  • 加入 Prometheus 做抓取性能监控。

做完这个项目,你会真正理解一个“企业级采集平台”要面对的复杂度。而最有趣的是,当这些机制搭好之后,你的系统会变得异常稳健——再多租户都能轻松扩展。