Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds an article text processing server #4

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/.pytest_cache/
.env.dist
README.md
5 changes: 5 additions & 0 deletions .env.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CHARGED_DICTS_FOLDER = charged_dict
MAX_ARTICLES_COUNT = 10
SERVICE_PORT = 8008
REDIS_URL = redis://127.0.0.1:6379/0
REDIS_STORAGE_TIME = 120
6 changes: 6 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[flake8]
max-line-length=119
max-doc-length=119
max-complexity=10
#extend=.svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg
extend-exclude=venv,dist,conf,scripts,migrations
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.pytest_cache/
.env
59 changes: 59 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Версия Docker образа Python.
ARG PYTHON=3.10.9

# Загружаем образ, в котором будем собирать необходимое нам окружение.
FROM python:${PYTHON} AS poetry-base

# Определяем необходимые переменные окружения,
# python пакеты будем ставить в папку /app/.local с использованием флага --user
ENV PYTHONUSERBASE="/app/.local" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1

# Устанавливаем необходимые библиотеки для сборки python пакетов и поддержки клиента БД Oracle.
RUN apt-get update && \
apt-get install --yes --no-install-recommends python3-dev libpq-dev libaio1 zip unzip && \
rm -rf /var/lib/apt/lists/*

# Устанавливаем временную рабочую директорию.
WORKDIR /tmp

# https://python-poetry.org/docs#ci-recommendations
ENV POETRY_VERSION=1.3.2
ENV POETRY_HOME=/opt/poetry
ENV POETRY_VENV=/opt/poetry-venv

# Tell Poetry where to place its cache and virtual environment
ENV POETRY_CACHE_DIR=/opt/.cache

# Creating a virtual environment just for poetry and install it with pip
RUN python3 -m venv $POETRY_VENV \
&& $POETRY_VENV/bin/pip install -U pip setuptools==58.2.0 \
&& $POETRY_VENV/bin/pip install poetry==${POETRY_VERSION}

# Загружаем образ, в котором будем собирать наш сервис.
FROM poetry-base

# Copy Poetry to app image
COPY --from=poetry-base ${POETRY_VENV} ${POETRY_VENV}

# Add Poetry to PATH
ENV PATH="${PATH}:/opt/poetry-venv/bin"

# Устанавливаем рабочую директорию.
WORKDIR /app

# Copy Dependencies
COPY poetry.lock pyproject.toml ./

# [OPTIONAL] Validate the project is properly configured
RUN poetry check

# [OPTIONAL] disable Poetry to manage my virtual environments
RUN poetry config virtualenvs.create false

# Install Dependencies
RUN poetry install --no-interaction

# Copy Application
COPY . /app
51 changes: 40 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,66 @@
# Фильтр желтушных новостей

[TODO. Опишите проект, схему работы]
Web-сервер анализа новостей на "желтушность".

Пример использования
```commandline
curl 127.0.0.1:8080/?urls=https://inosmi.ru/20230609/ukraina-263511718.html,https://inosmi.ru/20230528/mozg-263202267.html
```

В параметре urls перечисляются ссылки на новости, которые будут проанализированы.

Для анализа "желтушности" используются "заряженные слова", которые находятся в txt-файлах в папке, указанной в переменной окружения CHARGED_DICTS_FOLDER, см. раздел Настройка переменных окружения.

Пока поддерживается только один новостной сайт - [ИНОСМИ.РУ](https://inosmi.ru/). Для него разработан специальный адаптер, умеющий выделять текст статьи на фоне остальной HTML разметки. Для других новостных сайтов потребуются новые адаптеры, все они будут находиться в каталоге `adapters`. Туда же помещен код для сайта ИНОСМИ.РУ: `adapters/inosmi_ru.py`.

В перспективе можно создать универсальный адаптер, подходящий для всех сайтов, но его разработка будет сложной и потребует дополнительных времени и сил.

# Как установить

Вам понадобится Python версии 3.7 или старше. Для установки пакетов рекомендуется создать виртуальное окружение.
Вам понадобится Python версии 3.10 или старше. Для установки пакетов рекомендуется создать виртуальное окружение poetry.

Первым шагом установите пакеты:

```python3
pip install -r requirements.txt
```commandline
poetry install
```

Затем активируйте виртуальное окружение
```commandline
poetry shell
```

# Как запустить
# Настройка переменных окружения

```python3
python main.py
Скопируйте файл .env.dist в .env и задайте значения переменным окуржения. Пример:
```python
CHARGED_DICTS_FOLDER = charged_dict # название папки, в которой хранятся словари 'заряженных слов'
MAX_ARTICLES_COUNT = 10 # количество одновременно обрабатываемых статей для защиты от DOS-атак
SERVICE_PORT = 8080 # порт, на котором запустится web-сервер
REDIS_URL = redis://127.0.0.1:6379/0 # адрес REDIS для кэширования результатов запросов
REDIS_STORAGE_TIME = 120 # время хранения данных в кэше, в секундах
```

# Как запустить web-сервер

```commandline
python server.py
```

# Как запустить тесты

Для тестирования используется [pytest](https://docs.pytest.org/en/latest/), тестами покрыты фрагменты кода сложные в отладке: text_tools.py и адаптеры. Команды для запуска тестов:
Для тестирования используется [pytest](https://docs.pytest.org/en/latest/), тестами покрыты сложные в отладке фрагменты кода: text_tools.py и адаптеры. Команды для запуска тестов:

```
python -m pytest adapters/inosmi_ru.py
```commandline
python -m pytest tests/inosmi_ru.py
```

```commandline
python -m pytest tests/text_tools.py
```
python -m pytest text_tools.py

```commandline
python -m pytest tests/server.py
```

# Цели проекта
Expand Down
11 changes: 5 additions & 6 deletions adapters/html_tools.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
DEFAULT_BLACKLIST_TAGS = [
'script',
'time'
]
DEFAULT_BLACKLIST_TAGS = ['script', 'time']

DEFAULT_UNWRAPLIST_TAGS = [
'div',
Expand All @@ -10,7 +7,7 @@
'address',
'article',
'header',
'footer'
'footer',
]


Expand All @@ -31,7 +28,9 @@ def remove_buzz_attrs(soup):
return soup


def remove_buzz_tags(soup, blacklist=DEFAULT_BLACKLIST_TAGS, unwraplist=DEFAULT_UNWRAPLIST_TAGS):
def remove_buzz_tags(
soup, blacklist=DEFAULT_BLACKLIST_TAGS, unwraplist=DEFAULT_UNWRAPLIST_TAGS
):
"""Remove most of tags, leaves only tags significant for text analysis."""
for tag in soup.find_all(True):
if tag.name in blacklist:
Expand Down
34 changes: 1 addition & 33 deletions adapters/inosmi_ru.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
from bs4 import BeautifulSoup
import requests
import pytest

from .exceptions import ArticleNotFound
from .html_tools import remove_buzz_attrs, remove_buzz_tags, remove_all_tags


def sanitize(html, plaintext=False):
soup = BeautifulSoup(html, 'html.parser')
article = soup.select_one("div.layout-article")
article = soup.select_one('div.layout-article')

if not article:
raise ArticleNotFound()
Expand Down Expand Up @@ -36,33 +34,3 @@ def sanitize(html, plaintext=False):
remove_all_tags(article)
text = article.get_text()
return text.strip()


def test_sanitize():
resp = requests.get('https://inosmi.ru/economic/20190629/245384784.html')
resp.raise_for_status()
clean_text = sanitize(resp.text)

assert 'В субботу, 29 июня, президент США Дональд Трамп' in clean_text
assert 'За несколько часов до встречи с Си' in clean_text

assert '<img src="' in clean_text
assert '<h1>' in clean_text

clean_plaintext = sanitize(resp.text, plaintext=True)

assert 'В субботу, 29 июня, президент США Дональд Трамп' in clean_plaintext
assert 'За несколько часов до встречи с Си' in clean_plaintext

assert '<img src="' not in clean_plaintext
assert '<a href="' not in clean_plaintext
assert '<h1>' not in clean_plaintext
assert '</article>' not in clean_plaintext
assert '<h1>' not in clean_plaintext


def test_sanitize_wrong_url():
resp = requests.get('http://example.com')
resp.raise_for_status()
with pytest.raises(ArticleNotFound):
sanitize(resp.text)
Loading