java多线程-爬电影天堂上的电影下载地址

获取网页html

刚开始做的时候,在网上搜了一下资料。然后找到了一个获取网页最简单的dome,如下。

public static String getHtml(String urlstring) throws IOException {
        //得到地址
        URL url = new URL(urlstring);

        //建立连接
        URLConnection conn =  url.openConnection();

        //获得数据
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "gbk"));

        StringBuilder html = new StringBuilder();
        String line;

        //正则表达式匹配
//      String reg = "\\w+@\\w+";
//      Pattern pattern = Pattern.compile(reg);

        while ((line = bufferedReader.readLine()) != null) {
            html.append(line);
        }

        return html.toString();
    }

于是就按照这个开始编写我自己的爬虫。

如何爬url

获取电影详情URL

我发现电影天堂的网址是很有规律的。比如它的最新电影列表的url是:”http://www.ygdy8.net/html/gndy/dyzz/list_23_NUM.html“,NUM是1到258
所以我就没有用爬虫爬url,直接就循环NUM来获取它页面下电影详情的url。因为这个url数量不多,我就没有考虑用多线程。直接其爬完,然后序列化到文件中保存起来。

public class SpiderForMovies extends Spider{

    private static Vector<String> urlList = new Vector<String>();

    @Override
    public void execute(String url) throws IOException {
        //根据Url地址获取网页内容
        String html = HttpUtils.getHtml(url);

        if (html == null){
            throw new RuntimeException("无法获取url网址内容");
        }

        //对网页内容进行分析和提取
        Document doc = Jsoup.parse(html);
        Elements tables = doc.select("table.tbspan");//class等于tbspan的table标签
        Elements links = tables.select("a[href]"); //带有href属性的a元素

        for (Element link : links) {
            String linkHref = link.attr("href");
//          String linkHref = homepage + Href;

            //加入到Url队列
            urlList.add(linkHref);
        }

    }

    public Vector<String> getUrllist() {
        return urlList;
    }
}

public static void getMovieUrl() throws IOException, InstantiationException, IllegalAccessException {

        Integer i = 1;
        while(i <= 258) {
            String url = url1.replace("NUM", i.toString());
            SpiderForMovies spiderForMovies = new SpiderForMovies();
            spiderForMovies.execute(url);
            Vector<String>  temp = spiderForMovies.getUrllist();

            //合并两个hashset
            urlList.addAll(temp);
            i++;

            System.out.println(i);
        }


        Util.OutPut("E:\\ling\\url.txt",urlList);

    }

获取电影下载地址

上面总共爬到了八十万的电影详情地址。那么肯定要使用多线程来爬取每个地址下的电影下载地址。在编写多线程获取的时候碰到了很多问题。
现在在爬下载地址了,又发现了很多问题。太大了不能一下子保存,看来还是要放到数据库中。
首先来看代码吧

//获取电影详情
public static void getMovieParticular() throws InterruptedException {
        int N = 8;
        CountDownLatch doneSignal = new CountDownLatch(N);
        CountDownLatch startSignal = new CountDownLatch(1);// 开始执行信号

        urlList = Util.InPut("E:\\ling\\url.txt");


        SpiderThread spiderThread = new SpiderThread(urlList,doneSignal,startSignal);



        for (int i = 1; i <= N; i++) {
            new Thread(spiderThread).start();// 线程启动了
        }
        System.out.println("begin------------");
        startSignal.countDown();// 开始执行啦
        doneSignal.await();// 等待所有的线程执行完毕

        movieList = spiderThread.getMovies();
        Util.OutPutMovie("E:\\ling\\Movie.txt", movieList);
//      System.out.println(movieList);
        System.out.println("Ok");
}

/**
 * 获取电影详情爬虫
 * Created by ling on 2015/5/25.
 */
public class SpiderForMovParticular extends Spider {


    private Movie movie;

    @Override
    public void execute(String url) throws IOException {
        movie = new Movie();
        //根据Url地址获取网页内容
        String homepage = "http://www.ygdy8.net";
        String html = HttpUtils.getHtml(homepage + url);

        if (html == null){
            throw new RuntimeException("无法获取url网址内容");
        }

        //对网页内容进行分析和提取
        Document doc = Jsoup.parse(html);

        //提取标题
        Element link = doc.select("div.title_all").last();//class等于title_all的div标签
        String title = link.text();

        //提取链接
        Elements elements = doc.select("div#Zoom");
        Element element = elements.select("a[href]").first();
        String href = element.attr("href");

        //加入到Movie中
        movie.setTitle(title);
        movie.setDownlodeUrl(href);

    }

    public Movie getMovie() {
        return movie;
    }
}

/**
 * 爬虫线程
 * Created by ling on 2015/5/27.
 */

public class SpiderThread implements Runnable{

    //线程开始结束的信号
    private final CountDownLatch doneSignal;
    private final CountDownLatch startSignal;

    private static Vector<String> urlList;
    private static Set<Movie> movieSet = new HashSet<Movie>();

    private static int i = 0;

    public SpiderThread(Vector<String> urlList, CountDownLatch doneSignal,
                        CountDownLatch startSignal) {
        SpiderThread.urlList = urlList;
        this.doneSignal = doneSignal;
        this.startSignal = startSignal;
    }

