├── .gitignore ├── article.pdf ├── progress_bar ├── __pycache__ │ └── __init__.cpython-35.pyc └── __init__.py ├── Readme.md └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /article.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maks-sh/stbd-collector/master/article.pdf -------------------------------------------------------------------------------- /progress_bar/__pycache__/__init__.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maks-sh/stbd-collector/master/progress_bar/__pycache__/__init__.cpython-35.pyc -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Репозиторий для курса "Специальные технологии баз данных и экспертных систем" 2 | ========================= 3 | Используемая версия Python: 3.5.2 4 | 5 | Назначение программы 6 | ---------------------------- 7 | Программа предназначена для автоматического сбора информации для проведения дальнейшего ее анализа с целью выявления банков, у которых в будущем времени отзовут лицензию. 8 | 9 | С открытых источников и собирается информация о показателях банка, которые упоминаются в [статье Евстефеевой, Крылова и Рябкова](./article.pdf). 10 | 11 | Так как в отрытых источниках не было найдено ни одного сайта, где можно было бы экспортировать в удобном формате информацию об отозванных банков, был написан метод parser() класса Table, который парсит эти данные [отсюда](http://www.banki.ru/banks/memory/). -------------------------------------------------------------------------------- /progress_bar/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def print_progress(iteration, total, prefix='', suffix='', decimals=1, barLength=100): 5 | """ 6 | Call in a loop to create terminal progress bar 7 | @params: 8 | iteration - Required : current iteration (Int) 9 | total - Required : total iterations (Int) 10 | prefix - Optional : prefix string (Str) 11 | suffix - Optional : suffix string (Str) 12 | decimals - Optional : positive number of decimals in percent complete (Int) 13 | barLength - Optional : character length of bar (Int) 14 | """ 15 | formatStr = "{0:." + str(decimals) + "f}" 16 | percents = formatStr.format(100 * (iteration / float(total))) 17 | filledLength = int(round(barLength * iteration / float(total))) 18 | bar = '█' * filledLength + '-' * (barLength - filledLength) 19 | sys.stdout.write('\r%s |%s| %s%s %s' % (prefix, bar, percents, '%', suffix)), 20 | if iteration == total: 21 | sys.stdout.write('\n') 22 | sys.stdout.flush() -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import urllib.request 3 | import pandas as pd 4 | import re 5 | import gc 6 | import os 7 | import dateutil.relativedelta 8 | from datetime import datetime 9 | from progress_bar import print_progress 10 | from datetime import date 11 | 12 | 13 | class Table(object): 14 | def __init__(self, month, year, id, cols, names, bankir=True): 15 | self.month = month 16 | self.year = year 17 | ''' 18 | Список всех id: 19 | banki.ru: 20 | капитал: 21 | На banki.ru капитал представлен в двух id - 25 и 20. 22 | 25 доступен начиная с 01.09.2014 23 | 20 доступен до 31.12.2014 24 | Поэтому при создании объекта - если id==0, то это капитал. 25 | расчетные счета: 410 26 | средства ПиО: 500 27 | Вклады ф/л: 60 28 | 29 | bankir.ru: 30 | активы: 19 31 | ценные бумаги: 27 32 | обороты средств в банкоматах: 34 33 | кредиты предприятиям: 21 34 | потребительские кредиты: 24 35 | ''' 36 | if id == 0 and (prev >= date(2014, 9, 1)): 37 | self.id = 25 38 | elif id == 0 and d >= date(2014, 9, 1): 39 | self.id = id # осталвяем id=0 для случая когда дата запрашиваемого периода больше 01/09/2014, а предыдущая дата prev - меньше 40 | elif id == 0: 41 | self.id = 20 42 | else: 43 | self.id = id 44 | 45 | self.cols = cols 46 | self.bankir = bankir 47 | self.names = names 48 | 49 | def get_url(self): 50 | if self.bankir: 51 | url = u'http://bankir.ru/rating/export/csv/' + str(self.month) + '/' + str(self.year) + '/' + str(self.id) 52 | else: 53 | url = u'http://www.banki.ru/banks/ratings/export.php?PROPERTY_ID=' + str(self.id) + '&date1=' + str(self.year) + '-' + str(self.month) + '-01&date2=' + str(year_prev) + '-' + str(month_prev)+ '-01' 54 | return url 55 | 56 | self.url = get_url(self) 57 | 58 | def get_data(self): 59 | if self.bankir: 60 | data = pd.read_csv(urllib.request.urlopen(self.url), ";", skiprows=0, header=0, encoding='utf-8', quotechar="'", usecols=self.cols, names=('Лиц', 'Банк', 'city', 'a')) 61 | data.a /= 1000 # переводим из тысяч в миллионы 62 | data.city = data.city.apply(lambda row: row.capitalize()) 63 | else: 64 | if self.id == 0: 65 | self.url = u'http://www.banki.ru/banks/ratings/export.php?PROPERTY_ID=25&date1=' + str( 66 | self.year) + '-' + str(self.month) + '-01&date2=' + str(year_prev) + '-' + str( 67 | month_prev) + '-01' 68 | url_for_prev = u'http://www.banki.ru/banks/ratings/export.php?PROPERTY_ID=20&date1=' + str( 69 | year_prev) + '-' + str(month_prev) + '-01&date2=2008-03-01' 70 | print(url_for_prev) 71 | data = pd.read_csv(urllib.request.urlopen(self.url), ";", names=('Лиц', 'a'), skiprows=4, usecols=[3, 5], thousands=' ', decimal=',', na_values='-', encoding='windows-1251') 72 | data_for_prev = pd.read_csv(urllib.request.urlopen(url_for_prev), ";", names=('Лиц', 'b'), skiprows=4, usecols=[3, 5], thousands=' ', decimal=',', na_values='-', encoding='windows-1251') 73 | data = data.merge(data_for_prev, how='outer', on='Лиц') 74 | print(data) 75 | else: 76 | data = pd.read_csv(urllib.request.urlopen(self.url), ";", names=('Лиц', 'a', 'b'), skiprows=4, usecols=self.cols, thousands=' ', decimal=',', na_values='-', encoding='windows-1251') 77 | data.a /= 1000 # переводим из тысяч в миллионы 78 | data.b /= 1000 # переводим из тысяч в миллионы 79 | data.columns = self.names 80 | return data 81 | 82 | self.data = get_data(self) 83 | 84 | @property 85 | def getData(self): 86 | return self.data 87 | 88 | @property 89 | def getBankir(self): 90 | return self.bankir 91 | 92 | def __repr__(self): 93 | return str(self) 94 | 95 | @staticmethod 96 | def parser(): 97 | 98 | def chunks(l, n): 99 | list = [] 100 | for i in range(0, len(l), n): 101 | list.append(l[i:i+n]) 102 | return list 103 | 104 | url = 'http://www.banki.ru/banks/memory/?PAGEN_1=' 105 | site = urllib.request.urlopen(url) 106 | html = site.read().decode('windows-1251') 107 | n_pages = round(int(re.findall(r'totalItems:(.*?);', str(html), re.S)[0]) / 50.0 + 0.48) + 1 108 | table = re.findall(r'(.*?)
', str(html), re.S) 109 | thead = re.findall(r'(.*?)(?: .*?)?', str(table)) 110 | i = 0 111 | for th in thead: 112 | thead[i] = th.replace(' ', '_') 113 | i += 1 114 | trs = re.findall(r']*>(?:]*>)?(.*?)(?:)?', str(table)) 115 | print_progress(0, n_pages, prefix='Формирую файл с отозванными банками:', suffix='Выполнено', barLength=40) 116 | for i in range(2, n_pages): 117 | cur_url = url + str(i) 118 | site = urllib.request.urlopen(cur_url) 119 | html = site.read().decode('windows-1251') 120 | table = re.findall(r'(.*?)
', str(html), re.S) 121 | trs.extend(re.findall(r']*>(?:]*>)?(.*?)(?:)?', str(table))) 122 | print_progress(i, n_pages, prefix='Формирую файл с отозванными банками:', suffix='Выполнено', barLength=40) 123 | defunct = pd.DataFrame(data=chunks(trs, len(thead)), columns=thead) 124 | return defunct 125 | 126 | @staticmethod 127 | def my_merge(): 128 | skip_banki = 0 129 | skip_bankir = 0 130 | banki = pd.DataFrame() 131 | bankir = pd.DataFrame() 132 | for obj in gc.get_objects(): 133 | if isinstance(obj, Table): 134 | if obj.bankir: 135 | if skip_bankir == 0: 136 | bankir = obj.getData 137 | skip_bankir = 1 138 | else: 139 | bankir = bankir.merge(obj.getData, how='outer', on=['Лиц', 'Банк', 'Город']) 140 | else: 141 | if skip_banki == 0: 142 | banki = obj.getData 143 | skip_banki = 1 144 | else: 145 | banki = banki.merge(obj.getData, how='outer', on='Лиц') 146 | df = bankir.merge(banki, how='outer', on='Лиц') 147 | return df 148 | 149 | if __name__ == '__main__': 150 | month = 11 151 | year = 2015 152 | period = 1 153 | d = date(year, month, 1) 154 | prev = d - dateutil.relativedelta.relativedelta(months=period) 155 | next = d + dateutil.relativedelta.relativedelta(months=period) 156 | print(prev) 157 | print(next) 158 | 159 | month_prev = prev.month 160 | year_prev = prev.year 161 | month_next = next.month 162 | year_next = next.year 163 | filename = str(month) + '.' + str(year) + '.period=' + str(period) +'.xlsx' 164 | 165 | assets_prev = Table(month_prev, year_prev, 19, (1, 2, 3, 4), ('Лиц', 'Банк', 'Город', 'Активы_пред_(млн_руб)')) 166 | assets = Table(month, year, 19, (1, 2, 3, 4), ('Лиц', 'Банк', 'Город', 'Активы_(млн_руб)')) 167 | business = Table(month, year, 21, (1, 2, 3, 5), ('Лиц', 'Банк', 'Город', 'Кредиты_преприятиям_(млн_руб)')) 168 | consumers = Table(month, year, 24, (1, 2, 3, 5), ('Лиц', 'Банк', 'Город', 'Потребительские_кредиты_(млн_руб)')) 169 | atm = Table(month, year, 34, (1, 2, 3, 5), ('Лиц', 'Банк', 'Город', 'Оборот_средств_в_банкоматах_(млн_руб)')) 170 | accounts = Table(month, year, 410, (3, 5, 7), ('Лиц', 'Р/С_ф/л_(млн_руб)', 'Изменение_Р/С_ф/л_(млн_руб)'), False) 171 | money = Table(month, year, 500, (3, 5, 7), ('Лиц', 'Средства_ПиО_(млн_руб)', 'Изменение_Средства_ПиО_(млн_руб)'), False) 172 | deposits = Table(month, year, 60, (3, 5, 7), ('Лиц', 'Вклады_ф/л_(млн_руб)', 'Изменение_вклады_ф/л_(млн_руб)'), False) 173 | securities = Table(month, year, 70, (3, 5, 7), ('Лиц', 'Ценные_бумаги_(млн_руб)', 'Изменение_ценные_бумаги_(млн_руб)'), False) 174 | capital = Table(month, year, 0, (3, 5, 7), ('Лиц', 'Капитал_(млн_руб)', 'Изменение_капитал_(млн_руб)'), False) 175 | 176 | main = Table.my_merge() 177 | 178 | # print(main) 179 | # print(assets_prev.data.dropna(axis=0, how='any').shape) 180 | # print(assets.data.dropna(axis=0, how='any').shape) 181 | # print(business.data.dropna(axis=0, how='any').shape) 182 | # print(consumers.data.dropna(axis=0, how='any').shape) 183 | # print(atm.data.dropna(axis=0, how='any').shape) 184 | # print(accounts.data.dropna(axis=0, how='any').shape) 185 | # print(money.data.dropna(axis=0, how='any').shape) 186 | # print(deposits.data.dropna(axis=0, how='any').shape) 187 | # print(securities.data.dropna(axis=0, how='any').shape) 188 | # print(capital.data.dropna(axis=0, how='any').shape) 189 | # print(capital.url) 190 | 191 | ########################### произвожу определенную работу со столбцами 192 | main['Изменение_активы_(млн_руб)'] = main['Активы_(млн_руб)'] - main['Активы_пред_(млн_руб)'] 193 | main = main.drop(labels='Активы_пред_(млн_руб)', axis=1) 194 | main['Обороты_средств_в_банкоматах/Капитал'] = main['Оборот_средств_в_банкоматах_(млн_руб)']/main['Капитал_(млн_руб)'] 195 | main = main.drop(labels='Оборот_средств_в_банкоматах_(млн_руб)', axis=1) 196 | main['Кредиты_преприятиям/Капитал'] = main['Кредиты_преприятиям_(млн_руб)'] / main['Капитал_(млн_руб)'] 197 | main = main.drop(labels='Кредиты_преприятиям_(млн_руб)', axis=1) 198 | main['Потребительские_кредиты/Капитал'] = main['Потребительские_кредиты_(млн_руб)'] / main['Капитал_(млн_руб)'] 199 | main = main.drop(labels='Потребительские_кредиты_(млн_руб)', axis=1) 200 | main['Ценные_бумаги/Капитал'] = main['Ценные_бумаги_(млн_руб)'] / main['Капитал_(млн_руб)'] 201 | main = main.drop(labels='Ценные_бумаги_(млн_руб)', axis=1) 202 | 203 | ########################## добавляю столбец отзыв, для этого собираю данные с сайта banki.ru 204 | 205 | ##### для того, чтобы каждый раз не парсить сайт, проверяю как давно был изменен файл: если более 7 дней, то парсим 206 | if os.path.exists('withdraw.csv') and \ 207 | datetime.fromtimestamp(os.path.getmtime('withdraw.csv')-datetime.today().timestamp()).day < 7: 208 | defunct = pd.read_csv('withdraw.csv') 209 | else: 210 | defunct = Table.parser() 211 | defunct.to_csv('withdraw.csv') 212 | 213 | defunct[['дата_отзыва']] = defunct[['дата_отзыва']].apply(pd.to_datetime) 214 | defunct = defunct[(defunct.причина == 'отозв.') & (defunct.дата_отзыва <= pd.datetime(year_next, month_next, 1)) & (defunct.дата_отзыва >= pd.datetime(year, month, 1))] 215 | defunct = defunct[['номер_лицензии', 'причина']] 216 | defunct.columns = ['Лиц', 'Отзыв'] 217 | defunct['Лиц'] = defunct[['Лиц']].apply(lambda row: int(re.sub(r'[^\d]', '', str(row.Лиц))), axis=1) 218 | defunct['Лиц'] = defunct['Лиц'].astype(float) 219 | defunct['Отзыв'] = 1 220 | 221 | ########################### после добавления столбца "отзыв" делаю 222 | def handle(row): 223 | if row.Отзыв == 1: 224 | row.fillna(value=0, inplace=True) 225 | return row 226 | 227 | main = defunct.merge(main, how='outer', on='Лиц') 228 | main.dropna(axis=0, how='any', subset=['Банк', 'Город'], inplace=True) 229 | main.Отзыв = main.Отзыв.fillna(0) 230 | main = main.apply(lambda row: handle(row), axis=1) 231 | main.dropna(axis=0, how='any', inplace=True) 232 | 233 | ########################### заменяю на правильные имена столбцов 234 | final_names = [] 235 | for name in list(main.columns): 236 | final_names.append(re.sub('_', ' ', name)) 237 | main.columns = final_names 238 | 239 | main.sort_values(by='Активы (млн руб)', ascending=False, axis=0, inplace=True, kind='quicksort') 240 | main.to_excel(filename, index=False) 241 | # print(main) 242 | print(main.shape) 243 | --------------------------------------------------------------------------------