【问题标题】:Scraping a site with java regex使用 java regex 抓取网站
【发布时间】:2019-12-23 18:03:59
【问题描述】:

出于教育目的,我很想抓取前 250 部电影 (https://www.imdb.com/chart/top/) 的标题。

我尝试了很多东西,但每次都搞砸了。你能帮我用Java和正则表达式刮掉标题吗?

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class scraping {

    public static void main (String args[]) {
        try {
            URL URL1=new URL("https://www.imdb.com/chart/top/");

            URLConnection URL1c=URL1.openConnection();
            BufferedReader br=new BufferedReader(new 
            InputStreamReader(URL1c.getInputStream(),"ISO8859_7"));

            String line;int lineCount=0;

            Pattern pattern = Pattern.compile("<td\\s+class=\"titleColumn\"[^>]*>"+ ".*?</a>");
            Matcher matcher = pattern.matcher(br.readLine());

            while(matcher.find()){
                System.out.println(matcher.group());
            }
        } catch (Exception e) {
            System.out.println("Exception: " + e.getClass() + ", Details: " + e.getMessage());
        }
    }
}

感谢您的宝贵时间。

【问题讨论】:

  • 使用 Jsoup,你的生活应该会变得更轻松。
  • 你可以使用 selenium 和 scape xml
  • 你的 readLine 现在只返回一个空字符串^^
  • @azro 你能帮我吗?因为经过这么多次尝试,我的大脑会在这个时候爆炸。如何打印 td class=titleColumn 的内容?
  • 看看regex101.com/r/XfiaB7/1,如果你坚持让它在正则表达式中工作

标签: java regex web-scraping screen-scraping


【解决方案1】:

解析模式

要解析 XML 或 HTML 内容,专用解析器总是比正则表达式更容易,Java 中的 HTML 有 Jsoup,你会很容易得到你的电影:

Document doc = Jsoup.connect("https://www.imdb.com/chart/top/").get();
Elements films = doc.select("td.titleColumn");
for (Element film : films) {
    System.out.println(film);
}

<td class="titleColumn"> 1. <a href="/title/tt0111161/?pf_rd_m=A2FGELUUNOQJNL&amp;pf_rd_p=e31d89dd-322d-4646-8962-327b42fe94b1&amp;pf_rd_r=5BDHP4VZE8EGSEZC4ZSF&amp;pf_rd_s=center-1&amp;pf_rd_t=15506&amp;pf_rd_i=top&amp;ref_=chttp_tt_1" title="Frank Darabont (dir.), Tim Robbins, Morgan Freeman">Les évadés</a> <span class="secondaryInfo">(1994)</span> </td>
<td class="titleColumn"> 2. <a href="/title/tt0068646/?pf_rd_m=A2FGELUUNOQJNL&amp;pf_rd_p=e31d89dd-322d-4646-8962-327b42fe94b1&amp;pf_rd_r=5BDHP4VZE8EGSEZC4ZSF&amp;pf_rd_s=center-1&amp;pf_rd_t=15506&amp;pf_rd_i=top&amp;ref_=chttp_tt_2" title="Francis Ford Coppola (dir.), Marlon Brando, Al Pacino">Le parrain</a> <span class="secondaryInfo">(1972)</span> </td>

仅获取内容:

for (Element film : films) {
    System.out.println(film.getElementsByTag("a").text());
}

Les évadés
Le parrain
Le parrain, 2ème partie

正则表达式模式

您没有阅读网站的全部内容,而且它是XML类型,所以所有内容不在同一行,您无法在同一行找到应答器的开头和结尾,您可以阅读所有内容,然后使用正则表达式,它给出了这样的东西:

URL url = new URL("https://www.imdb.com/chart/top/");
InputStream is = url.openStream();

StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
    String line;
    while ((line = br.readLine()) != null) {
        sb.append(line);
    }
} catch (MalformedURLException e) {
    e.printStackTrace();
    throw new MalformedURLException("URL is malformed!!");
} catch (IOException e) {
    e.printStackTrace();
    throw new IOException();
}

// Full line
Pattern pattern = Pattern.compile("<td class=\"titleColumn\">.*?</td>");
String content = sb.toString();
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
    System.out.println(matcher.group());
}

// Title only
Pattern pattern = Pattern.compile("<td class=\"titleColumn\">.+?<a href=.+?>(.+?)</a>.+?</td>");
String content = sb.toString();
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
    System.out.println(matcher.group(1));
}

【讨论】:

  • 感谢您的代码。在正则表达式模式下,我的结果有问题。我想列出一个只有名字而不带标签的列表。如何从 HTML 标签中分离出标题?再次非常感谢您。
【解决方案2】:

正如existing answer 所说,为了正确起见,应该使用 Jsoup 或其他 HTML 解析器。

如果您想使用类似的方法来实现更合理的用例,我只会完成您当前的解决方案。它无法工作,因为您只读取缓冲区的第一行:

Matcher matcher = pattern.matcher(br.readLine);

Regex 模式也是错误的,因为您的解决方案似乎是为逐行读取 1 行并测试仅与 Regex 相对的行。网站来源显示表格行的内容分布在多行。

基于阅读 1 行的解决方案应该使用更简单的Regex(对不起,示例包含我母语的电影名称):

\" ?>([^<]+)<\/a>

一个工作代码的例子是:

try {
    URL URL1=new URL("https://www.imdb.com/chart/top/");

    URLConnection URL1c=URL1.openConnection();
    BufferedReader br=new BufferedReader(new
    InputStreamReader(URL1c.getInputStream(),"ISO8859_7"));

    String line;int lineCount=0;

    Pattern pattern = Pattern.compile("\" ?>([^<]+)<\\/a>"); // Compiled once

    br.lines()                       // Stream<String>
      .map(pattern::matcher)         // Stream<Matcher> 
      .filter(Matcher::find)         // Stream<Matcher> .. if Regex matches
      .limit(250)                    // Stream<Matcher> .. to avoid possible mess below
      .map(m -> m.group(1))          // String<String>  .. captured movie name
      .forEach(System.out::println); // Printed out

    } catch (Exception e) {
        System.out.println("Exception: " + e.getClass() + ", Details: " + e.getMessage());
    }

注意以下几点:

  1. 正则表达式适用于此。使用为此用例构建的库。
  2. 我的解决方案是一个工作示例,但性能很差(Stream API、每个行的正则表达式模式匹配)...
  3. 这样的解决方案并不能保证可能会造成混乱。正则表达式可以捕获超出预期的内容。
  4. 网站内容、CSS 类名等将来可能会发生变化。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-25
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 2020-09-28
    • 2013-05-09
    • 2019-12-15
    相关资源
    最近更新 更多