Блог программиста Еремина Вячеслава Викторовича
(Flex) Flex (2011 год)

Парсинг AJAX-сайтов в среде AIR (путем выполнения jQuery-запросов из ActionScript).

В 2005-году я работал в проекте электронного магазином http://digitalshop.ru/ и когда я подвел итоги этой работы - то обнаружил, что разнообразные пауки и парсеры составляли весьма существенную часть всей моей полезной деятельности в этом проекте. Это и неудивительно - ведь интернет-магазин набивает свою базу товаров для продажи из товаров, предлагаемых другими фирмами на своих сайтах. Но когда я оценил - настолько много в моей жизни занимали интернет-парсеры - я перестал относится к ним, как к некоторым вспомогательным программам, а стал рассматривать парсеры, как одну из своих основных специализаций.

Позднее я сделал WebDownloader_UltraLite - ваш личный поисковик по рунету с особыми возможностями поиска, который тоже представляет собой парсер опубликованных в интернете сайтов. Обходя сайт за сайтом в соответствии с заданной политикой, выковыривая из сайтов все новые и новые ссылки - этот мой паук за несколько месяцев работы обходит значительную часть рунета и получается некий аналог ГУГЛА или Яндекса. Но только в моей поисковой машине можно задавать поисковые запросы получше, чем в обычных убогих поисковых машинах ГУГЛА или Яндекса. Например о хостинге, владельцах домена и прочее. А задав своей поисковой машине вопрос о наличии на страничке скрытого поля VIEWSTATE - я получил ответ ( которому сам удивился) - об общем проценте сайтов, сделанных на фирменной билогейтсовской технологии ASP.NET - всего 0,4%.


Теперь перейдем от общих рассуждений о пауках и парсерах (и почему это не просто вспомогательные проги, а целое специализация-направление интернет-программирования) - к технологической основе пауков и парсеров. Обычная основа парсеров на платформе NET - это WebRequest - WebResponse - как я многократно описывал на своем сайте, например WCF_CLIENT - клиент Web-сервиса, Тестирование производительности Web-приложений, GoogleTranslate - англо-русский онлайн переводчик, Этюды на ASP NET2. Наблюдаем за своим домом с работы, Remote SQL execute for PostgreSQL on GSM/GPRS channel with extreme compress and cryptography. Причем зачастую реквесты выполняются не просто из кода сайта, а из SQL-CLR-сборок работающих внутри SQL-сервера, например SQL-Client_for_remote_XML-WebService - клиент meteonova.ru, Реализация таймаута на динамически создаваемых SQL JOB, вызывающих SQL CLR сборку.


С начала 2011-го года я начал интенсивно программировать Flex/Air и на просто влюбился в эту платформу - у меня на лету решаются практически неразрешимые на NET проблемы. И вот я получаю очередной заказ на очередной парсер сайта (работающего на jQuery и выводящими свои таблицы без постбека - только с помощью AJAX). Понятно, что запросто (по WebRequest - WebResponse) данные с таких страничек не забрать. Тем более там в AJAX-реквестах этого сайта ходят какие-то защитные ключи, которые удостоверяют что AJAX-реквест ушел в ответ на клики мышкой. Плюс часть полезной информации передается цветом и ее не возьмешь простым копипастом. Самый реальный путь забрать все данные с такого крученого сайта - положить эту страничку во внутрь браузера, встроенного или в NET или в AIR. На платформе Билла Гейтса - вариантов нет - войти внутрь контрола, вычитать HTML из контрола, потом есть некая кривая на всю голову библиотека mshtml (для которой Билогейтсовская бригада так и не удосужилась изготовить NET-врапперы), потом попытаться вычитанный HTML скормить этой библиотеке - и таким образом избежать строчного парсинга HTML. Собственно этой технологией я пользовался с момента начала программиования на платформе .NET - с 2002-го года. В 2006-м году я даже выкладывал у себе на сайте некий OpenSource, сделанный по такой технологии - SiteChecker - утилита оптимизации сайта.


А что если решить эту задачку на AIR? Ведь Flex и AIR - фактически это Яваскрипт, откомпилированный в байт-код. И у меня возникла бешеная идея - ведь мне надо парсить AJAX-сайт на jQuery ... jQuery уже есть внутри контрола ... а что если ... взять и прямо внутри кода AIR выполнить jQuery-запрос на языке ActionScript по html-данным внутри контрола?!? И вытащить в AIR сразу весь результат jQuery-запроса $('#table1 td'). Я попробовал - и получилось! Работает превосходно!



