【发布时间】:2011-08-06 23:51:52
【问题描述】:
我正在尝试编写一个快速的 HTML 抓取工具,此时我只专注于在不解析的情况下最大化我的吞吐量。我已经缓存了 URL 的 IP 地址:
public class Data {
private static final ArrayList<String> sites = new ArrayList<String>();
public static final ArrayList<URL> URL_LIST = new ArrayList<URL>();
public static final ArrayList<InetAddress> ADDRESSES = new ArrayList<InetAddress>();
static{
/*
add all the URLs to the sites array list
*/
// Resolve the DNS prior to testing the throughput
for(int i = 0; i < sites.size(); i++){
try {
URL tmp = new URL(sites.get(i));
InetAddress address = InetAddress.getByName(tmp.getHost());
ADDRESSES.add(address);
URL_LIST.add(new URL("http", address.getHostAddress(), tmp.getPort(), tmp.getFile()));
System.out.println(tmp.getHost() + ": " + address.getHostAddress());
} catch (MalformedURLException e) {
} catch (UnknownHostException e) {
}
}
}
}
我的下一步是测试 100 个 URL 的速度,方法是从 Internet 获取它们,读取前 64KB 并转到下一个 URL。我创建了一个FetchTaskConsumer 的线程池,并尝试运行多个线程(i7 四核机器上的 16 到 64 个线程),每个消费者的外观如下:
public class FetchTaskConsumer implements Runnable{
private final CountDownLatch latch;
private final int[] urlIndexes;
public FetchTaskConsumer (int[] urlIndexes, CountDownLatch latch){
this.urlIndexes = urlIndexes;
this.latch = latch;
}
@Override
public void run() {
URLConnection resource;
InputStream is = null;
for(int i = 0; i < urlIndexes.length; i++)
{
int numBytes = 0;
try {
resource = Data.URL_LIST.get(urlIndexes[i]).openConnection();
resource.setRequestProperty("User-Agent", "Mozilla/5.0");
is = resource.getInputStream();
while(is.read()!=-1 && numBytes < 65536 )
{
numBytes++;
}
} catch (IOException e) {
System.out.println("Fetch Exception: " + e.getMessage());
} finally {
System.out.println(numBytes + " bytes for url index " + urlIndexes[i] + "; remaining: " + remaining.decrementAndGet());
if(is!=null){
try {
is.close();
} catch (IOException e1) {/*eat it*/}
}
}
}
latch.countDown();
}
}
我最多可以在大约 30 秒内浏览 100 个 URL,但文献表明我应该能够每秒浏览 300150 个 URL。请注意,我可以访问千兆以太网,尽管我目前在家中使用 20 Mbit 连接运行测试......在任何一种情况下,连接都从未真正得到充分利用。
我尝试过直接使用Socket 连接,但我一定是做错了什么,因为这样更慢!关于如何提高吞吐量的任何建议?
附言
我有一个大约 100 万个流行 URL 的列表,因此如果 100 个不足以进行基准测试,我可以添加更多 URL。
更新:
literature I'm referring 是与 Najork Web Crawler 相关的论文,Najork 声明:
在 17 天内处理了 8.91 亿个 URL
即 ~ 每秒 606 次下载 [on] 4 Compaq DS20E Alpha Servers [with] 4 GB main 内存[,] 650 GB 磁盘空间 [和] 100 MBit/秒。
以太网 ISP 速率限制带宽为 160Mbits/秒
所以它实际上是每秒 150 页,而不是 300 页。我的计算机是 Core i7 和 4 GB RAM,我离它还很远。我没有看到任何说明他们特别使用的东西。
更新:
好的,统计一下……最终结果出来了!事实证明,100 个 URL 对于基准测试来说有点太低了。我将它增加到 1024 个 URL,64 个线程,我为每次提取设置了 2 秒的超时时间,并且我能够达到每秒 21 个页面(实际上我的连接速度约为 10.5 Mbps,所以每秒 21 个页面 * 64KB每页约为 10.5 Mbps)。下面是 fetcher 的样子:
public class FetchTask implements Runnable{
private final int timeoutMS = 2000;
private final CountDownLatch latch;
private final int[] urlIndexes;
public FetchTask(int[] urlIndexes, CountDownLatch latch){
this.urlIndexes = urlIndexes;
this.latch = latch;
}
@Override
public void run() {
URLConnection resource;
InputStream is = null;
for(int i = 0; i < urlIndexes.length; i++)
{
int numBytes = 0;
try {
resource = Data.URL_LIST.get(urlIndexes[i]).openConnection();
resource.setConnectTimeout(timeoutMS);
resource.setRequestProperty("User-Agent", "Mozilla/5.0");
is = resource.getInputStream();
while(is.read()!=-1 && numBytes < 65536 )
{
numBytes++;
}
} catch (IOException e) {
System.out.println("Fetch Exception: " + e.getMessage());
} finally {
System.out.println(numBytes + "," + urlIndexes[i] + "," + remaining.decrementAndGet());
if(is!=null){
try {
is.close();
} catch (IOException e1) {/*eat it*/}
}
}
}
latch.countDown();
}
}
【问题讨论】:
-
为刮板设置浏览器用户代理不是好习惯。
-
文学?你的意思是说javadocs?我找不到任何与 URLConnection 相关的每秒 300 个 URL。
-
URLConnection 主要是每 500 毫秒获取一个页面,java 在这个目的上很慢
-
@Mat,有时我必须模拟浏览器,因为网站可能会更改其内容,具体取决于页面是提供给浏览器还是机器人,如果我正在抓取内容,那么我不会不想错过对用户有价值的内容。我仍然遵守 robots.text 的规定。
-
@Lirik:这样做的网站是故意这样做的。您很可能违反了他们的使用政策。
标签: java performance url web-crawler