原网址: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便可。