jsoupのデフォルトのUser-Agentはなんなのか。
jsoup
JavaでHTMLをいい感じにParseできる便利なライブラリです。 WebページにアクセスしてHTMLを取得し、それを加工することでWebページ内のコンテンツを簡単に取得することができます。
スクレイピング先が、User-Agent(UA)によって表示を変えている場合、それに応じてjsoupのUAを変更しなければ、思っていたコンテンツが取得できない場合があります。
User-Agentの設定
Connection#userAgent(String)
で設定できます。次のようにすることで設定されたUAを確認できます。
String url = "http://www.useragentstring.com/"; Connection con = Jsoup.connect(url); con.userAgent("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0"); Doc doc = con.get(); doc.getElementById("uas_textfeld").val());
メソッドで指定した通り、
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0
が設定されているのがわかります。
未設定時
未設定のときにどうなるのか調べてみました。 Androidのエミュレータで前述のようにし、UAの設定をしないと次のようになりました。
- Genymotion
Dalvik/2.1.0 (Linux; U; Android 5.0; Nexus 5 - 5.0.0 - API 21 - 1080x1920_1 Build/LRX21M)
どこで誰が設定しているんだろうと思い、jsoupのコードを見ていたのですが、AndroidのSDKHttpURLConnection
が使われているところで詰んでしまいました。
frameworkのところまで調べていくと、 RuntimeInit
というクラスで、デフォルトのUAに使われる値が生成されていました。
- RuntimeInit.java
/** * Returns an HTTP user agent of the form * "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MASTER)". */ private static String getDefaultUserAgent() { StringBuilder result = new StringBuilder(64); result.append("Dalvik/"); result.append(System.getProperty("java.vm.version")); // such as 1.1.0 result.append(" (Linux; U; Android "); String version = Build.VERSION.RELEASE; // "1.0" or "3.4b5" result.append(version.length() > 0 ? version : "1.0"); // add the model for the release build if ("REL".equals(Build.VERSION.CODENAME)) { String model = Build.MODEL; if (model.length() > 0) { result.append("; "); result.append(model); } } String id = Build.ID; // "MASTER" or "M4-rc20" if (id.length() > 0) { result.append(" Build/"); result.append(id); } result.append(")"); return result.toString(); }
他のやり方ではどのように表示されるのか、いろんな方法でUAが確認できるサイトにアクセスしてみました。
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36
Mozilla/5.0 (Linux; Android 6.0.1; Nexus 6 Build/MMB29V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.105 Mobile Safari/537.36
curl/7.43.0
- httpie
HTTPie/0.8.0
httpieが一番わかりやすく、ソースを見ると、
... DEFAULT_UA = 'HTTPie/%s' % __version__ ... def get_default_headers(args): default_headers = { 'User-Agent': DEFAULT_UA } ...
となっていたので、アクセスするプログラムが決めているのだとわかりました。 curlやブラウザなどは中がわからなかったのですが、おそらく内部で同じように自分のUAを設定しているのだと思います。
間接的で、直接はわかっていないのですが、少しスッキリしました。
jsoupのデフォルトのUA
Androidから使うときは、Android SDKのHttpURLConnection
のデフォルトのUAが適応され、Android端末のUAとなります。
参考
jsoup: Java HTML Parser / jsoup
JSoup UserAgent, how to set it right? / StackOverFlow
User Agent String.Com / UserAgentString.com
What's the default Jsoup User Agent string? / StackOverFlow
Instagramのユーザページと詳細ページへ暗黙的Intentで遷移する。
普段アプリで表示しているであろうページに、ブラウザで表示する場合があります。
ブラウザアプリを作っていて、何もしないままだとWebページが表示してしまうので、普段表示している方であるアプリへ遷移させます。
ユーザページと詳細ページを表示する時に、Instagramのアプリで表示できるようにします。
ユーザページ
詳細ページ
検索ページ
Instagramがリダイレクトを行い、ログインなどを除けば最終的に上のURLになります。 検索ページはログインしていないとうまくリダイレクトされない場合があります。
ユーザページはそのままのURLだと、Instagramアプリが反応してくれません。
ユーザページをInstagramアプリで開くときは、パスに_u
を挟む必要があります。
つまり、ユーザページは次のようになります。
コード
this.packageName()
にはcom.instagram.android
this.url()
にはhttps://www.instagram.com/
が入っています。
public void instagram(Activity activity, String url) throws ActivityNotFoundException { Intent toInstagram = new Intent(); toInstagram.setAction(Intent.ACTION_VIEW); toInstagram.setPackage(this.packageName()); Uri uri = Uri.parse(url); List<String> paths =uri.getPathSegments(); switch (paths.size()) { case 1: String profile = this.url()+ "_u/" + paths.get(0); toInstagram.setData(Uri.parse(profile)); break; default: toInstagram.setData(uri); break; } activity.startActivity(toInstagram); }
URLのpathを取得して、一つしかpathがない場合にURLをInstagramアプリが反応できるユーザページに置き換えています。
このコードだと、URLが
https://www.instagram.com/
https://www.instagram.com/accounts/login/?next=/explore/
などの場合にActivityNotfoundException
を投げるので適切にハンドリングしてください。
また、https://www.instagram.com/explore/
のときは、explore
という存在しないユーザページに遷移するので、特殊な処理を入れてもいいかもしれません。
注意
Instagramのバージョンは7.20.0
, 検証した日は2016/4/10
のものですので、仕様変更やアプリのバージョンによっては思った通りに動かない場合もあるのでご了承ください。
以前紹介した方法の方が、変更には強いです。
素直なURLでInstagramのアプリで開けないということは、開いて欲しくない、もしくは無くしていく方針なのかもしれません。
以上。
参考
Intent to open Instagram user profile on Android / StackOverFlow
続・Androidアプリ開発を学生に教えた話
再び
以前と同じく、TrunkでAndroid開発のトレーニングを請け負いました。
今回は、1ヶ月でカメラアプリを作成してストアへ申請するというものでした。 1ヶ月と言っても、1回3時間の講義を4回行うという形式です。 前回の反省を踏まえ、一度の講義の時間を長くしています。
前回と異なり、スキルを身につけるというより、作り上げることをテーマにしていました。
対象は、学校やインターンでJavaを習った方限定としました。
募集
前回、知識レベルに差があったのが教えにくかったので、前提として、Javaの文法がわかる程度の学生を対象としました。 参加枠は4人でしたが、応募してきた方は半分の2人でした。。
第1回
狙い
- Android Studioでプロジェクトを作成した時にいろいろファイルが生成されるが、どれが自分で編集するもので、どれが編集しなくても良いものか。それぞれの役割を知る。
- 開発環境を使いこなし、ボイラープレートの手入力を避ける。
- デバッグ方法を知ることで、問題箇所の特定をできるようにする。
主に前回のスライドをベースに作成しました。説明として余計と思った、テーマ周りなどは省略しました。
実際
開発環境は事前に構築しておく前提だったのですが、構築ができていませんでした。 参加者のうち、Surfaceの方がいてびっくりしました。幸いなかはWindowsだったので、無事環境は作ることができました。もう少し早くにBashが来てほしかった。。
Gitの扱いも、講義の内容に入れていたのですが、Gitは片手間で教えられるものではないと感じました。 というのも、「ただソースコードをバージョン管理できるよ!」と言っても、実際経験がないと響かないし、実際に触ってみないと理解してもらうのは難しいと思いました。 でもこの部分は僕の教え方の問題も大きいと思います。
また、参加者の知識レベルも、想定していたのと異なり、独学で勉強中という方でした。
デバッグ周りは、実際にエラーを起こさせて、エラーの読み方をはじめに教えるべきでした。 Breakpointなどを教える前に、Stacktraceのどの部分が自分に関係あるもので、エラーの原因ぽいところなのか。ということを教えないといけないと今更気付きました。ここは講義内容で実習にして体験してもらうなどが必要です。
写真加工部分は、Adobe Creative SDKを使いました。Retrolambda環境を構築する必要があり、時間がかかると思っていたのですが、幸いAndroid Studioの環境構築を当日一緒に行ったために、思っていたほど苦戦しませんでした。
以前より講義の時間を長く取っていたのですが、環境構築とGitの説明で、今回も1時間オーバーしてしまいました。。
第2回
狙い
- 自分で好きなレイアウトを構築できるようになる。
- 好きなWidgetにイベントハンドリングを設定できるようになる。
同じく前回のスライドを流用しています。 Activityのライフサイクルは、触れておいた方がいいと思い入れた程度です。
実際
参加した学生がどちらもiPhoneユーザで、Androidの実機を持っていなかったため、UIを考えるのも難しかったようでした。 戻るボタン周りなど、iPhoneと比較したAndroidの特徴の話をはじめに入れておくと良かったかもしれないなと思いました。
RelativeLayoutとLinearLayoutを使いこなせるように、ライブコーディングの形でxmlを編集し、「この場合どうなるか?」というのをいくつもこなしました。自分の回答と比較できることで、理解が進んだように見えました。ただスライドを写経するより、一度考えたうえでコードを書くほうが理解を促進させると思いました。
イベントハンドリングは、ちょっと冗長になってしまいました。GitHubや検索してでてくるサンプルコードを見た際に、いろんなパターンでも理解ができるようにと思って前回から教えていたのですが、逆に複雑になっているのでは?と講義を聞いていた同期に指摘されました。 確かに、次週その辺りで苦戦しているように感じたので、振り返るとちょっと冗長だったなと思います。
第2.5回
実際は第3回の内容を行うつもりでしたが、宿題としていた
- 作りたいアプリのレイアウトを作る
- 各ボタンでイベントハンドリングを行う
ができていませんでした。 また、当日は一人しか参加できなかったので、宿題の部分を教えることで3時間が終わりました。 時間がかかった理由としては、極力自分で考えて実装してもらったのと、Javaに慣れていない部分が多々あったからです。
全4回の想定でしたので、翌週に第3回と第4回を繋げて行うことで調整しました。
第3回
狙い
- 各機能をメソッド化することで自在に呼びさせるようにする。
onActivityResult
を使った、他のComponentとの連携を理解する。
レイアウトとイベントハンドリングはできているので、好きな箇所で自由に機能をつけられるようにさせようと考えました。 説明の内容を刷新しました。
実際
他のカメラアプリを暗黙的Intentで呼ぶ構成上、メンバ変数を持たせたのが難しかったみたいでした。 ただ、事前のレイアウトやイベント周りはできていたので、一つずつ挙動を確認しながら組み込めたのは良かったと思います。
onActivityResultの中で暗黙的Intentを呼んでいたので、再度onActivityResultを通るというところが、なかなか理解が進まないところでした。 図説したところ、少しずつ組み合わせられるようになりました。コードでの説明より、グラフィカルに入出力を捉えることで機能単位で考えやすくなったのかなと思います。
実機がなかったのでエミュレータで開発していたのですが、シェア周りもエミュレータでは聞き馴染みのあるものがなく、うまく対応してくれるアプリがなかったので、実際に動かす感動は薄いようでした。この辺りは自身がAndroid端末を持っているかどうかがモチベーションに関わると感じました。
第3回のスライド分の講義の中でずっと触ってきた暗黙的Intentに疑問を抱くかと思い、暗黙的Intentに対応するにはどうすればいいのか?というのを蛇足的に入れていたのですが、本当に蛇足となり終わりました。実際に触る部分以外の知識は触れないようにしたほうがいいと痛感しました。
第4回
狙い
- ストア申請に必要な情報を把握する。
- 申請に必要なapkの作成できるようになる。
実際
第3回と連続して行った第4回です。 振り返ると署名周りが冗長だったなと思います。 申請自体は、レーティングや説明文など、やることがありましたが間違えるようなものでもなく、淡々とできました。
アイコンや宣伝用画像は、Trunkの別のトレーニングを受けている学生が作ってくれました。
第3回の分で、大幅に時間が押していたため、第4回の分を巻いても、合計7時間という長丁場となりました。。
振り返って
前回が通信やカスタムリストビューなど、実践的な知識を身につけることをテーマにしていたのと比べて、今回は画面一つで済ませるもの、かつ、作り切ることをテーマにしていたので、難易度は低めになっていました。 しかしそれでも、アプリを作り切るのだと、Javaの文法がわかっているレベルでないと教えるのも理解するのも難しいです。 その前提を合わせないと、資料作りや教え方にも影響してきます。
また、いかに教えないかが重要だと学びました。 答えを教えるのではなく考えさせること。今集中して覚えるべきこと以外のことをそぎ落とすこと。 必要な知識を必要な時に提供することが大事です。言われてみると至極当たり前のように感じるのですが、実際教える立場になってみると、すっかり抜け落ちていました。 わりと、効率よくもれなく学ばせようとしていました。
スライドやサンプルアプリはGitHubに公開していたのですが、あまり活用されていないようでした。
復習できる形だからしてるよね?課題にしたからしてるよね?というのは教える側のエゴなのかと思いました。
学生はいとも簡単に想像を超えてきます。学生に対して教える立場になるときは、その心構えが必要です(そういえば自分も入社時の課題をこなせなかったことを思い出しました。)。
自分の中での課題は明確になったので、続・続があれば挑戦したいです。 これから開発を始める方や、同じような境遇の人の参考になればいいなと思います。
参考
すべてのトレーニングを無料で、すべての学生に。 / Trunk
米マイクロソフトがBash on Windowsを発表、その目的は / @IT
gradle-retrolambdaを使ってビルドした時に you must set the path to jdk8
エラーが出た時の対処法 / Qiita