33524
初めまして、Swift3を学習中のものです。  
SwiftでのHTMLの取得方法について質問があります。  
Webサイトには、iPhoneなどで閲覧するモバイルサイトとパソコンでみる場合のPCサイト  
がサイト上で設定されていると思います。

例えば、下記のコードをモバイルサイト、PCサイトが設定されている任意のURLで実行するとモバイルサイトがiPhone上で見れます。

```

    let request = URLRequest(url: URL(string: "任意のURL")!)
    webview.loadRequest(request)
    
```

ここで、webviewでロードされているモバイルサイトのHTMLデータをString形式で取得したいと思い下記の関数を作成しました。

```

    func getHtmlData(url: String) -> String {
    
        let request = NSMutableURLRequest(url: URL(string: url)!)
        let userAgent = UIWebView().stringByEvaluatingJavaScript(from: "navigator.userAgent")
    
        request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
        do {
           myhtml = try String(contentsOf: request.url!, encoding: .utf8)
        }catch{
        }
    
        return myhtml
    
    }
    
```

しかしながら、このgetHtmlData関数を実行すると、PCサイトのHTMLがStringで返されます。  
User-
Agentを付与することで、モバイルサイトのHTMLデータをダウンロード出来るかと思い、setValueしたのですが、うまくいきません。どなたか、モバイルサイトのHTMLデータを取得する方法を教えてください。また、webview.loadRequestでは、なぜ何もしなくても(この短いコードで)モバイルサイトが表示されるのかも教えていただけると有り難いです。
CC BY-SA 3.0
2017-03-25T06:20:31.460
33522
2017-03-25T16:09:42.157
2017-03-25T16:09:42.157
76
21189
question
swift ios
Swift3 モバイルサイトのHtmlデータ 取得方法について
まずはこの部分です:  
**_Webサイトには、iPhoneなどで閲覧するモバイルサイトとパソコンでみる場合のPCサイト  
がサイト上で設定されていると思います。_**

各Webサイトが、リクエストをモバイルサイトと判定するかPCサイトと判定するかの詳細は、サイトによってバラバラです。あなたのターゲットとするサイトによっては、以下の内容は当てはまらないかもしれません。できればターゲットとするサイトの例を挙げていただいた方がいいでしょう。

* * *

次にあなたのコードですが、この部分:

```

    let request = NSMutableURLRequest(url: URL(string: url)!)
    let userAgent = UIWebView().stringByEvaluatingJavaScript(from: "navigator.userAgent")
    
    request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
    
```

あなたは、ここで`User-Agent`を設定した`NSMutableURLRequest`を作成している(*1, *2)わけですが、この後のこの行:

```

    myhtml = try String(contentsOf: request.url!, encoding: .utf8)
    
```

で、`request`の中の他の要素(`User-Agent`も含めて)は全て無視して`url`だけを取り出しています。従ってWebサイトには`User-
Agent`の指定などない(ほぼ空の)GETリクエストが送られます。あなたの試されたサイトでは、そのようなリクエストに対してPC用の応答を返すのでしょう。

*1 Swift3以降では特別な理由のない限り`NSMutableURLRequest`は使わず`URLRequest`を使うべきです。  
*2 `UIWebView`と言うのはブラウザアプリに匹敵する機能を持った「重い」viewです。`User-Agent`を取り出すためにだけ、毎回そのインスタンスを作成するなんてとんでもないことはやめましょう。またパラメータのないイニシャライザ`UIWebView()`の動作は未定義ですから、何が起こるのかわからないと言うコードになっています。(現在のiOSでは期待通りに動いているかもしれませんが、次のバージョンではアプリがクラッシュするかもしれません。)

* * *

そもそも`String.init(contentsOf:encoding:)`には(`String`の他のイニシャライザにも)、「`request`の中の他の要素」を一緒に送信してくれるような機能はありませんし、このメソッドを(外部URLに対して)メインスレッド内で呼んではいけません。

  * GET以外のHTTPメソッドでリクエストを送りたい場合
  * **`User-Agent`など、特定のHTTPヘッダ情報をセットしたい場合**
  * **メインスレッドの中から呼び出したい場合**

などには、`String.init(contentsOf:encoding:)`やら`Data.init(contentsOf:options:)`とかは使えず、必ず`URLSession`などを使わないといけないと思ってください。

* * *

上記のようなことを踏まえて、あなたのコードをざっくり書き換えるとこんな風になります。

```

    //###サイトがモバイルと判定するようなUser-Agentを定数として用意しておく
    let iPhoneUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2 like Mac OS X) AppleWebKit/602.3.12 (KHTML, like Gecko) Mobile/14C89"
    
    //###非同期処理を書くのは、完了ハンドラーパターンが簡単
    func getHtmlDataAsync(url: String, completion: @escaping(String)->Void) {
        var request = URLRequest(url: URL(string: url)!)
        request.setValue(iPhoneUserAgent, forHTTPHeaderField: "User-Agent")
        let task = URLSession.shared.dataTask(with: request) {data, response, error in
            if let error = error {
                completion("<error>"+error.localizedDescription+"</error>")
                return
            }
            guard let data = data else {
                //###ここを通ることはないはず
                completion("<error>data nil</error>")
                return
            }
            let html = String(data: data, encoding: .utf8) ?? "bad encoding"
            completion(html)
        }
        task.resume()
    }
    
```

呼び出す方はこんな感じになります。

```

        getHtmlDataAsync(url: "https://...") {html in
            //###取得したhtmlを使う処理はこの完了ハンドラーの中に書く
            print(html)
        }
    
```

最初に書いたように、あなたのターゲットとするサイトでどうなるかはわかりませんが、こちらでいくつかのサイトで試した範囲ではこのコードでモバイル版のHTMLを取得できるのを確認しています。お試しください。

* * *

最後にこの部分:  
**_webview.loadRequestでは、なぜ何もしなくても(この短いコードで)モバイルサイトが表示されるのか_**

先の*2に書いたように、`UIWebView`や`WKWebView`はブラウザそのものと言っても良い機能を備えています。従ってURLさえ渡してやれば、モバイル版ブラウザとして`User-
Agent`の他諸々のHTTPヘッダーを仕込んだリクエストをサーバ側に送信します。当然Webサイト側はモバイル版ブラウザと同じ応答を返すことになるわけです。

ちなみに、Webサーバからの応答を受信したWebViewは、その内容をDOMとして解析し、それを画面に描画できるようCSSも取得・解析し、JavaScriptのコードが含まれていれば実行し、imgタグが含まれていれば、その内容を取得するためのリクエストを送信する、なんてことまでブラウザと同様に行います。従って、それらの動作が不要なのであれば、「短いコードで」実現できると言う理由でWebViewを使用するべきではありません。
CC BY-SA 3.0
2017-03-25T07:56:49.517
33524
2017-03-25T07:56:49.517
13972
33522
answer