Итак, ниже вы можете увидеть - как я это сделал:


   1:  <?xml version="1.0" encoding="utf-8"?>
   2:  <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
   3:                         xmlns:s="library://ns.adobe.com/flex/spark" 
   4:                         xmlns:mx="library://ns.adobe.com/flex/mx"
   5:                         applicationComplete="windowedapplication1_applicationCompleteHandler(event)" 
   6:                         width="1000" height="820">
   7:      
   8:      <s:layout>
   9:          <s:BasicLayout/>
  10:      </s:layout>
  11:      
  12:      <fx:Script>
  13:          <![CDATA[
  14:              import mx.core.ByteArrayAsset;
  15:              import mx.events.FlexEvent;
  16:              import spark.components.TextArea;
  17:              import spark.components.Window;
  18:              
  19:              [Embed(source="AJAX_TEST.htm",mimeType="application/octet-stream")]
  20:              private var BENTOUR_TEST : Class;    
  21:              
  22:              protected var Win1:Window;
  23:              
  24:              protected function windowedapplication1_applicationCompleteHandler(event:FlexEvent):void
  25:              {
  26:                  //реальный запрос
  27:                  Browser1.location="http://AAA.BBB.CCC.DDD:EEE/monitor"
  28:                  
  29:                  //***********  или  ************************    
  30:                      
  31:                  //отлдка на тестовой страничке, встроенной в ресурс
  32:                  //var HTML:String;
  33:                  //var HTML_TEST_ByteArray:ByteArrayAsset = ByteArrayAsset(new BENTOUR_TEST());
  34:                  //HTML= HTML_TEST_ByteArray.readUTFBytes(HTML_TEST_ByteArray.length);
  35:                  //Browser1.htmlText=HTML;
  36:                  
  37:              }
  38:              
  39:              protected function GoButton_clickHandler(event:MouseEvent):void
  40:              {
  41:                  var TopJavaScriptObject=Browser1.domWindow;
  42:                  open_Win1(Parse(TopJavaScriptObject));    
  43:              }
  44:              
  45:              protected function Parse(TopJavaScriptObject):String{
  46:                  
  47:                  //TopJavaScriptObject.$("#Table_0211 tr")                   - доступ к jQuery  
  48:                  //TopJavaScriptObject.document.getElementById("Table_0211") - доступ к JavaScript
  49:                  //TopJavaScriptObject.document.links                        - доступ к элементам DOM
  50:                  
  51:                  var RetString:String = "";
  52:                  var Table_TD = [];
  53:                  var RowNumber:int = TopJavaScriptObject.$('.res tr').length
  54:                  Table_TD=TopJavaScriptObject.$('.res tr td');
  55:                  for (var i=0;i<RowNumber-1;i++){
  56:                      
  57:                      RetString = RetString.concat (Table_TD[16*i+1].innerText + ",");
  58:                      RetString = RetString.concat (Table_TD[16*i+2].innerText + ",");
  59:                      RetString = RetString.concat (Table_TD[16*i+3].innerText + ",");
  60:                      RetString = RetString.concat (Table_TD[16*i+4].innerText + ",");
  61:                      RetString = RetString.concat (Table_TD[16*i+5].innerText + ",");
  62:                      RetString = RetString.concat (Table_TD[16*i+6].innerText + ",");
  63:                      RetString = RetString.concat ( Table_TD[16*i+7].innerText + ",");
  64:                      RetString = RetString.concat ( Table_TD[16*i+8].innerText + ",");
  65:                      RetString = RetString.concat ( Table_TD[16*i+1].innerText + ",");
  66:                      RetString = RetString.concat ( Table_TD[16*i+10].innerText + ",");
  67:                      RetString = RetString.concat ( Table_TD[16*i+11].innerText + ",");
  68:                      RetString = RetString.concat ( Table_TD[16*i+12].innerText + ",");
  69:                      RetString = RetString.concat ( Table_TD[16*i+13].innerText + ",");
  70:                      RetString = RetString.concat ( Table_TD[16*i+14].innerText + ",");
  71:                      RetString = RetString.concat ( Table_TD[16*i+15].innerText + ",");
  72:                      RetString = RetString.concat ( Table_TD[16*i+16].innerText + "\n");
  73:                  }
  74:                  return (RetString);
  75:              }
  76:              
  77:              
  78:              protected function open_Win1(TXT:String):void{
  79:                  Win1 = new Window();
  80:                  Win1.width=800;
  81:                  Win1.height=500;
  82:                  Win1.title="Parse result";
  83:                  Win1.open(true);
  84:                  
  85:                  var Save_Button:Button = new Button();
  86:                  Save_Button.x=0;
  87:                  Save_Button.y=0;
  88:                  Save_Button.label="Save"
  89:                  Save_Button.addEventListener(MouseEvent.CLICK,Save_Dialog);
  90:                  Win1.addElement(Save_Button);
  91:                  
  92:                  
  93:                  var OK_Button:Button = new Button();
  94:                  OK_Button.x=80;
  95:                  OK_Button.y=0;
  96:                  OK_Button.label="OK"
  97:                  OK_Button.addEventListener(MouseEvent.CLICK,close_Win1);
  98:                  Win1.addElement(OK_Button);
  99:                  
 100:                  var TextArea1:TextArea=new TextArea (  );
 101:                  TextArea1.x=0;
 102:                  TextArea1.y=20;
 103:                  TextArea1.width=800;
 104:                  TextArea1.height=480;
 105:                  TextArea1.text=TXT;
 106:                  Win1.addElement(TextArea1);
 107:              }
 108:              
 109:              
 110:              protected function close_Win1(event:MouseEvent):void
 111:              {
 112:                  Win1.close();
 113:              }
 114:              
 115:              protected function Save_Dialog(event:Event):void
 116:              {
 117:                  var docsDir:File = File.documentsDirectory;
 118:                  try
 119:                  {
 120:                      docsDir.browseForSave("Save As");
 121:                      docsDir.addEventListener(Event.SELECT, saveFile);
 122:                  }
 123:                  catch (error:Error)
 124:                  {
 125:                      trace("Save failed:", error.message);
 126:                  }
 127:              }
 128:              
 129:              protected function saveFile(event:Event):void
 130:              {
 131:                  var outputStream:FileStream = new FileStream();
 132:                  var newFile:File = event.target as File;
 133:                  
 134:                  if (!newFile.exists)
 135:                  {
 136:                      outputStream.open(newFile, FileMode.WRITE);
 137:                      var TXT:String = (Win1.getElementAt(2) as TextArea).text;
 138:                      outputStream.writeUTFBytes(TXT)
 139:                      outputStream.close();
 140:                  }
 141:              }
 142:              
 143:              
 144:          ]]>
 145:      </fx:Script>
 146:      
 147:      <fx:Declarations>
 148:          <!-- Place non-visual elements (e.g., services, value objects) here -->
 149:      </fx:Declarations>
 150:      
 151:      <mx:HTML x="0" y="20" width="100%" height="800" id="Browser1"/>
 152:      <s:Button x="-1" y="0" label="Parse" id="GoButton" click="GoButton_clickHandler(event)"/>
 153:      
 154:  </s:WindowedApplication>

