greeta commited on
Commit
87112c1
·
verified ·
1 Parent(s): 3b50f03

Delete scraper.py

Browse files
Files changed (1) hide show
  1. scraper.py +0 -218
scraper.py DELETED
@@ -1,218 +0,0 @@
1
- """
2
- Скрапер для сайта ФИПИ (fipi.ru)
3
- Извлекает задания по русскому языку для ЕГЭ (задание 27)
4
- """
5
-
6
- import httpx
7
- from bs4 import BeautifulSoup
8
- from typing import List, Dict, Optional
9
- from datetime import datetime
10
- import re
11
- import logging
12
-
13
- logging.basicConfig(level=logging.INFO)
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- class FIPIScraper:
18
- """Парсер для сайта ФИПИ"""
19
-
20
- def __init__(self, base_url: str = "https://fipi.ru"):
21
- self.base_url = base_url
22
- self.headers = {
23
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
24
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
25
- "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
26
- }
27
-
28
- async def fetch_page(self, url: str) -> Optional[str]:
29
- """Получение HTML страницы"""
30
- # Создаем клиент с отключенной проверкой SSL (для fipi.ru поддоменов)
31
- import ssl
32
- ssl_context = ssl.create_default_context()
33
- ssl_context.check_hostname = False
34
- ssl_context.verify_mode = ssl.CERT_NONE
35
-
36
- async with httpx.AsyncClient(
37
- headers=self.headers,
38
- timeout=30.0,
39
- verify=ssl_context
40
- ) as client:
41
- try:
42
- response = await client.get(url)
43
- response.raise_for_status()
44
- return response.text
45
- except httpx.HTTPError as e:
46
- logger.error(f"Ошибка при получении {url}: {e}")
47
- return None
48
-
49
- def parse_task_page(self, html: str, url: str) -> Optional[Dict]:
50
- """Парсинг страницы с заданием"""
51
- soup = BeautifulSoup(html, 'lxml')
52
-
53
- # Извлечение заголовка - приоритет h1 в .content
54
- title_tag = soup.select_one('.content h1') or soup.find('h1')
55
- title = title_tag.get_text(strip=True) if title_tag else "Без названия"
56
-
57
- # Если заголовок пустой, пробуем извлечь из title документа
58
- if not title or title == "Без названия":
59
- title_doc = soup.find('title')
60
- if title_doc:
61
- title = title_doc.get_text(strip=True)
62
-
63
- # Извлечение основного контента - приоритет .content
64
- content_div = soup.select_one('.content') or soup.find('div', class_='field--name-body')
65
- if not content_div:
66
- content_div = soup.find('main') or soup.find('body')
67
-
68
- # Очистка текста - удаляем скрипты и стили
69
- for element in content_div.find_all(['script', 'style', 'nav', 'header', 'footer']):
70
- element.decompose()
71
-
72
- content = content_div.get_text(separator='\n', strip=True) if content_div else ""
73
-
74
- # Извлечение изображения (если есть)
75
- images = []
76
- for img in content_div.find_all('img'):
77
- src = img.get('src') or img.get('data-src')
78
- if src:
79
- if not src.startswith('http'):
80
- src = self.base_url + src
81
- images.append(src)
82
-
83
- # Извлечение ссылок на задания
84
- task_links = []
85
- for link in content_div.find_all('a', href=True):
86
- href = link['href']
87
- link_text = link.get_text(strip=True)
88
- if any(pattern in href for pattern in ['/ege/', '/oge/', '/task/', '/demo/', '/bank/']):
89
- if not href.startswith('http'):
90
- href = self.base_url + href
91
- task_links.append({"text": link_text, "url": href})
92
-
93
- # Определение типа задания
94
- task_type = self._detect_task_type(title, content)
95
-
96
- # Извлечение вариантов (если есть)
97
- variants = self._extract_variants(content)
98
-
99
- return {
100
- "title": title,
101
- "content": content,
102
- "source_url": url,
103
- "task_type": task_type,
104
- "images": images,
105
- "variants": variants,
106
- "task_links": task_links,
107
- "scraped_at": datetime.utcnow().isoformat(),
108
- }
109
-
110
- def _detect_task_type(self, title: str, content: str) -> str:
111
- """Определение типа задания"""
112
- text = (title + " " + content).lower()
113
-
114
- if any(word in text for word in ["сочинение", "эссе", "напишит"]):
115
- return "writing"
116
- elif any(word in text for word in ["тест", "выбер", "вариант"]):
117
- return "test"
118
- elif any(word in text for word in ["ауди", "слуш"]):
119
- return "listening"
120
- elif any(word in text for word in ["чит", "текст"]):
121
- return "reading"
122
- else:
123
- return "other"
124
-
125
- def _extract_variants(self, content: str) -> List[str]:
126
- """Извлечение вариантов ответов"""
127
- variants = []
128
-
129
- # Паттерн для вариантов типа "1) ... 2) ..."
130
- pattern = r'(\d+)[\.\)]\s*([^\n\d]+)'
131
- matches = re.findall(pattern, content)
132
-
133
- for _, variant in matches:
134
- variants.append(variant.strip())
135
-
136
- return variants[:10] # Ограничение на 10 вариантов
137
-
138
- async def scrape_tasks(self, subject: str = "russian") -> List[Dict]:
139
- """
140
- Скрапинг заданий по предмету
141
-
142
- Args:
143
- subject: Код предмета (по умолчанию russian)
144
-
145
- Returns:
146
- Список заданий
147
- """
148
- tasks = []
149
-
150
- # Актуальные URLs для скрапинга (fipi.ru) - только работающие
151
- urls_to_scrape = [
152
- f"{self.base_url}/ege/otkrytyy-bank-zadaniy-ege",
153
- f"{self.base_url}/oge/otkrytyy-bank-zadaniy-oge",
154
- ]
155
-
156
- for url in urls_to_scrape:
157
- logger.info(f"Скрапинг {url}")
158
- html = await self.fetch_page(url)
159
-
160
- if html:
161
- task = self.parse_task_page(html, url)
162
- if task:
163
- tasks.append(task)
164
-
165
- # Если есть ссылки на задания, скачиваем их
166
- for link_info in task.get('task_links', [])[:5]: # Ограничиваем количество
167
- link_url = link_info.get('url')
168
- if link_url:
169
- logger.info(f" -> Скачиваем задание: {link_url}")
170
- link_html = await self.fetch_page(link_url)
171
- if link_html:
172
- subtask = self.parse_task_page(link_html, link_url)
173
- if subtask:
174
- tasks.append(subtask)
175
-
176
- logger.info(f"Найдено {len(tasks)} заданий")
177
- return tasks
178
-
179
- async def scrape_task_by_id(self, task_id: str) -> Optional[Dict]:
180
- """Скрапинг конкретного задания по ID"""
181
- url = f"{self.base_url}/task/{task_id}"
182
- logger.info(f"Скрапинг задания {task_id}")
183
-
184
- html = await self.fetch_page(url)
185
- if html:
186
- return self.parse_task_page(html, url)
187
-
188
- return None
189
-
190
- async def search_tasks(self, query: str) -> List[Dict]:
191
- """Поиск заданий по ключевому слову"""
192
- tasks = []
193
- # Используем правильный URL для поиска на fipi.ru
194
- search_url = f"{self.base_url}/search?q={query}"
195
-
196
- html = await self.fetch_page(search_url)
197
- if not html:
198
- # Пробуем альтернативный поиск через банк заданий
199
- logger.info("Поиск не доступен, пробуем парсинг банка заданий")
200
- return await self.scrape_tasks()
201
-
202
- soup = BeautifulSoup(html, 'lxml')
203
-
204
- # Поиск ссылок на задания с правильными паттернами
205
- for link in soup.find_all('a', href=True):
206
- href = link['href']
207
- # Проверяем на наличие валидных URL заданий
208
- if any(pattern in href for pattern in ['/ege/', '/oge/', '/task/', '/demo/', '/bank/']):
209
- if not href.startswith('http'):
210
- href = self.base_url + href
211
-
212
- task_html = await self.fetch_page(href)
213
- if task_html:
214
- task = self.parse_task_page(task_html, href)
215
- if task:
216
- tasks.append(task)
217
-
218
- return tasks