Структура проекта
Файловая структура проекта
Заголовок раздела «Файловая структура проекта»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 # Метаданные проектаОписание основных файлов
Заголовок раздела «Описание основных файлов».gitignore
Заголовок раздела «.gitignore»# Python__pycache__/*.py[cod]*$py.class*.so.Pythonbuild/develop-eggs/dist/downloads/eggs/.eggs/lib/lib64/parts/sdist/var/wheels/*.egg-info/.installed.cfg*.egg
# Virtual Environmentvenv/env/ENV/.venv
# IDE.vscode/.idea/*.swp*.swo*~
# Project specificdata/raw/*data/processed/*!data/raw/.gitkeep!data/processed/.gitkeep
# Logs*.loglogs/
# OS.DS_StoreThumbs.db
# Environment.env.env.local
# Jupyter.ipynb_checkpoints/*.ipynb
# Database*.db*.sqlite*.sqlite3
# Airflowairflow.cfg.airflow/requirements.txt
Заголовок раздела «requirements.txt»# Web Scrapingrequests==2.31.0beautifulsoup4==4.12.2lxml==4.9.3
# Machine Learningtransformers==4.35.0torch==2.1.0sentencepiece==0.1.99
# Databasesqlalchemy==2.0.23psycopg2-binary==2.9.9
# Data Processingpandas==2.1.3numpy==1.25.2
# Airflowapache-airflow==2.7.0apache-airflow-providers-celery==3.5.1apache-airflow-providers-postgres==5.10.0apache-airflow-providers-slack==8.0.0
# Utilitiespython-dotenv==1.0.0pydantic==2.5.0tenacity==8.2.3
# Testingpytest==7.4.3pytest-cov==4.1.0
# Developmentblack==23.11.0flake8==6.1.0mypy==1.7.1README.md
Заголовок раздела «README.md»# Web Scraper and Sentiment Analysis
Автоматическая система сбора и анализа данных из новостных источников.
## Описание
Проект представляет собой систему для:- Сбор статей с Habr и Dzen- Анализа тональности текста- Сохранения результатов в базу данных- Автоматизации процессов через Airflow
## Установка
### Клонирование репозитория
```bashgit clone https://gitlab.com/username/web-scraper.gitcd web-scraperСоздание виртуального окружения
Заголовок раздела «Создание виртуального окружения»python -m venv venvsource venv/bin/activate # Linux/Mac# илиvenv\Scripts\activate # WindowsУстановка зависимостей
Заголовок раздела «Установка зависимостей»pip install -r requirements.txtНастройка переменных окружения
Заголовок раздела «Настройка переменных окружения»Создайте файл .env:
DATABASE_URL=sqlite:///data/scraper.dbLOG_LEVEL=INFOSENTRY_DSN=SLACK_WEBHOOK_URL=Использование
Заголовок раздела «Использование»Запуск скрапера
Заголовок раздела «Запуск скрапера»python scripts/run_scraper.pyЗапуск с Docker
Заголовок раздела «Запуск с Docker»docker-compose up -dДоступ к Airflow
Заголовок раздела «Доступ к Airflow»Web-интерфейс Airflow доступен по адресу: http://localhost:8080
Структура проекта
Заголовок раздела «Структура проекта»scrapers/— модуль скраперовdags/— DAG файлы для Airflowdata/— папка для данных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 = 100target-version = ['py310']
[tool.mypy]python_version = "3.10"warn_return_any = truewarn_unused_configs = trueНастройки проекта
Заголовок раздела «Настройки проекта»config/settings.py
Заголовок раздела «config/settings.py»from pydantic import BaseSettings, Fieldfrom 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()Скрипты
Заголовок раздела «Скрипты»scripts/setup.py
Заголовок раздела «scripts/setup.py»#!/usr/bin/env python3"""Скрипт установки проекта"""
import osimport subprocessimport 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 = """# DatabaseDATABASE_URL=sqlite:///data/scraper.db
# LoggingLOG_LEVEL=INFO
# ScrapingREQUEST_TIMEOUT=10DELAY_BETWEEN_REQUESTS=1.0
# NotificationsSLACK_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()scripts/run_scraper.py
Заголовок раздела «scripts/run_scraper.py»#!/usr/bin/env python3"""Скрипт запуска скрапера"""
import sysimport ossys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from scrapers.habr_scraper import HabrScraperfrom scrapers.dzen_scraper import DzenScraperfrom config.settings import settingsimport 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()tests/test_base_scraper.py
Заголовок раздела «tests/test_base_scraper.py»import pytestfrom scrapers.base_scraper import BaseScraperfrom 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()