3、数据源接入:常见数据源类型与适配器设计

聊到RAG系统的数据管道,第一个绕不开的坎就是——数据从哪来?

我见过不少团队,模型选型、向量库搭建都搞定了,结果卡在数据接入这一步。说白了,数据源接入是整个管道的「水龙头」。水龙头没拧对,后面再好的管道也白搭。

3.1 常见数据源类型

实际项目中,我接触到的数据源大概分四类。每一类都有它的脾气。

3.1.1 网页数据

网页是最常见的数据源。你想想看,公司官网、知识库、技术文档,大部分都在网页上。

我个人习惯用 requests + BeautifulSoup 做轻量抓取。但要注意,很多网站有反爬机制。我在项目中遇到过,某客户的知识库页面用了动态加载,直接请求拿不到内容。后来改用 Selenium 模拟浏览器才搞定。

避坑指南:我曾经因为没处理 robots.txt,导致 IP 被封了三天。现在我的每个爬虫都会先检查 robots.txt,这是基本素养。
import requests
from bs4 import BeautifulSoup

def fetch_web_page(url):
    headers = {'User-Agent': 'Mozilla/5.0'}
    resp = requests.get(url, headers=headers, timeout=10)
    resp.raise_for_status()
    soup = BeautifulSoup(resp.text, 'html.parser')
    # 提取正文,去掉导航、广告等干扰
    content = soup.find('article') or soup.find('main')
    return content.get_text(strip=True) if content else ''

3.1.2 PDF 文档

PDF 是 RAG 系统里最头疼的数据源,没有之一。

为什么?因为 PDF 的「排版」和「内容」是混在一起的。有的 PDF 是扫描件,有的带表格,有的有复杂的页眉页脚。我建议优先用 PyMuPDF(也就是 fitz),它对文本提取的支持比较稳定。

import fitz  # PyMuPDF

def extract_pdf_text(pdf_path):
    doc = fitz.open(pdf_path)
    text = ""
    for page in doc:
        text += page.get_text()
    return text
小技巧:对于扫描版 PDF,需要先 OCR。我常用 pytesseract + pdf2image 组合。但 OCR 速度慢,建议只在必要时启用。

3.1.3 数据库

数据库接入,说白了就是写 SQL。但这里有个坑——数据量。

我记得有一次,客户的生产库有上亿条记录。直接全量读取,内存直接爆了。后来我改成分批读取,每次取 10000 条,用 yield 做成生成器,问题就解决了。

import pymysql

def fetch_from_db(batch_size=10000):
    conn = pymysql.connect(host='...', user='...', password='...', db='...')
    cursor = conn.cursor()
    cursor.execute("SELECT id, content FROM documents")
    while True:
        rows = cursor.fetchmany(batch_size)
        if not rows:
            break
        for row in rows:
            yield {'id': row[0], 'content': row[1]}
    cursor.close()
    conn.close()

3.1.4 API 接口

API 接入是最「干净」的数据源。JSON 格式,结构清晰,不用清洗太多。

但要注意限流。我遇到过某个第三方 API,每分钟只能调用 60 次。解决方案是加一个 time.sleep(1),或者用 ratelimit 库做限流装饰器。

import requests
from time import sleep

def fetch_from_api(api_url, api_key, max_retries=3):
    headers = {'Authorization': f'Bearer {api_key}'}
    for attempt in range(max_retries):
        resp = requests.get(api_url, headers=headers)
        if resp.status_code == 200:
            return resp.json()
        sleep(2 ** attempt)  # 指数退避
    raise Exception("API 请求失败")

3.2 数据源适配器设计

你想想看,如果每个数据源都写一套独立的接入逻辑,那代码会乱成什么样?

所以我们需要适配器模式。说白了,就是定义一个统一的接口,让所有数据源都「长一个样」。

3.2.1 适配器接口定义

我个人习惯先定义一个抽象基类,规定好必须实现的方法:

from abc import ABC, abstractmethod
from typing import Iterator, Dict

class DataSourceAdapter(ABC):
    @abstractmethod
    def connect(self) -> bool:
        """建立连接"""
        pass
    
    @abstractmethod
    def fetch(self) -> Iterator[Dict]:
        """获取数据,返回迭代器"""
        pass
    
    @abstractmethod
    def close(self):
        """关闭连接"""
        pass

3.2.2 具体适配器实现

有了接口,每个数据源各自实现自己的逻辑。比如网页适配器:

class WebAdapter(DataSourceAdapter):
    def __init__(self, url):
        self.url = url
        self.session = None
    
    def connect(self):
        self.session = requests.Session()
        return True
    
    def fetch(self):
        resp = self.session.get(self.url)
        soup = BeautifulSoup(resp.text, 'html.parser')
        yield {'source': self.url, 'content': soup.get_text()}
    
    def close(self):
        self.session.close()

PDF 适配器类似,只是内部调用 PyMuPDF。数据库适配器则封装 SQL 查询逻辑。

3.2.3 适配器注册与工厂

我建议再加一个工厂类,根据数据源类型自动选择适配器:

class AdapterFactory:
    _adapters = {
        'web': WebAdapter,
        'pdf': PDFAdapter,
        'db': DatabaseAdapter,
        'api': APIAdapter,
    }
    
    @classmethod
    def create(cls, source_type: str, **kwargs) -> DataSourceAdapter:
        adapter_cls = cls._adapters.get(source_type)
        if not adapter_cls:
            raise ValueError(f"不支持的数据源类型: {source_type}")
        return adapter_cls(**kwargs)
核心思想:适配器模式让数据接入变得可扩展。以后加新的数据源(比如 Excel、Word、甚至视频字幕),只需要新增一个适配器类,不用改现有代码。

3.3 本章知识体系

下面这张图,是我自己画的数据源接入整体架构。你可以看到,适配器层就像「万能插座」,把各种数据源统一成标准格式,交给下游的清洗模块。

数据源接入架构图 数据源层 网页 PDF 数据库 API 适配器层 WebAdapter PDFAdapter DatabaseAdapter APIAdapter 统一接口:connect() / fetch() / close() 数据清洗与预处理模块 适配器层将异构数据源统一为标准格式,屏蔽底层差异
我的经验:适配器设计好了,后面加数据源就像「插U盘」一样简单。我曾经在一个项目中,从 3 种数据源扩展到 8 种,只花了半天时间——就是因为适配器架构搭得稳。

嗯,数据源接入这块,核心就这些。记住一句话:统一接口,隔离变化。下一层就是数据清洗了,到时候我们再细聊。


公众号:蓝海资料掘金营,微信deep3321