4

ウィキペディアから空港情報を集めています。空港名で非 ASCII 文字を保持したい。

コードが Z で始まる空港は、Web ブラウザーで次のように表示されます。

ここに画像の説明を入力

空港 DBE は「ドルニ ベネソフ空港」と呼ばれます。空港 ZBK は「Ž abljak 空港」と呼ばれます。出力で同じ値が期待されます。

次のような関数でデータをスクレイピングしています。

function Get-Airports ($Uri) {
  Invoke-WebRequest -Uri $Uri -UseBasicParsing |
  Select-Xml -XPath '//table/tr[td]' |
  % {
    $Kids = $_.Node.ChildNodes
    [PSCustomObject] @{
      Iata = $Kids[0].InnerText
      Icao = $Kids[1].InnerText
      AirportName = $Kids[2].InnerText
      LocationServed = $Kids[3].InnerText
    }
  }
}

この関数は、指定された URI をフェッチし、HTML 応答を暗黙的に XML に変換し、XPath を使用してテーブル データ行を抽出し、各列の値を新しい PowerShell オブジェクトのプロパティにマップします。

Z で始まるすべての空港を取得するには、次のようなコマンドを使用します。

$Airports = Get-Airports 'http://en.wikipedia.org/wiki/List_of_airports_by_IATA_code:_Z'

この$Airports変数には、新しい PowerShell オブジェクトのコレクション (テーブル内のデータ行ごとに 1 つずつ) が含まれています。

このコマンドは、スクレーパーが非 ASCII 文字を含む名前をマングルすることを示しています。

$Airports |
? { $_.AirportName -like '*[?]*' } |
Format-Table

空港名に疑問符を含めないでください。このコマンドは出力を生成しないと思います。

代わりに、Web ブラウザーに非 ASCII 文字が表示される名前に 2 つの疑問符が付いたオブジェクトがいくつかあります。

Iata  Icao   AirportName                              LocationServed                                               
----  ----   -----------                              --------------                                               
ZBE   LKZA   Doln?? Benesov Airport                   Z??b??eh, Czech Republic                                     
ZBK          ??abljak Airport                         ??abljak, Montenegro                                         
ZBM   CZBM   Bromont (Roland D??sourdy) Airport       Bromont, Quebec, Canada                                      
ZLG          La G??era Airport                        La G??era, Western Sahara                                    
ZLT          La Tabati??re Airport (TC: CTU5)         La Tabati??re, Quebec, Canada                                
ZOS   SCJO   Ca??al Bajo Carlos Hott Siebert Airport  Osorno, Chile                                                
ZPC   SCPC   Puc??n Airport                           Puc??n, Chile                                                
ZQW   EDRZ   Zweibr??cken Airport                     Zweibr??cken, Germany                                        
ZTB          T??te-??-la-Baleine Airport (TC: CTB6)   T??te-??-la-Baleine, Quebec, Canada     

それは確かに文字エンコーディングの問題です。ウィキペディアはUTF-8を生成しますが、PowerShell がWindows-1252またはその他のシングルバイト文字セットとしてデコードしているようです。

UTF-8 を指定できるInvoke-WebRequestコマンドレットまたはSelect-Xmlコマンドレットのスイッチが見つかりません。

この問題を解決する簡潔な方法はありますか? どんな方法でもかまいませんが、欠けている単純なものがあると思います。

4

1 に答える 1

4

簡単な回答: Content プロパティを使用する

Get-Airports で、パイプラインの開始を次の式に置き換えます。

(Invoke-WebRequest -Uri $Uri -UseBasicParsing).Content

そして、関数は期待される結果を生成します。

疑問符の付いた空港名はありません。

長い答え: Invoke-WebRequest に問題があります

Invoke-WebRequest は BasicHtmlWebResponseObject のインスタンスを返します。その ToString メソッドは、応答コンテンツをマングルします。

中国の空港リストは非 ASCII 文字でいっぱいなので、良いテスト ケースを提供します。このコードはそのページをスクレイピングし、Content プロパティと ToString メソッドを介してタイトルを抽出します。

$uri = 'http://zh.wikipedia.org/wiki/國際民航組織機場代碼_(Z)'
$response = (Invoke-WebRequest -Uri $uri -UseBasicParsing)
$pattern = '\<title\>.+\</title\>'
[Regex]::Match($response.Content, $pattern).Value
[Regex]::Match($response.ToString(), $pattern).Value

出力は次のようになります。

<title>國際民航組織機場代碼 (Z) - 维基百科,自由的百科全书</title>
<title>?????????????????????????????? (Z) - ????????????????????????????????????</title>

Contentプロパティには、適切にデコードされた応答が含まれています。

ToStringメソッドはガベージを返します。

ToString が Content のように振る舞うのは妥当と思われるため、ここに問題があるようです。

さらに掘り下げるために、オープン ソースの .NET アセンブリ ブラウザーおよび逆コンパイラーであるILSpyを使用しました。

BasicHtmlWebResponseObject コンストラクターは、InitializeContent を呼び出して Content プロパティを設定します。

// Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject
private void InitializeContent()
{
    string contentType = ContentHelper.GetContentType(base.BaseResponse);
    if (ContentHelper.IsText(contentType))
    {
        string characterSet = WebResponseHelper.GetCharacterSet(base.BaseResponse);
        this.Content = StreamHelper.DecodeStream(base.RawContentStream, characterSet);
        return;
    }
    this.Content = string.Empty;
}

この方法は、正しいデコードを驚異的に検出します。

BasicHtmlWebResponseObject は、WebResponseObject から ToString を継承します。

// Microsoft.PowerShell.Commands.WebResponseObject
public sealed override string ToString()
{
    char[] chars = Encoding.ASCII.GetChars(this.Content);
    for (int i = 0; i < chars.Length; i++)
    {
        if (!this.IsPrintable(chars[i]))
        {
            chars[i] = '.';
        }
    }
    return new string(chars);
}

WebResponseObject の ToString メソッドは単純に応答を ASCII としてデコードします。

デフォルトのASCII デコーダーは置換フォールバックを使用して、未知のバイトに対して疑問符を生成します。

どこにも文書化されていませんが、Select-Xml は ToString を呼び出してパイプライン オブジェクトを XML に変換していると思います。これは妥当な動作ですが、BasicHtmlWebResponseObject の設計に誤りがあるため、ここでは機能しません。

それが私のデフォルトのコードページであるため、Windows-1252 デコードで推測しました。しかし、それはありえませんでした。文字íには Windows-1252 のエンコーディングがありますが、出力では に置き換えられ?ます。

于 2013-10-08T19:08:25.687 に答える