В этой статье хочу познакомить читателя с таким ресурсом, как «Единый федеральный реестр сведений о фактах деятельности юридических лиц» (fedresurs.ru), рассказать о его применимости и показать возможность прошерстить ресурс не руками по большому количеству клиентов, а с использованием C# (в вашем случае можете использовать любой другой язык, который вам нравится, подходы, думаю, не изменятся) и море проксей, ну разумеется будем парсить. Возможно, тем кто этим займется, пригодятся статья, идеи или, чем черт не шутит, куски кода, а тем, кто просто проводит проверку контрагентов сможет либо понять, что можно взять с сайта либо придумает, что можно еще проверить.
Периодически возникает потребность в получении данных для анализа клиентов/заемщиков/контрагентов и эти данные мы можем получить из этого сайта. Что же нам предлагается?!
Возникновение обременений (залог, лизинг и т.д.) – посмотреть, например, на возможность заключения фиктивных договоров лизинга или на уже заложенное имущество и т.д.
Различные сообщения по клиенту, например, сообщения о банкротстве и уже с ними – проверка, все ли сообщает клиент и может пора пошевелиться в сторону него и предпринять какие-либо меры.
Торги и т.д.
Составляем запрос для получения желаемого
string strQuery;
linq2: switch (inn.Length)
{
case (10):
strQuery = "https://fedresurs.ru/backend/companies?limit=15&offset=0&code=" + inn;
break;
case (12):
strQuery = "https://fedresurs.ru/backend/persons?limit=15&offset=0&code=" + inn;
break;
case (9):
case (11):
inn = 0 + inn;
goto linq2;
default:
return;
}
Определяем по длине строки, с кем имеем дело – с физическим или юридическим лицом (10 символов – юрик, 12 символов – физик) от этого зависит запрос. Бывает, что 0 впереди ИНН куда-то теряется, так что можем поставить проверку, что если 9 или 11 символов, то дописываем 0 впереди. В худшем случае добудем избыточную информацию.
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(strQuery);
request.UserAgent = "Mozila/4.0 (compatible; MSIE 6.0;Windows NT 5.1; SV1; MyIE2; ";
request.CookieContainer = new CookieContainer();
request.Accept = "image/gif, image/x-xbitmap, image/jpeg,image / pjpeg, application / x - shockwave - flash, application / vnd.ms - excel, application / vnd.ms - powerpoint, application / msword,";
request.Headers.Add("Accept-Language", "ru");
request.ContentType = "application/json";
request.KeepAlive = true;
request.Timeout = 200000;
request.Referer = "https://fedresurs.ru/search/entity?code=" + inn;
HttpWebResponse response = new HttpWebResponse();
response = (HttpWebResponse)request.GetResponse();
Стоит обратить внимание на Referer – без него, сервер будет отпинывать, сообщая, что страница не найдена.
Берем любой ИНН, например 3327848813 и делаем запрос. Вернется нам json:
{"pageData":[{"guid":"b99c2ded-1a6c-4239-b728-804461ad074b","ogrn":"1103327002393","inn":"3327848813","name":"ООО \"СУЗДАЛЬСКАЯ ПИВОВАРНЯ\"","egrulAddress":"600033, ОБЛАСТЬ ВЛАДИМИРСКАЯ, ГОРОД ВЛАДИМИР, УЛИЦА СУЩЁВСКАЯ, ДОМ 37, ПОМЕЩЕНИЕ 12","status":"Юридическое лицо признано несостоятельным (банкротом) и в отношении него открыто конкурсное производство","statusUpdateDate":"2017-11-28T00:00:00","isActive":true}],"found":1}
При разложении в таблицу выглядит чуть проще:
И вот мы получили guid и еще чуть-чуть информации сверху.
Дальше можем получить список сообщений по данному клиенту и вот запрос:
string strQuery, referer;
switch (search.inn.Length)
{
case (10):
strQuery = "https://fedresurs.ru/backend/companies/" + search.guid + "/publications?" +
"&limit=" + limit +
"&offset=" + offset +
"&searchCompanyEfrsb=true" +
"&searchAmReport=true" +
"&searchFirmBankruptMessage=true" +
"&searchFirmBankruptMessageWithoutLegalCase=false" +
"&searchSfactsMessage=true" +
"&searchSroAmMessage=true" +
"&searchTradeOrgMessage=true";
referer = "https://fedresurs.ru/company/b99c2ded-1a6c-4239-b728-804461ad074b" + search.guid;
break;
case (12):
strQuery = "https://fedresurs.ru/backend/persons/" + search.guid + "/publications?" +
"limit=" + limit +
"&offset=" + offset +
"&searchPersonEfrsbMessage=true&searchAmReport=true" +
"&searchPersonBankruptMessage=true" +
"&searchMessageOnlyWithoutLegalCase=false" +
"&searchSfactsMessage=true" +
"&searchArbitrManagerMessage=true" +
"&searchTradeOrgMessage=true";
referer = "https://fedresurs.ru/person/b99c2ded-1a6c-4239-b728-804461ad074b" + search.guid;
break;
default:
return pageDatas;
}
Ничего неожиданного, все так же надо определять юр или физ лицо мы смотрим, подставлять guid, limit-количество сколько выведет сообщений, offset-сколько пропустить сообщений, все так же необходимо указывать Referer.
Делаем запрос и получаем:
{"pageData":[{"guid":"A10151349AE45D484C24D08CF2451AA7","number":"7344583","datePublish":"2021-09-17T12:37:52.11","isAnnuled":false,"isLocked":false,"title":"Объявление о проведении торгов","publisherName":"Потапов Павел Викторович","type":"BankruptcyMessage","publisherType":"ArbitrManager","bankruptName":"ООО \"СУЗДАЛЬСКАЯ ПИВОВАРНЯ\"","isRefuted":false},….
Часть данных обрезал, чтоб сильно не захламлять текст. И по традиции табличный вид
А теперь перейду к нюансу, куда же без них иначе было бы неинтересно. По какой-то причине сайт готов выдавать только первые 500 сообщений, не более. Чтобы обойти эту данность, можно начать ставить фильтры либо на типы сообщений, либо на промежуток времени, по которому смотрим, либо и то, и другое Все это записывается в запрос.
После составления запроса добавляется
if (useDate)
{
endDate = startDate.AddDays(deltDays);
strQuery += string.Format("&startDate=" + GetDateStr(startDate) + "T00:00:00.000Z&endDate=" + GetDateStr(endDate) + "T00:00:00.000Z");
}
А после части, где получаем ответ от сайта
if (found > 500 && deltDays > 0)
{
if (useDate)
{
deltDays /= 2; // меняем кол-во дней если слишком много сообщений
break;
}
else
{
useDate = true; // включаем поиск по датам если слишком много сообщений
break;
}
}
После получения ответа смотрим сколько всего сообщений есть и если их больше 500, то начинаем добавлять к запросу фильтр по датам Если в ответе все равно много, то снова уменьшаем период времени и так пока не станет <= 500.
Теперь если просто информации что за сообщение, нам недостаточно, и мы хотим (аналитик хочет, а мы вынуждены) получить больше информации, то как и прежде берем guid, только на этот раз это guid сообщения и таким же образом строим запрос:
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("https://fedresurs.ru/backend/sfactmessages/" + guid);
request.UserAgent = "Mozila/4.0 (compatible; MSIE 6.0;Windows NT 5.1; SV1; MyIE2; ";
request.CookieContainer = new CookieContainer();
request.Accept = "image/gif, image/x-xbitmap, image/jpeg,image / pjpeg, application / x - shockwave - flash, application / vnd.ms - excel, application / vnd.ms - powerpoint, application / msword,";
request.Headers.Add("Accept-Language", "ru");
request.ContentType = "application/json";
request.Referer = "fedresurs.ru";
request.Proxy = ProxyClass.GetProxy();
request.KeepAlive = true;
request.Referer = "https://fedresurs.ru/sfactmessage/" + guid;
Не стану повторяться, но тут есть момент, а именно, что запрос формируется исходя из типа сообщения. Т.е. sfactmessage (существенные факты) в запросе и в Referer соответственно может принять значения, например, bankruptmessage или какой-либо другой, и под это все json и соответственно конечные таблицы становятся разными. Примерное содержание одного из сообщений:
Осталась рассказать про одну неприятную, но вполне ожидаемую вещь: блокировки по ip((((. За одну айпишку можно успеть сделать от 100 запросов, на некоторых проксях дает сделать лишь один и под замену, и ip улетает в блок примерно на час. Для этой части написал класс подстановки проксей.
public static class ProxyClass
{
private static List<WebProxy> Proxys = new List<WebProxy>();// список проксей
private static int NumPr = 0;//используемая сейчас прокси
private static int QuerCnt = 0;//сколько запросов сделано с прокси
private static DateTime lastUseNullProxy=DateTime.Now;//последнее использование без проксли
private static int minutesForNullProxy = 90;// через какое время начинать обход списка с нуля
/// <summary>
/// меняет прокси (в try catch)
/// </summary>
public static void NexProxy()
{
if (NumPr == 0)
{
lastUseNullProxy = DateTime.Now;// меняет дату
} // если без прокси(0)
if (QuerCnt == 1)
{
if (NumPr == 0)
{
} // если без прокси(0)
else
{
Proxys.Remove(Proxys[NumPr]);
if (Proxys.Count == 1) { GetFromTxt(); }
NumPr--;
}// если с прокси
} //если был всего один запрос
if (NumPr== Proxys.Count-1)
{
NumPr = 0;
}//
else
{
NumPr++;
}
if (DateTime.Now.Subtract(lastUseNullProxy).TotalMinutes >= minutesForNullProxy)
{
NumPr = 0;
}
QuerCnt = 0;
}
/// <summary>
/// возвращает прокси для использования
/// </summary>
/// <returns></returns>
public static WebProxy GetProxy()
{
QuerCnt++;
if (QuerCnt == 2 && NumPr!=0)
{
Write(append:true);
}
return Proxys[NumPr];
}
}
В класс собираются списки проксей, и в коде при запросе идет обращение к данному классу, для получения актуальной прокси, в принципе вся работа этого куска описана в комментах. Основная проблема — найти большое количество нормальных проксей, или чтобы хотя бы половина из них были работающие.
Прикладываю также и ссылку на Git, там можно посмотреть больше кода.
Комментарии (7)
navferty
20.01.2022 12:03Вместо
HttpWebRequest
в современном .NET рекомендуется использовать новый классHttpClient
, вот тут подробнее о различиях. Также сразу обращаю внимание, что экземплярHttpClient
лучше переиспользовать.Также сильно бросается в глаза множество неочевидных условий в
ProxyClass
: я бы предложил заняться рефакторингом, чтобы уменьшить вложенность кода, а также переименовать поля и переменные, чтобы не требовалось читать комментарии для понимания происходящего.Про goto выше уже написали. Я бы не стал экономить на строках кода, и выделил бы отдельный метод, например
private string EnsureTrailingZero(string rawInn)
, который бы инкапсулировал логику проверки "утерянного" нуля в начале.
aborouhin
20.01.2022 14:41У Федресурса же API есть официальный и, в отличие от многих других, за вполне разумный ценник (пару лет назад было, ЕМНИП, 48 т.р. в год). За пулы прокси как бы дороже не вышло платить :)
ivanovlev
21.01.2022 09:05Так пул можно не на одном федресурсе использовать. А вообще печаль, теперь там наверняка все позакрывают и проверок натыкают, наш модуль для своей системки отсохнет
aborouhin
21.01.2022 16:31Пул для других ресурсов пригодится, где всё гораздо печальнее :) А тут просто сумма незначительная, комплект ЕГРЮЛ+ЕГРИП от ФНС, который всё равно нужен как самый базовый источник данных, обходится в 6 раз дороже.
ZhilkinSerg
goto?! смело
EvilOkta
Но оправдано, а как бы вы оптимизировали без goto?
Если только в case 9 и 11 прибавлять 0 и повторно прописывать strQuery но это уже ненужное дублирование на мой взгляд.
serge-phi
if (inn.Length == 9 || inn.Length == 11) inn = 0 + inn;
switch(inn.Length) ....