
No meu trabalho (jurídico), estou pronto para automatizar tudo o que só se presta a isso. Mas até que os robôs bombeados por redes neurais da utopia do Gref alemão não apareçam e não tirem todo o trabalho de advogados comuns, a rotina continuará sendo nossa principal companheira por muito tempo. A automação dessa rotina é algo que venho fazendo periodicamente nos últimos anos, sejam várias tabelas no Excel com várias fórmulas que permitem imprimir rapidamente uma centena do mesmo tipo de documentos de correspondência em word ou relatórios gerados automaticamente. Mas há coisas que você não pode fazer com fórmulas e substituições simples. É aqui que a programação vem em socorro, da qual gosto desde a infância, e aconteceu que começou com o delphi. Agora é mais fácil para mim do que em C # ou python, que comecei a aprender recentemente, criar rapidamente algum tipo de projeto no ambiente Lazarus usando o freepascal. E sim, acredito seriamente que as capacidades desse ambiente são mais que suficientes. Portanto, automatize o USRLE, você adivinhou, isso deve ser feito usando pascal.
Um advogado de uma empresa de consultoria que conduz negócios de dezenas de entidades legais, um advogado corporativo baseado em pão gratuito e qualquer outro advogado que se comprometa a garantir as atividades das organizações - todos sabem como dezenas e centenas de nomes diferentes, números TIN, PSRN se misturam em sua cabeça, como é fácil esquecer quem é o gerente e, quando o prazo de renovação é apropriado, há problemas com as ações da LLC e com o pagamento de seu capital social. Bem, a necessidade de criar rapidamente algum tipo de documento, que inclui muitos detalhes em constante mudança, implica erros e erros de digitação periódicos. Para automatizar apenas esses processos, eu precisava de uma solução de banco de dados que me permitisse criar documentos usando modelos, manter vários registros, acompanhar alterações e não perder prazos. Bem, uma das simplificações necessárias da vida é o recebimento rápido de um novo arquivo com informações do USRLE no site do Serviço Fiscal Federal . Obviamente, ninguém diz que usar o site diretamente é longo e difícil, mas concorda que clicar em um botão sem sair do aplicativo é muito mais divertido, e você pode fazer isso sem interromper a ligação (ou uma xícara de café).
Então, para começar, decidiremos o que queremos obter. O site permite que você procure no registro oficial do USRLE um número único OGRN ou TIN e forneça um resultado relevante na forma de uma breve informação sobre a pessoa e um link para baixar o arquivo pdf com um extrato. Além disso, a pesquisa pode ser confusa por nome, com um filtro adicional por região (assunto da Federação Russa). E, nesse caso, o site emite uma tabela com todas as pessoas adequadas e com o mesmo conjunto de dados, incluindo links para pdf.
Portanto, em um caso específico, a função pronta deve retornar pdf na forma de um arquivo (ou, melhor, um fluxo), tendo um OGRN ou TIN na entrada. Mas para a universalização e a possibilidade de expansão adicional, não negligenciaremos todos os recursos do site e também faremos uma função de pesquisa difusa com o retorno de um conjunto de dados encontrado pelo nome da organização, levando em consideração o filtro por região ou sem ele. Vamos tentar descrever as interfaces dessas funções:
IEGRULstreamer = interface procedure GetExtractByOGRN(OGRN: string; ; isLegal: boolean; var Extract: TStream); procedure GetLegalsListByName(Name, Region: string; ; var LegalsList: TCollection); end;
Para entender o que o misterioso parâmetro X e a coleção da qual a segunda função retornará, veremos como o site executa a solicitação.
1. O site possui um formulário com campos de entrada para identificadores para pesquisar e verificar captcha:

