Automatisierung des Abrufs von Informationen von USRLE mit Freepascal



In meiner Arbeit (legal) bin ich bereit, alles zu automatisieren, was sich nur dafür eignet. Aber bis die Roboter, die von neuronalen Netzen aus der Utopie des deutschen Gref gepumpt wurden, nicht erschienen sind und nicht die ganze Arbeit von gewöhnlichen Anwälten übernommen haben, wird die Routine für lange Zeit unser Hauptbegleiter bleiben. Die Automatisierung dieser Routine habe ich in den letzten Jahren regelmäßig durchgeführt, sei es in zahlreichen Excel-Tabellen mit einer Reihe von Formeln, mit denen Sie schnell hundert der gleichen Art von Versanddokumenten in Word oder gut automatisch generierten Berichten drucken können. Es gibt jedoch Dinge, die Sie mit einfachen Formeln und Ersetzungen nicht tun können. Hier kommt die Programmierung zum Einsatz, die ich seit meiner Kindheit sehr mag, und es ist einfach so passiert, dass es mit Delphi begann. Jetzt ist es für mich einfacher als in C # oder Python, was ich kürzlich gelernt habe, schnell ein Projekt in der Lazarus-Umgebung mit Freepascal zu erstellen. Und ja, ich glaube ernsthaft, dass die Fähigkeiten dieser Umgebung mehr als genug sind. Daher sollte die USRLE, wie Sie vermutet haben, mit Pascal automatisiert werden.

Ein Anwalt eines Beratungsbüros, der Geschäfte von Dutzenden von juristischen Personen tätigt, ein Anwalt für Unternehmensbrot und jeder andere Anwalt, der mit der Sicherstellung der Aktivitäten von Organisationen konfrontiert ist - alle wissen, wie sich Dutzende und Hunderte verschiedener Namen, TIN- und PSRN-Nummern in Ihrem Kopf vermischen, wie Es ist leicht zu vergessen, wer der Manager ist, und wenn seine Verlängerungsfrist angemessen ist, gibt es Probleme mit den Anteilen an der LLC und der Zahlung des genehmigten Kapitals. Nun, die Notwendigkeit, schnell eine Art Dokument zu erstellen, das viele sich ständig ändernde Details enthält, führt zu regelmäßigen Fehlern und Tippfehlern. Um genau solche Prozesse zu automatisieren, benötigte ich eine Datenbanklösung, mit der ich Dokumente mithilfe von Vorlagen erstellen, verschiedene Register verwalten, Änderungen verfolgen und keine Fristen verpassen konnte. Nun, eine der notwendigen Vereinfachungen im Leben ist der schnelle Erhalt einer neuen Datei mit Informationen von der USRLE von der Website des Federal Tax Service . Natürlich sagt niemand, dass die direkte Nutzung der Website langwierig und schwierig ist, aber Sie sind sich einig, dass das Klicken auf eine Schaltfläche ohne Verlassen der Anwendung viel mehr Spaß macht, und Sie können dies tun, ohne den Anruf (oder eine Tasse Kaffee) zu unterbrechen.

Also werden wir zunächst entscheiden, was wir bekommen wollen. Auf dieser Website können Sie im offiziellen Register der USRLE nach einer eindeutigen OGRN- oder TIN-Nummer suchen und ein relevantes Ergebnis in Form einer kurzen Information über die Person und eines Links zum Herunterladen der PDF-Datei mit einem Auszug angeben. Die Suche kann auch nach Namen mit einem zusätzlichen Filter nach Region (Betreff der Russischen Föderation) unscharf sein. In diesem Fall gibt die Site eine Tabelle mit allen geeigneten Personen und demselben Datensatz aus, einschließlich Links zu PDF.

In einem bestimmten Fall sollte die vorgefertigte Funktion PDF in Form einer Datei (oder besser eines Streams) mit einem OGRN oder TIN am Eingang zurückgeben. Für die Universalisierung und die Möglichkeit einer weiteren Erweiterung werden wir jedoch nicht alle Funktionen der Site vernachlässigen und auch eine Fuzzy-Suchfunktion mit der Rückgabe eines Datensatzes durchführen, der unter dem Namen der Organisation gefunden wurde, wobei der Filter nach Region oder ohne Region berücksichtigt wird. Versuchen wir, die Schnittstellen dieser Funktionen zu beschreiben:

IEGRULstreamer = interface procedure GetExtractByOGRN(OGRN: string; ; isLegal: boolean; var Extract: TStream); procedure GetLegalsListByName(Name, Region: string; ; var LegalsList: TCollection); end; 

Um zu verstehen, was der mysteriöse Parameter X und dessen Sammlung die zweite Funktion zurückgibt, werden wir sehen, wie die Site die Anforderung ausführt.

1. Die Site verfügt über ein Formular mit Eingabefeldern für Bezeichner zum Suchen und Überprüfen von Captcha:



