|
1 | | -# NEWS_API_ETL_Project |
| 1 | +# NEWS_API_ETL_Project |
2 | 2 |
|
3 | | -### Цель |
4 | | -Обрабатывать из внешнего API новости и используя принцип ETL сохранять нужные новости в базу данных. |
| 3 | +Production-oriented ETL pipeline that collects articles from NewsAPI, validates/transforms them, and loads clean records into PostgreSQL. |
5 | 4 |
|
6 | | -### Стек |
7 | | -Extract: requests(страна, категория, ключевое слово, размер страницы (опционально)) -> сохранение статей в папку data/raw |
| 5 | +## What this project does |
| 6 | +1. `extract`: gets paginated articles from NewsAPI with timeout, retries, and backoff. |
| 7 | +2. `transform`: validates each article, normalizes fields and timestamps, and collects rejection stats. |
| 8 | +3. `load`: upserts articles and links them to user requests with deduplication. |
| 9 | +4. `worker`: atomically claims queued search requests from PostgreSQL and processes them safely. |
8 | 10 |
|
9 | | -Transform: Python скрипт читает данные из нового файла data/raw, проверяет чтобы были указаны: автор, заголовок, описание не меньше 20 символов, url ссылка |
10 | | - |
11 | | -Load: psycopg2 подключается к PostgreSQL, перед вставкой разрешается конфликт с уникальной url ссылкой. Если такая ссылка уже есть то новость не добавляется в таблицу |
12 | | - |
13 | | -db: создается база данных и таблица если их еще нет. url присваивается UNIQUE |
14 | | - |
15 | | -Pipeline: программа запускается скриптом разделенным на 5 основных модулей (db.py, extract.py, load.py, transform.py, main.py) |
16 | | - |
17 | | -### Схема ETL |
18 | | -Everything из NewsApi (extract) -> Филтр на наличие автора, заголовка, описания не меньше 20 символов, наличие url (transform) -> загрузка статей чей url отсутствует в базе данных (load) |
19 | | - |
20 | | -### Структура папок |
| 11 | +## Repository structure |
21 | 12 | ```text |
22 | 13 | project/ |
23 | | -├── config |
24 | | -| └── config.py |
25 | | -├── data / |
26 | | -| ├──raw / # тут будут храниться статьи до обработки в формате json |
27 | | -| └──clean / # тут будут храниться статьи после обработки |
28 | | -├── notebooks / |
29 | | -| └── 01_eda.ipynb |
30 | | -├── src / |
31 | | -| ├── __init__.py |
32 | | -| ├── db.py |
33 | | -| ├── extract.py |
34 | | -| ├── load.py |
35 | | -| └── transform.py |
36 | | -├──.env.example |
| 14 | +├── config/ |
| 15 | +│ └── config.py |
| 16 | +├── data/ |
| 17 | +│ ├── raw/ |
| 18 | +│ └── clean/ |
| 19 | +├── notebooks/ |
| 20 | +│ └── 01_eda.ipynb |
| 21 | +├── src/ |
| 22 | +│ ├── __init__.py |
| 23 | +│ ├── db.py |
| 24 | +│ ├── extract.py |
| 25 | +│ ├── load.py |
| 26 | +│ ├── pipeline.py |
| 27 | +│ ├── transform.py |
| 28 | +│ └── worker.py |
| 29 | +├── .env.example |
| 30 | +├── DockerFile |
| 31 | +├── docker-compose.yml |
37 | 32 | ├── main.py |
38 | | -└── requirements.txt |
| 33 | +├── requirements.txt |
| 34 | +└── requirements-dev.txt |
39 | 35 | ``` |
40 | 36 |
|
41 | | -### Порядок запуска |
42 | | -#### склонировать репозиторий |
| 37 | +## Environment variables |
| 38 | +Copy `.env.example` to `.env` and fill your values: |
| 39 | + |
43 | 40 | ```bash |
44 | | -git clone [сслыка на репозиторий] |
| 41 | +cp .env.example .env |
45 | 42 | ``` |
46 | 43 |
|
47 | | -#### перейти в папку проекта |
| 44 | +Key variables: |
| 45 | +- `NEWSAPI_KEY`: required for extract. |
| 46 | +- `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NEWS`. |
| 47 | +- Optional reliability settings: `REQUEST_MAX_RETRIES`, `REQUEST_TIMEOUT_SECONDS`, `MAX_PAGES_PER_REQUEST`, `DB_CONNECT_TIMEOUT_SECONDS`. |
| 48 | + |
| 49 | +## Local run |
| 50 | +### 1. Install |
48 | 51 | ```bash |
49 | | -cd <repo_name> |
| 52 | +python -m venv .venv |
| 53 | +.venv\Scripts\activate |
| 54 | +pip install -r requirements.txt |
50 | 55 | ``` |
51 | | -#### создать виртуальное окружение |
| 56 | + |
| 57 | +### 2. Initialize DB objects |
52 | 58 | ```bash |
53 | | -py -m venv .venv |
| 59 | +python main.py --init-only |
54 | 60 | ``` |
55 | | -после чего |
| 61 | +Alternative for one-shot run: add `--bootstrap` to any run command below. |
| 62 | + |
| 63 | +### 3. Debug run (raw/clean JSON + DB table `bad_news_bears`) |
56 | 64 | ```bash |
57 | | -.venv\Scripts\Activate.ps1 |
| 65 | +python main.py --debug --keyword python --limit 20 --page_size 50 --language en |
58 | 66 | ``` |
59 | 67 |
|
60 | | -#### установить библиотеки |
61 | | -``` bash |
62 | | -pip install -r requirements.txt |
| 68 | +### 4. Web-mode run (requires existing `app_users` and `search_requests` rows) |
| 69 | +```bash |
| 70 | +python main.py --keyword python --limit 20 --page_size 50 --language en --user_id 1 --search_request_id 1 |
63 | 71 | ``` |
64 | 72 |
|
65 | | -#### скопировать .env.exsample |
66 | | -``` bash |
67 | | -copy .env.example .env |
| 73 | +### 5. Worker loop |
| 74 | +```bash |
| 75 | +python main.py --worker --poll_interval 3 |
68 | 76 | ``` |
69 | | -Заполнить .env вашими данными |
70 | 77 |
|
71 | | -#### запустить код |
| 78 | +## Docker |
| 79 | +Run app + Postgres with Docker Compose: |
| 80 | + |
72 | 81 | ```bash |
73 | | -python main.py --keyword your_key_word --limit your_articles_limit --page_size your_page_size |
| 82 | +docker compose up --build |
74 | 83 | ``` |
75 | 84 |
|
76 | | -### пример .env |
77 | | -```.env |
78 | | -DB_HOST=localhost |
79 | | -DB_PORT=5432 |
80 | | -DB_USER=postgres |
81 | | -DB_PASSWORD=1234 |
82 | | -DB_ADMIN_DB=postgres |
83 | | -DB_NEWS=db_news |
84 | | -NEWSAPI_KEY =your_key |
| 85 | +By default, app container runs worker mode. |
| 86 | + |
| 87 | +## Quality checks |
| 88 | +```bash |
| 89 | +python -m compileall . |
| 90 | +ruff check . |
| 91 | +pytest -q |
| 92 | +bandit -r src config main.py |
| 93 | +pip-audit |
85 | 94 | ``` |
86 | 95 |
|
87 | | -### Важные моменты |
88 | | -В папку data/raw сохраняются все статьи которые были получены за 1 запрос по вашим критериям. Им в качестве имени присваивается текущее дата-время, ключевое слово и текущая страница. |
| 96 | +## Reliability and safety guarantees |
| 97 | +- Request retry with backoff and HTTP status handling (`429`, `5xx`). |
| 98 | +- Input validation in transform layer (bad records are rejected with reason stats). |
| 99 | +- Idempotent article upsert by URL. |
| 100 | +- Atomic worker dequeue (`FOR UPDATE SKIP LOCKED`) to avoid duplicate processing across workers. |
| 101 | +- DB connection timeout and statement timeout support. |
| 102 | + |
| 103 | +## Notes |
| 104 | +- `.env`, `.venv`, `__pycache__`, and generated files under `data/raw` and `data/clean` are ignored by git. |
| 105 | +- Keep secrets only in `.env` (never commit real keys). |
89 | 106 |
|
90 | | -В папку data/clean попадают все статьи которые имеют: автора, заголовок, описание 20+ символов и url ссылку. им присвается имя аналогичным способом что и в data/raw однако первым словом в имени является cleaned |
| 107 | +## Troubleshooting |
| 108 | +- `Database 'news_db' does not exist`: |
| 109 | + - Run `python main.py --init-only` once, or add `--bootstrap` to your run command. |
| 110 | +- `password authentication failed`: |
| 111 | + - Verify `.env` values `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`. |
| 112 | + - Manually test credentials with psql/pgAdmin for the same host/port/user. |
91 | 113 |
|
92 | | -В папке data/clean/stats содержит статистику по отклоненным статьям. Считаются все недочеты статей, а так же высчитывается первая блокирующая ошибка каждой статьи. |
|
0 commit comments