├── 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 |
--------------------------------------------------------------------------------