我已经搜索过但找不到答案 - 这不是“代码明智”,而是性能更明智。如果你记得我的问题的答案,我会很高兴被指出:)
我使用了一个非常简单的架构 - 我有一个主线程,其中包含要“抓取”的URL列表以及抓取的URL列表(使用其HTML)。两种结构都是并发的。 我为5或6个起始站点中的每一个创建了一个初始线程,然后:
主线程一直循环:如果“要爬网”的URL列表不为空,并且我们还没有达到最大线程数(再次 - 抱歉缺少执行程序和繁忙-wait ...),创建一个新的线程,遍历列表中的下一个URL等。
问题是 - 这个过程有效,但太慢。我的连接速度大约是5Mbits(已测试)。这些网站以每秒约1次的速度进行爬网(slowwww ....)。我打印了下载的html的大小(简单地通过doc.html()。size())并由我自己计算,在25秒的时间内,我使用大约71Kbs。很明显,即使使用这种简单的设计,我也会做错事,您的意见将非常受欢迎。
注1 - 我知道抓取器礼貌和robots.txt。我会尽快使用这些,为什么我爬得这么慢。 注2 - 真的,我知道不那么好的设计:)但是据我所知,它不应该考虑爬行速度。
感谢阅读!
public class Crawler {
public static final int MAX = 500;
public static final int MAX_THREADS = 100;
public static final String[] initialUrls = {"http://www.bbc.com",
"http://www.facebook.com",
"http://www.apple.com",
"http://www.twitter.com",
"http://www.tumblr.com",
"http://www.microsoft.com"};
public static Vector<String> urls;
public static ConcurrentHashMap<String,Vector<String>> db;
public static Vector<Thread> threads;
public static void main(String[] args)
{
db = new ConcurrentHashMap<String, Vector<String>>();
urls = new Vector<String>();
threads = new Vector<Thread>();
for(int i = 0; i < initialUrls.length; i++)
{
Thread t = new Parser(db, urls, initialUrls[i]);
threads.add(t);
t.start();
}
String address;
while(db.size() < MAX)
{
if((urls.size() != 0) && (threads.size() < MAX_THREADS))
{
address = urls.get(0);
urls.remove(0);
Thread t = new Parser(db, urls, address);
t.start();
}
}
try
{
for(Thread t : threads)
{
t.join();
}
}
catch(Exception e)
{
}
// do something
}
}
public class Parser extends Thread{
public Vector<String> urls;
public ConcurrentHashMap<String, Vector<String>> db;
public String start;
public Parser(ConcurrentHashMap<String, Vector<String>> db, Vector<String> urls, String start)
{
this.db = db;
this.start = start;
this.urls = urls;
}
public void start()
{
Crawler.threads.add(this);
parsePage(start);
Crawler.threads.remove(this);
}
private void parsePage(String page)
{
if(db.size() >= Crawler.MAX)
{
return;
}
try
{
Document doc = Jsoup.connect(page).get();
doc = Jsoup.parse(doc.toString());
Vector<String> pageText = doc.html();
if(db.size() < Crawler.MAX)
{
db.put(page, pageText);
}
String newPage;
Elements links = doc.select("a[href]");
for(int i = 0; i < links.size(); i++)
{
newPage = links.get(i).attr("abs:href");
if(!db.containsKey(newPage) && !urls.contains(newPage))
{
urls.add(newPage);
}
}
}
catch(IllegalArgumentException e)
{
}
catch(IOException e)
{
}
}
}
答案 0 :(得分:1)
使用ExecutorService
和Map<String, Future<X>>
。
首先:创建一个ExecutorService
:
final ExecutorService executor = Executors.newFixedThreadPool(...);
第二:创建一个方法,该方法将返回Callable<X>
,其中X
是您要返回的内容:
private Callable<X> callableForURL(final String url)
{
// implementation here
}
第三步:获取您的网址列表,将作业提交给他们:
for (final String url: initialUrls)
map.put(url, executor.submit(callableForURL(url)));
第四步:走你的Map
并在每个值上调用.get()
以获得结果。
查看ExecutorService
和Executors
的javadoc。