
Dans mon travail (juridique), je suis prêt à automatiser tout ce qui ne se prête qu'à cela. Mais tant que les robots pompés par les réseaux de neurones de l'utopie du Gref allemand ne sont pas apparus et n'ont pas pris tout le travail des avocats ordinaires, la routine restera longtemps notre principal compagnon. L'automatisation de cette routine est quelque chose que j'ai fait périodiquement au cours des dernières années, que ce soit de nombreux tableaux en Excel avec un tas de formules qui vous permettent d'imprimer rapidement une centaine de documents de même type dans Word, ou bien, des rapports générés automatiquement. Mais il y a des choses que vous ne pouvez pas faire avec des formules simples et des substitutions. C'est là que la programmation vient à la rescousse, que j'aime depuis l'enfance, et il se trouve que cela a commencé avec delphi. Maintenant, c'est plus facile pour moi qu'en C # ou en python, que j'ai commencé à apprendre récemment, de faire rapidement une sorte de projet dans l'environnement Lazarus en utilisant freepascal. Et oui, je crois sérieusement que les capacités de cet environnement sont plus que suffisantes. Par conséquent, l'automatisation de l'USRLE, vous l'aurez deviné, doit se faire en utilisant pascal.
Un avocat d'un bureau de conseil exerçant des activités avec des dizaines d'entités juridiques, un avocat spécialisé dans le pain d'entreprise et tout autre avocat qui doit assurer les activités des organisations - ils savent tous comment des dizaines et des centaines de noms différents, les numéros TIN et PSRN se mélangent dans votre tête, comment il est facile d’oublier qui est le gestionnaire et, lorsque son mandat de renouvellement est approprié, y a-t-il des problèmes avec les actions de la LLC et avec le paiement de son capital d’affrètement. Eh bien, la nécessité de créer rapidement une sorte de document, qui comprend de nombreux détails en constante évolution, entraîne des erreurs périodiques et des fautes de frappe. Pour automatiser de tels processus, j'avais besoin d'une solution de base de données qui me permettait de créer des documents à l'aide de modèles, de maintenir divers registres, de suivre les modifications et de ne manquer aucun délai. Eh bien, l'une des simplifications de vie nécessaires est la réception rapide d'un nouveau fichier contenant des informations de l'USRLE sur le site Web du Federal Tax Service . Bien sûr, personne ne dit que l'utilisation directe du site est longue et difficile, mais convenez que cliquer sur un bouton sans quitter l'application est beaucoup plus amusant, et vous pouvez le faire sans interrompre l'appel téléphonique (ou une tasse de café).
Donc, pour commencer, nous déciderons ce que nous voulons obtenir. Le site vous permet de rechercher dans le registre officiel de l' USRLE un numéro OGRN ou TIN unique et de donner un résultat pertinent sous la forme d'une brève information sur la personne et d'un lien pour télécharger le fichier pdf avec un extrait. En outre, la recherche peut être floue par nom avec un filtre supplémentaire par région (sujet de la Fédération de Russie). Et dans ce cas, le site publie un tableau avec toutes les personnes appropriées et avec le même ensemble de données, y compris des liens vers le pdf.
Ainsi, dans un cas particulier, la fonction prête à l'emploi devrait retourner un pdf sous la forme d'un fichier (ou, mieux, d'un flux), avec un OGRN ou un TIN en entrée. Mais pour l'universalisation et la possibilité d'expansion, nous ne négligerons pas toutes les fonctionnalités du site et ferons également une fonction de recherche floue avec le retour d'un ensemble de données trouvées par le nom de l'organisation, en tenant compte du filtre par région ou sans. Essayons de décrire les interfaces de ces fonctions:
IEGRULstreamer = interface procedure GetExtractByOGRN(OGRN: string; ; isLegal: boolean; var Extract: TStream); procedure GetLegalsListByName(Name, Region: string; ; var LegalsList: TCollection); end;
Afin de comprendre ce que le mystérieux paramètre X et la collection dont retournera la deuxième fonction, nous verrons comment le site exécute la requête.
1. Le site a un formulaire avec des champs de saisie pour les identifiants de recherche et de vérification du captcha:

2. Captcha est généré à l'aide d'un champ caché pré-généré avec le nom captchaToken, qui utilise un script Java pour générer une image captcha pour ce jeton.
3. Après avoir cliqué sur le bouton "Rechercher", une requête POST est envoyée au serveur, dans les résultats de traitement desquels JSON avec un tableau d'objets est retourné. Cette réponse JSON utilise un autre script Java pour remplir le tableau que nous voyons dans les résultats de la recherche.
Ainsi, le premier hic est le chèque captcha. Afin de ne pas surcharger nos méthodes d'interaction avec le site avec des fonctionnalités supplémentaires, nous prendrons des mesures pour traiter le captcha comme une fonction distincte. Et dans X, nous aurons un paramètre pour la méthode de rappel, qui a une image captcha à l'entrée et une chaîne avec captcha reconnu à la sortie:
TCapthcaRecognizeFunc = function(Captha: TStream): string of object; ... procedure GetExtractByOGRN(OGRN: string; CaptchaFunc: TCapthcaRecognizeFunc; isLegal: boolean; var Extract: TStream);
La fonction de traitement du captcha peut le faire comme vous le souhaitez: laisser l'utilisateur entrer manuellement, envoyer l'image à un serveur payant pour une reconnaissance automatique, la reconnaître indépendamment en utilisant le savoir-faire unique de l'algorithme. Pour la simplicité de l'image, et comme dans mon cas aucun flux de captcha n'est attendu à l'échelle industrielle, nous sélectionnons la première option:
function TForm1.RecognizeFunc(captcha: TStream): string; begin CaptchaImg.Picture.LoadFromStream(captcha); Result := InputBox('',' ', ''); end;
La deuxième question concerne le contenu de la réponse JSON du serveur. Voici un exemple de ce qui s'y trouve:
Réponse au format 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"}, ] }
Comme vous pouvez le voir, le résultat renvoie un objet de requête qui contient les paramètres de recherche initiaux (afin qu'ils restent dans les champs du formulaire pour une réutilisation) et un tableau de lignes. Le lien vers le fichier pdf est combiné avec un script java utilisant l'expression:
"https://egrul.nalog.ru/download/"
et la valeur clé "T" de l'objet. La durée de vie du fichier pdf généré est de quelques minutes.
Les deux principales difficultés que j'ai rencontrées lors de la création de la requête http sont les valeurs d'en-tête correctes et la combinaison de la chaîne avec les paramètres de la requête POST. Mais une simple analyse de la page à l'aide des outils de navigation intégrés (dans Chrome sont appelés en appuyant sur F12) a donné tout ce dont vous avez besoin. Voici un exemple d'en-têtes avec lesquels le serveur donne la bonne réponse au lieu de 400 Bad request:
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
Et voici la ligne avec les paramètres:
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
Armé de ces données initiales, nous procédons à la mise en œuvre de la tâche. J'utiliserai les bibliothèques suivantes pour freepascal:
Synapse est une bibliothèque très pratique avec la fonction la plus simplifiée (à utiliser) d'envoyer des requêtes http au serveur, elle fonctionne également avec SSL, mais cela nécessite la présence de bibliothèques openSSL dans le dossier ou le système du projet, ainsi que la connexion d'un module supplémentaire. Il suffit de connecter les modules de bibliothèque suivants à notre projet: httpsend, ssl_openssl, synautil.
La bibliothèque intégrée fcl-json - les modules nécessaires: fpjson et fpjsonrtti - pour une commodité maximale de traitement des objets renvoyés à JSON.
Modules séparés de la bibliothèque intégrée fcl-xml - pour certaines fonctions, vous devrez travailler avec des parties de HTML en tant qu'objets DOM, nous allons donc connecter les modules SAX_HTML, DOM_HTML, DOM.
Décrivons les types et classes d'objets qui se sont finalement révélés:
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;
Dans cette classe, nous emballerons les objets qui seront retournés dans le tableau de lignes dans la réponse JSON du serveur. Nous les lirons à l'aide de JSONToCollection, mais pour cela, nous devons faire de chaque objet un élément de la collection et déclarer toutes les propriétés associées comme publiées. Les fonctions RTTI en freepascal (ainsi qu'en delphi) n'ont accès aux noms de propriété que lorsqu'elles sont déclarées dans cette étendue. Et la fonction JSONToCollection du module fpjsonrtti n'est qu'une fonction RTTI qui compare les noms des clés de l'objet JSON avec les noms des propriétés de la classe.
Il y a aussi une fonction GetPdfLink dans l'interface de classe, qui retourne un lien pour télécharger un fichier pdf avec des informations depuis USRLE en concaténant l'adresse Web et la valeur de la propriété «T».
La classe principale implémentant l'interface déclarée ci-dessus sera comme ceci:
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;
Comme vous pouvez le voir, en plus de l'implémentation des deux fonctions principales de l'interface, toutes les autres propriétés et méthodes de la classe seront masquées et nécessaires uniquement pour l'implémentation interne. En général, ils pourraient être inclus dans les méthodes principales, mais nous avons déjà suivi les leçons sur le code en double, la visualisation et la refactorisation en général.
Étant donné l'encapsulation des actions préparatoires, les principales méthodes ne différeront généralement qu'en formant une chaîne de paramètres de requête http et le type de données renvoyé.
code de méthode 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;
Ici, comme nous pouvons le voir, la méthode utilise également le paramètre booléen isLegal, et s'il n'est pas défini sur true, la recherche passe par la base de données des entrepreneurs au lieu des entités juridiques.
code de méthode 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;
Le rôle des méthodes utilitaires est le suivant:
ProcessCaptcha - télécharge la page html initiale du Federal Tax Service, recherche un jeton captcha, télécharge une image générée par ce jeton et la redirige vers une méthode de rappel pour la reconnaissance captcha. À la fin, la méthode définit également les en-têtes corrects pour la demande POST suivante.
GetCaptchaToken - charge tous les champs d'entrée de la page dans la structure DOM, recherche un champ caché avec l'identifiant capthcaToken et renvoie sa valeur.
GetLegalsList - en utilisant la fonction RTTI, JSONToCollection renvoie une collection d'objets de type TEGRULItem décrits ci-dessus.
GetPdfLink - pour rechercher par OGRN ou TIN, dans le cas approprié, un seul résultat sera toujours renvoyé, donc dans GetExtractByOGRN, la fonction est appelée pour le premier élément de la collection.
Comme c'est ma première expérience avec un réseau en freepascal, je suis très heureux que tout se soit déroulé exactement comme je le souhaitais. Sous une forme fonctionnelle, la bibliothèque a été réalisée en moins d'une journée (grâce aux utilisateurs du forum de freepascal.ru, qui ont parlé de synapse).
Une archive avec un test de la bibliothèque résultante et de son code est ici .
Comme toujours, je serai heureux de toute critique constructive à la fois sur le projet et sur la mise en œuvre. Je comprends qu'il existe de nombreux facteurs qui peuvent encore être pris en compte: le retard dans la réponse à la demande http, à la suite de quoi l'application va se bloquer; Réponses http incorrectes et autres situations.
À l'avenir, je prévois de connecter une bibliothèque en ligne à la base de données d'adresses FIAS et de réaliser la capacité de générer des modèles de demande remplis, qui sont généralement édités dans le programme de préparation de documents pour l'enregistrement par l'État .

PS Désolé, Sberbank, pour le rôle du lapin expérimental et des centaines de fois l'extrait téléchargé. Tout cela au nom de la science, bien sûr.