前のパートでは、Webソースからデータを収集するプロセスの概要を説明しました。 この投稿では、WatiNを使用してさまざまなサイトを処理するための汎用ホストを作成する方法を示します。 また、WatiNを使用する際のマルチスレッドの問題に対処します。 ソースは、いつものように
ここにあり
ます 。
MEFを使用する一般的なWatiNホスト
複数のWatiN管理サービスを実行するのは危険なので、プラグインアーキテクチャを実装するサービス(ホスト)を使用してこのプロセスを制御する必要があります。 まず、WatiNが管理するサービスが機能するインターフェースを定義しましょう。
public abstract class WatinDataAcquisitionService : DataAcquisitionService<br/>
{<br/>
/// <summary>
/// This method must be implemented by any scraping service that needs to
/// use WatiN.
/// </summary>
/// <param name="browser">A preinitialized <c>Browser</c> object
/// that one can use for scraping.</param>
/// <remarks>Do not pass the <c>browser</c> object into other
/// threads or asynchronous operations.</remarks>
public abstract void AcquireData(Browser browser, ILog log);<br/>
}<br/>
インターフェースには、スクレイピングを実行するための1つの方法しかありません。 このサービスでは、タイプが
Browser
(
IE
または
FireFox
いずれか)の既に初期化されたオブジェクトと、メインサービスからロガーへのリンクを渡します。これにより、メインホストからプロセスを記録できます。
使用可能なすべてのWatiNサービスを取得するために、ホストはMEFを使用して、
WatinDataAcquisitionService
タイプのすべてのオブジェクトをダウンロードすることを宣言しています。
[ImportMany( typeof (WatinDataAcquisitionService))]<br/>
public WatinDataAcquisitionService[] WatinServices { get; set; }<br/>
利用可能なサービスのロードは、サービス自体の初期化で発生します。 この場合、
plugins
サブディレクトリ内のすべてのDLLを見つけるだけです。
cat = new DirectoryCatalog( "plugins" );<br/>
cc = new CompositionContainer(cat);<br/>
cc.ComposeParts( this );<br/>
ステレオタイプの
DoWork()
メソッドはかなりツイスティに見えます。 最初に見せましょう:
private void DoWork()<br/>
{<br/>
while ( true )<br/>
{<br/>
log.InfoFormat( "Found {0} WatiN services" , WatinServices.Length);<br/>
if (WatinServices.Length > 0)<br/>
using ( var browser = new IE())<br/>
{<br/>
browser.Visible = false ;<br/>
foreach ( var s in WatinServices)<br/>
{<br/>
using ( var timer = new MyTimer(s.GetType().FullName, log))<br/>
{<br/>
// prevent errors from bleeding through
try <br/>
{<br/>
s.AcquireData(browser, log);<br/>
}<br/>
catch (Exception ex)<br/>
{<br/>
log.Error(<br/>
string .Format( "WatiN service {0} threw an exception" , s.GetType().FullName),<br/>
ex);<br/>
}<br/>
}<br/>
}<br/>
}<br/>
// do some work, then
Thread.Sleep(pollingFrequency);<br/>
}<br/>
}<br/>
ここでは、時間の測定、サービスの開始、作成者が髄膜バリアの突破を許可した場合のエラーの記録(ハウスを監視する必要があります)が発生します。 サービスは順番に呼び出されるため、すべて互いに干渉することなくブラウザを使用します。
プラグインに関しては、すべてが非常に簡単です-これは、
Export
属性でマークされたクラスが存在するDLLです。 このようなもの:
[Export( typeof (WatinDataAcquisitionService))]<br/>
public class PokemonService : WatinDataAcquisitionService<br/>
{<br/>
public override void AcquireData(Browser browser, ILog log)<br/>
{<br/>
log.Info( "Pokemon service running" );<br/>
browser.GoTo( "http://www.pokemon.com" );<br/>
var doc = new HtmlDocument();<br/>
doc.LoadHtml(browser.Body.OuterHtml);<br/>
var h3 = doc.DocumentNode.SelectNodes( "//h3" ).First();<br/>
log.Info(h3.InnerText);<br/>
}<br/>
}<br/>
MEFの利点は、生成されたDLLを
plugins
コピーするだけで、すべてが機能することです。 危険、ウィルロビンソン:依存関係もこのフォルダーにコピーするか、ILmergeを実行する必要があります(2つ目が望ましい)。
真剣に、マルチスレッドについてはどうですか?
実際、WatiNのマルチスレッド使用は確かに可能です-IEの複数のコピーを同時に開くことができるからですよね? しかし、それほど単純ではありません。
まず、たとえばIEのコピーを100個すぐに開くことはできません。正確に何が壊れているかは明確ではありません(COM例外は非常に有益です...)が、問題は保証されています。 一方、たとえば
2*Environment.ProcessorCount
コピーを開いて、すべてが多かれ少なかれ機能します。
2番目の問題は、たとえばTPLを使用する場合、MTAではなくSTAストリームを作成する独自の
StaTaskScheduler
を作成する必要があることです。 幸いなことに、そのようなソリューションは既にネットワーク上(
MSDN )にあり、私はそれを例に挿入しました。 IEの4つのコピーを毎回実行する方法の例を次に示します。
var po = new ParallelOptions();<br/>
po.TaskScheduler = new StaTaskScheduler(4);<br/>
Parallel.For(0, 100, po, x =><br/>
{<br/>
using ( var browser = new IE( "http://news.bbc.co.uk" ))<br/>
{<br/>
browser.Visible = false ;<br/>
var doc = new HtmlDocument();<br/>
doc.LoadHtml(browser.Body.OuterHtml);<br/>
var h3 = doc.DocumentNode.SelectNodes( "//h3" ).First();<br/>
Console.WriteLine(h3.InnerText);<br/>
}<br/>
});<br/>
このアプローチと同様に、ホストサーバーは複数のブラウザーを開くことができますが、制御されたサービスに選択的に転送できる、たとえば10個のブラウザーのプール全体があります。