我正在创建一个网络抓取工具,用于从网络中提取链接和电子邮件。链接将用于查找新地点以搜索电子邮件,然后电子邮件将存储在一组中。每个链接都传递到自己的线程中的固定线程池,以查找更多电子邮件。我从小开始只寻找10封电子邮件但由于某种原因,我的代码返回了大约13封电子邮件。
while (emailSet.size() <= EMAIL_MAX_COUNT) {
link = linksToVisit.poll();
linksToVisit.remove(link);
linksVisited.add(link);
pool.execute(new Scraper(link));
}
pool.shutdownNow();
emailSet.stream().forEach((s) -> {
System.out.println(s);
});
System.out.println(emailSet.size());
虽然我知道有可能创建额外的线程,在我收到10封电子邮件之后仍然会运行但不应该pool.shutdownNow()
结束这些线程吗?
如果有帮助,这是我的线程代码。
class Scraper implements Runnable {
private String link;
Scraper(String s) {
link = s;
}
@Override
public void run() {
try {
Document doc = (Document) Jsoup.connect(link).get();
Elements links = doc.select("a[href]");
for (Element href : links) {
String newLink = href.attr("abs:href");
if (!linksVisited.contains(newLink) && !linksToVisit.contains(newLink)) {
linksToVisit.add(newLink);
}
}
Pattern p = Pattern.compile(
"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+");
Matcher matcher = p.matcher(doc.text());
while (matcher.find()) {
emailSet.add(matcher.group());
}
} catch (Exception e) {
//Catch on of the many exceptions Jsoup.connect might throw
// and just let the thread expire.
}
}
}
编辑1:
我应该第一次包含这个,但我使用的是线程安全集和队列。
Set<String> emailSet = Collections.synchronizedSet(new HashSet());
BlockingQueue<String> linksToVisit = new ArrayBlockingQueue(10000);
Set<String> linksVisited = Collections.synchronizedSet(new HashSet());
final int EMAIL_MAX_COUNT = 10;
ExecutorService pool = newFixedThreadPool(25);
编辑2
想到我应该用答案更新我的问题,所以这就是我的问题所在。
while (emailSet.size() <= EMAIL_MAX_COUNT) {
link = linksToVisit.poll();
linksToVisit.remove(link);
linksVisited.add(link);
pool.execute(new Scraper(link));
}
我的列表只从一个链接开始。删除第一个链接后,我有一个空列表,不断创建新的线程,没有链接搜索。在列表可以填充之前,我已经创建了数百个线程,只是放慢系统速度直到它最终崩溃。
以下是代码修复,以确保在没有搜索链接的情况下不会创建任何线程。
while (emailSet.size() <= EMAIL_MAX_COUNT) {
if (linksToVisit.size() > 0) {
link = linksToVisit.poll();
linksToVisit.remove(link);
linksVisited.add(link);
pool.execute(new Scraper(link));
//System.out.println("Emails " + emailSet.size());
} else {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(Crawler.class.getName())
.log(Level.SEVERE, null, ex);
}
}
}
答案 0 :(得分:1)
你在一个检查emailSet大小的循环中以异步方式启动scaper,但是在循环的一个循环期间,一个scraper可以找到多个电子邮件,或者你可以启动多个scraper并在你启动之后它添加了alla电子邮件链接页面 考虑以下时间
T1 loop start ->T2 loop schedule Scaper ->T3 loop check emailSet ->T4 Scraper finds 13 email -> T5 loop check emailSet
或以下
T1 loop start ->T2 loop schedule Scaper "1" ->T3 loop check emailSet ->T4 loop schedule Scaper "2" T5 -> Scraper "1" finds 6 emails -> T6 loop check emailSet -> Scraper "1" finds 7 emails
等等。
如果您想在发现10封电子邮件时停止,则必须更改以下内容
while (matcher.find()) {
emailSet.add(matcher.group());
}
到
while (matcher.find()) {
if (emailSet.size() <= EMAIL_MAX_COUNT) {
emailSet.add(matcher.group());
}
}
甚至这并不能完全保证您可以在EMAIL_MAX_COUNT处停止,因为有多个线程(例如3个)可以检查大小并获得9然后所有它们都插入一封电子邮件。
如果要确保精确的emailSet大小,必须在单个块(使用synchronized(emailSet)
或使用Lock)内同步读写操作;
while (matcher.find()) {
synchronized(emailSet) {
if (emailSet.size() <= EMAIL_MAX_COUNT) {
emailSet.add(matcher.group());
}
}
}