Парсинг 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..
|