mirror of
https://github.com/ada-dmitry/CourseWork_IRFM.git
synced 2026-09-24 01:10:18 +00:00
Refactor loader and processor documentation; enhance index module with README
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
## Как работает index
|
||||||
|
|
||||||
|
Index находится в `modules/index.py`. Его задача: взять исходный текст УК РФ и построить предметный указатель — топ-N наиболее частотных лемм с позицией первого вхождения.
|
||||||
|
|
||||||
|
Модуль использует `good_words_from_line` из `modules/processor.py` и ничего не знает о загрузке или обходе страниц.
|
||||||
|
|
||||||
|
## Что делает index
|
||||||
|
|
||||||
|
1. Обходит текст построчно и извлекает «хорошие» слова через `processor.py`.
|
||||||
|
2. Считает частоту каждой леммы.
|
||||||
|
3. Запоминает, где лемма встретилась впервые.
|
||||||
|
4. Возвращает топ-N по частоте.
|
||||||
|
5. Сохраняет результат в CSV и JSON.
|
||||||
|
|
||||||
|
## Генератор prepared_terms
|
||||||
|
|
||||||
|
```python
|
||||||
|
prepared_terms(text)
|
||||||
|
```
|
||||||
|
|
||||||
|
Обходит текст построчно и для каждого слова, прошедшего фильтрацию, выдаёт кортеж:
|
||||||
|
|
||||||
|
```python
|
||||||
|
(лемма, номер_строки, позиция_символа, исходное_слово)
|
||||||
|
```
|
||||||
|
|
||||||
|
Нумерация строк начинается с `1`, позиция символа — 1-based, как в `processor.py`.
|
||||||
|
|
||||||
|
## Построение предметного указателя
|
||||||
|
|
||||||
|
```python
|
||||||
|
build_subject_index(text, top_n=100)
|
||||||
|
```
|
||||||
|
|
||||||
|
Алгоритм:
|
||||||
|
|
||||||
|
1. Проходит по всем словам через `prepared_terms`.
|
||||||
|
2. Считает частоты через `Counter`.
|
||||||
|
3. Запоминает первое вхождение каждой леммы через `setdefault` — первый вызов выигрывает, последующие игнорируются.
|
||||||
|
4. Берёт `top_n` самых частотных лемм.
|
||||||
|
|
||||||
|
Одна запись результата:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"word": "срок",
|
||||||
|
"count": 3342,
|
||||||
|
"line": 34,
|
||||||
|
"char": 151,
|
||||||
|
"source_word": "срок",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Запись результатов
|
||||||
|
|
||||||
|
Для сохранения индекса есть две функции.
|
||||||
|
|
||||||
|
```python
|
||||||
|
write_subject_index_csv(entries, path)
|
||||||
|
```
|
||||||
|
|
||||||
|
Сохраняет CSV с разделителем `;`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
word;count;line;char;source_word
|
||||||
|
срок;3342;34;151;срок
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
write_subject_index_json(entries, path)
|
||||||
|
```
|
||||||
|
|
||||||
|
Сохраняет JSON с отступами и без ASCII-экранирования кириллицы (`ensure_ascii=False`).
|
||||||
|
|
||||||
|
## Где используется index
|
||||||
|
|
||||||
|
В `main.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
subject_index = build_subject_index(original_text, top_n=100)
|
||||||
|
write_subject_index_csv(subject_index, output_dir / "uk_rf_subject_index.csv")
|
||||||
|
write_subject_index_json(subject_index, output_dir / "uk_rf_subject_index.json")
|
||||||
|
```
|
||||||
|
|
||||||
|
Входные данные — исходный текст статей до лемматизации (`original_text`), а не подготовленный. Лемматизация происходит внутри через `processor.py`.
|
||||||
@@ -89,14 +89,9 @@ download_html(url, retries=3, timeout=30.0)
|
|||||||
|
|
||||||
Логика выбора загрузчика:
|
Логика выбора загрузчика:
|
||||||
|
|
||||||
- если домен заканчивается на `consultant.ru`, сначала пробуется `curl`, затем `urllib`;
|
- всегда сначала пробуется `curl`, затем `urllib`.
|
||||||
- для остальных сайтов сначала пробуется `urllib`, затем `curl`.
|
|
||||||
|
|
||||||
Это задается строкой:
|
`curl` надёжнее обходит защиты ConsultantPlus, поэтому стоит первым для любых URL.
|
||||||
|
|
||||||
```python
|
|
||||||
is_consultant = urlparse(url).netloc.endswith("consultant.ru")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Повторные попытки
|
## Повторные попытки
|
||||||
|
|
||||||
|
|||||||
@@ -211,47 +211,6 @@ prepare_text(text)
|
|||||||
output/uk_rf_prepared.txt
|
output/uk_rf_prepared.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## Предметный указатель
|
## Где используется processor
|
||||||
|
|
||||||
Функция:
|
`good_words_from_line` экспортируется в `modules/index.py`, где используется для построения предметного указателя. Логика подсчёта частот и записи результатов находится в `index.py`, а не здесь.
|
||||||
|
|
||||||
```python
|
|
||||||
build_subject_index(text, top_n=100)
|
|
||||||
```
|
|
||||||
|
|
||||||
строит индекс так:
|
|
||||||
|
|
||||||
1. Проходит по всем подготовленным словам.
|
|
||||||
2. Считает частоты через `Counter`.
|
|
||||||
3. Запоминает первое появление каждого слова: номер строки, номер символа и исходную форму слова.
|
|
||||||
4. Возвращает 100 самых частотных слов.
|
|
||||||
|
|
||||||
Одна запись индекса выглядит так:
|
|
||||||
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"word": "срок",
|
|
||||||
"count": 3342,
|
|
||||||
"line": 34,
|
|
||||||
"char": 151,
|
|
||||||
"source_word": "срок",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Запись результатов
|
|
||||||
|
|
||||||
Для сохранения индекса есть две функции:
|
|
||||||
|
|
||||||
```python
|
|
||||||
write_subject_index_csv(entries, path)
|
|
||||||
write_subject_index_json(entries, path)
|
|
||||||
```
|
|
||||||
|
|
||||||
Они создают:
|
|
||||||
|
|
||||||
```text
|
|
||||||
output/uk_rf_subject_index.csv
|
|
||||||
output/uk_rf_subject_index.json
|
|
||||||
```
|
|
||||||
|
|
||||||
CSV удобен для просмотра в таблице, JSON удобен для дальнейшей обработки программой.
|
|
||||||
|
|||||||
@@ -12,13 +12,18 @@ def main():
|
|||||||
output_dir.mkdir(exist_ok=True)
|
output_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
pages = crawl_document(SOURCE_URL) # Вызов краулера на исходный url для рекурсивного обхода
|
pages = crawl_document(SOURCE_URL) # Вызов краулера на исходный url для рекурсивного обхода
|
||||||
original_text = "\n".join(page["text"] for page in pages) # Складываем результат работы краулера в единую строку
|
original_text = "\n".join(page["text"] for page in pages) # Складываем результат работы краулера в единую строку
|
||||||
|
|
||||||
prepared_text = prepare_text(original_text) # Вызов препаратора на полученную строку для обработки
|
prepared_text = prepare_text(original_text) # Вызов препаратора на полученную строку для обработки
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
subject_index = build_subject_index(original_text, top_n=100) # Строим предметный указатель
|
subject_index = build_subject_index(original_text, top_n=100) # Строим предметный указатель
|
||||||
|
|
||||||
(output_dir / "uk_rf_original.txt").write_text(original_text, encoding="utf-8") # Вывод исходного текста в файл
|
(output_dir / "uk_rf_original.txt").write_text(original_text, encoding="utf-8") # Вывод исходного текста в файл
|
||||||
(output_dir / "uk_rf_prepared.txt").write_text(prepared_text, encoding="utf-8") # Вывод токенизированного и обработанного текста в файл
|
(output_dir / "uk_rf_prepared.txt").write_text(prepared_text, encoding="utf-8") # Вывод токенизированного и обработанного текста в файл
|
||||||
|
|
||||||
write_subject_index_csv(subject_index, output_dir / "uk_rf_subject_index.csv") # Вывод предметного указателя в csv
|
write_subject_index_csv(subject_index, output_dir / "uk_rf_subject_index.csv") # Вывод предметного указателя в csv
|
||||||
write_subject_index_json(subject_index, output_dir / "uk_rf_subject_index.json") # Предметные указатель в json
|
write_subject_index_json(subject_index, output_dir / "uk_rf_subject_index.json") # Предметные указатель в json
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -14,7 +14,8 @@ def prepared_terms(text: str):
|
|||||||
|
|
||||||
|
|
||||||
def build_subject_index(text: str, top_n: int = 100) -> list[dict]:
|
def build_subject_index(text: str, top_n: int = 100) -> list[dict]:
|
||||||
"""Строит предметный указатель: топ-N лемм по частоте с позицией первого вхождения.
|
"""
|
||||||
|
Строит предметный указатель: топ-N лемм по частоте с позицией первого вхождения.
|
||||||
|
|
||||||
first_place.setdefault гарантирует, что запоминается именно первое вхождение —
|
first_place.setdefault гарантирует, что запоминается именно первое вхождение —
|
||||||
prepared_terms обходит текст сверху вниз, поэтому первый же setdefault выигрывает.
|
prepared_terms обходит текст сверху вниз, поэтому первый же setdefault выигрывает.
|
||||||
|
|||||||
+2
-1
@@ -54,7 +54,8 @@ def download_with_urllib(url: str, timeout: float) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def download_html(url: str, retries: int = 3, timeout: float = 30.0) -> str:
|
def download_html(url: str, retries: int = 3, timeout: float = 30.0) -> str:
|
||||||
"""Загружает HTML-страницу с retry-логикой.
|
"""
|
||||||
|
Загружает HTML-страницу с retry-логикой.
|
||||||
|
|
||||||
Порядок загрузчиков: curl → urllib (curl обходит часть защит consultant.ru).
|
Порядок загрузчиков: curl → urllib (curl обходит часть защит consultant.ru).
|
||||||
На каждой итерации retry пробует оба; пауза 0.5 с между попытками.
|
На каждой итерации retry пробует оба; пауза 0.5 с между попытками.
|
||||||
|
|||||||
@@ -83,7 +83,8 @@ else:
|
|||||||
|
|
||||||
@lru_cache(maxsize=100_000)
|
@lru_cache(maxsize=100_000)
|
||||||
def parse_word(word: str):
|
def parse_word(word: str):
|
||||||
"""Возвращает (лемма, тег) для слова. Кешируем, чтобы не прогонять одно и то же через модуль постоянно.
|
"""
|
||||||
|
Возвращает (лемма, тег) для слова. Кешируем, чтобы не прогонять одно и то же через модуль постоянно.
|
||||||
|
|
||||||
«ё» → «е» для единообразия леммы в словаре и при сравнении.
|
«ё» → «е» для единообразия леммы в словаре и при сравнении.
|
||||||
Если pymorphy3 не установлен, тег пустой, лемма = lower().
|
Если pymorphy3 не установлен, тег пустой, лемма = lower().
|
||||||
@@ -103,7 +104,9 @@ def parse_word(word: str):
|
|||||||
|
|
||||||
@lru_cache(maxsize=10_000)
|
@lru_cache(maxsize=10_000)
|
||||||
def _tag_parts(tag: str) -> frozenset[str]:
|
def _tag_parts(tag: str) -> frozenset[str]:
|
||||||
"""Разбивает строку тега pymorphy3 на множество граммем для быстрого поиска."""
|
"""
|
||||||
|
Разбивает строку тега pymorphy3 на множество граммем для быстрого поиска.
|
||||||
|
"""
|
||||||
return frozenset(re.split(r"[, ]+", tag))
|
return frozenset(re.split(r"[, ]+", tag))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user