├── LICENSE ├── README.md ├── sdamgia ├── __init__.py └── images.py └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 The Python Packaging Authority 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SdamGIA Api 2 | === 3 | **SdamGIA Api** – Python модуль для взаимодействия с образовательным порталом СДАМ ГИА 4 | 5 | ## Структура СдамГИА 6 | Чтобы было проще понять, как устроена база заданий СдамГИА, предлагаю воспользоваться следующей схемой: 7 | ``` 8 | СдамГИА 9 | └── Предмет (subject) 10 | ├── Каталог заданий (catalog) 11 | │ └── Задание (topic) 12 | │ └── Категория (category) 13 | │ └── Задача (problem) 14 | └── Тест (test) 15 | └── Задача (problem) 16 | ``` 17 | У каждой задачи, категории или теста есть свой идентификатор. 18 | Задания тоже имеют номера, которые в свою очередь могут иметь такие значения как "Д1" или "C4". Этим они отличаются от идентификаторов. 19 | 20 | ## Установка 21 | 22 | $ pip3 install sdamgia-api 23 | 24 | ### Установка зависимостей 25 | Для поиска задач по тексту на изображении необходимо установить pytesseract: 26 | 27 | $ pip3 install pytesseract 28 | 29 | А также [Tesseract-OCR](https://www.severcart.ru/blog/all/install_tesseract/) 30 | 31 | Обратите внимание, что для корректной работы нужен русский языковой пакет 32 | 33 | ## Использование 34 | 35 | ### Инициализация 36 | ```python 37 | from sdamgia import SdamGIA 38 | 39 | sdamgia = SdamGIA() 40 | ``` 41 | 42 | ### Поиск задачи по ее идентификатору 43 | ```python 44 | subject = 'math' 45 | id = '1001' 46 | sdamgia.get_problem_by_id(subject, id) 47 | ``` 48 | ```shell 49 | { 50 | 'id': '1001', 51 | 'topic': '4', 52 | 'condition': { 53 | 'text': 'На экзамен вынесено 60 вопросов, Андрей не выучил 3 из них. Найдите вероятность того, что ему попадется выученный вопрос.', 54 | 'images': [] 55 | }, 56 | 'solution': { 57 | 'text': 'Решение.Андрей выучил 60\xa0–\xa03\xa0=\xa057 вопросов. Поэтому вероятность того, что на экзамене ему попадется выученный вопрос равна\xa0Ответ: 0,95.', 58 | 'images': ['https://ege.sdamgia.ru/formula/svg/9f/9fbf55ab44a507fb47ba8a2666cd7644.svg'] 59 | }, 60 | 'answer': '0,95', 61 | 'analogs': ['1001', '1002', '1003', '1004', '1005', '1006', '1007', '1008', '1009', '1010'], 62 | 'url': 'https://math-ege.sdamgia.ru/problem?id=1001' 63 | } 64 | ``` 65 | Можно сгенерировать задачу в виде изображения: 66 | ```python 67 | path_to_img = '/imgs/problem.png' 68 | sdamgia.get_problem_by_id(subject, id, path_to_img=path_to_img) 69 | ``` 70 | 71 | 72 | ### Поиск задач по запросу 73 | ```python 74 | subject = 'math' 75 | request = 'Найдите количество' 76 | sdamgia.search(subject, request) 77 | ``` 78 | ```shell 79 | ['6407', '8795', '8799', '27501', '519508', '519534', '525371', '512436', '6401', '6421', '6427', '7321', '7325', '7801', '7803', '7807', '7809', '8037', '8039', '8045'] 80 | ``` 81 | 82 | ### Поиск теста по его идентификатору 83 | ```python 84 | subject = 'math' 85 | id = '1770' 86 | sdamgia.get_test_by_id(subject, id) 87 | # Возвращает список задач, входящих в тест 88 | ``` 89 | ```shell 90 | ['77345', '28765', '77374', '27903', '26675', '27700', '77411', '27506', '27132', '28008', '26703', '99592'] 91 | ``` 92 | 93 | ### Поиск категории по ее идентификатору 94 | ```python 95 | subject = 'math' 96 | id = '1' 97 | sdamgia.get_category_by_id(subject, id) 98 | # Возвращает список задач, входящих в категорию 99 | ``` 100 | ```shell 101 | ['77334', '323512', '501201', '509077', '509106'] 102 | ``` 103 | 104 | ### Получение каталога 105 | ```python 106 | subject = 'math' 107 | sdamgia.get_catalog(subject) 108 | ``` 109 | ```shell 110 | [ 111 | { 112 | 'topic_id': '1', 113 | 'topic_name': 'Простейшие текстовые задачи', 114 | 'categories': [ 115 | {'category_id': '174', 'category_name': 'Вычисления'}, 116 | {'category_id': '1', 'category_name': 'Округление с недостатком'}, 117 | {'category_id': '2', 'category_name': 'Округление с избытком'}, 118 | {'category_id': '249', 'category_name': 'Проценты'}, 119 | {'category_id': '5', 'category_name': 'Проценты и округление'} 120 | ] 121 | }, 122 | { 123 | ... 124 | } 125 | ] 126 | ``` 127 | 128 | ### Генерация теста 129 | По умолчанию генерируется тест, включающий по одной задаче из каждого задания предмета.
130 | Так же можно вручную указать одинаковое количество задач для каждого из заданий: {'full': <кол-во задач>}
131 | Указать определенные задания с определенным количеством задач для каждого: {<номер задания>: <кол-во задач>, ... } 132 | ```python 133 | subject = 'math' 134 | problems = {1: 1, 2: 2, 3: 4} 135 | sdamgia.generate_test(subject, problems) 136 | # Возвращает идентификатор сгенерированного теста 137 | ``` 138 | ```shell 139 | 38299510 140 | ``` 141 | Обратите внимание, что в этом случае идентификатор задания - только науральное число. Т.е. если после задания 15 идет задание Д1, оно должно будет записываться как 16 задание. 142 | 143 | ### Генерация pdf-версии теста 144 | ```python 145 | sdamgia.generate_pdf('math', '38299510', pdf='h') 146 | ``` 147 | ```shell 148 | https://math-ege.sdamgia.ru/pdf/1fe7d7d8408f8d5195fabfd8ab393d63.pdf 149 | ``` 150 | Список параметров: 151 | ``` 152 | subject: Наименование предмета 153 | testid: Идентифигатор теста 154 | solution: Пояснение 155 | nums: № заданий 156 | answers: Ответы 157 | key: Ключ 158 | crit: Критерии 159 | instruction: Инструкция 160 | col: Нижний колонтитул 161 | pdf: Версия генерируемого pdf документа 162 | По умолчанию генерируется стандартная вертикальная версия 163 | h - горизонтальная версия 164 | z - версия с крупным шрифтом 165 | m - версия с большим полем 166 | ``` 167 | 168 | ### Поиск задач по изображению beta 169 | 170 | С помощью sdamgia-api вы можете искать задачи по тексту на изображении. Например, на фотографии распечатки. 171 | 172 | Для начала, необходимо указать путь к исполняемому файлу Tesseract-OCR: 173 | ```python 174 | sdamgia.tesseract_src = "C:/Program Files/Tesseract-OCR/tesseract.exe" 175 | ``` 176 | Теперь мы можем запустить поиск: 177 | ```python 178 | sdamgia.search_by_img('rus', 'Image.jpg') 179 | # Возвращает список найденных задач 180 | ``` 181 | ```shell 182 | ['12629', '14062', '2846', '2836', '2837', '2838', '2839', '2845', '2847', '7776', '10242', '874', '864', '865', '866', '867', '873', '2359', '456', '446', '447', '448', '449', '455', '2348', '7815', '691', '863', '14426', '7867', '1262', '1889', '6716', '6706', '6707', '6708', '6709', '6715', '6717', '8899', '8895', '8896', '8897', '8898', '8900', '4194', '4184', '4185', '4186', '4187', '4193', '4195', '30', '28', '29', '31', '37', '38', '2337', '676', '674', '675', '677', '683', '684', '2168', '1094', '1092', '1093', '1095', '1101', '1102', '2365', '6893', '6891', '6892', '6894', '6900', '6901', '6902', '599', '598', '600', '601', '607', '608', '2352', '1710', '1700', '1701', '1702', '1703', '1709', '2381', '3600', '3599', '3601', '3602', '3608', '3609', '3610', '8327', '8323', '8324', '8325', '8326', '8328', '950', '940', '941', '942', '943', '949', '2361', '11087', '11065', '1304', '1299', '1342', '1337', '1474', '1472', '1473', '1475', '1481', '1482', '2375', '105', '104', '106', '107', '113', '114', '2339', '181', '180', '182', '183', '189', '190', '2341', '257', '256', '258', '259', '265', '266', '1321', '2343', '333', '332', '334', '335', '341', '342', '2345', '380', '370', '371', '372', '373', '379', '2346', '532', '522', '523', '524', '525', '531', '2350', '656', '652', '759', '750', '751', '752', '753', '760', '2356', '789', '788', '790', '791', '797', '798', '2357', '844', '842', '988', '978', '979', '980', '981', '987', '2362', '997', '995', '1026', '1016', '1017', '1018', '1019', '1025', '2363', '1254', '1244', '1245', '1246', '1247', '1253', '2369', '1292', '1282', '1283', '1284', '1285', '1291', '2370', '7568'] 183 | ``` 184 | Поиск может занять продолжительное время в зависимости от объема текста и количества найденных задач 185 | -------------------------------------------------------------------------------- /sdamgia/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os.path 3 | 4 | from bs4 import BeautifulSoup 5 | import requests 6 | import threading 7 | from os import path, remove 8 | 9 | 10 | class SdamGIA: 11 | def __init__(self): 12 | self._BASE_DOMAIN = 'sdamgia.ru' 13 | self._SUBJECT_BASE_URL = { 14 | 'math': f'https://math-ege.{self._BASE_DOMAIN}', 'mathb': f'https://mathb-ege.{self._BASE_DOMAIN}', 15 | 'phys': f'https://phys-ege.{self._BASE_DOMAIN}', 16 | 'inf': f'https://inf-ege.{self._BASE_DOMAIN}', 17 | 'rus': f'https://rus-ege.{self._BASE_DOMAIN}', 18 | 'bio': f'https://bio-ege.{self._BASE_DOMAIN}', 19 | 'en': f'https://en-ege.{self._BASE_DOMAIN}', 20 | 'chem': f'https://chem-ege.{self._BASE_DOMAIN}', 21 | 'geo': f'https://geo-ege.{self._BASE_DOMAIN}', 22 | 'soc': f'https://soc-ege.{self._BASE_DOMAIN}', 23 | 'de': f'https://de-ege.{self._BASE_DOMAIN}', 24 | 'fr': f'https://fr-ege.{self._BASE_DOMAIN}', 25 | 'lit': f'https://lit-ege.{self._BASE_DOMAIN}', 26 | 'sp': f'https://sp-ege.{self._BASE_DOMAIN}', 27 | 'hist': f'https://hist-ege.{self._BASE_DOMAIN}', 28 | } 29 | self.tesseract_src = 'tesseract' 30 | self.html2img_chrome_path = 'chrome' 31 | self.grabzit_auth = {'AppKey': 'grabzit', 'AppSecret': 'grabzit'} 32 | 33 | def get_problem_by_id(self, 34 | subject, id, 35 | img=None, path_to_img=None, path_to_tmp_html=''): 36 | """ 37 | Получение информации о задаче по ее идентификатору 38 | 39 | :param subject: Наименование предмета 40 | :type subject: str 41 | 42 | :param id: Идентификатор задачи 43 | :type subject: str 44 | 45 | :param img: Принимает одно из двух значений: pyppeteer или grabzit; 46 | В результате будет использована одна из библиотек для генерации изображения с задачей. 47 | Если не передавать этот аргумент, изображение генерироваться не будет 48 | :type img: str 49 | 50 | :param path_to_img: Путь до изображения, куда сохранить сохранить задание. 51 | :type path_to_img: str 52 | 53 | :param path_to_html: Можно указать директорию, куда будут сохраняться временные html-файлы заданий при использовании pyppeteer 54 | :type path_to_html: str 55 | 56 | :param grabzit_auth: При использовании GrabzIT укажите данные для аутентификации: {"AppKey":"...", "AppSecret":"..."} 57 | :type grabzit_auth: dict 58 | """ 59 | 60 | doujin_page = requests.get( 61 | f'{self._SUBJECT_BASE_URL[subject]}/problem?id={id}') 62 | soup = BeautifulSoup(doujin_page.content, 'html.parser') 63 | 64 | probBlock = soup.find('div', {'class': 'prob_maindiv'}) 65 | if probBlock is None: 66 | return None 67 | 68 | for i in probBlock.find_all('img'): 69 | if not 'sdamgia.ru' in i['src']: 70 | i['src'] = self._SUBJECT_BASE_URL[subject] + i['src'] 71 | 72 | URL = f'{self._SUBJECT_BASE_URL[subject]}/problem?id={id}' 73 | 74 | TOPIC_ID = ' '.join(probBlock.find( 75 | 'span', {'class': 'prob_nums'}).text.split()[1:][:-2]) 76 | ID = id 77 | 78 | CONDITION, SOLUTION, ANSWER, ANALOGS = {}, {}, '', [] 79 | 80 | try: 81 | CONDITION = {'text': probBlock.find_all('div', {'class': 'pbody'})[0].text, 82 | 'images': [i['src'] for i in probBlock.find_all('div', {'class': 'pbody'})[0].find_all('img')] 83 | } 84 | except IndexError: 85 | pass 86 | 87 | try: 88 | SOLUTION = {'text': probBlock.find_all('div', {'class': 'pbody'})[1].text, 89 | 'images': [i['src'] for i in probBlock.find_all('div', {'class': 'pbody'})[1].find_all('img')] 90 | } 91 | except IndexError: 92 | pass 93 | except AttributeError: 94 | pass 95 | 96 | try: 97 | ANSWER = probBlock.find( 98 | 'div', {'class': 'answer'}).text.replace('Ответ: ', '') 99 | except IndexError: 100 | pass 101 | except AttributeError: 102 | pass 103 | 104 | try: 105 | ANALOGS = [i.text for i in probBlock.find( 106 | 'div', {'class': 'minor'}).find_all('a')] 107 | if 'Все' in ANALOGS: 108 | ANALOGS.remove('Все') 109 | except IndexError: 110 | pass 111 | except AttributeError: 112 | pass 113 | 114 | if not img is None: 115 | 116 | for i in probBlock.find_all('div', {'class': 'minor'}): # delete the information parts of problem 117 | i.decompose() 118 | probBlock.find_all('div')[-1].decompose() 119 | 120 | # Pyppeteer 121 | if img == 'pyppeteer': 122 | import asyncio 123 | from pyppeteer import launch 124 | open(f'{path_to_tmp_html}{id}.html', 'w', encoding='utf-8').write(str(probBlock)) 125 | async def main(): 126 | browser = await launch() 127 | page = await browser.newPage() 128 | await page.goto('file:' + path.abspath(f'{path_to_tmp_html}{id}.html')) 129 | await page.screenshot({'path': path_to_img, 'fullPage': 'true'}) 130 | await browser.close() 131 | asyncio.get_event_loop().run_until_complete(main()) 132 | remove(path.abspath(f'{path_to_tmp_html}{id}.html')) 133 | 134 | # Grabz.it 135 | elif img == 'grabzit': 136 | from GrabzIt import GrabzItClient, GrabzItImageOptions 137 | grabzIt = GrabzItClient.GrabzItClient(self.grabzit_auth['AppKey'], self.grabzit_auth['AppSecret']) 138 | options = GrabzItImageOptions.GrabzItImageOptions() 139 | options.browserWidth = 800 140 | options.browserHeight = -1 141 | grabzIt.HTMLToImage(str(probBlock), options=options) 142 | grabzIt.SaveTo(path_to_img) 143 | 144 | # HTML2Image 145 | elif img == 'html2img': 146 | from html2image import Html2Image 147 | if self.html2img_chrome_path == 'chrome': hti = Html2Image() 148 | else: hti = Html2Image(chrome_path=self.html2img_chrome_path, custom_flags=['--no-sandbox']) 149 | hti.screenshot(html_str=str(probBlock), save_as=path_to_img) 150 | 151 | return {'id': ID, 'topic': TOPIC_ID, 'condition': CONDITION, 'solution': SOLUTION, 'answer': ANSWER, 152 | 'analogs': ANALOGS, 'url': URL} 153 | 154 | def search(self, subject, request, page=1): 155 | """ 156 | Поиск задач по запросу 157 | 158 | :param subject: Наименование предмета 159 | :type subject: str 160 | 161 | :param request: Запрос 162 | :type request: str 163 | 164 | :param page: Номер страницы поиска 165 | :type page: int 166 | """ 167 | doujin_page = requests.get( 168 | f'{self._SUBJECT_BASE_URL[subject]}/search?search={request}&page={str(page)}') 169 | soup = BeautifulSoup(doujin_page.content, 'html.parser') 170 | return [i.text.split()[-1] for i in soup.find_all('span', {'class': 'prob_nums'})] 171 | 172 | def get_test_by_id(self, subject, testid): 173 | """ 174 | Получение списка задач, включенных в тест 175 | 176 | :param subject: Наименование предмета 177 | :type subject: str 178 | 179 | :param testid: Идентификатор теста 180 | :type testid: str 181 | """ 182 | doujin_page = requests.get( 183 | f'{self._SUBJECT_BASE_URL[subject]}/test?id={testid}') 184 | soup = BeautifulSoup(doujin_page.content, 'html.parser') 185 | return [i.text.split()[-1] for i in soup.find_all('span', {'class': 'prob_nums'})] 186 | 187 | def get_category_by_id(self, subject, categoryid, page=1): 188 | """ 189 | Получение списка задач, включенных в категорию 190 | 191 | :param subject: Наименование предмета 192 | :type subject: str 193 | 194 | :param categoryid: Идентификатор категории 195 | :type categoryid: str 196 | 197 | :param page: Номер страницы поиска 198 | :type page: int 199 | """ 200 | 201 | doujin_page = requests.get( 202 | f'{self._SUBJECT_BASE_URL[subject]}/test?&filter=all&theme={categoryid}&page={page}') 203 | soup = BeautifulSoup(doujin_page.content, 'html.parser') 204 | return [i.text.split()[-1] for i in soup.find_all('span', {'class': 'prob_nums'})] 205 | 206 | def get_catalog(self, subject): 207 | """ 208 | Получение каталога заданий для определенного предмета 209 | 210 | :param subject: Наименование предмета 211 | :type subject: str 212 | """ 213 | 214 | doujin_page = requests.get( 215 | f'{self._SUBJECT_BASE_URL[subject]}/prob_catalog') 216 | soup = BeautifulSoup(doujin_page.content, 'html.parser') 217 | catalog = [] 218 | CATALOG = [] 219 | 220 | for i in soup.find_all('div', {'class': 'cat_category'}): 221 | try: 222 | i['data-id'] 223 | except: 224 | catalog.append(i) 225 | 226 | for topic in catalog[1:]: 227 | TOPIC_NAME = topic.find( 228 | 'b', {'class': 'cat_name'}).text.split('. ')[1] 229 | TOPIC_ID = topic.find( 230 | 'b', {'class': 'cat_name'}).text.split('. ')[0] 231 | if TOPIC_ID[0] == ' ': 232 | TOPIC_ID = TOPIC_ID[2:] 233 | if TOPIC_ID.find('Задания ') == 0: 234 | TOPIC_ID = TOPIC_ID.replace('Задания ', '') 235 | 236 | CATALOG.append( 237 | dict( 238 | topic_id=TOPIC_ID, 239 | topic_name=TOPIC_NAME, 240 | categories=[ 241 | dict( 242 | category_id=i['data-id'], 243 | category_name=i.find( 244 | 'a', {'class': 'cat_name'}).text 245 | ) 246 | for i in 247 | topic.find('div', {'class': 'cat_children'}).find_all('div', {'class': 'cat_category'})] 248 | ) 249 | ) 250 | 251 | return CATALOG 252 | 253 | def generate_test(self, subject, problems=None): 254 | """ 255 | Генерирует тест по заданным параметрам 256 | 257 | :param subject: Наименование предмета 258 | :type subject: str 259 | 260 | :param problems: Список заданий 261 | По умолчанию генерируется тест, включающий по одной задаче из каждого задания предмета. 262 | Так же можно вручную указать одинаковое количество задач для каждого из заданий: {'full': <кол-во задач>} 263 | Указать определенные задания с определенным количеством задач для каждого: {<номер задания>: <кол-во задач>, ... } 264 | :type problems: dict 265 | """ 266 | 267 | if problems is None: 268 | problems = {'full': 1} 269 | 270 | if 'full' in problems: 271 | dif = {f'prob{i}': problems['full'] for i in range( 272 | 1, len(self.get_catalog(subject)) + 1)} 273 | else: 274 | dif = {f'prob{i}': problems[i] for i in problems} 275 | 276 | return requests.get(f'{self._SUBJECT_BASE_URL[subject]}/test?a=generate', dif, 277 | allow_redirects=False).headers['location'].split('id=')[1].split('&nt')[0] 278 | 279 | def generate_pdf(self, subject, testid, solution='', nums='', 280 | answers='', key='', crit='', 281 | instruction='', col='', pdf=True): 282 | """ 283 | Генерирует pdf версию теста 284 | 285 | :param subject: Наименование предмета 286 | :type subject: str 287 | 288 | :param testid: Идентифигатор теста 289 | :type testid: str 290 | 291 | :param solution: Пояснение 292 | :type solution: bool 293 | 294 | :param nums: № заданий 295 | :type nums: bool 296 | 297 | :param answers: Ответы 298 | :type answers: bool 299 | 300 | :param key: Ключ 301 | :type key: bool 302 | 303 | :param crit: Критерии 304 | :type crit: bool 305 | 306 | :param instruction: Инструкция 307 | :type instruction: bool 308 | 309 | :param col: Нижний колонтитул 310 | :type col: str 311 | 312 | :param pdf: Версия генерируемого pdf документа 313 | По умолчанию генерируется стандартная вертикальная версия 314 | h - горизонтальная версия 315 | z - версия с крупным шрифтом 316 | m - версия с большим полем 317 | :type pdf: str 318 | 319 | """ 320 | 321 | def a(a): 322 | if a == False: 323 | return '' 324 | return a 325 | 326 | return self._SUBJECT_BASE_URL[subject] + requests.get(f'{self._SUBJECT_BASE_URL[subject]}/test?' 327 | f'id={testid}&print=true&pdf={pdf}&sol={a(solution)}&num={a(nums)}&ans={a(answers)}' 328 | f'&key={a(key)}&crit={a(crit)}&pre={a(instruction)}&dcol={a(col)}', 329 | allow_redirects=False).headers['location'] 330 | 331 | def search_by_img(self, subject, path): 332 | """ 333 | Поиск задач по тексту на изображении 334 | 335 | :param subject: 336 | :param path: Путь до изображения 337 | :type path: str 338 | """ 339 | 340 | from sdamgia import images 341 | 342 | result = [] 343 | words_from_img = images.img_to_str(path, self.tesseract_src).split() 344 | 345 | def parse(i): 346 | try: 347 | request_phrase = ' '.join( 348 | [words_from_img[x] for x in range(i, i + 10)]) 349 | 350 | doujin_page = requests.get( 351 | f'{self._SUBJECT_BASE_URL[subject]}/search?search={request_phrase}&page={str(1)}') 352 | soup = BeautifulSoup(doujin_page.content, 'html.parser') 353 | problem_ids = [i.text.split()[-1] 354 | for i in soup.find_all('span', {'class': 'prob_nums'})] 355 | 356 | for id in problem_ids: 357 | if id not in result: 358 | result.append(id) 359 | except Exception as E: 360 | pass 361 | 362 | thread_pool = [] 363 | 364 | for i in range(0, len(words_from_img)): 365 | thread = threading.Thread(target=parse, args=(i,)) 366 | thread_pool.append(thread) 367 | thread.start() 368 | 369 | for thread in thread_pool: 370 | thread.join() 371 | 372 | return result 373 | 374 | 375 | if __name__ == '__main__': 376 | sdamgia = SdamGIA() 377 | test = sdamgia.get_problem_by_id('math', '1001') 378 | print(test) 379 | -------------------------------------------------------------------------------- /sdamgia/images.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PIL import Image 3 | except ImportError: 4 | import Image 5 | import pytesseract 6 | 7 | 8 | 9 | def img_to_str(src, path_to_tesseract): 10 | pytesseract.pytesseract.tesseract_cmd = path_to_tesseract 11 | return pytesseract.image_to_string(Image.open(src), lang='rus+eng') 12 | 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | name='sdamgia-api', 7 | version='0.1.8', 8 | author='anijack', 9 | author_email='anijackich@gmail.com', 10 | description='Python модуль для взаимодействия с образовательным порталом СДАМ ГИА', 11 | long_description=open('README.md', encoding="utf8").read(), 12 | long_description_content_type='text/markdown', 13 | url='https://github.com/anijackich/sdamgia-api', 14 | license='MIT', 15 | install_requires=['requests', 'beautifulsoup4', 'pyppeteer', 'grabzit', 'html2image'], 16 | packages = ['sdamgia'], 17 | classifiers=[ 18 | 'License :: OSI Approved :: MIT License', 19 | 'Operating System :: OS Independent', 20 | 'Programming Language :: Python :: 3' 21 | ] 22 | ) 23 | --------------------------------------------------------------------------------