java-改善crawler4j的性能

我需要编写一个网络抓取器,在大约100万个网站上抓取并将其标题,描述和关键字保存到1个大文件中(包含抓取的URL和相关单词). URL应该从一个大文件中提取.

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

晚上运行此程序后,我仅下载了大约200K的URL.我正在使用有线连接在一台机器上运行刮板.由于大多数URL都是不同的主机,因此我认为politeness参数在这里没有任何重要性.

编辑

我尝试使用非阻塞启动来启动Crawler4j,但它刚被阻塞.我的Crawler4j版本是: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("[\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;
    }
}

所以我有两个问题:

>有人可以建议其他方法来使此过程花费更少的时间吗?也许以某种方式调整搜寻器线程的数量?也许还有其他一些优化?我更喜欢不需要几台机器的解决方案,但是如果您认为这是唯一的角色扮演方式,那么有人可以建议如何做吗?也许是代码示例?
>有什么方法可以使搜寻器开始在某些URL上工作并在搜寻期间继续添加更多URL?我看了一下crawler.startNonBlocking,但效果似乎不太好

提前致谢

解决方法:

默认情况下,rawler4j设计为在一台计算机上运行.从网络爬网的领域,我们知道网络爬网的性能主要取决于以下四个资源:

>磁盘
> CPU
>带宽
>(RAM)

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

此外,rawer4j并非默认设计为加载如此大的种子文件.这是由于事实,rawler4j代表了爬虫的礼貌.这意味着-在抓取开始之前-检查每个种子点是否存在robots.txt,这可能会花费很多时间.

如果在非阻塞模式下开始爬网,则可以在爬网开始后添加种子,并且应该可以.但是,可能需要一段时间才能处理完URL.

对于多机设置,您可以查看Apache Nutch.但是,Nutch很难学习.

编辑:

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

以这种方式启动搜寻器

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

将调用每个搜寻器线程的run()方法.在研究此方法时,我们找到了一个名为frontier.getNextURLs(50,signedURLs);的方法,该方法负责从边界中获取看不见的URL进行处理.在此方法中,我们找到了一个所谓的waitingList,它导致线程等待.由于直到控制器关闭后才在waitingList上调用notifyAll,因此线程将永远不会重新计划新的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()方法.

上一篇:如何处理从一个客户端到一个PHP脚本的多个并行请求


下一篇:如何减少android中的活动数量?