2. O Captcha é gerado usando um campo oculto pré-gerado com o nome captchaToken, que usa um script Java para gerar uma imagem captcha para esse token.
3. Depois de clicar no botão "localizar", uma solicitação POST é enviada ao servidor, nos resultados do processamento dos quais o JSON com uma matriz de objetos é retornado. Essa resposta JSON usa outro script Java para preencher a tabela que vemos nos resultados da pesquisa.
Portanto, o primeiro problema é a verificação do captcha. Para não sobrecarregar nossos métodos de interação com o site com excesso de funcionalidade, tomaremos ações para processar o captcha como uma função separada. E no X, teremos um parâmetro para o método de retorno de chamada, que possui uma imagem captcha na entrada e uma sequência com captcha reconhecido na saída:
TCapthcaRecognizeFunc = function(Captha: TStream): string of object; ... procedure GetExtractByOGRN(OGRN: string; CaptchaFunc: TCapthcaRecognizeFunc; isLegal: boolean; var Extract: TStream);
A função que processa o captcha pode fazer o que você quiser: deixe o usuário inseri-lo manualmente, envie a imagem para um servidor pago para reconhecimento automático, reconheça-o independentemente usando o know-how exclusivo do algoritmo. Para simplificar a imagem, e como no meu caso não é esperado nenhum fluxo de captcha em escala industrial, selecionamos a primeira opção:
function TForm1.RecognizeFunc(captcha: TStream): string; begin CaptchaImg.Picture.LoadFromStream(captcha); Result := InputBox('',' ', ''); end;
A segunda pergunta é o conteúdo da resposta JSON do servidor. Aqui está um exemplo do que vem nele:
Resposta formatada em JSON { "query": {"captcha":"382915", "ogrninnfl":null, "fam":null, "nam":null, "otch":null, "region":null, "ogrninnul":null, "namul":"", "regionul":"73", "kind":"ul", "ul":true, "searchByOgrn":false, "nameEq":false, "searchByOgrnip":true}, "rows": [ {"T":"ED346E713D4A1AC851F9B589C6D2AECD1D809D5B6B5D1B98E697B6E0FD873E137B828AC59A60D159BB2894F11D00AB5639E2ACEE4E2ED5B7AC7A6EFE28FD987BC288B93C4D3D3EC1008DA0F128BA7E5E", "INN":"7325001144", "NAME":" ", "OGRN":"1027301175110", "ADRESTEXT":"432017, , , , 1", "CNT":"4", "DTREG":"03.12.2002", "KPP":"732501001"}, {"T":"2ECB284C7682E5F1D1129AA3074FABB4B74BB28EA426AF79C091CEDEA0D9E391CA26FF405A7C9742466E19C78FBE5A59BDCBCD21268FFD8AFD3A8509CCA84541", "INN":"7303007375", "NAME":" \" \"", "OGRN":"1027301173283", "ADRESTEXT":"432063, , , , 7", "CNT":"4", "DTREG":"27.11.2002", "KPP":"732501001", "DTEND":"01.09.2010"}, ] }
Como você pode ver, o resultado retorna um objeto de consulta que contém os parâmetros de pesquisa inicial (para que eles permaneçam nos campos do formulário para reutilização) e uma matriz de linhas. O link para o arquivo pdf é combinado com um script java usando a expressão:
"https://egrul.nalog.ru/download/"
e o valor da chave "T" do objeto. O tempo de vida do arquivo pdf gerado é de alguns minutos.
As duas principais dificuldades que encontrei ao criar a solicitação http são os valores de cabeçalho corretos e a combinação da string com os parâmetros da solicitação POST. Mas uma análise simples da página usando as ferramentas internas do navegador (no Chrome são chamadas pressionando F12) deu tudo o que você precisa. Aqui está um exemplo de cabeçalhos com os quais o servidor fornece a resposta correta em vez de 400 solicitações incorretas:
POST / HTTP/1.1 Host: egrul.nalog.ru Connection: keep-alive Accept: application/json, text/javascript, */*; q=0.01 Origin: https://egrul.nalog.ru X-Requested-With: XMLHttpRequest User-Agent: Chrome/67.0.3396.99 Safari/537.36 Content-Type: application/x-www-form-urlencoded Referer: https://egrul.nalog.ru/ Accept-Encoding: gzip, deflate, br Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
E aqui está a linha com os parâmetros:
kind=ul&srchUl=name&ogrninnul=7716819629&namul=%D0%BF%D1%80%D0%B0%D0%B2% D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D1%82%D0%B2%D0%BE®ionul=73 &srchFl=ogrn&ogrninnfl=&fam=&nam=&otch=®ion=&captcha=449023&captchaToken=DAEDA 7504CACAC82CF09E08319B68DF5F9BD62B2F44D33DD679DDE55B5CF58B17FEC84E78CEEB9639 84D2B2BD8C3AA15
Armado com esses dados iniciais, prosseguimos para a implementação da tarefa. Usarei as seguintes bibliotecas para o freepascal:
O Synapse é uma biblioteca muito conveniente, com a função mais simplificada (para uso) de enviar solicitações http para o servidor, também funciona com SSL, mas isso requer a presença de bibliotecas openSSL na pasta ou sistema do projeto, bem como a conexão de um módulo adicional. É suficiente conectar os seguintes módulos de biblioteca ao nosso projeto: httpsend, ssl_openssl, synautil.
A biblioteca interna fcl -json - os módulos necessários: fpjson e fpjsonrtti - para a máxima conveniência do processamento de objetos retornados ao JSON.
Módulos separados da biblioteca interna fcl -xml - para algumas funções, você precisará trabalhar com partes do HTML como objetos DOM, para conectar os módulos SAX_HTML, DOM_HTML, DOM.
Vamos descrever os tipos e classes de objetos que acabaram sendo:
TEGRULItem = class(TCollectionItem) private fT, fINN, fNAME, fOGRN, fADRESTEXT, fCNT, fDTREG, fDTEND, fKPP: string; public function GetPdfLink: string; published property T: string read fT write fT; property INN: string read fINN write fINN; property NAME: string read fNAME write fNAME; property OGRN: string read fOGRN write fOGRN; property ADRESTEXT: string read fADRESTEXT write fADRESTEXT; property CNT: string read fCNT write fCNT; property DTREG: string read fDTREG write fDTREG; property DTEND: string read fDTEND write fDTEND; property KPP: string read fKPP write fKPP; end;
Nesta classe, embalaremos objetos que serão retornados na matriz de linhas na resposta JSON do servidor. Nós os leremos usando JSONToCollection, mas para isso precisamos tornar cada objeto um elemento da coleção e declarar todas as propriedades relacionadas conforme publicadas. As funções RTTI no freepascal (assim como no delphi) obtêm acesso aos nomes de propriedades somente quando são declarados neste escopo. E a função JSONToCollection do módulo fpjsonrtti é apenas uma função RTTI que compara os nomes das chaves do objeto JSON com os nomes das propriedades da classe.
Há também uma função GetPdfLink na interface da classe, que retorna um link para baixar um arquivo pdf com informações do USRLE, concatenando o endereço da Web e o valor da propriedade "T".
A classe principal que implementa a interface declarada acima será assim:
TEGRULStreamer = class(TInterfacedObject, IEGRULStreamer) private HTTPSender: THTTPSend; Doc: THTMLDocument; Inputs: TDOMNodeList; captchaURL, captchaToken, captcha, Params: string; function GetCaptchaToken: string; function GetLegalsList: TCollection; procedure PrepareHeaders; procedure ProcessCaptcha(CaptchaFunc: TCapthcaRecognizeFunc); public procedure GetExtractByOGRN(OGRN: string; CaptchaFunc: TCapthcaRecognizeFunc; isLegal: boolean; var Extract: TStream); procedure GetLegalsListByName(Name, Region: string; CaptchaFunc: TCapthcaRecognizeFunc; var LegalsList: TCollection); destructor Destroy; override; end;
Como você pode ver, além da implementação das duas funções principais da interface, todas as outras propriedades e métodos da classe estarão ocultos e necessários apenas para a implementação interna. Em geral, eles podem ser incluídos nos métodos principais, mas já aprendemos as lições sobre código duplicado, visualização e refatoração em geral.
Dado o encapsulamento de ações preparatórias, os métodos principais geralmente diferem apenas ao formar uma sequência de parâmetros de solicitação de http e o tipo de dados retornado.
código de método TEGRULStreamer.GetExtractByOGRN procedure TEGRULStreamer.GetExtractByOGRN(OGRN: string; CaptchaFunc: TCapthcaRecognizeFunc; isLegal: boolean; var Extract: TStream); begin ProcessCaptcha(CaptchaFunc); if isLegal then Params := 'kind=ul' else Params := 'kind=fl'; Params += '&srchUl=ogrn&srchFl=ogrn&ogrninnul='; if isLegal then Params += OGRN; Params += '&namul=®ionul=&ogrninnfl='; if not isLegal then Params += OGRN; Params += '&fam=&nam=&otch=®ion&captcha=' + captcha + '&captchaToken=' + captchaToken; WriteStrToStream(HTTPSender.Document, Params); if not HTTPSender.HTTPMethod('POST', EGRUL_URL) then raise Exception.Create(' '); HTTPSender.Headers.Clear; if HTTPSender.HTTPMethod('GET', TEGRULItem(GetLegalsList.Items[0]).GetPdfLink) then Extract := HTTPSender.Document else Extract := nil;
Aqui, como podemos ver, o método também usa o parâmetro booleano isLegal e, se não estiver definido como true, a pesquisa passará pelo banco de dados de empreendedores, e não de entidades legais.
código de método TEGRULStreamer.GetLegalsListByName procedure TEGRULStreamer.GetLegalsListByName(Name, Region: string; CaptchaFunc: TCapthcaRecognizeFunc; var LegalsList: TCollection); begin ProcessCaptcha(CaptchaFunc); Params := 'kind=ul&srchUl=name&srchFl=ogrn&ogrninnul=&namul='; Params += Name + '®ionul=' + Region + '&ogrninnfl=&fam=&nam=&otch=®ion'; Params += '&captcha=' + captcha + '&captchaToken=' + captchaToken; WriteStrToStream(HTTPSender.Document, Params); if not HTTPSender.HTTPMethod('POST', EGRUL_URL) then raise Exception.Create(' '); LegalsList := GetLegalsList; end;
O papel dos métodos utilitários é o seguinte:
ProcessCaptcha - baixa a página html inicial do Federal Tax Service, procura um token captcha, baixa uma imagem gerada por esse token e a redireciona para um método de retorno de chamada para reconhecimento de captcha. No final, o método também define os cabeçalhos corretos para a solicitação POST subsequente.
GetCaptchaToken - carrega todos os campos de entrada da página na estrutura do DOM, procura um campo oculto com o identificador capthcaToken e retorna seu valor.
GetLegalsList - usando a função RTTI JSONToCollection retorna uma coleção de objetos do tipo TEGRULItem descritos acima.
GetPdfLink - para pesquisar por OGRN ou TIN, no caso correto, sempre será retornado apenas um resultado; portanto, em GetExtractByOGRN, a função é chamada para o primeiro elemento da coleção.
Como esta é minha primeira experiência com uma rede em freepascal, estou muito feliz que tudo tenha saído exatamente como eu pretendia. Em uma forma de trabalho, a biblioteca foi criada em menos de um dia (graças aos usuários do fórum freepascal.ru, que falaram sobre sinapse).
Um arquivo com um teste da biblioteca resultante e seu código está aqui .
Como sempre, terei prazer em receber críticas construtivas, tanto no projeto quanto na implementação. Entendo que existem muitos fatores que ainda podem ser levados em consideração: o atraso na resposta à solicitação de http, como resultado do qual o aplicativo congelará; Respostas http incorretas e outras situações.
No futuro, pretendo conectar uma biblioteca on-line ao banco de dados de endereços FIAS e perceber a capacidade de gerar modelos de aplicativos completos, que geralmente são editados no Programa de Preparação de Documentos para o registro do estado .

PS Desculpe, Sberbank, pelo papel do coelho experimental e centenas de vezes que o extrato foi baixado. Tudo em nome da ciência, é claro.