В данном случае я описываю принцип работы коммерческого приложения, поэтому я прикрыл логотип сайта, для которого я написал этот парсер. Кроме того, в окончательном варианте - это гораздо более крученое приложение, чем я показываю тут. В окончательном варианте это приложение позволяет вводить наценку на билет, комментарии и отправлять данные на сервер по технологии, описанной здесь Как сделать SOAP/WSDL-вебсервис на ASP.NET/MONO для вызова его из FLEX. В коде выше я показал 10% кода готового приложения - но эти 10% демонстрируют методику изготовления парсеров для крученых и защищенных сайтов. Причем не просто парсеров, а парсеров, которые довольно-таки издевательски пользуется защитой сайта. Вся защита сайта организована на jQuery, расположенной непосредственно на самой защищенной страничке сайта. А я вызвал эту самую jQuery прямо из ActionScript и вынес ею же всю нужную мне информацию прямо в свои структуры данных в AIR (и оттуда куда мне надо).

Обратите также внимание, как я отлаживал этот парсер (я специально оставил свои отладочные методики в коде) - я вложил в ресурс копию странички и на ней все отладил собственно парсер - ибо сайт, который я парсил имел еще какую-то защиту - один-два запроса с одного айпишника в сутки и сайт закрывался (по словам моего заказчика). Поэтому я отладил парсер на вложенном ресурсе. И дергать сайт при отладке мне не потребовалось совсем - я даже не стал проверять, после скольких реквестов сайт закрывается. В процессе работы надо этой задачкой мне понадобилось ровно два входа на этот сайт - в первый вход я понял как сайт работает и сохранил себе копию странички для отладки (которую вложил в ресурсы AIR-приложения), а через день - я вошел на этот сайт второй раз с уже отлаженным парсером и за пару кликов мышкой забрал с него всю полезную информацию (включая информацию кодированную цветом).

Еще почитать про мои AIR-приложения вы можете на страничке - AIR приложения для платформ Android, Macintosh и Linux..



Комментарии к этой страничке ( )
ссылка на эту страничку: http://www.vb-net.ru/jQuery_in_AIR/index.htm
<На главную>  <В раздел ASP>  <В раздел NET>  <В раздел SQL>  <В раздел Разное>  <Написать автору>  < Поблагодарить>