Перейти к содержимому

Структура проекта

scraper-project/
├── scrapers/ # Модуль скраперов
│ ├── __init__.py # Инициализация модуля
│ ├── base_scraper.py # Базовый класс скрапера
│ ├── habr_scraper.py # Скрапер для Habr
│ └── dzen_scraper.py # Скрапер для Dzen
├── dags/ # DAG файлы для Airflow
│ └── scraper_dag.py # DAG для скрапинга
├── data/ # Папка для данных
│ ├── raw/ # Сырые данные
│ ├── processed/ # Обработанные данные
│ └── logs/ # Логи выполнения
├── config/ # Конфигурационные файлы
│ ├── settings.py # Настройки приложения
│ └── logging_config.py # Конфигурация логирования
├── tests/ # Тесты
│ ├── test_base_scraper.py # Тесты базового скрапера
│ ├── test_habr_scraper.py # Тесты Habr скрапера
│ └── test_dzen_scraper.py # Тесты Dzen скрапера
├── notebooks/ # Jupyter notebooks
│ └── analysis.ipynb # Ноутбук для анализа данных
├── scripts/ # Вспомогательные скрипты
│ ├── setup.py # Скрипт установки
│ └── run_scraper.py # Скрипт запуска скрапера
├── .gitignore # Git игнорируемые файлы
├── requirements.txt # Зависимости Python
├── Dockerfile # Инструкция Docker
├── docker-compose.yml # Конфигурация docker-compose
├── README.md # Описание проекта
└── pyproject.toml # Метаданные проекта
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environment
venv/
env/
ENV/
.venv
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Project specific
data/raw/*
data/processed/*
!data/raw/.gitkeep
!data/processed/.gitkeep
# Logs
*.log
logs/
# OS
.DS_Store
Thumbs.db
# Environment
.env
.env.local
# Jupyter
.ipynb_checkpoints/
*.ipynb
# Database
*.db
*.sqlite
*.sqlite3
# Airflow
airflow.cfg
.airflow/
# Web Scraping
requests==2.31.0
beautifulsoup4==4.12.2
lxml==4.9.3
# Machine Learning
transformers==4.35.0
torch==2.1.0
sentencepiece==0.1.99
# Database
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
# Data Processing
pandas==2.1.3
numpy==1.25.2
# Airflow
apache-airflow==2.7.0
apache-airflow-providers-celery==3.5.1
apache-airflow-providers-postgres==5.10.0
apache-airflow-providers-slack==8.0.0
# Utilities
python-dotenv==1.0.0
pydantic==2.5.0
tenacity==8.2.3
# Testing
pytest==7.4.3
pytest-cov==4.1.0
# Development
black==23.11.0
flake8==6.1.0
mypy==1.7.1
# Web Scraper and Sentiment Analysis
Автоматическая система сбора и анализа данных из новостных источников.
## Описание
Проект представляет собой систему для:
- Сбор статей с Habr и Dzen
- Анализа тональности текста
- Сохранения результатов в базу данных
- Автоматизации процессов через Airflow
## Установка
### Клонирование репозитория
```bash
git clone https://gitlab.com/username/web-scraper.git
cd web-scraper
Окно терминала
python -m venv venv
source venv/bin/activate # Linux/Mac
# или
venv\Scripts\activate # Windows
Окно терминала
pip install -r requirements.txt

Создайте файл .env:

DATABASE_URL=sqlite:///data/scraper.db
LOG_LEVEL=INFO
SENTRY_DSN=
SLACK_WEBHOOK_URL=
Окно терминала
python scripts/run_scraper.py
Окно терминала
docker-compose up -d

Web-интерфейс Airflow доступен по адресу: http://localhost:8080

  • scrapers/ — модуль скраперов
  • dags/ — DAG файлы для Airflow
  • data/ — папка для данных
  • tests/ — тесты
  • notebooks/ — Jupyter notebooks
Окно терминала
pytest tests/

MIT License

### pyproject.toml
```toml
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "web-scraper"
version = "1.0.0"
description = "Web Scraper and Sentiment Analysis System"
readme = "README.md"
requires-python = ">=3.10"
authors = [
{name = "Your Name", email = "your.email@example.com"}
]
keywords = ["scraper", "sentiment-analysis", "airflow"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
dependencies = [
"requests>=2.31.0",
"beautifulsoup4>=4.12.0",
"transformers>=4.35.0",
"torch>=2.1.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.4.0",
"black>=23.0.0",
"flake8>=6.0.0",
"mypy>=1.7.0",
]
[project.urls]
Homepage = "https://gitlab.com/username/web-scraper"
Repository = "https://gitlab.com/username/web-scraper.git"
Issues = "https://gitlab.com/username/web-scraper/issues"
[tool.black]
line-length = 100
target-version = ['py310']
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
from pydantic import BaseSettings, Field
from typing import Optional
class Settings(BaseSettings):
"""Настройки приложения"""
# Database
database_url: str = Field(default="sqlite:///data/scraper.db")
# Scraping
request_timeout: int = Field(default=10)
delay_between_requests: float = Field(default=1.0)
max_retries: int = Field(default=3)
# Sentiment Analysis
sentiment_model: str = Field(
default="blanchefort/rubert-base-cased-sentiment"
)
# Logging
log_level: str = Field(default="INFO")
log_file: str = Field(default="logs/scraper.log")
# Airflow
airflow_dags_folder: str = Field(default="dags/")
airflow_schedule_interval: str = Field(default="@daily")
# Notifications
slack_webhook_url: Optional[str] = None
email_recipients: list = Field(default_factory=list)
class Config:
env_file = ".env"
env_prefix = "SCRAPER_"
settings = Settings()
#!/usr/bin/env python3
"""
Скрипт установки проекта
"""
import os
import subprocess
import sys
def create_directories():
"""Создание необходимых директорий"""
directories = [
"data/raw",
"data/processed",
"data/logs",
"logs",
"config",
]
for directory in directories:
os.makedirs(directory, exist_ok=True)
print(f"Создана директория: {directory}")
def install_dependencies():
"""Установка зависимостей"""
print("Установка зависимостей...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
def create_env_file():
"""Создание файла .env"""
env_content = """# Database
DATABASE_URL=sqlite:///data/scraper.db
# Logging
LOG_LEVEL=INFO
# Scraping
REQUEST_TIMEOUT=10
DELAY_BETWEEN_REQUESTS=1.0
# Notifications
SLACK_WEBHOOK_URL=
"""
if not os.path.exists(".env"):
with open(".env", "w") as f:
f.write(env_content)
print("Создан файл .env")
else:
print("Файл .env уже существует")
def main():
"""Основная функция"""
print("Установка проекта...")
create_directories()
create_env_file()
install_dependencies()
print("\nУстановка завершена!")
print("Для продолжения:")
print("1. Настройте файл .env")
print("2. Запустите: python scripts/run_scraper.py")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Скрипт запуска скрапера
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from scrapers.habr_scraper import HabrScraper
from scrapers.dzen_scraper import DzenScraper
from config.settings import settings
import logging
def setup_logging():
"""Настройка логирования"""
logging.basicConfig(
level=getattr(logging, settings.log_level),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(settings.log_file),
logging.StreamHandler()
]
)
def run_habr_scraper():
"""Запуск скрапера для Habr"""
logger = logging.getLogger(__name__)
logger.info("Запуск скрапера для Habr")
scraper = HabrScraper()
urls = [f"https://habr.com/ru/hub/python/page{i}/" for i in range(1, 3)]
data = scraper.run(urls, "data/raw/habr_articles.json")
logger.info(f"Собрано {len(data)} статей с Habr")
return data
def run_dzen_scraper():
"""Запуск скрапера для Dzen"""
logger = logging.getLogger(__name__)
logger.info("Запуск скрапера для Dzen")
scraper = DzenScraper()
urls = [f"https://dzen.ru/news/python/page{i}/" for i in range(1, 3)]
data = scraper.run(urls, "data/raw/dzen_articles.json")
logger.info(f"Собрано {len(data)} статей с Dzen")
return data
def main():
"""Основная функция"""
setup_logging()
logger = logging.getLogger(__name__)
logger.info("=" * 50)
logger.info("Запуск скрапера")
logger.info("=" * 50)
try:
# Запуск скраперов
habr_data = run_habr_scraper()
dzen_data = run_dzen_scraper()
total = len(habr_data) + len(dzen_data)
logger.info(f"Всего собрано {total} статей")
logger.info("Завершено успешно!")
except Exception as e:
logger.error(f"Ошибка: {e}", exc_info=True)
sys.exit(1)
if __name__ == "__main__":
main()
import pytest
from scrapers.base_scraper import BaseScraper
from unittest.mock import Mock, patch
class TestBaseScraper:
"""Тесты базового скрапера"""
@pytest.fixture
def scraper(self):
"""Фикстура скрапера"""
return BaseScraper("https://example.com")
def test_initialization(self, scraper):
"""Тест инициализации"""
assert scraper.base_url == "https://example.com"
assert scraper.session is not None
assert "User-Agent" in scraper.headers
@patch("requests.Session.get")
def test_get_page_success(self, mock_get, scraper):
"""Тест успешного получения страницы"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.text = "<html></html>"
mock_get.return_value = mock_response
response = scraper.get_page("https://example.com")
assert response is not None
assert response.status_code == 200
def test_save_to_json(self, scraper, tmp_path):
"""Тест сохранения в JSON"""
data = [{"title": "Test"}]
file_path = tmp_path / "test.json"
scraper.save_to_json(data, str(file_path))
assert file_path.exists()