Jeremy Kendall 在 PHP Architecture 有一篇文章介紹怎樣使你的網站加入地理定位 (geolocation) 的功能,好處是可以根據訪客的身處的位置,提供適合的內容和語言,令他們享受更貼心的服務。加入這項功能比你想像的容易,甚至完全免費,Jeremy 提供了兩個解決方案,任何一個都可以在十分鐘內搞定。
Jeremy 說,地理定位屬於一個完全解決了的問題,很多人提供了解決方案,我們沒有必要自行編寫這項功能,他介紹了兩個免費的應用程式界面 (API),一個是 Maxmind 的 GeoLite API,另一個是 Quova。
GeoLite
GeoLite 的確有些獨特,他們的服務是免費的的,無須登記,他們提供了數據庫下載。這意味著數據存放在你自己的網站上,並利用他們提供的 PHP 函式庫來存取數據庫。
以下的步驟讓 GeoLite 在你的網頁應用程序中運行:
- 下載 GeoLiteCity 數據庫
- 把數據庫解壓在你的網頁應用程序中一個可讀取的子目錄
- 安裝 PEAR GeoLite 函式庫(
pear install Net_GeoIP) - 編程!
你需要編寫的程式可說簡單得無法置信,下面是 Jeremy 測試用的程式:
| PHP | | copy code | | ? |
| 1 | $geoip = Net_GeoIP::getInstance(dirname(__FILE__) . '/data/GeoLiteCity.dat'); |
| 2 | $ipaddress = '72.30.2.43'; // Yahoo! |
| 3 | $location = $geoip->lookupLocation($ipaddress); |
| 4 | var_dump($location); |
程式的輸出是這樣:
object(Net_GeoIP_Location)[2]
protected 'aData' =>
array
'countryCode' => string 'US' (length=2)
'countryCode3' => string 'USA' (length=3)
'countryName' => string 'United States' (length=13)
'region' => string 'CA' (length=2)
'city' => string 'Sunnyvale' (length=9)
'postalCode' => string '94089' (length=5)
'latitude' => float 37.4249
'longitude' => float -122.0074
'areaCode' => int 408
'dmaCode' => float 807Net_GeoIP_Location 物件使用 __get() 和 __set() 魔術成員函式,所以從這物件中檢索數據,只要編寫簡單的 $location->city; 。Quova
GeoLite 的安裝和使用的確非常簡單,但為了有一個比較公道和全面的概念,Jeremy 找來另一個同樣是免費的 Quova 測試。
首先我們要申請一個免費的 Quova 開發者帳戶 ,並獲得 API 密鑰。Quova 有十分出色的文檔,Jeremy 在很短時間內便把它啟動和運作。很可惜,他們提供的 PHP 範例卻有點醜陋,Jeremy 花了數分鐘時間把它改寫得漂亮些:
| PHP | | copy code | | ? |
| 01 | /** |
| 02 | * Quova ipinfo API class |
| 03 | * |
| 04 | * @category Example |
| 05 | * @package Example_Quova |
| 06 | * @subpackage GeoIP |
| 07 | * @version $Id$ |
| 08 | */ |
| 09 | /** |
| 10 | * Uses Quova's GeoIP API to get geographical location by IP address |
| 11 | * |
| 12 | * To obtain your Quova API key (apikey) and the shared secret |
| 13 | * that you need to build a digital signature, register your |
| 14 | * application at http://developer.quova.com/. |
| 15 | * |
| 16 | * @category Example |
| 17 | * @package Example_Quova |
| 18 | * @subpackage GeoIP |
| 19 | */ |
| 20 | class Example_Quova_GeoIP |
| 21 | { |
| 22 | /** |
| 23 | * Quova API key |
| 24 | * |
| 25 | * @var string |
| 26 | */ |
| 27 | private $_apiKey; |
| 28 | /** |
| 29 | * Quova shared secret |
| 30 | * |
| 31 | * @var string |
| 32 | */ |
| 33 | private $_secret; |
| 34 | /** |
| 35 | * Default URL for Quova's GeoIP service |
| 36 | * |
| 37 | * @var string |
| 38 | */ |
| 39 | private $_defaultService = 'http://api.quova.com/v1/ipinfo/'; |
| 40 | /** |
| 41 | * Public constructor |
| 42 | * |
| 43 | * @param string $apiKey Quova API Key |
| 44 | * @param string $secret Quova shared secret |
| 45 | */ |
| 46 | public function __construct($apiKey, $secret) |
| 47 | { |
| 48 | $this->_apiKey = $apiKey; |
| 49 | $this->_secret = $secret; |
| 50 | } |
| 51 | /** |
| 52 | * Get geographical location of IP address from Quova's API |
| 53 | * |
| 54 | * @param string $ipaddress |
| 55 | * @param string $format json or XML |
| 56 | * @return string XML or json, depending on the value of $format |
| 57 | */ |
| 58 | public function getLocation($ipaddress, $format = 'json') |
| 59 | { |
| 60 | $ch = curl_init(); |
| 61 | $parameters = array( |
| 62 | 'apikey' => $this->_apiKey, |
| 63 | 'sig' => $this->generateSig(), |
| 64 | 'format' => $format |
| 65 | ); |
| 66 | $url = $this->_defaultService |
| 67 | . $ipaddress |
| 68 | . '?' |
| 69 | . http_build_query($parameters); |
| 70 | curl_setopt($ch, CURLOPT_URL, $url); |
| 71 | curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); |
| 72 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); |
| 73 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
| 74 | $data = curl_exec($ch); |
| 75 | $headers = curl_getinfo($ch); |
| 76 | // Check headers here |
| 77 | curl_close($ch); |
| 78 | return $data; |
| 79 | } |
| 80 | /** |
| 81 | * Checks headers for response code and issues error message if necessary |
| 82 | * |
| 83 | * @param mixed $headers |
| 84 | */ |
| 85 | public function checkHeaders($headers) |
| 86 | { |
| 87 | // this is where I'd test HTTP response codes |
| 88 | } |
| 89 | /** |
| 90 | * Generates sig, an MD5 hash of the API key, the shared secret, and Unix timestamp |
| 91 | * |
| 92 | * @return string MD5 hash |
| 93 | */ |
| 94 | public function generateSig() |
| 95 | { |
| 96 | return md5($this->_apiKey . $this->_secret . gmdate('U')); |
| 97 | } |
| 98 | } |
以下是調用 Quova API 的簡單例子:
| PHP | | copy code | | ? |
| 1 | $apikey = 'dummyApiKey'; |
| 2 | $secret = 'dummySecret'; |
| 3 | $ipaddress = '72.30.2.43'; // Yahoo! |
| 4 | $quova = new Example_Quova_GeoIp($apikey, $secret); |
| 5 | $location = json_decode($quova->getLocation($ipaddress)); |
| 6 | var_dump($location); |
這段程式的輸出如下:
object(stdClass)[4]
public 'ipinfo' =>
object(stdClass)[5]
public 'ip_address' => string '72.30.2.43' (length=10)
public 'ip_type' => string 'Mapped' (length=6)
public 'Network' =>
object(stdClass)[6]
public 'organization' => string 'inktomi corporation' (length=19)
public 'OrganizationData' =>
object(stdClass)[7]
public 'organization_type' => string 'Business Conglomerate' (length=21)
public 'carrier' => string 'inktomi corporation' (length=19)
public 'asn' => int 14777
public 'connection_type' => string 'tx' (length=2)
public 'line_speed' => string 'high' (length=4)
public 'ip_routing_type' => string 'fixed' (length=5)
public 'Domain' =>
object(stdClass)[8]
public 'tld' => string 'com' (length=3)
public 'sld' => string 'yahoo' (length=5)
public 'Location' =>
object(stdClass)[9]
public 'continent' => string 'north america' (length=13)
public 'latitude' => float 37.33053
public 'longitude' => float -121.83823
public 'CountryData' =>
object(stdClass)[10]
public 'country' => string 'united states' (length=13)
public 'country_code' => string 'us' (length=2)
public 'country_cf' => int 99
public 'region' => string 'southwest' (length=9)
public 'StateData' =>
object(stdClass)[11]
public 'state' => string 'california' (length=10)
public 'state_code' => string 'ca' (length=2)
public 'state_cf' => int 94
public 'dma' => int 807
public 'msa' => int 41940
public 'CityData' =>
object(stdClass)[12]
public 'city' => string 'san jose' (length=8)
public 'postal_code' => string '95122' (length=5)
public 'time_zone' => int -8
public 'area_code' => string '408' (length=3)
public 'city_cf' => int 90調用
$location->ipinfo->Location->StateData->state_code; 送回兩個字母的美國州代號,太容易了!陷阱
至於陷阱,也不是太多,比較大的問題是準確性。 免費地理定位數據集一般承諾的準確性大約是半徑 25 英里的範圍,而且不同的數據集也可能給出不同的答案。你會注意到在以上的例子中,同一個 IP 地址 GeoLite 和 Quova 便送回不同的城市和郵政編碼。 Jeremy 用他自己的 IP 地址 (密西西比州 Southaven 市) 測試,GeoLite 送回的地址是田納西州 Collierville 市,而 Quova 則認為在田納西州 Memphis 市。 這兩個城市都在25英里的準確性半徑內,但俱錯誤地報告州份。
結束語
要把地理定位功能添加到網頁應用程序嗎?你只需要 10 分鐘閒暇時間便足夠了。參照上面的例子,你可以毫不費力地啟動這項功能,你必須編寫的程式碼將是微乎其微的,相對於回報你投入的時間肯定是值得的。
