原網址:http://blog.dayanjia.com/2010/12/fetch-anti-stealing-linked-images-and-show-them-in-browser-automatically/ 感謝分享。php
許多網站或者處於保護資源的目的,或者處於節省服務器資源的考慮,將其下的圖片等多媒體資源設置了防盜鏈保護,只有從原始網站訪問時纔會正確顯示。如果有人將這些圖片外鏈到其餘地方,就會沒法顯示,或者顯示一個開發者預先指定好的防盜鏈替代圖片。當咱們想要真正獲取這些圖片的時候,就會遇到一些麻煩。固然,麻煩老是能被解決的,今天就來結合最近的一個項目來看看如何作到自動獲取這些圖片,並在本地進行緩存,同時發送給瀏覽器顯示。
以南京大學小百合BBS爲例,咱們已經使用腳本實現了獲取某帖子中樓主貼的全部信息而且轉換成了乾淨的HTML。這時候全部的圖片都是以這樣的形式呈現的:html
<img src="http://bbs.nju.edu.cn/XXXXXXXXXXXX" alt="" />
這時咱們將這些HTML發佈到其餘網站時,圖片所有顯示爲一張帶有來源說明的防盜鏈圖片,瀏覽效果不好。python
事實上,大多數網站判斷訪問來源是經過HTTP Request Header中的Referer判斷的。瀏覽器訪問資源時,會自動附帶上這個Referer字段表示用戶是從那個網址訪問到該資源的。在RFC 2616 超文本傳輸協議 HTTP/1.1中,有對它的詳細描述。linux
當咱們從外站訪問這些圖片時,瀏覽器自動在Header中Referer字段提供了當前的網址,那麼對方服務器一判斷,不是從本身網站訪問的,天然就拒絕顯示了。正則表達式
爲了破解這種限制,天然要請來強大的curl。這裏咱們使用的是PHP自帶的curl庫。在PHP中使用curl,基本上分爲三步,首先curl_init初始化一個鏈接,而後用curl_setopt指定鏈接的各類操做和屬性,最後用curl_exec執行。讓咱們來看具體代碼:瀏覽器
function fetch_bbs_image($url) { $curl = curl_init($url); //初始化 curl_setopt($curl, CURLOPT_HEADER, FALSE); //將結果輸出到一個字符串中,而不是直接輸出到瀏覽器 curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); //最重要的一步,手動指定Referer curl_setopt($curl, CURLOPT_REFERER, 'http://bbs.nju.edu.cn'); $re = curl_exec($curl); //執行 if (curl_errno($curl)) { return NULL; } return $re; }
相信你們都很明白了吧,設置CURLOPT_REFERER這個屬性是最關鍵的一步。緩存
通常狀況下,咱們在PHP中echo 'hello'是將字符串做爲純文本輸出到瀏覽器中的。至於爲何是純文本,這就又要扯到Response Header中的Content-Type了,這即是用來指定內容類型的。這個Content-Type其實是MIME(Multipurpose Internet Mail Extensions,多用途互聯網郵件擴展)標準中的一部分,是經過好幾個RFC定義的。大多數網頁都是「text/html」。若是要用來顯示圖片,就須要修改這個字段,對應不一樣的圖片格式,有image/jpeg、image/png、image/gif等等。服務器
爲了在PHP中使用echo命令輸出圖片信息,咱們就須要修改header信息。根據源圖片URL中的後綴名,咱們能夠相應地使用諸如header("Content-Type: image/jpeg");來修改header信息。接下來,echo fetch_bbs_image($url);便可。curl
若是每次訪問圖片咱們都須要在服務器上使用curl遠程下載一個下來,是比較消耗資源的,咱們能夠作一個簡單的本地緩存,第一次調用時進行下載的操做,從此就能夠直接從本地緩存調取圖片了。這時候咱們須要保證下載回來的圖片的文件名都是惟一的。這個好辦,經過分析小百合BBS的文件路徑,咱們能夠發現文件路徑都是相似http://bbs.nju.edu.cn/file/xxx/xxxx.jpg的。因此咱們只要把xxx/xxxx.jpg保存做爲文件名便可,固然須要把其中的斜槓替換成其餘字符。函數
define(CACHE_DIR, './lily_images/'); function get_filename($url) { return CACHE_DIR . str_replace('/', '-', substr($url, 27)); } if (file_exists(get_filename($url))) { // cache hit! echo file_get_contents(get_filename($url)); exit(); } else { // save cache $filename = get_filename($url); file_put_contents( $filename, fetch_bbs_image($url) ); echo file_get_contents($filename); }
實際應用時,咱們發現有時候小百合BBS中的圖片都是幾百萬像素的照片原圖,在校園網內訪問這些圖片天然是毫無壓力的,並且Web版BBS中有JavaScript來自動將過大的圖片強制縮小顯示,以避免撐破版面。可是到了外站,如此大的圖片就顯得有些誇張了,利用PHP中的GD圖形庫,咱們能夠方便地進行圖片的二次處理,首要的需求天然是將過大的圖片縮小。GD庫並無直接按比例縮小圖片的功能(若是有也過高級了),好在網上早已有許多現成的代碼片斷,咱們便無需再次發明輪子了。參考了Maxim Chernyak的代碼片斷,咱們能夠很輕鬆地實現這一功能。須要說明的是,原始的代碼片斷中對於小圖片也會進行放大處理,並且它對GIF動畫的處理會讓它變成靜止圖片,所以須要對其進行小小的修改來知足咱們的須要。
修改後的主腳本和圖片縮小函數
switch (strtolower(substr($url, - 3))) { case 'jpg' : case 'pge' : $type = 'image/jpeg'; break; case 'png' : $type = 'image/png'; break; case 'gif' : $type = 'image/gif'; break; default : $type = ''; } header("Content-Type: $type"); if (file_exists(get_filename($url))) { // cache hit! echo file_get_contents(get_filename($url)); exit(); } else { // resize it and save cache $filename = get_filename($url); $img_content = fetch_bbs_image($url); file_put_contents($filename, $img_content); if ($type == 'image/png' || $type == 'image/jpeg') { smart_resize_image($filename, 550, 550, true); } echo file_get_contents($filename); } /** * Smart Image Resizing while Preserving Transparency With PHP and GD Library * tinily modified by @author clippit * * @author Maxim Chernyak * @link http://mediumexposure.com/smart-image-resizing-while-preserving-transparency-php-and-gd-library/ */ function smart_resize_image($file, $width = 0, $height = 0, $proportional = false, $output = 'file', $delete_original = true, $use_linux_commands = false) { if ($height <= 0 && $width <= 0) { return false; } $info = getimagesize($file); $image = ''; if ($info [0] <= $width || $info [1] <= $height) { // if the original image is too small to the target width and height, then do not zoom in return false; } $final_width = 0; $final_height = 0; list ( $width_old, $height_old ) = $info; if ($proportional) { if ($width == 0) $factor = $height / $height_old; elseif ($height == 0) $factor = $width / $width_old; else $factor = min($width / $width_old, $height / $height_old); $final_width = round($width_old * $factor); $final_height = round($height_old * $factor); } else { $final_width = ($width <= 0) ? $width_old : $width; $final_height = ($height <= 0) ? $height_old : $height; } switch ($info [2]) { case IMAGETYPE_GIF : $image = imagecreatefromgif($file); break; case IMAGETYPE_JPEG : $image = imagecreatefromjpeg($file); break; case IMAGETYPE_PNG : $image = imagecreatefrompng($file); break; default : return false; } $image_resized = imagecreatetruecolor($final_width, $final_height); if (($info [2] == IMAGETYPE_GIF) || ($info [2] == IMAGETYPE_PNG)) { $trnprt_indx = imagecolortransparent($image); // If we have a specific transparent color if ($trnprt_indx >= 0) { // Get the original image's transparent color's RGB values $trnprt_color = imagecolorsforindex($image, $trnprt_indx); // Allocate the same color in the new image resource $trnprt_indx = imagecolorallocate($image_resized, $trnprt_color ['red'], $trnprt_color ['green'], $trnprt_color ['blue']); // Completely fill the background of the new image with allocated color. imagefill($image_resized, 0, 0, $trnprt_indx); // Set the background color for new image to transparent imagecolortransparent($image_resized, $trnprt_indx); } // Always make a transparent background color for PNGs that don't have one allocated already elseif ($info [2] == IMAGETYPE_PNG) { // Turn off transparency blending (temporarily) imagealphablending($image_resized, false); // Create a new transparent color for image $color = imagecolorallocatealpha($image_resized, 0, 0, 0, 127); // Completely fill the background of the new image with allocated color. imagefill($image_resized, 0, 0, $color); // Restore transparency blending imagesavealpha($image_resized, true); } } imagecopyresampled($image_resized, $image, 0, 0, 0, 0, $final_width, $final_height, $width_old, $height_old); if ($delete_original) { if ($use_linux_commands) exec('rm ' . $file); else @unlink($file); } switch (strtolower($output)) { case 'browser' : $mime = image_type_to_mime_type($info [2]); header("Content-type: $mime"); $output = NULL; break; case 'file' : $output = $file; break; case 'return' : return $image_resized; break; default : break; } switch ($info [2]) { case IMAGETYPE_GIF : imagegif($image_resized, $output); break; case IMAGETYPE_JPEG : imagejpeg($image_resized, $output); break; case IMAGETYPE_PNG : imagepng($image_resized, $output); break; default : return false; } return true; }
咱們將這個腳本放在能夠訪問到的Web目錄中,而且創建一個CACHE_DIR中指定的目錄,給它賦予775權限。URL參數咱們經過GET參數來得到。爲了防止一些莫名其妙的編碼問題,而且掩耳盜鈴一下,這個參數咱們採用Base64編碼後再進行URL轉義。
同時,若是傳入的URL參數不是來自http://bbs.nju.edu.cn的,就直接用header("Location: $url")重定向到目標網址,不處理該圖片文件。
最開始咱們說到,圖片都是<img src="http://bbs.nju.edu.cn/XXXXXXXXXXXX" alt="" />這樣的,在發佈的時候就須要把其中的src所有改掉了。使用強大的正則表達式,咱們能夠輕鬆地在大量HTML中替換這些,以Python腳本爲例,咱們只需兩個函數:
import base64, re, urllib def encode_url(match): url = urllib.pathname2url( base64.b64encode(match.group(1)) ) return ''.join( ('<img alt="" src="', GET_IMAGE, url, '"') ) def image_proxy(text): return re.sub(r'<img alt="" src="([^"]+)"', encode_url, text)
在須要的時候,將HTML代碼字符串傳入image_proxy便可。