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