重構:抓取一個視訊網站上 2016 年所有電影的下載連結

zifangsky發表於2017-02-05

前言:我在2015年底寫過一篇使用基本的Java網路程式設計抓取一個視訊網站上2015年所有電影的下載連結的文章。但是以我現在看來當時的程式碼有的地方其邏輯無疑還是比較複雜的,因此在接觸到更好用的工具(webmagic框架)之後就一直想將當初的程式碼重構一下,所以也就有了本篇文章

注:之前的那篇文章:[https://www.zifangsky.cn/244.html](https://www.zifangsky.cn/244.html)

一 思路分析

下面我將跟大家一起來分析下如何實現這樣的一個爬蟲:

重構:抓取一個視訊網站上 2016 年所有電影的下載連結
電影列表頁

首先觀察我們爬蟲的起始頁面是:http://www.80s.tw/movie/list/-2016—
同時在當前的電影列表頁面,每個電影詳情頁的URL用XPath表示式來表示就是://ul[@class=’me1 clearfix’]/li/a/@href

注:關於XPath表示式的用法可以參考這裡的介紹:www.w3school.com.cn/xpath/xpath…

重構:抓取一個視訊網站上 2016 年所有電影的下載連結
其他列表頁

接著,我們繼續觀察該列表頁最下面指向其他列表頁的URL,可以發現這類URL如果用XPath表示式來表示就是://div[@class=’pager’]/a/@href

當然,上面我們介紹了電影列表頁如何獲取電影詳情頁以及其他列表頁的XPath表示式。那麼,如果是電影詳情頁面(PS:http://www.80s.tw/movie/17807 這種頁面),我們該如何獲取電影的名字和下載連結呢?下面我們就一起來分析下吧:

重構:抓取一個視訊網站上 2016 年所有電影的下載連結
電影詳情頁

從上面的截圖可以看出,很顯然這裡是一個很好的獲取電影名字的地方。那麼它對應的XPath表示式就是://div[@class=’info’]/h1/text()
當然,如果你需要獲取其他的如導演名字、電影簡介之類的資訊也可以根據同樣的原理來獲取

接下來我們再看看電影的下載連結該如何來獲取:

重構:抓取一個視訊網站上 2016 年所有電影的下載連結
電影下載地址

從上面的截圖可以看出,這裡出現了電影的下載地址,它對應的XPath表示式就是://li[@class=’clearfix dlurlelement backcolor1′]/span[@class=’dlname nm’]/input/@value

好了,我們上面已經將在程式碼中需要獲取的關鍵資訊的XPath表示式都找到了,接下來就可以正式寫程式碼來實現了

二 程式碼實現

在程式碼實現部分我決定採用webmagic框架,因為這樣比使用基本的的Java網路程式設計要簡單得多
注:關於webmagic框架的一些基本用法可以參考我之前寫過的這篇文章:www.zifangsky.cn/853.html

(1)爬蟲的核心程式碼:

package cn.zifangsky.webmagic.movie;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;

public class MovieSpider implements PageProcessor{

    private Site site = Site.me().setTimeOut(30000).setRetryTimes(3)
            .setSleepTime(1000).setCharset("UTF-8");

    @Override
    public Site getSite() {
        Set<Integer> acceptStatCode = new HashSet<>();
        acceptStatCode.add(200);
        site = site.setAcceptStatCode(acceptStatCode).addHeader("Accept-Encoding", "/")
                .setUserAgent(UserAgentUtils.radomUserAgent());

        return site;
    }

    @Override
    public void process(Page page) {
        String url =  page.getUrl().toString();
        Pattern pattern1 = Pattern.compile("http://www.80s.tw/movie/list/-2016---(-p\\d*)?");
        Matcher matcher1 = pattern1.matcher(url);

        Pattern pattern2 = Pattern.compile("/movie/\\d+");
        Matcher matcher2 = pattern2.matcher(url);

        //列表頁面
        if(matcher1.find()){
            //電影詳情頁URL集合
            List<String> moviePageUrls = page.getHtml().xpath("//ul[@class='me1 clearfix']/li/a/@href").all();

            if(moviePageUrls != null && moviePageUrls.size() > 0){
                //將當前列表頁的所有電影頁面新增進去
                page.addTargetRequests(moviePageUrls);
            }

            //當前列表頁中的其他列表頁的連結
            List<String> listUrls = page.getHtml().xpath("//div[@class='pager']/a/@href").all();

            if(listUrls != null && listUrls.size() > 0){            
                page.addTargetRequests(listUrls);
            }

        }else if(matcher2.find()){  //電影頁面    
            //獲取電影名字
            String movieName = page.getHtml().xpath("//div[@class='info']/h1/text()").toString();
            //獲取電影播放連結
            String movieUrl = page.getHtml().xpath("//li[@class='clearfix dlurlelement backcolor1']/span[@class='dlname nm']/input/@value").toString();

            Movie movie = new Movie(movieName, movieUrl);
            page.putField("movie", movie);  //後面做資料的持久化
        }    
    }

}複製程式碼

程式碼中的XPath表示式都已經在上面專門介紹了,其他程式碼自行參考註釋來理解即可,這裡就不多做解釋了

實體類Movie:

package cn.zifangsky.webmagic.movie;

public class Movie {
    private String movieName;
    private String movieLink;

    public Movie() {

    }

    public Movie(String movieName, String movieLink) {
        this.movieName = movieName;
        this.movieLink = movieLink;
    }

    public String getMovieName() {
        return movieName;
    }

    public void setMovieName(String movieName) {
        this.movieName = movieName;
    }

    public String getMovieLink() {
        return movieLink;
    }

    public void setMovieLink(String movieLink) {
        this.movieLink = movieLink;
    }

    @Override
    public String toString() {
        return "Movie [movieName=" + movieName + ", movieLink=" + movieLink + "]";
    }

}複製程式碼

UserAgentUtils.java:

package cn.zifangsky.webmagic.movie;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class UserAgentUtils {

    /**
     * 從預定義的User-Agent列表中隨機抽取一個返回
     * @return
     */
    public static String radomUserAgent(){
        List<String> list = new ArrayList<>();
        list.add("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36");
        list.add("Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04");
        list.add("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36");
        list.add("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0");
        list.add("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/4.0; InfoPath.2; SV1; .NET CLR 2.0.50727; WOW64)");
        list.add("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36");
        list.add("Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko");
        list.add("Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.1)");
        list.add("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0");
        list.add("Mozilla/5.0 (X11; Linux i686; rv:40.0) Gecko/20100101 Firefox/40.0");
        list.add("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36");
        list.add("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)");
        list.add("Opera/9.80 (X11; Linux i686; U; ru) Presto/2.8.131 Version/11.11");
        list.add("Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25");
        list.add("Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)");
        list.add("Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1");

        Random random = new Random();
        return list.get(random.nextInt(list.size()));
    }
}複製程式碼

注:這個User-Agent資訊可以省略

(2)資料的持久化:

package cn.zifangsky.webmagic.movie;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;

public class SaveDataPipeline implements Pipeline {

    /**
     * 爬蟲資料的持久化
     */
    @Override
    public void process(ResultItems resultItems, Task task) {
        Movie movie = resultItems.get("movie");
        if (movie != null) {
            try {
                Connection connection = JDBCConnection.getConnection();

                PreparedStatement pStatement = connection
                        .prepareStatement("insert into movie(MovieName,MovieLink) values(?,?)");
                pStatement.setString(1, movie.getMovieName());
                pStatement.setString(2, movie.getMovieLink());
                pStatement.executeUpdate();
                pStatement.close();

                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}複製程式碼

這裡主要是使用了基本的JDBC將資料儲存到資料庫中,對應的獲取JDBC連線程式碼是:

package cn.zifangsky.webmagic.movie;

import java.sql.Connection;
import java.sql.DriverManager;

public class JDBCConnection {
    private static final String driver = "com.mysql.jdbc.Driver";
    private static final String url = "jdbc:mysql://127.0.0.1:3306/movie?useUnicode=true&characterEncoding=utf-8";
    private static final String username = "root";
    private static final String password = "root";

    public static Connection getConnection(){
        try {
            Class.forName(driver);

            return DriverManager.getConnection(url, username, password);
        } catch (Exception e) {
            e.printStackTrace();
        }    
        return null;
    }
}複製程式碼

同樣,對應的SQL語句是:

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for movie
-- ----------------------------
DROP TABLE IF EXISTS `movie`;
CREATE TABLE `movie` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `MovieName` varchar(500) DEFAULT NULL,
  `MovieLink` varchar(500) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;複製程式碼

(3)測試:

package cn.zifangsky.webmagic.movie;

import org.junit.Test;

import us.codecraft.webmagic.model.OOSpider;
import us.codecraft.webmagic.pipeline.ConsolePipeline;

public class TestSpider {

    @Test
    public void saveMovie(){
        OOSpider.create(new MovieSpider())
        .addUrl("http://www.80s.tw/movie/list/-2016---")
        .addPipeline(new ConsolePipeline())
        .addPipeline(new SaveDataPipeline())
        .thread(5)
        .run();
    }
}複製程式碼

執行這個單元測試之後,等待一會時間之後觀察資料庫就可以發現電影的下載連結已經全部獲取到了:

重構:抓取一個視訊網站上 2016 年所有電影的下載連結
爬蟲結果

並且我們可以發現:到目前為止這個視訊網站收錄的2016年的電影一共有801個。至此,整個爬蟲程式碼就全部結束了。程式碼邏輯實際是很簡單的,我這篇文章的目的主要還是重構之前寫過的那個類似的程式碼

最後,我已經將抓取到的電影結果上傳到網盤了,感興趣的童鞋可以下載來看看:pan.baidu.com/s/1pLwbXSf

相關文章