使用 java 正则表达式抓取网站

Scraping a site with java regex

出于教育目的,我很乐意抓取前 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());
        }
    }
}

感谢您的宝贵时间。

解析模式

要解析 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));
}

所述,为了正确起见,应使用 Jsoup 或其他 HTML 解析器。

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

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

Regex 模式也是错误的,因为您的解决方案似乎是为逐行读取 1 行并测试仅与 Regex 匹配的行而构建的。该网站的来源显示 table 行的内容分布在多行中。

基于阅读 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. Regex not suitable。使用为此用例构建的库。
  2. 我的解决方案是一个工作示例,但性能很差(Stream API,each 行的正则表达式模式匹配)...
  3. 像这样的解决方案并不能保证可能出现混乱。正则表达式 可以 捕获比预期更多的内容。
  4. 网站内容、CSS class 名称等将来可能会发生变化。