生在紅旗下長在春風裏,長期浸泡在河蟹社會裏面所以久而久之就有了一些河蟹的思維方式,正好有一段時間在做一個.NET的網站訪問統計系統,順便想着怎麼“監視”下每一個留言的博主們的所在地,於是就有了如今下圖(網易留言板的樣式)所示的根據獲取到的留言者的IP地址得到留言者所在的地區,當然並沒有河蟹社會監視人民羣衆的意思,純屬瞭解一下各位博主所在的真實地點,萬一是個美女博主不就可以讓我有線索可循。
想要把IPv4地址轉爲真實的地址,肯定要參考IP數據庫,商業的IP數據庫存儲在關係型數據庫中,查詢和使用都非常方便,但是成本不是個人和小公司願意承受的,所以簡單應用的思路就是利用一些免費的IP數據庫或者一些大網站提供的查詢API,他們的數據量足夠我們使用了。
1. 利用純真IP數據庫
利用本地的QQWry.Dat文件,優點是查詢速度非常快,缺點是數據庫文件要放在自己的空間內並且要偶爾更新數據庫。時間關係廢話不多說,下面是使用這個文件的函數,如果是在WordPress裏面使用這個功能,把下面的代碼寫入主題下面的functions.php裏面,然後在comments-list的輸出<?php echo convertip(get_comment_author_ip()); ?>即可;如果是其他程序引用,輸入一個有效的IPv4地址就可以得到一個真實的地址。
- function convertip($ip) {
- //IP數據文件路徑
- $dat_path = 'QQWry.Dat';
- //檢查IP地址
- //if(!preg_match("/^d{1,3}.d{1,3}.d{1,3}.d{1,3}$/", $ip)) {
- // return 'IP Address Error';
- //}
- //打開IP數據文件
- if(!$fd = @fopen($dat_path, 'rb')){
- return 'IP date file not exists or access denied';
- }
- //分解IP進行運算,得出整形數
- $ip = explode('.', $ip);
- $ipNum = $ip[0] * 16777216 + $ip[1] * 65536 + $ip[2] * 256 + $ip[3];
- //獲取IP數據索引開始和結束位置
- $DataBegin = fread($fd, 4);
- $DataEnd = fread($fd, 4);
- $ipbegin = implode('', unpack('L', $DataBegin));
- if($ipbegin < 0) $ipbegin += pow(2, 32);
- $ipend = implode('', unpack('L', $DataEnd));
- if($ipend < 0) $ipend += pow(2, 32);
- $ipAllNum = ($ipend - $ipbegin) / 7 + 1;
- $BeginNum = 0;
- $EndNum = $ipAllNum;
- //使用二分查找法從索引記錄中搜索匹配的IP記錄
- while($ip1num>$ipNum || $ip2num<$ipNum) {
- $Middle= intval(($EndNum + $BeginNum) / 2);
- //偏移指針到索引位置讀取4個字節
- fseek($fd, $ipbegin + 7 * $Middle);
- $ipData1 = fread($fd, 4);
- if(strlen($ipData1) < 4) {
- fclose($fd);
- return 'System Error';
- }
- //提取出來的數據轉換成長整形,如果數據是負數則加上2的32次冪
- $ip1num = implode('', unpack('L', $ipData1));
- if($ip1num < 0) $ip1num += pow(2, 32);
- //提取的長整型數大於我們IP地址則修改結束位置進行下一次循環
- if($ip1num > $ipNum) {
- $EndNum = $Middle;
- continue;
- }
- //取完上一個索引後取下一個索引
- $DataSeek = fread($fd, 3);
- if(strlen($DataSeek) < 3) {
- fclose($fd);
- return 'System Error';
- }
- $DataSeek = implode('', unpack('L', $DataSeek.chr(0)));
- fseek($fd, $DataSeek);
- $ipData2 = fread($fd, 4);
- if(strlen($ipData2) < 4) {
- fclose($fd);
- return 'System Error';
- }
- $ip2num = implode('', unpack('L', $ipData2));
- if($ip2num < 0) $ip2num += pow(2, 32);
- //沒找到提示未知
- if($ip2num < $ipNum) {
- if($Middle == $BeginNum) {
- fclose($fd);
- return 'Unknown';
- }
- $BeginNum = $Middle;
- }
- }
- $ipFlag = fread($fd, 1);
- if($ipFlag == chr(1)) {
- $ipSeek = fread($fd, 3);
- if(strlen($ipSeek) < 3) {
- fclose($fd);
- return 'System Error';
- }
- $ipSeek = implode('', unpack('L', $ipSeek.chr(0)));
- fseek($fd, $ipSeek);
- $ipFlag = fread($fd, 1);
- }
- if($ipFlag == chr(2)) {
- $AddrSeek = fread($fd, 3);
- if(strlen($AddrSeek) < 3) {
- fclose($fd);
- return 'System Error';
- }
- $ipFlag = fread($fd, 1);
- if($ipFlag == chr(2)) {
- $AddrSeek2 = fread($fd, 3);
- if(strlen($AddrSeek2) < 3) {
- fclose($fd);
- return 'System Error';
- }
- $AddrSeek2 = implode('', unpack('L', $AddrSeek2.chr(0)));
- fseek($fd, $AddrSeek2);
- } else {
- fseek($fd, -1, SEEK_CUR);
- }
- while(($char = fread($fd, 1)) != chr(0))
- $ipAddr2 .= $char;
- $AddrSeek = implode('', unpack('L', $AddrSeek.chr(0)));
- fseek($fd, $AddrSeek);
- while(($char = fread($fd, 1)) != chr(0))
- $ipAddr1 .= $char;
- } else {
- fseek($fd, -1, SEEK_CUR);
- while(($char = fread($fd, 1)) != chr(0))
- $ipAddr1 .= $char;
- $ipFlag = fread($fd, 1);
- if($ipFlag == chr(2)) {
- $AddrSeek2 = fread($fd, 3);
- if(strlen($AddrSeek2) < 3) {
- fclose($fd);
- return 'System Error';
- }
- $AddrSeek2 = implode('', unpack('L', $AddrSeek2.chr(0)));
- fseek($fd, $AddrSeek2);
- } else {
- fseek($fd, -1, SEEK_CUR);
- }
- while(($char = fread($fd, 1)) != chr(0)){
- $ipAddr2 .= $char;
- }
- }
- fclose($fd);
- //最後做相應的替換操作後返回結果
- if(preg_match('/http/i', $ipAddr2)) {
- $ipAddr2 = '';
- }
- $ipaddr = "$ipAddr1 $ipAddr2";
- $ipaddr = preg_replace('/CZ88.Net/is', '', $ipaddr);
- $ipaddr = preg_replace('/^s*/is', '', $ipaddr);
- $ipaddr = preg_replace('/s*$/is', '', $ipaddr);
- if(preg_match('/http/i', $ipaddr) || $ipaddr == '') {
- $ipaddr = 'Unknown';
- }
- $ipaddr = iconv('gbk', 'utf-8//IGNORE', $ipaddr); //轉換編碼,如果網頁的gbk可以刪除此行
- return $ipaddr;
- }
2. 利用門戶網站的接口
目前已知的有騰訊、新浪、網易、搜狐和Google提供IP地址查詢API,但是找得到的只有騰訊、新浪和網易的,Google的貌似要用Google Maps所以沒有研究。看了下國內的幾個騰訊提供的是JavaScript的,網易提供的是XML,而新浪的有多種格式可以用,注意非XML的數據源都是GBK格式的,不管是JavaScript調用還是PHP調用都要轉換一下編碼,不然得到的是亂碼。而更需要注意的是,如果一次性查詢多個IP,使用門戶網站的API來查詢會非常緩慢,我大概寫了個for循環試了下,不管是用PHP解析XML還是file_get_contents()函數獲取內容,查詢10次以上會變得非常緩慢,甚至可能超時。
騰訊的IP地址API接口地址:http://fw.qq.com/ipaddress,返回的是數據格式爲:var IPData = new Array("123.124.2.85","","北京市","");,一個JavaScript的對象,目前還不知道如何輸入IP查詢。
新浪的IP地址查詢接口:http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js
新浪多地域測試方法:http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js&ip=123.124.2.85
網易有道的IP地址查詢接口:http://www.youdao.com/smartresult-xml/search.s?type=ip&q=123.124.2.85
騰訊的調用方法
- //騰訊API的PHP調用方法
- function getIpPlace(){
- $ip=file_get_contents("http://fw.qq.com/ipaddress");
- $ip=str_replace('"',' ',$ip);
- $ip2=explode("(",$ip);
- $a=substr($ip2[1],0,-2);
- $b=explode(",",$a);
- return $b;
- }
- $ip=getIpPlace();
- print_r($ip);
網易有道的調用方法
- //有道API的PHP調用方法
- $url = "http:www.youdao.com/smartresult-xml/search.s?type=ip&q=".$ip;
- $doc = new DOMDocument();
- $doc->load($url);
- $smartresult = $doc->getElementsByTagName("product");
- foreach($smartresult as $product)
- {
- $locations = $product->getElementsByTagName("location");
- $location = $locations->item(0)->nodeValue;
- }
- if($location != "")
- {
- echo $i.".".$ip;
- echo " 來自".$location."的網友";
- }
- else
- {
- echo $i.".".$ip;
- echo " 來自火星的網友";
- }
新浪的調用方法
- public function sinaIPApi($ip){
- $str = file_get_contents("http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=".$ip);
- $str = iconv("gbk", "utf-8//IGNORE", $str);
- preg_match_all("/[\x{4e00}-\x{9fa5}]+/u",$str,$get);
- $add = implode('',$get[0]);
- return $add;
- }
- //$get是一個非常棒的二維數組
其中有道和新浪的是我自己寫的,新浪API也可以像騰訊API那樣用file_get_contents()函數獲取完地址後使用一連串的字符串函數處理,我寫的函數使用正則表達式從新浪的返回結果中提供包含中文的字符串,並且分段存入一個二維數組,這個可能只是針對新浪的API有用並且存在bug。舉個例子查詢學校分配給我的IP地址後var_dump()一下函數裏面的$get變量得到以下結果: array(1) { [0]=> array(6) { [0]=> string(6) "中國" [1]=> string(6) "北京" [2]=> string(6) "北京" [3]=> string(9) "教育網" [4]=> string(6) "學校" [5]=> string(18) "中國地質大學" } },而函數輸出的結果則是“中國北京北京教育網學校中國地質大學”,希望我的思路和方法能對別人有用。 最後再次提醒,如果是WordPress請使用第一種方法,否則使用API同時查詢所有留言者的真實地址會讓PHP超時的,希望各路大牛有更好的方法,至於限制顯示和顯示方式等神馬的都是WordPress應用問題,同時對於Java和C#來說思路也是一樣的,這些後續的問題等我考完試再細說。