2.2 Загрузчики документов LangChain
В приложениях, работающих с данными и диалоговыми интерфейсами на базе LLM, критично уметь эффективно загружать, приводить к единому формату и использовать данные из разных источников. В экосистеме LangChain для этого существуют «загрузчики» — компоненты, извлекающие сведения из веб‑сайтов, баз данных и мультимедийных файлов и приводящие их к стандартному виду документа с контентом и метаданными. Поддерживаются десятки форматов (PDF, HTML, JSON и др.) и источники как публичные (YouTube, Twitter, Hacker News), так и корпоративные (Figma, Notion); отдельно доступны загрузчики для табличных и сервисных данных (Airbyte, Stripe, Airtable и др.), что позволяет строить семантический поиск и QA не только по неструктурированным, но и по строго структурированным наборам. Такая модульность даёт возможность собирать целевые конвейеры: где‑то достаточно загрузить и очистить текст, а где‑то — автоматически подготавливать эмбеддинги, извлекать сущности, агрегировать и суммировать.
Практика начинается с базовой подготовки окружения: устанавливаем зависимости, настраиваем ключи API и читаем их из .env
, чтобы безопасно работать с внешними данными.
# Установите необходимые пакеты (Примечание: они могут быть уже установлены в вашей среде)
# !pip install langchain dotenv
import os
from dotenv import load_dotenv, find_dotenv
# Загружаем переменные окружения из файла .env
load_dotenv(find_dotenv())
# Устанавливаем ключ OpenAI API из переменных окружения
openai_api_key = os.environ['OPENAI_API_KEY']
Один из частых сценариев — работа с PDF. Ниже показано, как загрузить документ (например, стенограмму лекции), очистить и токенизировать текст, посчитать частоты слов и сохранить очищенный вариант для последующего анализа; при этом пустые страницы мы явно обрабатываем и логируем, а метаданные доступны для выборочной инспекции.
from langchain.document_loaders import PyPDFLoader
import re
from collections import Counter
# Инициализируем PDF Loader с путём к PDF документу
pdf_loader = PyPDFLoader("docs/lecture_series/Lecture01.pdf")
# Загружаем страницы документа
document_pages = pdf_loader.load()
# Функция для очистки и токенизации текста
def clean_and_tokenize(text):
# Удаляем неалфавитные символы и разбиваем текст на слова
words = re.findall(r'\b[a-z]+\b', text.lower())
return words
# Инициализируем объект Counter для отслеживания частоты слов
word_frequencies = Counter()
# Перебираем каждую страницу в документе
for page in document_pages:
# Проверяем, что страница не пустая
if page.page_content.strip():
# Очищаем и токенизируем содержимое страницы
words = clean_and_tokenize(page.page_content)
# Обновляем частоту слов
word_frequencies.update(words)
else:
# Обрабатываем пустую страницу
print(f"Найдена пустая страница с индексом {document_pages.index(page)}")
# Пример: Выводим 10 самых частых слов в документе
print("Самые частые слова в документе:")
for word, freq in word_frequencies.most_common(10):
print(f"{word}: {freq}")
# Получаем метаданные первой страницы в качестве примера
first_page_metadata = document_pages[0].metadata
print("\nМетаданные первой страницы:")
print(first_page_metadata)
# Опционально: Сохраняем очищенный текст документа в файл
with open("cleaned_lecture_series_lecture01.txt", "w") as text_file:
for page in document_pages:
if page.page_content.strip(): # Проверяем, что страница не пустая
cleaned_text = ' '.join(clean_and_tokenize(page.page_content))
text_file.write(cleaned_text + "\n")
Видеоконтент — не менее важный источник. Мы можем выгрузить аудио с YouTube, транскрибировать его через Whisper в рамках LangChain и сразу приступить к анализу: разбить на предложения, оценить тональность (полярность и субъективность) с помощью TextBlob
, а при необходимости расширить обработку извлечением сущностей, ключевых фраз и суммаризацией.
from langchain.document_loaders.generic import GenericLoader
from langchain.document_loaders.parsers import OpenAIWhisperParser
from langchain.document_loaders.blob_loaders.youtube_audio import YoutubeAudioLoader
from nltk.tokenize import sent_tokenize
from textblob import TextBlob
import os
# Убедитесь, что ресурсы nltk загружены (например, пункт для токенизации предложений)
import nltk
nltk.download('punkt')
# Указываем URL видео YouTube и директорию для сохранения аудиофайлов
video_url = "https://www.youtube.com/watch?v=example_video_id"
audio_save_directory = "docs/youtube/"
# Убеждаемся, что директория существует
os.makedirs(audio_save_directory, exist_ok=True)
# Инициализируем Generic Loader с YouTube Audio Loader и Whisper Parser
youtube_loader = GenericLoader(
YoutubeAudioLoader([video_url], audio_save_directory),
OpenAIWhisperParser()
)
# Загружаем документ
youtube_documents = youtube_loader.load()
# Пример: Получаем первую часть транскрибированного содержимого
transcribed_text = youtube_documents[0].page_content[:500]
print(transcribed_text)
# Разбиваем транскрипцию на предложения
sentences = sent_tokenize(transcribed_text)
# Выводим первые 5 предложений в качестве примера
print("\nПервые 5 предложений транскрипции:")
for sentence in sentences[:5]:
print(sentence)
# Выполняем анализ тональности транскрибированного содержимого
sentiment = TextBlob(transcribed_text).sentiment
print("\nАнализ тональности:")
print(f"Полярность: {sentiment.polarity}, Субъективность: {sentiment.subjectivity}")
# Полярность — это число с плавающей точкой в диапазоне [-1.0, 1.0], где -1 означает негативную тональность, а 1 — позитивную.
# Субъективность — это число с плавающей точкой в диапазоне [0.0, 1.0], где 0.0 очень объективно, а 1.0 очень субъективно.
# Дополнительный анализ или обработка может быть выполнена здесь, например:
# - Извлечение именованных сущностей (имена, места и т.д.)
# - Определение ключевых фраз или тем
# - Суммаризация содержимого
Для извлечения и обработки веб‑контента мы загружаем страницу по URL, очищаем HTML от технических тегов, извлекаем ссылки и заголовки, а затем выполняем простую суммаризацию: токенизируем на предложения, фильтруем стоп‑слова, оцениваем частоты и выводим краткое резюме.
from langchain.document_loaders import WebBaseLoader
from bs4 import BeautifulSoup
from nltk.tokenize import sent_tokenize
from nltk.corpus import stopwords
from nltk.probability import FreqDist
from nltk import download
download('punkt')
download('stopwords')
# Инициализируем Web Base Loader с целевым URL
web_loader = WebBaseLoader("https://example.com/path/to/document")
# Загружаем документ
web_documents = web_loader.load()
# Используем BeautifulSoup для парсинга HTML-содержимого
soup = BeautifulSoup(web_documents[0].page_content, 'html.parser')
# Пример: Очистка веб-содержимого путём удаления элементов script и style
for script_or_style in soup(["script", "style"]):
script_or_style.decompose()
# Получаем текст из HTML-страницы и заменяем множественные пробелы/переносы строк на одинарный пробел
clean_text = ' '.join(soup.stripped_strings)
# Выводим первые 500 символов очищенного веб-содержимого
print(clean_text[:500])
# Извлечение конкретной информации
# Пример: Извлечение всех гиперссылок
links = [(a.text, a['href']) for a in soup.find_all('a', href=True)]
print("\nИзвлечённые ссылки:")
for text, href in links[:5]: # Выводим первые 5 ссылок в качестве примера
print(f"{text}: {href}")
# Пример: Извлечение заголовков (h1)
headings = [h1.text for h1 in soup.find_all('h1')]
print("\nЗаголовки, найденные на странице:")
for heading in headings:
print(heading)
# Суммаризация текста
# Токенизируем предложения
sentences = sent_tokenize(clean_text)
# Фильтруем стоп-слова
stop_words = set(stopwords.words("english"))
filtered_sentences = [' '.join([word for word in sentence.split() if word.lower() not in stop_words]) for sentence in sentences]
# Распределение частоты слов
word_freq = FreqDist(word.lower() for sentence in filtered_sentences for word in sentence.split())
# Выводим 5 самых частых слов
print("\nСамые частые слова:")
for word, frequency in word_freq.most_common(5):
print(f"{word}: {frequency}")
# Суммаризируем: Выводим первые 5 предложений как простое резюме
print("\nРезюме содержимого:")
for sentence in sentences[:5]:
print(sentence)
Структурированный экспорт из Notion также легко поддаётся обработке: загрузим Markdown‑файлы, преобразуем их в HTML для удобного парсинга, извлечём заголовки и ссылки, сложим метаданные и извлечённое содержимое в DataFrame, применим фильтры (например, по ключевому слову в заголовке) и, при наличии, посчитаем разбивку по категориям.
from langchain.document_loaders import NotionDirectoryLoader
import markdown
from bs4 import BeautifulSoup
import pandas as pd
# Указываем директорию, содержащую экспортированные данные Notion
notion_directory = "docs/Notion_DB"
# Инициализируем Notion Directory Loader
notion_loader = NotionDirectoryLoader(notion_directory)
# Загружаем документы
notion_documents = notion_loader.load()
# Пример: Выводим первые 200 символов содержимого документа Notion
print(notion_documents[0].page_content[:200])
# Получаем метаданные документа Notion
print(notion_documents[0].metadata)
# Конвертируем Markdown в HTML для более легкого парсинга и извлечения
html_content = [markdown.markdown(doc.page_content) for doc in notion_documents]
# Парсим HTML для извлечения структурированных данных (например, заголовки, списки, ссылки)
parsed_data = []
for content in html_content:
soup = BeautifulSoup(content, 'html.parser')
# Пример: Извлечение всех заголовков (h1, h2 и т.д.)
headings = [heading.text for heading in soup.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])]
# Пример: Извлечение всех ссылок
links = [(a.text, a['href']) for a in soup.find_all('a', href=True)]
parsed_data.append({'headings': headings, 'links': links})
# Организуем данные в DataFrame для дальнейшего анализа
df = pd.DataFrame({
'metadata': [doc.metadata for doc in notion_documents],
'parsed_content': parsed_data
})
# Пример фильтрации: Находим документы с конкретными ключевыми словами в метаданных
keyword = 'Project'
filtered_docs = df[df['metadata'].apply(lambda x: keyword.lower() in x.get('title', '').lower())]
print("\nДокументы, содержащие ключевое слово в заголовке:")
print(filtered_docs)
# Суммаризация или генерация отчетов на основе агрегированного содержимого
# Пример: Подсчет документов по категориям (предполагая, что категории являются частью метаданных)
if 'category' in df['metadata'].iloc[0]: # Проверяем, существует ли категория в метаданных
category_counts = df['metadata'].apply(lambda x: x['category']).value_counts()
print("\nКоличество документов по категориям:")
print(category_counts)
# Это базовый подход к обработке и анализу экспортированных данных Notion.
# Он демонстрирует, как парсить, фильтровать и суммаризировать содержимое для получения инсайтов или отчетности.
Работая с загрузчиками, следите за затратами на внешние API (например, Whisper) и оптимизируйте вызовы; сразу после загрузки приводите данные к удобному для анализа виду (очистка, разбиение на чанки и т. п.); а если какого‑то источника пока нет — смело контрибьютите собственный загрузчик в open‑source LangChain. Для ориентира держите под рукой исходники и руководства: документацию LangChain (https://github.com/LangChain/langchain) и репозиторий OpenAI Whisper (https://github.com/openai/whisper). Такая практика закладывает основу дальнейшей, более продвинутой обработки и интеграции данных в ваши LLM‑приложения.
Теоретические вопросы
- Что такое загрузчики документов в LangChain и какую роль они играют?
- В чём заключается различие между загрузчиками для неструктурированных и структурированных данных?
- Как подготовить окружение для работы с загрузчиками (необходимые пакеты, ключи API, файл
.env
)? - Как работает
PyPDFLoader
и что даёт предварительная обработка PDF-документов? - Зачем очищать и токенизировать текст при обработке PDF?
- Как транскрибировать видео с YouTube с помощью Whisper через LangChain?
- Как применить токенизацию предложений и анализ тональности к полученному транскрипту?
- Как загрузить и обработать веб-контент с помощью
WebBaseLoader
? - Как извлекать и суммировать содержимое страниц по URL?
- Как
NotionDirectoryLoader
помогает анализировать экспортированные данные Notion? - Какие практики важны при работе с загрузчиками (учёт стоимости, предварительная обработка)?
- Зачем и как можно контрибьютить новые загрузчики в LangChain?
Практические задания
- Модифицируйте анализ PDF так, чтобы игнорировать стоп-слова (
nltk.stopwords
); выведите топ-5 самых частых слов без стоп-слов. - Напишите функцию, которая по URL YouTube транскрибирует видео (Whisper) и возвращает первые 100 слов; предусмотрите обработку ошибок.
- Создайте скрипт: загрузите страницу по URL, удалите HTML-теги и выведите чистый текст (используйте BeautifulSoup).
- По каталогу экспорта Notion: сконвертируйте Markdown в HTML, извлеките и выведите все ссылки (текст + href).
- Дополните транскрипцию YouTube анализом тональности (
TextBlob
): выведите полярность (polarity) и общую оценку (позитив/нейтрал/негатив). - Создайте DataFrame из документов Notion, добавьте столбец «количество слов» и выведите заголовки трёх самых длинных документов.
- Для заданного URL — загрузите страницу, извлеките основной текст и выведите простое резюме (первое и последнее предложения).