使用Freepascal自动从USRLE获取信息



在我的工作(合法)中,我准备使仅适用于此的一切自动化。 但是,直到出现了来自德国Gref乌托邦的由神经网络泵送的机器人 ,并且没有从普通律师那里获得所有工作,这种惯例在很长一段时间内仍将是我们的主要伴侣。 我在过去的几年中定期执行此例程的自动化操作,无论它是Excel中的众多表格,还是一堆公式,这些公式都可以让您以文字或自动生成的报告的形式快速打印一百种相同类型的邮件文档。 但是有些事情是简单的公式和替换无法做到的。 这是我从小就喜欢的编程方法,而恰好是从delphi开始的。 现在,对我来说,比起我最近开始学习的C#或python来,使用freepascal在Lazarus环境中快速进行某种项目要容易得多。 是的,我坚信这种环境的功能已绰绰有余。 因此,您猜对了,要使用pascal自动完成USRLE。

咨询办公室的律师,负责数十个法律实体的业务,公司面包律师以及任何其他需要确保组织活动的律师-他们都知道在您的脑海中有成百上千个不同的名称,TIN,PSRN编号如何出现,很容易忘记经理是谁,并且在他的续约期限合适的时候,LLC的股份和其注册资本的支付是否存在任何问题。 好吧,需要快速制作某种文档,其中包括许多不断变化的细节,这会带来周期性的错误和错别字。 为了使这样的过程自动化,我需要一个数据库解决方案,该解决方案允许我使用模板创建文档,维护各种注册表,跟踪更改并且不错过任何截止日期。 好吧,简化生活的必要措施之一是快速收到一份新文件,其中包含来自美国联邦税务局网站的USRLE信息。 当然,没有人说直接使用该网站既漫长又困难,但是同意在不离开应用程序的情况下单击一个按钮会更加有趣,并且您可以做到这一点而不会打断电话(或一杯咖啡)。

因此,一开始,我们将决定我们想要得到什么。 该站点允许您 USRLE的正式注册簿中搜索唯一的OGRN或TIN编号,并以有关此人的简要信息以及带有该说明的pdf文件下载链接的形式给出一个相关结果。 同样,搜索可能会因名称模糊而带有按区域的附加过滤器(俄罗斯联邦的主题)。 在这种情况下,该站点将发布一张表,其中包含所有合适的人和具有相同数据集的数据,包括指向pdf的链接。

因此,在特定情况下,现成的函数应以文件(或更好的是流)的形式返回pdf,在输入处带有OGRN或TIN。 但是,为了实现通用性和进一步扩展的可能性,我们不会忽略站点的所有功能,并且会提供模糊搜索功能,并返回通过组织名称找到的一组数据(带或不带区域过滤器)。 让我们尝试描述这些函数的接口:

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

为了了解神秘参数X以及第二个函数将返回的集合,我们将看到站点如何执行请求。

1.该网站的表单中包含用于搜索和检查验证码的标识符的输入字段:



2.验证码是使用名称为captchaToken的预先生成的隐藏字段生成的,该字段使用Java脚本为此令牌生成验证码图像。

3.单击“查找”按钮后,将向服务器发送POST请求,并在处理结果中返回带有对象数组的JSON。 该JSON响应使用另一个Java脚本填充我们在搜索结果中看到的表。

因此,第一个障碍是验证码检查。 为了不给我们过多的功能与网站交互方法带来负担,我们将采取措施将验证码作为单独的功能进行处理。 在X中,我们将为回调方法提供一个参数,该参数的输入端是一个验证码图像,而输出端是一个带有识别的验证码的字符串:

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

处理验证码的功能可以按照您的意愿进行:让用户手动输入验证码,将图像发送到付费服务器以进行自动识别,并使用该算法的专有技术独立地对其进行识别。 为了简化图片,并且就我而言,在工业规模上不需要验证码流,因此我们选择第一个选项:

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

第二个问题是服务器JSON响应的内容。 这是其中的一个示例:

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"}, ] } 


如您所见,结果返回一个查询对象,该对象包含初始搜索参数(以便它们保留在表单字段中以供重用)和行数组。 pdf文件的链接使用以下表达式与Java脚本组合:
  “ https://egrul.nalog.ru/download/” 