2. Captcha wird mithilfe eines vorgenerierten ausgeblendeten Felds mit dem Namen captchaToken generiert, das mithilfe eines Java-Skripts ein Captcha-Bild für dieses Token generiert.

3. Nach dem Klicken auf die Schaltfläche "Suchen" wird eine POST-Anforderung an den Server gesendet, in deren Verarbeitungsergebnissen JSON mit einem Array von Objekten zurückgegeben wird. Diese JSON-Antwort verwendet ein anderes Java-Skript, um die Tabelle zu füllen, die in den Suchergebnissen angezeigt wird.

Der erste Haken ist also der Captcha-Check. Um unsere Interaktionsmethoden mit der Site nicht mit übermäßiger Funktionalität zu belasten, werden wir Maßnahmen ergreifen, um Captcha als separate Funktion zu verarbeiten. Und in X haben wir einen Parameter für die Rückrufmethode, der ein Captcha-Bild am Eingang und einen String mit erkanntem Captcha am Ausgang enthält:

 TCapthcaRecognizeFunc = function(Captha: TStream): string of object; ... procedure GetExtractByOGRN(OGRN: string; CaptchaFunc: TCapthcaRecognizeFunc; isLegal: boolean; var Extract: TStream); 

Die Funktion, die das Captcha verarbeitet, kann dies nach Ihren Wünschen tun: Lassen Sie den Benutzer es manuell eingeben, senden Sie das Bild zur automatischen Erkennung an einen kostenpflichtigen Server und erkennen Sie es unabhängig anhand des einzigartigen Know-hows des Algorithmus. Zur Vereinfachung des Bildes und da in meinem Fall kein Captcha-Fluss im industriellen Maßstab erwartet wird, wählen wir die erste Option:

 function TForm1.RecognizeFunc(captcha: TStream): string; begin CaptchaImg.Picture.LoadFromStream(captcha); Result := InputBox('','    ', ''); end; 

Die zweite Frage ist der Inhalt der Server-JSON-Antwort. Hier ist ein Beispiel dafür, was darin enthalten ist:

JSON-formatierte Antwort
 { "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"}, ] } 


Wie Sie sehen können, gibt das Ergebnis ein Abfrageobjekt zurück, das die anfänglichen Suchparameter (damit sie zur Wiederverwendung in den Formularfeldern verbleiben) und ein Array von Zeilen enthält. Der Link zur PDF-Datei wird mit einem Java-Skript unter Verwendung des folgenden Ausdrucks kombiniert:
  "https://egrul.nalog.ru/download/" 
und der Schlüsselwert "T" des Objekts. Die Lebensdauer der generierten PDF-Datei beträgt einige Minuten.

Die beiden Hauptschwierigkeiten, auf die ich beim Erstellen der http-Anforderung gestoßen bin, sind die korrekten Header-Werte und das Kombinieren der Zeichenfolge mit den Parametern der POST-Anforderung. Eine einfache Analyse der Seite mit den integrierten Browser-Tools (in Chrome wird durch Drücken von F12 aufgerufen) ergab jedoch alles, was Sie benötigen. Hier ist ein Beispiel für Header, mit denen der Server die richtige Antwort anstelle von 400 Bad Request gibt:

 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 

Und hier ist die Zeile mit den Parametern:

 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&regionul=73 &srchFl=ogrn&ogrninnfl=&fam=&nam=&otch=&region=&captcha=449023&captchaToken=DAEDA 7504CACAC82CF09E08319B68DF5F9BD62B2F44D33DD679DDE55B5CF58B17FEC84E78CEEB9639 84D2B2BD8C3AA15 

Ausgerüstet mit diesen Anfangsdaten fahren wir mit der Implementierung der Aufgabe fort. Ich werde die folgenden Bibliotheken für Freepascal verwenden:

Synapse ist eine sehr praktische Bibliothek mit der einfachsten (zur Verwendung) Funktion zum Senden von http-Anforderungen an den Server. Sie funktioniert auch mit SSL. Dies erfordert jedoch das Vorhandensein von openSSL-Bibliotheken im Projektordner oder -system sowie den Anschluss eines zusätzlichen Moduls. Es reicht aus, die folgenden Bibliotheksmodule mit unserem Projekt zu verbinden: httpsend, ssl_openssl, synautil.

Die integrierte Bibliothek fcl-json - die erforderlichen Module: fpjson und fpjsonrtti - für die maximale Bequemlichkeit der Verarbeitung von an JSON zurückgegebenen Objekten.

Separate Module der integrierten Bibliothek fcl-xml - Für einige Funktionen müssen Sie mit Teilen von HTML als DOM-Objekte arbeiten, damit wir die Module SAX_HTML, DOM_HTML, DOM verbinden.

Beschreiben wir die Arten und Klassen von Objekten, die sich letztendlich herausstellten:

 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; 

