Перейти к содержанию

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‑приложения.

Теоретические вопросы

  1. Что такое загрузчики документов в LangChain и какую роль они играют?
  2. В чём заключается различие между загрузчиками для неструктурированных и структурированных данных?
  3. Как подготовить окружение для работы с загрузчиками (необходимые пакеты, ключи API, файл .env)?
  4. Как работает PyPDFLoader и что даёт предварительная обработка PDF-документов?
  5. Зачем очищать и токенизировать текст при обработке PDF?
  6. Как транскрибировать видео с YouTube с помощью Whisper через LangChain?
  7. Как применить токенизацию предложений и анализ тональности к полученному транскрипту?
  8. Как загрузить и обработать веб-контент с помощью WebBaseLoader?
  9. Как извлекать и суммировать содержимое страниц по URL?
  10. Как NotionDirectoryLoader помогает анализировать экспортированные данные Notion?
  11. Какие практики важны при работе с загрузчиками (учёт стоимости, предварительная обработка)?
  12. Зачем и как можно контрибьютить новые загрузчики в LangChain?

Практические задания

  1. Модифицируйте анализ PDF так, чтобы игнорировать стоп-слова (nltk.stopwords); выведите топ-5 самых частых слов без стоп-слов.
  2. Напишите функцию, которая по URL YouTube транскрибирует видео (Whisper) и возвращает первые 100 слов; предусмотрите обработку ошибок.
  3. Создайте скрипт: загрузите страницу по URL, удалите HTML-теги и выведите чистый текст (используйте BeautifulSoup).
  4. По каталогу экспорта Notion: сконвертируйте Markdown в HTML, извлеките и выведите все ссылки (текст + href).
  5. Дополните транскрипцию YouTube анализом тональности (TextBlob): выведите полярность (polarity) и общую оценку (позитив/нейтрал/негатив).
  6. Создайте DataFrame из документов Notion, добавьте столбец «количество слов» и выведите заголовки трёх самых длинных документов.
  7. Для заданного URL — загрузите страницу, извлеките основной текст и выведите простое резюме (первое и последнее предложения).