我需要写一个网络爬虫,在~1M网站上抓取,并将其标题,描述和关键字保存到1个大文件中(包含抓取的URL和相关单词(。URL 应从大文件中提取。
我已经在 1M URL 文件上运行了 Crawler4j,并使用以下方法启动了网络爬虫:controller.start(MyCrawler.class, 20)
. 20 是一个任意数字。每个爬网程序将生成的单词传递到阻塞队列中,以便单个线程将这些单词和 URL 写入文件。我使用了 1 个编写器线程,以便在文件上不同步。我将抓取深度设置为 0(我只需要抓取我的种子列表(
运行了一晚后,我只下载了大约 200K 的 URL。我使用有线连接在 1 台机器上运行刮板。由于大多数 URL 来自不同的主机,我认为礼貌参数在这里没有任何重要性。
编辑
我尝试使用非阻塞启动启动 Crawler4j,但它只是被阻止了。我的爬虫4j版本是:4.2。这是我正在使用的代码:
CrawlConfig config = new CrawlConfig();
List<Header> headers = Arrays.asList(
new BasicHeader("Accept", "text/html,text/xml"),
new BasicHeader("Accept-Language", "en-gb, en-us, en-uk")
);
config.setDefaultHeaders(headers);
config.setCrawlStorageFolder(crawlStorageFolder);
config.setMaxDepthOfCrawling(0);
config.setUserAgentString("testcrawl");
config.setIncludeBinaryContentInCrawling(false);
config.setPolitenessDelay(10);
PageFetcher pageFetcher = new PageFetcher(config);
RobotstxtConfig robotstxtConfig = new RobotstxtConfig();
RobotstxtServer robotstxtServer = new RobotstxtServer(robotstxtConfig, pageFetcher);
BlockingQueue<String> urlsQueue = new ArrayBlockingQueue<>(400);
controller = new CrawlController(config, pageFetcher, robotstxtServer);
ExecutorService executorService = Executors.newSingleThreadExecutor();
Runnable writerThread = new FileWriterThread(urlsQueue, crawlStorageFolder, outputFile);
executorService.execute(writerThread);
controller.startNonBlocking(() -> {
return new MyCrawler(urlsQueue);
}, 4);
File file = new File(urlsFileName);
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String url;
while ((url = br.readLine()) != null) {
controller.addSeed(url);
}
}
编辑 1 - 这是 MyCrawler 的代码
public class MyCrawler extends WebCrawler {
private final static Pattern FILTERS = Pattern.compile(".*(\.(css|js|gif|jpg|png|mp3|mp3|zip|gz))$");
public static final String DELIMETER = "||||";
private final StringBuilder buffer = new StringBuilder();
private final BlockingQueue<String> urlsQueue;
public MyCrawler(BlockingQueue<String> urlsQueue) {
this.urlsQueue = urlsQueue;
}
@Override
public boolean shouldVisit(Page referringPage, WebURL url) {
String href = url.getURL().toLowerCase();
return !FILTERS.matcher(href).matches();
}
@Override
public void visit(Page page) {
String url = page.getWebURL().getURL();
if (page.getParseData() instanceof HtmlParseData) {
HtmlParseData parseData = (HtmlParseData) page.getParseData();
String html = parseData.getHtml();
String title = parseData.getTitle();
Document document = Jsoup.parse(html);
buffer.append(url.replaceAll("[nr]", "")).append(DELIMETER).append(title);
Elements descriptions = document.select("meta[name=description]");
for (Element description : descriptions) {
if (description.hasAttr("content"))
buffer.append(description.attr("content").replaceAll("[nr]", ""));
}
Elements elements = document.select("meta[name=keywords]");
for (Element element : elements) {
String keywords = element.attr("content").replaceAll("[nr]", "");
buffer.append(keywords);
}
buffer.append("n");
String urlContent = buffer.toString();
buffer.setLength(0);
urlsQueue.add(urlContent);
}
}
private boolean isSuccessful(int statusCode) {
return 200 <= statusCode && statusCode < 400;
}
}
所以我有两个问题:
- 有人可以建议任何其他方法使此过程花费更少的时间吗?也许以某种方式调整爬虫线程的数量?也许还有其他一些优化?我更喜欢不需要多台机器的解决方案,但如果您认为这是角色的唯一方法,有人可以建议如何做到这一点吗?也许是一个代码示例?
- 有没有办法让爬虫开始处理某些 URL 并在抓取过程中不断添加更多 URL?我看过
crawler.startNonBlocking
但它似乎效果不佳
提前致谢
crawler4j
默认设计为在一台计算机上运行。从web-crawling
领域我们知道,网络爬虫的性能主要取决于以下四种资源:
- 磁盘
- 中央处理器
- 带宽
- (内存(
定义最佳线程数取决于您的硬件设置。因此,更多的机器将导致更高的吞吐量。下一个硬限制是网络带宽。如果您没有通过高速互联网连接,这将是您方法的瓶颈。
此外,crawler4j
并非旨在默认加载如此庞大的种子文件。这是因为crawler4j
隔离了爬虫政治性。这意味着,在爬行开始之前,每个种子点都会被检查robots.txt
,这可能需要相当长的时间。
如果在非阻塞模式下启动爬网,则可以在爬网启动后添加种子,并且应该可以工作。但是,可能需要一段时间才能处理 URL。
对于多机设置,您可以查看Apache Nutch。但是,Nutch有点难学。
编辑:
重现您的设置后,我能够以动态方式回答您有关添加种子页面的问题。
以这种方式启动爬网程序
controller.startNonBlocking(() -> {
return new MyCrawler(urlsQueue);
}, 4);
将调用每个爬网程序线程的 run()
方法。研究这种方法,我们发现一个名为 frontier.getNextURLs(50, assignedURLs);
,它负责从边界获取看不见的 URL 以处理它们。在这种方法中,我们发现一个所谓的 waitingList
,这会导致线程等待。由于在控制器关闭之前,notifyAll
永远不会在waitingList
上调用,因此线程永远不会重新计划新的 URL。
要解决此问题,您有两种可能的解决方案:
只需为每个线程添加至少一个 URL 作为种子点。死锁情况不会发生。在非阻塞模式下启动线程后,您可以根据需要添加种子。
controller.addSeed("https://www.google.de"); controller.startNonBlocking(() -> { return new MyCrawler(urlsQueue); }, 4); controller.addSeed("https://www.google.de/test"); controller.waitUntilFinish();
选择 Github 项目的分支并调整
Frontier.java
代码,以便在动态添加种子页面后可以从CrawlController
调用waitingList.notifyAll()
方法。