Загрузчики документов в LangChain: как файлы и страницы попадают в RAG-пайплайн
Как загрузчики превращают PDF, текст, CSV и веб-страницы в объекты Document, какой PDF-загрузчик выбрать и когда ленивая загрузка экономит память на больших папках.
LLM не может сама пройтись по вашим папкам и прочитать файлы. Генерация с дополненной выборкой (RAG) появилась потому, что модели не знают ваших приватных данных, не видят ничего свежего и иногда выдумывают, когда им не хватает контекста. Решение — подтягивать нужный текст в момент запроса. Но до этого PDF, веб-страница, CSV или Word-файл вообще должны как-то попасть в пайплайн. Это работа загрузчика документов: его легко упустить из виду, а сделать правильно стоит.
Что делает загрузчик
Загрузчик читает данные из какого-то источника и приводит их к формату, который понимает LangChain. По сути, это переводчик. Ваши материалы могут лежать где угодно:
- Обычные текстовые файлы
- CSV-файлы
- Сайты
- Документы Word
- Базы данных
Всё, что идёт после загрузчика, ждёт одну стандартную форму, и задача загрузчика — закрыть этот разрыв.
Почему PDF нельзя сразу резать на чанки
Новички часто представляют себе процесс так:
PDF → Chunking → Embeddings
На деле всё сложнее. PDF — это не текст. Внутри шрифты, картинки, данные о вёрстке, таблицы, колонтитулы и метаданные. Чанкер работает только с текстом, так что сначала кто-то должен вытащить слова. Реальный процесс ближе к такому:
PDF → Document Loader → Text Extraction → Chunking → Embeddings
Загрузчик извлекает текст и упаковывает его в общий формат.
Загрузчики, которые встречаются чаще всего
В LangChain есть загрузчики для множества источников. Вход меняется, принцип — нет: прочитать откуда-то и выдать документы LangChain. Вот те, к которым вы будете обращаться чаще всего:
- TextLoader работает с обычными .txt-файлами.
- PyPDFLoader работает с PDF.
- WebBaseLoader работает с веб-страницами и URL.
- CSVLoader работает с CSV-файлами.
- DirectoryLoader работает с множеством файлов в папке.
Один тип на выходе: Document
Какой бы загрузчик ни работал, обычно вы получаете список объектов Document. В каждом есть текст и немного сведений о том, откуда он взялся:
Document(
page_content="Employees are eligible for 24 paid leaves per year.",
metadata={
"source": "employee_policy.pdf",
"page": 5
}
)
Важнее всего два поля. page_content — сам текст, а metadata описывает источник, здесь это имя файла и номер страницы. Такая стандартная форма удобна: следующим этапам всё равно, откуда пришли данные. И PDF-загрузчик, и веб-загрузчик отдают Document, а этап чанкинга обрабатывает их одинаково.
TextLoader
Самый простой вариант читает обычные текстовые файлы. Сначала импортируем его:
from langchain_community.document_loaders import TextLoader
Потом указываем файл, загружаем и выводим результат:
loader = TextLoader("notes.txt")documents = loader.load()print(documents)
Получаем список:
[
Document(...)
]
Даже для одного файла возвращается список — так интерфейс остаётся единообразным. Каждый элемент — это Document.
Берите его, когда данные уже в виде текста: логи, заметки, текстовые выгрузки или чистые датасеты. Но он совсем простой. Он не разбирает PDF, не читает таблицы, не понимает вёрстку и не извлекает картинки. Если источник не текстовый, выбирайте что-то другое.
PyPDFLoader
PDF — пожалуй, самый частый источник в RAG-проектах, и PyPDFLoader — обычная отправная точка. Импортируем:
from langchain_community.document_loaders import PyPDFLoader
Затем загружаем отчёт:
loader = PyPDFLoader("annual_report.pdf")documents = loader.load()
Допустим, в файле 25 страниц. Считаем результат:
len(documents)
и получаем:
25
Каждая страница становится отдельным Document. Смотрим на первый:
documents[0]
и видим вот что:
Document(
page_content="Page 1 text...",
metadata={
"source": "annual_report.pdf",
"page": 0
}
)
Выходит, 25-страничный PDF даёт 25 документов. Под капотом загрузчик использует библиотеку PyPDF. С простыми PDF он справляется хорошо, но спотыкается на сложной вёрстке, многоколоночных страницах, таблицах и сканах — поэтому выбор PDF-загрузчика важен.
Как выбрать PDF-загрузчик
Одного загрузчика, лучшего для всех файлов, нет. Примерная карта:
- PyPDFLoader подходит для простых текстовых PDF.
- PDFPlumberLoader — для PDF с таблицами и структурированной вёрсткой.
- PyMuPDFLoader быстрый и хорош как универсальный вариант.
- UnstructuredPDFLoader рассчитан на сложные корпоративные документы.
- AmazonTextractPDFLoader закрывает сканы и задачи, где много OCR.
Рабочее правило: начните с PyPDF. Если извлечённый текст выглядит плохо, переходите на PyMuPDF или Unstructured. Если документ — скан, используйте OCR-сервис вроде Textract.
DirectoryLoader
Загрузить один файл — пустяк. Загрузить вручную 5000 PDF или 10 000 текстовых файлов — уже нет, и для этого есть DirectoryLoader. Импортируем:
from langchain_community.document_loaders import DirectoryLoader
Потом передаём ему папку и glob-шаблон:
loader = DirectoryLoader(
"data/",
glob="**/*.pdf"
)documents = loader.load()
Так он подхватит все PDF в каталоге. Представьте такую структуру:
data/
├── report1.pdf
├── report2.pdf
├── report3.pdf
Загрузчик проходит по каждому подходящему файлу и загружает его, и вы получаете:
[
Document(...),
Document(...),
...
]
Для большой папки это могут быть сотни или тысячи документов.
Проблема огромных папок
Представьте 20 000 PDF или 100 000 текстовых файлов и такой вызов:
documents = loader.load()
LangChain сразу читает всё в память. Это жадная загрузка. Она простая и нормально работает на небольших наборах данных, но съедает много памяти и замедляет старт.
Жадная загрузка против ленивой
Есть альтернатива:
loader.lazy_load()
В жадном варианте всё загружается разом:
documents = loader.load()
а возвращается:
List[Document]
Ленивый вариант выдаёт документы только по мере надобности:
documents = loader.lazy_load()
и возвращает генератор:
Generator[Document]
Вспомните стриминг видео. Сервис не скачивает весь фильм до того, как вы нажмёте «play», он отдаёт данные по запросу. Ленивая загрузка устроена так же.
load() подходит для маленьких и средних коллекций и для прототипов. lazy_load() — для больших коллекций и сред с ограниченной памятью.
WebBaseLoader
Не все знания лежат в файлах. Иногда они на сайте, и тут помогает WebBaseLoader. Импортируем:
from langchain_community.document_loaders import WebBaseLoader
Затем передаём URL:
loader = WebBaseLoader(
"https://python.langchain.com"
)documents = loader.load()
Внутри он разбирает HTML через BeautifulSoup и вытаскивает текст. Это подходит для блогов, документации, статей, баз знаний и сайтов компаний.
Лучше всего он работает со статичными страницами. Сайты, нагруженные JavaScript, динамический контент и рендеринг на клиенте доставляют ему проблем. Для таких случаев есть SeleniumURLLoader: он сначала рендерит страницу в браузере, а потом уже извлекает текст.
CSVLoader
Структурированные данные часто приходят в CSV. Возьмём небольшой файл с сотрудниками:
employee_id,name,department
1,John,Engineering
2,Sarah,HR
Импортируем загрузчик:
from langchain_community.document_loaders import CSVLoader
И загружаем файл:
loader = CSVLoader(
file_path="employees.csv"
)documents = loader.load()
Каждая строка превращается в Document: названия столбцов и значения записываются текстом, а номер строки сохраняется в метаданных:
Document(
page_content="""
employee_id: 1
name: John
department: Engineering
""",
metadata={
"source": "employees.csv",
"row": 0
}
)
Подходит для каталогов товаров, данных клиентов, складских остатков, финансовых записей и похожих бизнес-данных.
Три вещи, которые стоит запомнить
- Любой RAG-пайплайн начинается с загрузки. До чанкинга, эмбеддингов и поиска данные должны попасть внутрь.
- Разным источникам нужны разные загрузчики: текстовым файлам — TextLoader, PDF — PyPDFLoader, сайтам — WebBaseLoader, CSV — CSVLoader, папкам — DirectoryLoader.
- Почти всё в итоге становится объектом Document, и эта общая форма позволяет остальному пайплайну работать единообразно.
Полный список загрузчиков есть в документации по интеграциям LangChain.
Когда документы загружены, следующая задачка — как их разбить: по символам, словам, предложениям, абзацам или токенам, и какого размера должны быть куски для 100-страничного файла. Нарежете слишком мелко — потеряете контекст. Слишком крупно — будете вытаскивать шум. Это решение заслуживает отдельного внимательного разбора, потому что это искусство резать документы, не ломая RAG-систему.