    @Override
    public void run() {

        try {
            startSignal.await(); // 等待开始执行信号的发布
            SpiderForMovParticular spider = new SpiderForMovParticular();
            while (!urlList.isEmpty()) {
                //得到Url
                String tmp = getAurl();


                if (tmp != null) {
                    try {
                        System.out.println(i + ":" + tmp);
                        i++;
                        spider.execute(tmp);
                        movieSet.add(spider.getMovie());
//                      System.out.println(movieSet);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            doneSignal.countDown();
        }


//      SpiderForMovParticular spider = new SpiderForMovParticular();
//      while (!urlList.isEmpty()) {
//          //得到Url
//          String tmp = getAurl();
//
//          if (tmp != null) {
//              try {
//                  spider.execute(tmp);
//                  movieSet.add(spider.getMovie());
//              } catch (IOException e) {
//                  e.printStackTrace();
//              }
//          }
//
//      }

    }

    public synchronized String getAurl() {
        if (urlList.isEmpty()){
            return null;
        }
        String tmpAUrl;
        tmpAUrl = urlList.get(0);
        urlList.remove(0);
        return tmpAUrl;
    }

    public Set<Movie> getMovies() {
        return movieSet;
    }
}

首先是几个线程一起来爬存在UrlList中的地址。一开始就是用实现Runnable接口。
在run方法中:取出一个Url地址,执行爬虫的execute方法获取电影名称和下载地址,存在Movie对象中,再将此对象,加入MovieSet中。然后将MovieSet序列化到文件中。接下来可以将其放到数据库中。

过程就是这么简单,但是当用多线程方式将其实现时,碰到了如下几个问题:

1、从urlList中取url的时,同一个url多次被取出。
因为当多个线程同时去访问urlList取数据,就可能会取出相同的url。
所以应该在取出一条数据后将其删除,并保证其他线程不会同时取到别的线程去到的url。那么就要将取url这步同步。

public synchronized String getAurl() {
        if (urlList.isEmpty()){
            return null;
        }
        String tmpAUrl;
        tmpAUrl = urlList.get(0);
        urlList.remove(0);
        return tmpAUrl;
    }

2、多个线程如何在所有线程结束后将整个movieSet序列化
一开始我使用了 thread.join() 命令 ,看起来好像也可以使几个线程运行完以后再执行下面的步骤。但是这样做不规范。
后来我上网查了资料,使用了开始信号和结束信号作为线程开始和结束的标志。

//线程开始结束的信号
    private final CountDownLatch doneSignal;
    private final CountDownLatch startSignal;

    public SpiderThread(Vector<String> urlList, CountDownLatch doneSignal,
                        CountDownLatch startSignal) {
        SpiderThread.urlList = urlList;
        this.doneSignal = doneSignal;
        this.startSignal = startSignal;
    }

@Override
    public void run() {

        try {
            startSignal.await(); // 等待开始执行信号的发布
            SpiderForMovParticular spider = new SpiderForMovParticular();
            while (!urlList.isEmpty()) {
                //得到Url
                String tmp = getAurl();

                if (tmp != null) {
                    try {
                        System.out.println(i + ":" + tmp);
                        i++;
                        spider.execute(tmp);
                        movieSet.add(spider.getMovie());

                        synchronized (this) {
                            if (movieSet.size() >= (10000)) {
                                //储存movieSet
                                saveSet();
                            }
                        }
//                      System.out.println(movieSet);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            doneSignal.countDown();
        }

    //获取电影详情
public static void getMovieParticular() throws InterruptedException {
        int N = 8;
        CountDownLatch doneSignal = new CountDownLatch(N);
        CountDownLatch startSignal = new CountDownLatch(1);// 开始执行信号

        urlList = Util.InPut("E:\\ling\\url.txt");
//      urlList = Util.InPut("G:\\test.txt");

        SpiderThread spiderThread = new SpiderThread(urlList,doneSignal,startSignal);

        for (int i = 1; i <= N; i++) {
            new Thread(spiderThread).start();// 线程启动了
        }
        System.out.println("begin------------");
        startSignal.countDown();// 开始执行啦
        doneSignal.await();// 等待所有的线程执行完毕

//只有所有线程执行完毕后才执行接下来的几部
        movieList = spiderThread.getMovies();
        Util.OutPutMovie("E:\\ling\\Movie\\MovieLast.txt", movieList);
//      System.out.println(movieList);
        System.out.println("Ok");
    }

3、在一开始每次在movieSet中开几个线程,才有几个数据,前面的数据会被覆盖掉。找了很多,最后才发现原来是在SpiderForMovParticular类中,我将Movie 直接实例化了。就像:

private Movie movie = new Movie();  

这样就不能每次都将不同的movie加入到movieSet中。
应该只创建一个引用,然后在方法中再实例化。
如:

public class SpiderForMovParticular extends Spider {


    private Movie movie;

    @Override
    public void execute(String url) throws IOException {
        movie = new Movie();
        //根据Url地址获取网页内容
        String homepage = "http://www.ygdy8.net";
        String html = HttpUtils.getHtml(homepage + url);

        if (html == null){
            throw new RuntimeException("无法获取url网址内容");
        }

        //对网页内容进行分析和提取
        Document doc = Jsoup.parse(html);

        //提取标题
        String title = null;
        Element link = doc.select("div.title_all").last();//class等于title_all的div标签
        if (link != null){
            title = link.text();
        }

        //提取链接
        String href = null;
        Elements elements = doc.select("div#Zoom");
        Element element = elements.select("a[href]").first();
        if (element != null) {
            href = element.attr("href");
        }

        //加入到Movie中
        movie.setTitle(title);
        movie.setDownlodeUrl(href);
    }

    public Movie getMovie() {
        return movie;
    }
}

最后

总共爬到了825694条电影地址,现在还在爬电影的下载地址。应该很快就能爬完。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值