Не так давно прочел статью Что нам стоит сайт распарсить. Основы webdriver API и вспомнил, что давно собирался довести хотя бы до относительно рабочего состояния одну забавную задумку. Руки все-таки дошли, а значит пора поведать, что же получилось.
Есть такая замечательная программа — Fiddler, позволяющая перехватывать и модифицировать http/https запросы. Есть замечательная штука под названием NW.js, она же node-webkit, позволяющая тыр… парсить разнообразные сайты в том числе. Вы красивы, я прекрасен — почему бы нам не подружиться?
Собственно, затея вот в чем: можно было бы, конечно, отдельно поднять Fiddler, написать в нем логику и гонять через него трафик с node-webkit-но это не так интересно. А значит, будем совмещать все под одной крышей, благо у Fiddler есть библиотека на C# — FiddlerCore.
Под ноду есть отличный модуль — Edge.js. Это такая хитрая штука, которая позволяет исполнять код C# (и не только). Есть под ноду? Замечательно, можно завести и под nw.js, благо даже мануал есть — да вот же он!
Итак, пропустим пару часов секаса по сборке этой замечательной библиотеки(сколько раз говорить себе, читай мануалы внимательно! Нужна 13 студия, не 12 и не 15!) и приступим к написанию кода. Останавливаться на подключении и загрузке модулей тоже не буду, предположим, что те, кого заинтересует эта статья, мануалы читать умеют(да понял я, понял, что мануалы нужно читать внимательно!).
//#r "System.Windows.Forms.dll"
//#r "fiddler/FiddlerCore.dll"
using Fiddler;
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
public class Startup
{
Func<object, Task<object>> _console;
Func<object, Task<object>> _html;
public async void _print(object text){
if(_console!=null)
await _console(text);
}
public async void _getHtml(object html)
{
if (_html != null)
await _html(html);
}
public async Task<object> Invoke(dynamic data)
{
_console = (Func<object, Task<object>>)data.console;
_html = (Func<object, Task<object>>)data._html;
_print("Started");
FiddlerApplication.Shutdown();
new FiddlerLogic
{
_beforeRequest = (oS) => {
var proxy = oS.oRequest.headers["POverride"];
if (proxy != null)
{
oS["X-OverrideGateway"] = proxy;
}
},
_beforeResponse = (oS) =>
{
var response = oS.GetResponseBodyAsString();
_html(response);
}
}._start(5000);
return Task.FromResult("Done");
}
}
class FiddlerLogic
{
public Action<Session> _beforeRequest;
public Action<Session> _beforeResponse;
public void _start(int port=5555)
{
FiddlerApplication.BeforeRequest += (oS) => _beforeRequest(oS);
FiddlerApplication.BeforeResponse += (oS) => _beforeResponse(oS);
FiddlerApplication.Startup(port, false, true);
}
}
Итак, что же здесь происходит?
Эти вот 2 строки
//#r «System.Windows.Forms.dll»
//#r «fiddler/FiddlerCore.dll»
являются маркером для edge.js, нужны для подключения библиотек.
Класс FiddlerLogic — всего лишь небольшой враппер над Fiddler'ом, в котором был код для подключения сертификатов, но потом при помощи какой-то уличной магии(скорее всего, нашел в загашнике старую версию, которая этот самый код не требовала) оно заработало и так. Теперь этот класс, по факту, ничего особого не делает, но куда ж нынче без legacy-кода? Собственно, в конструкторе объекта мы указываем 2 callback'а, которые будут вызываться перед/после отправки запроса, порт(при необходимости) и все.
Ах да, в FiddlerApplication.Startup 2 и 3 аргументы отвечают за использование системного прокси/перехват https-запросов соответственно. Так как хотелось бы перехватывать https-запросы и нафиг не нужно использование системного прокси, значения — false и true.
Теперь про Startup. Это такой забавный класс, нужный для работы с edge.js(об этом модуле позже). Собственно, Invoke — это точка входа, console/_html — функции из js. FiddlerApplication.Shutdown() отвечает за завершение всех предыдущих инстансов фиддлера. В _beforeRequest происходит подмена прокси при наличии заголовка POverride в запросе. В _beforeResponse ничего шибко полезного не происходит, оставил для примера. Функции _print и _getHtml — просто обертки с проверкой на наличие переданных из js функций.
Теперь рассмотрим js-часть.
var edge = require('edge');
var gui = require('nw.gui');
var async = require('async');
var request = require('request');
var start=Date.now();
var arr=[];
var count=50;
var prev=Date.now();
var proxyList = ['160.92.56.41:80'];
_init();
for(var i=0; i<count; i++){
arr.push('http://myip.ru/index_small.php')
}
var i=0;
var _node=function(url, c){
console.log(url);
var options = {
url: url,
headers: {
'POverride': proxyList[0]
}
};
request(options, function(err, response, html){
if(err)
console.log(err);
var j=_html(html);
console.log('Container:', j.find('.network-info tbody>:nth-child(2) td').text());
c();
});
};
async.map(arr, _node,
function(){
var ms=Date.now()-start;
console.info('Node parse got %f seconds. Mid time: %f. Mid page per second: %f', ms/1000, ms/count, count*1000/ms);
}
);
/***DEFINITIONS***/
function _html(html){
return $('<div></div>').html(html);
};
function _init(){
try{
request=request.defaults({'proxy':'http://localhost:5000'});
gui.App.setProxyConfig("http://localhost:5000");
func = edge.func("fiddler/Main.cs");
func({
console: function(data, callback){
console.log(data);
},
_html: function(html, callback){
//var container=_html(html);
//console.log('Container:', container, container.find('.network-info tbody>:nth-child(2) td'));
}
});
}
catch(ex){
console.log(ex);
}
};
В первую очередь стоит обратить внимание на 2 функции снизу — _html и _init. Первая занимается непотребством в виде построения DOM из строки, 2 — базовые настройки и подключение C# кода.
Edge.func подгружает содержимое Main.cs(см. код выше), и передает в качестве аргументов ссылки на нужные функции. Собственно, аргумент функции func и есть data из public async Task
Комментарии (4)
RouR
17.12.2015 22:29Собственно, затея вот в чем: можно было бы, конечно, отдельно поднять Fiddler, написать в нем логику и гонять через него трафик с node-webkit-но это не так интересно. А значит, будем совмещать все под одной крышей, благо у Fiddler есть библиотека на C#
Что-то не уловил суть происходящего, зачем это делается, можете рассказать подробнее в контексте бизнес-логики?Demogor
17.12.2015 23:11+1В самом посте содержится простой пример — возможность изменения прокси для каждого запроса по отдельности либо по необходимости. Кроме того, удобнее все держать в 1 пакете, нежели в отдельных приложениях, для каждого из которых нужно писать инструкцию в случае доставки конечному пользователю. Также fiddler позволяет перехватывать и менять не только управляемые пользователем запросы, а вообще весь входящий/исходящий трафик, включая(если не ошибаюсь) веб-сокеты. Соответственно, получаем достаточно удобный инструмент для отладки происходящего на сайте, включая те случаи, когда отладчика вебкита недостаточно(впрочем, никто не мешает использовать и его тоже). К примеру, та же консоль отлично подходит для тех случаев, когда нужно увидеть исходящие/входящие запросы, однако довольно проблематично поменять, к примеру, User-Agent.
sopov
Если есть DLL, то не проще ли использовать github.com/node-ffi/node-ffi
Demogor
DLL нет. код можно менять на лету в .CS. Кроме того. ffi умеет вроде только unmanaged dll. Впрочем. ffi я тоже использовал. но для других задач. удобная штука.