みなさーん!位置偽装してますかー!!位置偽装大好き @M_Ishikawa です!
位置偽装の魅力はなんといっても、いつでも世界中のどこにでも旅ができる、そんな気分を味わえることですよね!
というわけで、Seleniumを使って位置偽装しGoogle検索して、世界中を駆け巡りましょう!!
これは Selenium/Appium Advent Calendar 2018 の10日目の記事です。
位置偽装ってなあに?
位置偽装 (Faking Geolocation) とは、ブラウザがGPSやらIP(GeoIP)やらを元にユーザーのいる位置情報を偽装して、東京にいるのにサンフランシスコでググったときの検索結果を出したりすることです。
ググるといいましたが、これはGoogle検索に限らず位置情報の取得は、Geolocation APIを利用することでいろんなサイトでも使われています。Google MapsだってスマホでもGPS内蔵していないPCでも、アクセスすれば現在地が分かっちゃいますよね。
Geolocation API を確認してみる
ブラウザのコンソールで
navigator.geolocation.getCurrentPosition(function(e) {console.log(e)})
と打てば確認できます。位置情報送信の許可ダイアログが出てくるので許可すると情報が出てきます。
ぼくの位置情報(表参道駅のカフェで、公衆Wi-Fiに繋いでいます)もバレたところで、試しに「焼肉」で検索してみます。
きっとみなさんの結果はこれとは違い、自分のいる場所に近い焼肉やさんが出てきたと思います。
仕組みの説明はここでは割愛します。
ブラウザで位置偽装するにはエクステンション等使って確認したりすることもできます。
Seleniumで位置偽装する
Laravel + Docker + Selenium で位置偽装にチャレンジしてみました。
結論からいうと、seleniumではfirefoxの、しかもバージョンが3.8.1以前だと、位置偽装することに成功しました。
selenium/firefoxの3.9以降ではサポートされなくなり動作しません。また、selenium/chromeでもチャレンジしたのですがうまくいきませんでした。 1
これはひょっとすると使用したライブラリの facebook/WebDriver に依存していることで、解消できるかもしれませんが、とりあえずぼくはこれで満足ですw (でも解決した人いたらぜひ教えてください!
- 使用したもの
- PHP 7.1
- Laravel 5.7
- facebook/WebDriver 1.6
- Docker
- selenium/standalone-firefox-debug:3.8.1
- PHP 7.1
selenium/standalone-firefox:3.8.1 だと日本語フォントが入っていないので、VNC接続できるdebug版を使用。VNCでも確認したいしね。
standalone版を使っているのは、ただ楽をしたかったからです。
実装: 博多駅で焼肉を検索
では博多駅で焼肉を探してみましょう!
docker-compose.yml
version: "2"
services:
php:
build: docker/php
working_dir: /var/www/faking
volumes:
- .:/var/www/faking
- /dev/shm:/dev/shm
tty: true
stdin_open: true
privileged: true
firefox:
# -enablePassThrough が使えるのは3.8.1まで
image: selenium/standalone-firefox-debug:3.8.1 # vnc付き日本語フォントあり
ports:
- "15905:5900" # localhost:15905でローカルからvncアクセスできるように
environment:
- SE_OPTS=-enablePassThrough false # これやらないとfacebook/WebDriverが動かない
volumes:
- /dev/shm:/dev/shm
app/Console/Commands/travel.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\Firefox\FirefoxProfile;
use Facebook\WebDriver\Firefox\FirefoxDriver;
class travel extends Command
{
・・・
public function handle()
{
$this->info('start');
$profile = new FirefoxProfile();
// geolocationの準備
$profile->setPreference('geo.prompt.testing', true);
$profile->setPreference('geo.prompt.testing.allow', true);
$profile->setPreference('geo.enabled', true);
$profile->setPreference('geo.provider.use_corelocation', false);
$profile->setPreference('geo.provider.ms-windows-location', false);
// localeを日本、languageを日本語、に
$profile->setPreference('intl.accept_languages', 'ja');
// 博多駅
$lat = 33.5897275;
$lng = 130.4207274;
// 緯度経度算出
$profile->setPreference('geo.wifi.uri', sprintf(
'data:application/json,{"location": {"lat": "%s", "lng": "%s"}, "accuracy": 10.0, "status": "OK"}',
$lat,
$lng
));
$caps = DesiredCapabilities::firefox();
$caps->setCapability(FirefoxDriver::PROFILE, $profile);
$this->info('open browser');
$driver = RemoteWebDriver::create('http://firefox:4444/wd/hub', $caps);
// Googleを開く
$this->info('open google');
$driver->get('https://www.google.co.jp/');
// 検索ボックスが現れるのを待つ
$driver->wait(10, 500)->until(
WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::name('q'))
);
// 検索ボックスにキーワードを入力して検索実行
$this->info('send query');
$element = $driver->findElement(WebDriverBy::name('q'));
$element->sendKeys('焼肉');
$element->submit();
// ページ遷移を待つ
$driver->wait(10, 500)->until(
// ページ遷移したかどうかは #swml-upd = 「正確な現在地を使用」リンクがあるかどうかで判定
WebDriverExpectedCondition::elementToBeClickable(WebDriverBy::id('swml-upd'))
);
// スクリーンショットを保存
$this->info('save screenshot');
$driver->takeScreenshot(storage_path('yakiniku-hakata.png'));
// ブラウザを閉じる
$this->info('close browser');
$driver->close();
$this->info('end');
}
実行キャプチャ ※クリックでYoutube開きます
結果ページ
博多駅を指定しているはずなのに、自分のいる場所(西麻布に移動しちゃいました)で検索されています。
実はこれではうまくいきません。コツがあります。
実装: 「正確な現在地を使用」を利用
Google検索結果ページの下部にある「正確な現在地を使用」を利用することで位置情報を更新できます。
↓ // 「正確な現在地を使用」をクリック
$this->info('click swml-upd');
$driver->findElement(WebDriverBy::id('swml-upd'))->click();
// 現在地更新されるまで待つ
$driver->wait(10, 500)->until(
WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::cssSelector('#loc.known_loc'))
);
// reload
$this->info('reload');
$driver->navigate()->refresh();
$driver->wait(10, 500)->until(
// ページ遷移したかどうかは #swml-upd = 「正確な現在地を使用」リンクがあるかどうかで判定
WebDriverExpectedCondition::elementToBeClickable(WebDriverBy::id('swml-upd'))
);
// スクリーンショットを保存
$this->info('save screenshot');
$driver->takeScreenshot(storage_path('yakiniku-hakata-retry.png'));
うまくいきました!
え、そんなの「博多駅 焼肉」で検索すればいいだろって?
じゃあ検索しにくいので試してみましょう。
例えばカリフォルニアにあるFacebook本社周辺の焼肉を探してみます。「Facebook 焼肉」では流石にぱっとは出てこないですからね。
緯度経度を調整して、
やったね!
これでFacebook本社に行った(つもりになった)ときも焼肉に困りません!
サンプル
Githubにサンプル上げてますのでご利用ください〜
https://github.com/ishikawam/faking-geolocation-sample
REDME.mdとかMakefileとか読めば一瞬で動作できるようにしてます。dockerバンザイ!
上記の実装の流れもcommitごとに分けてますので確認しやすいかと。
-
オプション(SE_OPTS)に
-enablePassThrough false
を付けないとfacebook/WebDriverが動かないが、-enablePassThrough
は3.8.1で打ち切られた。 ↩