【问题标题】:Java ExecutorService Runnable doesn't update valueJava ExecutorService Runnable 不更新值
【发布时间】:2015-11-12 23:25:02
【问题描述】:

我正在使用 Java 下载其 URL 存储在数据库中的网站的 HTML 内容。我也想将他们的 HTML 放入数据库。 我为此使用Jsoup

public String downloadHTML(String byLink) {
        String htmlInPage = "";
        try {
            Document doc = Jsoup.connect(byLink).get();
            htmlInPage = doc.html();
        } catch (org.jsoup.UnsupportedMimeTypeException e) {
            // process this and some other exceptions
        } 
        return htmlInPage;
    }

我想同时下载网站并使用此功能:

public void downloadURL(int websiteId, String url, 
                        String categoryName, ExecutorService executorService) {
   executorService.submit((Runnable) () -> {
       String htmlInPage = downloadHTML(url);
       System.out.println("Category: " + categoryName + " " + websiteId + " " + url);
       String insertQuery = 
              "INSERT INTO html_data (website_id, html_contents)  VALUES (?,?)";
       dbUtils.query(insertQuery, websiteId, htmlInPage);   
   });
}

dbUtils 是我的基于 Apache Commons DbUtils 的课程。详情在这里:http://pastebin.com/iAKXchbQ
而且我正在以这样的方式使用上面提到的所有内容:(List<Object[]> 详细信息也在 pastebin 上进行了解释)

public static void main(String[] args) {
        DbUtils dbUtils = new DbUtils("host", "db", "driver", "user", "pass");
        List<String> categoriesList = 
                     Arrays.asList("weapons", "planes", "cooking", "manga");
        String sql = "SELECT lw.id, lw.website_url, category_name " +
                "FROM list_of_websites AS lw JOIN list_of_categories AS lc " +
                "ON lw.category_id = lc.id " +
                "where category_name = ? ";

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (String category : categoriesList) {
            List<Object[]> sitesInCategory = dbUtils.select(sql, category );
            for (Object[] entry : sitesInCategory) {
                int websiteId = (int) entry[0];
                String url = (String) entry[1];
                String categoryName = (String) entry[2];
                downloadURL(websiteId, url, categoryName, executorService);
            }
        }
        executorService.shutdown();
}

我不确定这个解决方案是否正确,但它确实有效。现在我想修改代码以保存不是来自我数据库中所有网站的 HTML,而是只保存每个类别中的固定数量。
例如,从“武器”类别中下载并保存 50 个网站的 HTML,从“飞机”类别中下载 50 个等。我认为没有必要为此使用 sql:如果我们为每个类别选择 50 个网站,则它没有'这意味着我们将它们全部保存,因为可能存在不正确的语法和连接问题。
我尝试创建单独的类来实现Runnable 的字段:countermaxWebsitesPerCategory,但这些变量没有更新。另一个想法是创建字段Map&lt;String,Integer&gt; sitesInCategory 而不是counter,将每个类别作为键放在那里并增加其值直到达到maxWebsitesPerCategory,但它也不起作用。请帮我!
P.S:我也将感谢任何与我实现并发下载相关的建议(我之前没有在 Java 中使用过并发,这是我的第一次尝试)

【问题讨论】:

    标签: java concurrency runnable executorservice java.util.concurrent


    【解决方案1】:

    这个怎么样?

    for (String category : categoriesList) {
            dbUtils.select(sql, category).stream()
                .limit(50)
                .forEach(entry -> {
                    int websiteId = (int) entry[0];
                    String url = (String) entry[1];
                    String categoryName = (String) entry[2];
                    downloadURL(websiteId, url, categoryName, executorService);
                });
        }
    

    sitesInCategory 已替换为最多包含 50 个元素的流,然后您的代码将在每个条目上运行。

    编辑

    关于 cmets。我已经进行了一些重组,您可以修改/实现我建议的方法的内容。

    public void werk(Queue<Object[]> q, ExecutorService executorService) {
        executorService.submit(() -> {
            try {
                Object[] o = q.remove();
                try {
                    String html = downloadHTML(o); // this takes one of your object arrays and returns the text of an html page
    
                    insertIntoDB(html); // this is the code in the latter half of your downloadURL method
                }catch (/*narrow exception type indicating download failure*/Exception e) {
                    werk(q, executorService);
                }
            }catch (NoSuchElementException e) {}
        });
    }
    

    ^^^ 这个方法完成了大部分工作。

    for (String category : categoriesList) {
        Queue<Object[]> q = new ConcurrentLinkedQueue<>(dbUtils.select(sql, category));
        IntStream.range(0, 50).forEach(i -> werk(q, executorService));
    }
    

    ^^^ 这是你的 main 中的 for 循环

    现在每个类别都尝试下载 50 个页面,当下载一个页面失败时,它会继续前进并尝试下载另一个页面。这样,您将下载 50 个页面或尝试下载该类别中的所有页面。

    【讨论】:

    • 非常好的解决方案!它确实从列表中获得的每个类别不超过 50 个条目。但是downloadURL 由于无效的 URL 语法或连接问题,在每个类别中保存的网站少于 50 个。尽管存在这些问题,是否可以为每个类别准确下载 50 个?也许,我们应该在Runnable 中插入计数器并更新它的值?困难在于该过程是异步的,并且类别不是一一处理的。事实上,下载过程看起来是混合的:网站来自“飞机”、“武器”、“烹饪”、“武器”、“烹饪”等
    • 因此,最好通过确保您放入数据库中的任何内容都是有效的 url 来解决 url 语法问题,然后再将其放入。您的连接问题是源于您的终端,还是URL指向的服务器可能不再存在的事实?因为如果只是您的连接,我们可以构建异常处理以重试,直到我们成功,但如果您不确定服务器是否仍在另一端,那么我们可以做其他事情。
    • 好的,我会让语法正确,但是连接问题呢?它们不在我身边,它们是由远程服务器(404 错误、403、503 等)引起的。从广义上讲,我在做什么:1)下载网站的分类列表,例如:shallalist.de 2)将它们放入数据库,3)按类别下载 HTML 内容(例如,每个类别 1000 个 wensites - 数量是可取的相等),4)从HTML源中提取文本,从标签中提取文本:和<meta>,计算HTML标签的频率。这些功能用于使用 RapidMiner 进行数据挖掘。
    • 我同意你的想法:它似乎清晰有效。我尝试稍微修改一下这段代码并启动它......很抱歉打扰你,但网站的数量仍然少于50个:(我认为它与类别中的少量网站(619 in武器,飞机上的 687 等等)。但是问题出在哪里?如果你有时间,你能看一下整个代码吗?在这里:pastebin.com/Cs8MZRqMpastebin.com/ZFCj0tmm
    • @Tofrar 在我看其他内容之前有个快速建议,不要在 main 结束时关闭 executor 服务。将其关闭将等待当前任务完成,但不再需要。因此,如果下载失败,它将无法尝试 q 中的下一个站点。此外,看起来您并没有使用 werk 中第 72 行的 catch。您在 59 和 69 上的 else 块应该抛出新的 DownloadMessedUpException 或其他东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-01
    • 2012-11-28
    • 1970-01-01
    相关资源
    最近更新 更多