In dieser Klasse packen wir Objekte, die im Zeilenarray in der Server-JSON-Antwort zurückgegeben werden. Wir werden sie mit JSONToCollection lesen, aber dafür müssen wir jedes Objekt zu einem Element der Sammlung machen und alle zugehörigen Eigenschaften als veröffentlicht deklarieren. RTTI-Funktionen in freepascal (sowie in delphi) erhalten nur dann Zugriff auf Eigenschaftsnamen, wenn sie in diesem Bereich deklariert sind. Die JSONToCollection-Funktion aus dem fpjsonrtti-Modul ist nur eine RTTI-Funktion, die die Namen der Schlüssel aus dem JSON-Objekt mit den Namen der Klasseneigenschaften vergleicht.

Es gibt auch eine GetPdfLink-Funktion in der Klassenschnittstelle, die einen Link zum Herunterladen einer PDF-Datei mit Informationen aus der USRLE zurückgibt, indem die Webadresse und der Wert der Eigenschaft "T" verkettet werden.


Die Hauptklasse, die die oben deklarierte Schnittstelle implementiert, sieht folgendermaßen aus:

  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; 


Wie Sie sehen, werden neben der Implementierung der beiden Hauptfunktionen der Schnittstelle alle anderen Eigenschaften und Methoden der Klasse ausgeblendet und nur für die interne Implementierung benötigt. Im Allgemeinen könnten sie in die Hauptmethoden aufgenommen werden, aber wir haben bereits die Lektionen über doppelten Code, Visualisierung und Refactoring im Allgemeinen durchgearbeitet .

Angesichts der Kapselung vorbereitender Maßnahmen unterscheiden sich die Hauptmethoden im Allgemeinen nur durch die Bildung einer Zeichenfolge von http-Anforderungsparametern und des zurückgegebenen Datentyps.

Methodencode 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=&regionul=&ogrninnfl='; if not isLegal then Params += OGRN; Params += '&fam=&nam=&otch=&region&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; 


Wie wir sehen können, verwendet die Methode hier auch den booleschen Parameter isLegal. Wenn dieser Wert nicht auf true gesetzt ist, wird die Suche in der Datenbank von Unternehmern anstelle von juristischen Personen durchgeführt.

Methodencode 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 + '&regionul=' + Region + '&ogrninnfl=&fam=&nam=&otch=&region'; Params += '&captcha=' + captcha + '&captchaToken=' + captchaToken; WriteStrToStream(HTTPSender.Document, Params); if not HTTPSender.HTTPMethod('POST', EGRUL_URL) then raise Exception.Create('   '); LegalsList := GetLegalsList; end; 


Die Rolle von Dienstprogrammmethoden ist wie folgt:

ProcessCaptcha - Lädt die erste HTML-Seite des Federal Tax Service, sucht nach einem Captcha-Token, lädt ein von diesem Token generiertes Bild herunter und leitet es an eine Rückrufmethode zum Erkennen von Captcha weiter. Am Ende setzt die Methode auch die richtigen Header für die nachfolgende POST-Anforderung.

GetCaptchaToken - Lädt alle Eingabefelder von der Seite in die DOM-Struktur, sucht nach einem versteckten Feld mit dem Bezeichner capthcaToken und gibt dessen Wert zurück.

GetLegalsList - Mit der RTTI-Funktion JSONToCollection wird eine Sammlung von Objekten vom oben beschriebenen Typ TEGRULItem zurückgegeben.

GetPdfLink - Um nach OGRN oder TIN zu suchen, wird im richtigen Fall immer nur ein Ergebnis zurückgegeben. Daher wird in GetExtractByOGRN die Funktion für das erste Element in der Auflistung aufgerufen.

Da dies meine erste Erfahrung mit einem Netzwerk in Freepascal ist, bin ich sehr froh, dass alles genau so gelaufen ist, wie ich es beabsichtigt hatte. In einer funktionierenden Form wurde die Bibliothek in weniger als einem Tag erstellt (dank der Forumbenutzer von freepascal.ru, die über Synapse sprachen).

Ein Archiv mit einem Test der resultierenden Bibliothek und ihres Codes ist hier .

Wie immer freue ich mich über jede konstruktive Kritik sowohl am Projekt als auch an der Umsetzung. Ich verstehe, dass noch viele Faktoren berücksichtigt werden können: die Verzögerung bei der Beantwortung der http-Anfrage, wodurch die Anwendung einfriert; Falsche http-Antworten und andere Situationen.

In Zukunft plane ich, eine Online-Bibliothek mit der FIAS-Adressdatenbank zu verbinden und die Möglichkeit zu realisieren, fertige Anwendungsvorlagen zu generieren, die im Allgemeinen im Dokumentvorbereitungsprogramm für die staatliche Registrierung bearbeitet werden.


PS Entschuldigung, Sberbank, für die Rolle des experimentellen Kaninchens und das hundertefache Herunterladen des Extrakts. Natürlich alles im Namen der Wissenschaft.

Source: https://habr.com/ru/post/de419063/


All Articles