1 前言
- 这里先说一些基础概念,主要给还不是特别清楚的同志一些学习的机会。
1.1 标准ISO7498——开放式系统互联参考模型(ISO/RM)
- 有国际标准化组织定义的一个模型,包含了硬件与软件的组织与设计所必须遵循的规定。
- 物理层:媒介;用于建立、保持和断开物理接口。
- 数据链路层:主要负责数据链路的建立、维持的拆除等。
- 网络层:又名通信子网。控制子网的运行。
- 传输层:在经济而又有效的前提下保证通信的质量。
- 会话层:提供一种有效方法,以组织并协商两个表示层进程之间的会话,并管理它们之间的数据交换。
- 表示层:解决用户信息的语法表示问题。
- 应用层:ISO最高层,直接面向用户,是利用网络资源,唯一向应用程序提供服务的层。
1.2 TCP/IP
- ISO/RM的一个子类,构筑在物理层硬件概念性层次基础之上。
- 应用层:提供一组常用的应用程序,主要有文件传输协议、远程访问协议及其电子邮件协议等,负责接收和发送数据,并把数据按传输层格式组织好向下层传输。
- 传输层:基本任务是提供应用程序之间端到端的通信,并把数据分组可靠地传给下一层。
- 网间网层:负责相邻计算机之间的通信。其功能主要是处理来自传输层的分组发送请求,并将该分组装入IP数据报中,再把它交给下一层。网间网层具有路径选择、流量控制、拥塞和差错报告等功能。
- 网络接口层:对应于OSI的数据链路层和物理层,包含了各种逻辑链路控制和介质访问协议,实现了不同网络间的物理层连接。
1.3 超文本传输协议(HTTP协议)
- 用于在客户端和服务器间请求和应答的协议,它并不是TCP/IP的子集。
- HTTP 并不局限于使用网络协议(TCP/IP)及其相关支持层,尽管这是它在互联网上最为流行的应用程序。 事实上,HTTP可以“在任何其他互联网协议之上执行,或者在其他网络上执行。HTTP 只认可可靠的传输,任何能够提供这种保证的协议都可以被其使用,详情见HTTP协议的几个重要概念。
- 请求信息,发出的请求信息包括以下几个:
- 请求行,例如GET /images/logo.gif HTTP/1.1,表示从/images 目录下请求logo.gif 这个文件。
- 标题,例如Accept-Language: en
- 空行,请求行和标题必须以
作为结尾 - 可选信息
- 请求方法,HTTP 定义了八种方法来指示确认的资源执行所需的行为:
- HEAD
- 要求与GET请求相应的回复一样的应答,但是没有回应的内容。这对找回写在回应标题中的meta-infomation 有帮助,不需要传输整个内容。
- GET
- 请求某个特殊的资源,是目前网上最通用的方法。不应该用于一些会造成副作用的操作中(在网络软件中使用是一个常见的错误用法)。参看下个目录的安全方法。
- POST
- 向确定的资源提交需要处理的数据。这些数据包括在请求的内容里。这可以造成新资源的产生和更新已有资源。
- PUT
- 上传特定资源
- DELETE
- 删除特定资源
- TRACE
- 返回接收的请求,客户端可因此察看在请求过程中什么中间服务器被加进来或者有所改变。
- OPTIONS
- 返回服务器支持的HTTP方法,这可以用来检查网络服务器的功能。
- CONNECT
- 将请求连接转换成透明的TCP/IP通道,通常通过非加密的HTTP代理利用SSL-加密通讯(HTTPS)。
- HEAD
1.4 HTTP代理
- 代理就等于一个网络中转站。举个例子,A页面要访问B页面,但是A页面无法直接访问,代理可以直接访问B页面,而A页面又可以直接访问代理,那么通过代理中转A页面仍然可以访问到B页面,有点类似 ssh tunnel 。
- HTTP代理按匿名功能(是否具有隐藏IP的功能)分类。
- 非匿名代理:不具有匿名功能。
- 匿名代理。使用此种代理时,虽然被访问的网站不能知道你的IP地址,但仍然可以知道你在使用代理,有些侦测IP的网页也仍然可以查到你的IP。
- 高度匿名代理:使用此种代理时,被访问的网站不知道你的IP地址,也不知道你在使用代理进行访问。此种代理的隐藏IP地址的功能最强。
- 按请求信息的安全性分类
- 全匿名代理:不改变你的request fields,使服务器端看来就像有个真正的客户浏览器在访问它。当然,你的真实IP是隐藏起来的。服务器的网管不会认为你使用了代理。
- 普通匿名代理:能隐藏你的真实IP,但会更改你的request fields,有可能会被认为使用了代理,但仅仅是可能,一般说来是没问题的。不过不要受它的名字的误导,其安全性 可能 比全匿名代理更高,有的代理会剥离你的部分信息(就好比防火墙的stealth mode),使服务器端探测不到你的操作系统版本和浏览器版本。
- elite代理:匿名隐藏性更高,可隐藏系统及浏览器资料信息等。此种代理安全性特强。
- 透明代理(简单代理):透明代理的意思是客户端根本不需要知道有代理服务器的存在,它改编你的request fields(报文),并会传送真实IP。注意,加密的透明代理则是属于匿名代理,意思是不用设置使用代理了,例如Garden 2程序。
- 代理通用格式,以 218.249.83.87:8080@HTTP$6&94,1024,1209#北京市电信通 例子为例
- 218.249.83.87 表示为代理服务器的IP地址为218.249.83.87
- :8080 表示“:”后的8080表示该代理服务器的服务端口为8080(21、23、80、81、1080、3128、8080等)
- @HTTP 表示“@”后的HTTP表示该代理服务器的类型为HTTP代理(HTTP、FTP、SOCKS4/5、TELNET五类)
- $ 表示“$”后的数值表示代理服务器验证状态
- $4:正在验证
- $5:验证超时(网络连接太慢,再校验多几次会有所发现)
- $6:免费的(这才是我们所要的^.^)
- $7:要密码(可以用demo/demo、guest/gues、temp/temp、share/ahare、test/test作为口令/密码试试)
- $8:不合符协议
- $9:不匹配(如果代理服务器太忙也会出现这种情况)
- $10:不支持的协议
- $11:无法确定
- &94,1024,1209 表示“&”后的以 “,” 分隔的三个数值是反映该代理本地连接的三个时间特性,第一个是反应速度,第二个是校验时间,第三个是连接时间,所以当然也就是数值越小的代理就是越快的。
- #北京市电信通 表示代理为北京市电信通(自定义)。
1.5 举例及工具
- 通过HTTP协议直接访问网站,在Dos窗口或者Shell环境下,直接输入:
telnet www.yahoo.cn 80会有什么结果?就会发现和在打开浏览器http://www.yahoo.cn首页的源码一模一样,这个是没有任何封装的效果,简单的完成一次服务器与客户端的通信。 代理是通过什么方式呢?其实也很简单,在Dos窗口或者Shell环境下,直接输入:
GET / HTTP/1.1
Host: www.yahoo.cn
发现除了连接目标和返回的HTTP头文件不同外,其他内容都是一样的,都是返回雅虎全能搜索的html源代码,从返回的头来看说明这个代理是可用的,在这里,就有人会问道,有什么好的工具来检测一下传输的协议以及数据包和数据流呢?
telnet 124.133.37.248 8080
GET / HTTP/1.1
Host: www.yahoo.cn
- 这里顺便介绍一下一个强劲的是一个协议分析器/包嗅探应用程序工具: Wireshark 。
- Wireshark
- 关于 Wireshark ,其实就是以前的 Ethereal , 关于为什么更名感兴趣的可以参考一下http://blog.ijliao.info/archives/2006/06/10/2358/,这里就不多说了。
- Wireshark 有Unix版本也有Windows版本,是开源的,可以去http://www.wireshark.org/download.html下载最新的版本,里面也有很多的Third-Party Packages。
- 这里介绍一下 Wireshark 简单的用法,对下面分析很有帮助,首先,打开 Wireshark ,界面如图:
- 初始界面:
- 选择菜单:
- 选择网卡:
- 选择filter:
- GET请求监控:
- 服务器响应请求并返回:
2 抓取代理
2.1 构造代理
- 和以前做过抓取的同事沟通以及以往做代理的经验,总结了两种方案可行:
- 种子站点查找法
- 此方式是利用已知网页所提供的代理来得到代理,这种方式比较容易获得代理,只需要抓取种子站点的页面进行文档分析即可。但有一个缺点,不能保证所以的代理都能使用,而且针对服务器来说根本不知道该代理服务器的速度等情况,需要进行下步验证。
- IP段扫描法
- 针对对一段IP段和http常用端口进行扫描,扫描的过程中对ip进行即时验证,该方案能比较全面的验证代理,缺点是扫描时间比较长,比较难获得代理。其实一些大型代理网站的代理除了人为提供外,基本上都采用此方式;
- 种子站点查找法
- 由于寻找大量代理并不是我们的目的,于是采用第一种方案。
2.2 抓取代理
- 由于需求量小,我们直接从一些提供代理的网站抓取,比如:proxycn.com,获取一些具有代理的页面,这样简单而有效获得了一个代理列表;
- 利用Perl的强大的正则将代理抠出来,生成一个列表。
3 验证代理
- 整个流程最关键的部分,以前介绍的都是为验证代理做铺垫;
3.1 验证方案
- 抓取代理流程中生成的一个代理列表,每日逐个对其进行速率、日有效积累次数的验证;
- 使用Perl脚本对其简单验证如下:
#!/usr/bin/perl
# simple.pl By Cnangel
use strict;
use warnings;
if (@ARGV != 1) { print "$0 <proxy>\n";exit; }
my $proxy = $ARGV[0];
&chooseproxy($proxy);
sub chooseproxy
{
my $proxy = shift;
my $url = 'http://fans.huhoo.net/log/getlog.php';
my $ref = &gethtml($url, $proxy);
my $thiscont = join("", @$ref);
my $contpos = index($thiscont, "\r\n\r\n");
my $content = substr($thiscont, $contpos + 4);
print "ok, $proxy is right proxy!\n" ($$ref && $$ref =~ /(\d+\.){3}\d+/ && $$ref !~ /101\.1\.23\.33/); #自己的IP地址
}
sub gethtml
{
my ($url, $proxy) = @_;
eval("use Socket;");
my ($host, $port, $rhost, $rport, $path);
if ($proxy ne '' && $proxy ne 'direct')
{
($rhost, $rport) = split(/:/, $proxy);
$rport ||= 80;
$path = $url;
$path =~ s/^http:\/\///i;
($host, undef) = split(/\//, $path);
($host, $port) = split(/:/, $host);
$port ||= 80;
$path = $url !~ /^http:\/\//i ? "http://$url" : $url;
}
else
{
$url =~ s/^http:\/\///i;
($host, undef) = split(/\//, $url);
$path = $url;
$path =~ s/^$host//iso;
($host, $port) = split(/:/, $host);
$port ||= 80;
$path = "/$path" if ($path !~ /^\//);
}
my ($name, $aliases, $type, $len, @thataddr, $a, $b, $c, $d, $that);
($name, $aliases, $type, $len, @thataddr) = gethostbyname($rhost);
print "$rhost:$rport\n";
($a, $b, $c, $d) = unpack("C4", $thataddr[0]);
$that = pack('S n C4 x8', 2, $rport, $a, $b, $c, $d);
return "" unless (socket(S, 2, 1, 0));
select(S);
$| = 1;
select(STDOUT);
return "" unless (connect(S, $that));
print S "GET $path HTTP/1.0\r\n";
print S "Host: $host\r\n";
print S "Accept: */*\r\n";
print S "User-Agent: Mozilla/4.0 (compatible; MSIE 6.00; Windows NT 5.1)\r\n";
print S "Pragma: no-cache\r\n";
print S "Cache-Control: no-cache\r\n";
print S "Connection: close\r\n";
print S "\r\n";
my @results = <S>;
close(S);
return \@results;
}
- 其中 http://fans.huhoo.net/log/getlog.php 是返回一个 $_SERVER['REMOTE_ADDR'] 的值,整个流程就是返回整个值是否是一个ip,且不能为本机ip;
- arrayref gethtml(string $url, string $proxy)
- 如果没有代理,则直接通过socket进行连接;
- 如果使用代理,则使用代理进行socket通信,并将HTTP协议响应头与内容作为数组引用返回;
- 在请求的协议头里,如果$host值为代理host,则是寻找透明代理。
- 如果GET部分使用HTTP1.1协议进行请求,如果服务器返回 “Transfer-Encoding: chunked” 字样的头,返回的主体信息有两个部分:
- 第一部分的第一行是该主题信息chunk的长度和单位(单位一般省略),接着是chunk的内容;
- 最后部分的第一行是该尾部信息chunk的长度和单位(单位也一般省略),一般情况而言,该尾部信息是没有的,所以结果只能看到一个0字符。
3.2 各种不同的方式验证代理
- 上面的举例是最原始的验证代理方式,也是直接接触HTTP协议的方法,这里举集中其他方式验证代理的方法,供大家灵活选用。
- C使用 libcurl库 :
#include <stdio.h>
#include <stdlib.h>
#include <curl/curl.h>
static char errorBuffer[CURL_ERROR_SIZE];
int main(int argc, char *argv[])
{
CURL *curl;
CURLcode res;
if (argc != 2)
{
fprintf(stderr, "Usage: %s <proxy>\n", argv[0]);
exit(EXIT_FAILURE);
}
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if (curl)
{
res = curl_easy_setopt(curl, CURLOPT_URL, "http://fans.huhoo.net/log/getlog.php");
if (res != CURLE_OK)
{
fprintf(stderr, "Failed to set URL [%s]\n", errorBuffer);
exit(EXIT_FAILURE);
}
res = curl_easy_setopt(curl, CURLOPT_PROXY, argv[1]);
if (res != CURLE_OK)
{
fprintf(stderr, "Failed to connect Proxy [%s]\n", errorBuffer);
exit(EXIT_FAILURE);
}
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return 0;
}
- perl的LWP模块:
#!/usr/bin/perl
use strict;
use warnings;
if (@ARGV != 1) { print "$0 <proxy>\n";exit; }
my $proxy = $ARGV[0];
$res = getpage($proxy);
print $$res;
exit;
sub getpage
{
my ($url, $proxy) = @_;
my $content = '';
eval("use LWP::UserAgent");
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
# $ua->proxy(['http', 'ftp'], 'http://'.$proxy);
$ua->proxy('http', 'http://' . $proxy) if ($proxy);
# 也可以使用环境变量进行指定相应的代理
# if($proxy)
# {
# $ENV{HTTP_PROXY} = $proxy;
# $ua->env_proxy;
# }
my $res = $ua->get($url);
if ($res->is_success)
{
$content = $res->content;
}
return \$content;
}
- Perl的Curl模块(基于libcurl)
方法类似C语言的curl,不再累述。
- PHP的常见连接代理方式
- fsockopen方式
<?php
$content= '';
$proxy = '124.133.37.247:8080';
$url = 'http://fans.huhoo.net/log/getlog.php';
getpage($url, $content, $proxy);
echo $content;
function getpage($url, &$content, $proxy = '', $timeout = 30)
{
# 分析 Url 构成
$path = '/';
$port = 80;
$hostpos = strpos($url, ':');
$host = $hostpos !== false ? substr($url, $hostpos + 3) : $url;
$pathpos = strpos($host, '/');
if ($pathpos !== false)
{
$path = substr($host, $pathpos);
$host = substr($host, 0, $pathpos);
}
$portpos = strpos($host, ':');
if ($portpos !== false)
{
$port = substr($host, $portpos + 1);
$host = substr($host, 0, $portpos);
}
$fp = '';
if (empty($proxy))
{
$fp = fsockopen($host, $port, &$errno, &$errstr, $timeout);
}
else
{
$rportpos = strpos($proxy, ':');
if ($rportpos !== false)
{
$rport = substr($proxy, $rportpos + 1);
$rhost = substr($proxy, 0, $rportpos);
}
$fp = fsockopen($rhost, $rport, &$errno, &$errstr, $timeout);
}
# 打开一个socket连接
if (!$fp)
{
echo "$errstr ($errno)<br />\n";
}
else
{
fputs($fp, "GET $path HTTP/1.1\r\n");
fputs($fp, "Host: $host\r\n");
fputs($fp, "Accept: */*\r\n");
fputs($fp, "Referer: http://$host/\r\n");
fputs($fp, "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\r\n");
fputs($fp, "Connection: Close\r\n\r\n");
}
while ($str = fread($fp, 4096))
{
$content .= $str;
}
fclose($fp);
return;
}
?>
-
- curl方式
见上面的C语言举例或php手册curl函数部分,这里也不再累述。
4 代理的使用
- 这里主要介绍怎么使用代理列表,我们经常去抓百度、Google等站点的页面和图片,抓取频度过高会导致IP被封,这时候代理列表就起到作用了, 。
- 其实,上面的验证代理部分已经详细说明了如果通过代理访问其它页面,从而得到想要的数据,这里不再累述,通过前面的实例可以很清楚的使用各种你最熟悉的语言运用了。
5 参考文档
- 《计算机网络基础》
- Hypertext Transfer Protocol -- HTTP/1.1
- libcurl官方文档
- PHP官方手册(中文)
- HTTP协议大全(中文)