test1 / scraper /html_scraper.py
vydrking's picture
Upload 19 files
53fe915 verified
import requests
import hashlib
import json
import os
from typing import List, Dict
from bs4 import BeautifulSoup
import re
class HTMLScraper:
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
})
self.program_urls = {
'ai': 'https://abit.itmo.ru/program/master/ai',
'ai_product': 'https://abit.itmo.ru/program/master/ai_product'
}
def scrape_programs(self) -> Dict:
programs = {}
for program_id, url in self.program_urls.items():
try:
print(f'Скрапинг программы: {program_id}')
program_data = self._scrape_program_page(url, program_id)
if program_data:
programs[program_id] = program_data
print(f'Успешно обработана программа: {program_data["title"]}')
except Exception as e:
print(f'Ошибка при скрапинге {program_id}: {e}')
return programs
def _scrape_program_page(self, url: str, program_id: str) -> Dict:
response = self.session.get(url, timeout=30)
response.raise_for_status()
soup = BeautifulSoup(response.content, 'html.parser')
# Извлечение заголовка
title = self._extract_title(soup)
# Извлечение описания
description = self._extract_description(soup)
# Поиск ссылок на PDF учебные планы
pdf_links = self._extract_pdf_links(soup, url)
# Создание хэша контента для отслеживания изменений
content_hash = self._calculate_content_hash(response.content)
return {
'id': program_id,
'title': title,
'description': description,
'url': url,
'pdf_links': pdf_links,
'content_hash': content_hash,
'last_updated': response.headers.get('last-modified', '')
}
def _extract_title(self, soup: BeautifulSoup) -> str:
# Поиск заголовка программы
title_selectors = [
'h1',
'.program-title',
'.title',
'[class*="title"]',
'[class*="header"]'
]
for selector in title_selectors:
title_elem = soup.select_one(selector)
if title_elem and title_elem.get_text().strip():
title = title_elem.get_text().strip()
if len(title) > 5: # Минимальная длина заголовка
return title
# Fallback - поиск по ключевым словам
for elem in soup.find_all(['h1', 'h2', 'h3']):
text = elem.get_text().strip()
if any(keyword in text.lower() for keyword in ['искусственный интеллект', 'ai', 'продукт']):
return text
return f'Программа {program_id.upper()}'
def _extract_description(self, soup: BeautifulSoup) -> str:
# Поиск описания программы
desc_selectors = [
'.program-description',
'.description',
'.about',
'[class*="description"]',
'[class*="about"]',
'p'
]
for selector in desc_selectors:
desc_elem = soup.select_one(selector)
if desc_elem:
desc = desc_elem.get_text().strip()
if len(desc) > 50: # Минимальная длина описания
return desc[:500] + '...' if len(desc) > 500 else desc
# Fallback - поиск по ключевым словам
for elem in soup.find_all('p'):
text = elem.get_text().strip()
if any(keyword in text.lower() for keyword in ['магистратура', 'программа', 'обучение', 'подготовка']):
if len(text) > 30:
return text[:500] + '...' if len(text) > 500 else text
return 'Описание программы магистратуры ITMO'
def _extract_pdf_links(self, soup: BeautifulSoup, base_url: str) -> List[Dict]:
pdf_links = []
# Поиск всех ссылок на PDF
for link in soup.find_all('a', href=True):
href = link['href']
text = link.get_text().strip().lower()
# Проверка на PDF файлы
if href.endswith('.pdf') or 'pdf' in href:
# Определение типа документа по тексту ссылки
doc_type = self._determine_document_type(text)
# Получение полного URL
if href.startswith('http'):
full_url = href
else:
full_url = self._make_absolute_url(href, base_url)
# Генерация имени файла
filename = self._generate_filename(href, doc_type)
pdf_links.append({
'url': full_url,
'filename': filename,
'type': doc_type,
'text': text
})
# Поиск по ключевым словам в тексте
if not pdf_links:
pdf_links = self._search_pdf_by_keywords(soup, base_url)
return pdf_links
def _determine_document_type(self, text: str) -> str:
text_lower = text.lower()
if any(word in text_lower for word in ['учебный план', 'curriculum', 'plan']):
return 'curriculum'
elif any(word in text_lower for word in ['программа', 'program']):
return 'program'
elif any(word in text_lower for word in ['описание', 'description']):
return 'description'
else:
return 'document'
def _make_absolute_url(self, href: str, base_url: str) -> str:
if href.startswith('//'):
return 'https:' + href
elif href.startswith('/'):
# Извлекаем домен из base_url
from urllib.parse import urlparse
parsed = urlparse(base_url)
return f"{parsed.scheme}://{parsed.netloc}{href}"
else:
return base_url.rstrip('/') + '/' + href.lstrip('/')
def _generate_filename(self, href: str, doc_type: str) -> str:
# Извлекаем имя файла из URL
filename = href.split('/')[-1]
if not filename.endswith('.pdf'):
filename += '.pdf'
# Добавляем префикс типа документа
return f"{doc_type}_{filename}"
def _search_pdf_by_keywords(self, soup: BeautifulSoup, base_url: str) -> List[Dict]:
pdf_links = []
# Ключевые слова для поиска учебных планов
keywords = [
'учебный план',
'curriculum',
'программа обучения',
'образовательная программа'
]
# Поиск по тексту страницы
page_text = soup.get_text().lower()
for keyword in keywords:
if keyword in page_text:
# Попытка найти ссылку рядом с ключевым словом
for elem in soup.find_all(['a', 'p', 'div']):
text = elem.get_text().lower()
if keyword in text:
# Ищем ссылки в этом элементе или рядом
links = elem.find_all('a', href=True)
for link in links:
href = link['href']
if href.endswith('.pdf') or 'pdf' in href:
full_url = self._make_absolute_url(href, base_url)
pdf_links.append({
'url': full_url,
'filename': f"curriculum_{href.split('/')[-1]}",
'type': 'curriculum',
'text': link.get_text().strip()
})
return pdf_links
def _calculate_content_hash(self, content: bytes) -> str:
return hashlib.sha256(content).hexdigest()
def save_programs(self, programs: Dict):
os.makedirs('data/processed', exist_ok=True)
with open('data/processed/programs.json', 'w', encoding='utf-8') as f:
json.dump(programs, f, ensure_ascii=False, indent=2)
print(f'Программы сохранены: {len(programs)} программ')
def check_updates(self, programs: Dict) -> Dict:
updates = {}
for program_id, program in programs.items():
try:
response = self.session.get(program['url'], timeout=30)
current_hash = self._calculate_content_hash(response.content)
if current_hash != program.get('content_hash'):
updates[program_id] = {
'old_hash': program.get('content_hash'),
'new_hash': current_hash,
'last_modified': response.headers.get('last-modified', '')
}
print(f'Обнаружены изменения в программе: {program_id}')
except Exception as e:
print(f'Ошибка проверки обновлений для {program_id}: {e}')
return updates
def main():
scraper = HTMLScraper()
programs = scraper.scrape_programs()
scraper.save_programs(programs)
print(f'Обработано программ: {len(programs)}')
for program_id, program in programs.items():
print(f'{program_id}: {program["title"]} - {len(program["pdf_links"])} PDF')
if __name__ == '__main__':
main()