提高crawler4j的性能

我需要编写一个webscraper,它可以擦除大约1M个网站并将其标题,描述和关键字保存到一个大文件中(包含已删除的URL和相关的单词)。 应从大文件中提取URL。

我在1M URL文件上运行了Crawler4j,并使用以下命令启动了webcrawler: controller.start(MyCrawler.class, 20) 。 20是任意数。 每个爬网程序将生成的单词传递到阻塞队列,以便单个线程将这些单词和URL写入文件。 我使用了1个编写器线程,以便不在文件上同步。 我将爬网深度设置为0(我只需要抓取我的种子列表)

运行这个晚上后,我只下载了大约200K的URL。 我正在使用有线连接在一台机器上运行刮刀。 由于大多数URL都是不同的主机,我不认为礼貌参数在这里有任何重要性。

编辑

我尝试使用非阻塞启动启动Crawler4j,但它刚被阻止。 我的Crawler4j版本是:4.2。 这是我正在使用的代码:

 CrawlConfig config = new CrawlConfig(); List
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 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 urlsQueue; public MyCrawler(BlockingQueue 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("[\n\r]", "")).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("[\n\r]", "")); } Elements elements = document.select("meta[name=keywords]"); for (Element element : elements) { String keywords = element.attr("content").replaceAll("[\n\r]", ""); 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; } } 

所以我有两个问题:

  1. 有人可以建议任何其他方式使这个过程花费更少的时间吗? 也许以某种方式调整爬虫线程的数量? 也许其他一些优化? 我更喜欢一种不需要多台机器的解决方案,但如果您认为这是角色的唯一途径,那么有人会建议如何做到这一点? 也许是一个代码示例?
  2. 有没有办法让抓取工具开始处理某些url并在抓取过程中不断添加更多url? 我看过crawler.startNonBlocking但它看起来效果不好

提前致谢

crawler4j默认设计为在一台机器上运行。 我们知道,从web-crawling领域来看, web-crawling网程序的性能主要依赖于以下四种资源:

  • 磁盘
  • 中央处理器
  • 带宽
  • (内存)

定义最佳线程数取决于您的硬件设置。 因此,更多的机器将导致更高的吞吐量。 下一个硬性限制是网络带宽。 如果您没有通过高速互联网连接,这将是您的方法的瓶颈。

此外, crawler4j并非设计为默认加载如此庞大的种子文件。 这是因为crawler4j重写了爬虫政治。 这意味着 – 在爬网开始之前 – 检查每个种子点的robots.txt ,这可能需要相当长的时间。

如果以非阻塞模式启动爬网,则可以在爬网开始后添加种子并且应该可以正常工作。 但是,处理URL可能需要一段时间。

对于多机器设置,您可以查看Apache Nutch 。 但是,Nutch有点难学。

编辑:

在重现您的设置后,我能够以动态的方式回答有关添加种子页面的问题。

以这种方式启动爬虫

 controller.startNonBlocking(() -> { return new MyCrawler(urlsQueue); }, 4); 

将调用每个爬虫程序线程的run()方法。 研究这个方法,我们找到一个名为frontier.getNextURLs(50, assignedURLs); ,负责从边界获取看不见的URL以便处理它们。 在这个方法中,我们找到了一个所谓的waitingList ,它导致线程等待。 由于在控制器关闭之前从不在waitingList调用notifyAll ,因此线程永远不会重新安排新的URL。

要解决此问题,您有两种可能的解决方案:

  1. 只需在每个线程中添加至少一个URL作为种子点。 死锁情况不会发生。 在非阻塞模式下启动线程后,您可以根据需要添加种子。

     controller.addSeed("https://www.google.de"); controller.startNonBlocking(() -> { return new MyCrawler(urlsQueue); }, 4); controller.addSeed("https://www.google.de/test"); controller.waitUntilFinish(); 
  2. 转到Github项目的一个分支并调整Frontier.java的代码,以便在动态添加种子页面后可以从CrawlController调用waitingList.notifyAll()方法。