和对象的键值“ T”。 生成的pdf文件的生命周期为几分钟。

创建http请求时遇到的两个主要困难是正确的头值以及将字符串与POST请求的参数组合在一起。 但是,使用内置的浏览器工具(通过按F12键在Chrome中调用)对页面进行简单分析即可提供所需的一切。 这是服务器提供正确答案而不是400错误请求的标头示例:

 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 

这是带有参数的行:

 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 

有了这些初始数据,我们便开始执行任务。 我将对freepascal使用以下库:

Synapse是一个非常方便的库,具有将HTTP请求发送到服务器的最简化(使用)功能,它也可与SSL一起使用,但这需要在项目文件夹或系统中存在openSSL库 ,以及连接其他模块。 将以下库模块连接到我们的项目就足够了:httpsend,ssl_openssl,synautil。

fcl-json内置库 -必需的模块:fpjson和fpjsonrtti-为了最大程度地方便处理返回JSON的对象。

fcl-xml内置库的单独模块 -对于某些功能,您需要将HTML的某些部分作为DOM对象使用,因此我们将连接SAX_HTML,DOM_HTML,DOM模块。

让我们描述最终出现的对象的类型和类别:

 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; 

在此类中,我们将打包将在服务器JSON响应中的rows数组中返回的对象。 我们将使用JSONToCollection读取它们,但是为此,我们需要使每个对象成为集合的元素,并将所有相关属性声明为已发布。 freepascal(以及delphi)中的RTTI函数只有在此范围内声明时才能访问属性名称。 fpjsonrtti模块中的JSONToCollection函数只是一个RTTI函数,它将JSON对象中的键名与类属性名进行比较。

类接口中还有一个GetPdfLink函数,该函数通过连接网址和“ T”属性的值返回一个链接,以从USRLE下载带有信息的pdf文件。


实现上面声明的接口的主类将如下所示:

  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; 


如您所见,除了实现接口的两个主要功能之外, 该类的所有其他属性和方法都将被隐藏 ,并且仅在内部实现时才需要。 通常,它们可以包含在主要方法中,但是我们已经大致学习了有关重复代码,可视化和重构的课程。

给定准备动作的封装方式,主要方法通常仅通过形成一串http请求参数和返回的数据类型而有所不同。

方法代码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; 


如我们所见,这里的方法还使用布尔参数isLegal,如果未将其设置为true,则搜索将遍历企业家数据库而不是法人实体。

方法代码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; 


实用程序方法的作用如下:

ProcessCaptcha-下载联邦税收服务的初始html页面,搜索验证码令牌,下载由该令牌生成的图片,然后将其重定向到用于识别验证码的回调方法。 最后,该方法还为后续的POST请求设置正确的标头。

GetCaptchaToken-将页面中的所有输入字段加载到DOM结构中,使用标识符capthcaToken搜索隐藏字段并返回其值。

GetLegalsList-使用RTTI函数JSONToCollection返回上述TEGRULItem类型的对象的集合。

GetPdfLink-用OGRN或TIN搜索,在正确的情况下,将始终只返回一个结果,因此在GetExtractByOGRN中,将为集合中的第一个元素调用该函数。

由于这是我第一次使用freepascal网络,因此我很高兴一切都按我的预期进行。 以一种有效的形式,该库是在不到一天的时间内完成的(感谢来自freepascal.ru的论坛用户,他们谈到了突触)。

带有结果库及其代码测试的存档在这里

一如既往,无论是对项目还是对实施的建设性批评,我都会感到高兴。 我了解仍然有许多因素需要考虑:响应http请求的延迟,导致应用程序冻结; 错误的http响应和其他情况。

将来,我计划将在线图书馆与FIAS地址数据库连接起来,并实现生成完整的应用程序模板的功能,这些模板通常在“文档准备程序”中进行编辑以进行状态注册


PS Sorry,Sberbank,非常感谢实验兔的角色,并提取了数百倍的摘录。 当然,所有这些都是以科学的名义。

Source: https://habr.com/ru/post/zh-CN419063/


All Articles