大数据专业课学习pa虫! 第一次接触,记录下吧

@TOC

1.环境准备

  • idea2021
  • jdk 1.8
  • mysql 5.7
  • 需要的jar包
commons-codec-1.10.jar
commons-httpclient-3.1.jar
commons-logging-1.1.1.jar
httpclient-4.5.3.jar
httpcore-4.4.6.jar
json-20090211.jar
jsoup-1.10.2.jar
mysql-connector-java-5.1.18.jar
  • 当然也可以用maven,等有时间做其他的后就用下,然后数据库也用mybatis试试!(这里留个坑)
  • 创建一个普通的java项目,创建lib包,加载jar包
  • 在src下创建两个java文件
  •   CrawlDatatoBase	数据库存储类
    
  •   CrawlPage	 抓取类
    
  • 这样就可以写代码了

2.代码编写

2.1.CrawlDatatoBase 数据库存储类

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

/***
 *      数据库存储类
 */
public class CrawlDatatoBase {
    static Connection conn;
    static Statement st;
    /*** 主方法测试数据库连接
     * @param args
     */
    public static void main(String[] args) {
        setConn();
        System.out.println("测试成功");
    }
    /**
     * 将数据插入数据库
     */
    public static boolean InsertProduct(ArrayList<String> datalist) {
        try {
            //   获取当前日期
            Date now = new Date();
            //   通过指定格式实例化日期转化为字符串模板
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
            //   用模板将Date类型日期格式化成字符串
            String redate = dateFormat.format(now);
//               将日期加入datalist集合
            datalist.add(redate);
            //   定义将执行插入操作的insql语句
//            String insql = "INSERT INTO film(title,time,director,actor,amount,redate) VALUES(?,?,?,?,?,?)";
            String insql = "INSERT INTO film(title,time,director,actor,amount,redate) VALUES(?,?,?,?,?,?)";

            //   实例化PreparedStatement对象,预处理insql语句
            PreparedStatement ps = conn.prepareStatement(insql);
            int i;
            for (i = 0; i < datalist.size(); i++) {
                //   获取datalist集合中的每一条数据
                String strvalue = datalist.get(i);
                //   循环取得datalist中的数据并设置进VALUES中的?里面
                ps.setString(i + 1, strvalue);
            }
            //   执行insql语句,若成功,则返回一个正数,否则返回0
            int result = ps.executeUpdate();
            //   关闭PreparedStatement对象
            ps.close();
            //    result大于0说明插入操作成功
            if (result > 0) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
        /**
         * 调用getConnection方法连接数据库,增加安全性
         */
        public static void setConn(){
            conn = getConnection();
        }
        /**
         *   关闭数据库连接
         */
        public static void closeConn(){
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        public static Connection getConnection(){
            Connection con = null;
            // 定义驱动类
            String DBDRIVER = "com.mysql.jdbc.Driver";
            //   定义用户名
            String DBUSER = "root";
            //   定义数据库密码
            String DBPASS = "123456";
            //   定义url
            String DBURL = "jdbc:mysql://localhost:3306/clawler";
            try {
                //   加载数据库驱动类
                Class.forName(DBDRIVER);
                //   连接数据库
                con = DriverManager.getConnection(DBURL, DBUSER, DBPASS);
                //System.out.println("数据库连接成功...");
            } catch (Exception e) {
                //   如果连接失败,获取失败的信息
                System.out.println("数据库连接失败" + e.getMessage());
            }
            return con;
        }
    }

2.2.CrawlPage Pa取类

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.File;

import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;

/**
 * 爬取类
 */
public class CrawlPage {
    //创建一个客户端 类似于一个浏览器
//    private static HttpClient httpClient = new HttpClient();
    //   创建一个客户端
    private static CloseableHttpClient httpclient = HttpClients.createDefault();
    //   定义输出html文件的路径
    private static String filename = "F:\\BigDataSpider" + File.separator + File.separator + "film.html";
    //   定义输出csv文件的路径
    private static String outfile = "F:\\BigDataSpider" + File.separator + File.separator + "film.csv";
    // 定义控制输出file的boolean变量
    private static boolean bfile = true;
    // 定义控制输出file的boolean变量
    private static boolean bdb = true;
    //   定义Arraylist类集用来保存每一条数据的信息
    private static ArrayList<String> datalist = new ArrayList<String>();
    //   打印的标题头
    private static String headtitle = "电影名称,上映时间,导演,演员,评价人数";
    //   计数变量
    private static int countrs = 0;
    /**
     * 下载页面
     *
     * @param url
     * @return
     * @throws Exception
     */
    public static String downloadPage(String url) throws Exception {
        //   定义返回的String变量
        String htmlString = "";
        //   请求资源 get方式
        HttpGet request = new HttpGet(url);
//        设置下请求头
        request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36");
        //   得到回应
        CloseableHttpResponse response = httpclient.execute(request);
        try {
            //   打印状态码   获取状态码并打印
            System.out.println(response.getStatusLine());
            //   获得Entity对象
            HttpEntity entity = response.getEntity();
//            System.out.println("========================打印entity对象===================================");
//            System.out.println(entity);
            // 将Entity对象转化为字符串
            //当ip被封掉的时候,可能会显示空指针异常,可以查看打印下这个htmlString就知道是不是被封了
            // <p>有异常请求从你的 IP 发出,请 <a 会有这句话的  一般等一天就能再用
            htmlString = EntityUtils.toString(entity);
//            System.out.println("==============打印htmlString=============================================");
//            System.out.println(htmlString);
            //   销毁对象
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
        //   调用htmltoFile()方法在制定路径输出html文件
        htmltoFile(htmlString);
        return htmlString;
    }
    /**
     * 输出html文件
     * @param htmlString
     * @throws Exception
     */
    public static void htmltoFile(String htmlString) throws Exception {
        // 获得文件输出流
        FileOutputStream output = new FileOutputStream(filename);

        // 以utf-8编码的形式输出到文件(utf-8是中文编码,ISO-8859-1是英文编码)
        output.write(htmlString.getBytes("utf-8"));
        if (output != null) {
            output.close();
        }
    }
    /**
     * 获取所有豆瓣电影列表
     * @param surl
     * @throws Exception
     */
    public static void getDouBanList(String surl) throws Exception {
        //   通过url下载页面
        String html = CrawlPage.downloadPage(surl);
        //   用"star_clearfix"替代"star clearfix"
        html = html.replace("star clearfix", "star_clearfix");
        // 解析获取Document对象
        Document doc = Jsoup.parse(html);
        //   通过getElementsByClass方法获取class为"grid_view"的div节点对象 网格视图
        Element divNode = doc.getElementsByClass("grid_view").first();
        //   通过select选择器选择有class属性的li标签节点,返回Element元素的集合
        Elements liTag = divNode.select("li[class]");
        String title, time, director, actor, amount;
        //   对于liTag Element集合中的每一个元素liNode
        for (Element liNode : liTag) {
            //   取得liNode的第一个dd节点对象
            Element dd = liNode.select("dd").first();
            //   使用getElementsByTag方法,通过标签名称取得a标签节点对象,然后取其中的文本元素,即为电影名称
            title = dd.getElementsByTag("a").text();
            //   添加每一条数据前先清空之前添加的内容(由于是循环添加,一定要清空前一次添加的内容)
            datalist.clear();
            //   将title(电影名称)添加进datalist集合
            datalist.add(title);
            //   选择dd节点里面的第一个h6节点对象
            Element h6 = dd.select("h6").first();
            //   进一步选择h6节点对象的第一个span节点对象
            Element a = h6.select("span").first();
            //   取得第一个span节点对象的文本内容,初步取出时间
            time = a.text();
            //   进一步处理文本内容,去掉左括号
            time = time.replace("(", "");
            //   进一步处理文本内容,去掉右括号
            time = time.replace(")", "");
            //   将time(上映时间)添加进datalist集合
            datalist.add(time);
            //   通过select选择器选择dd节点的第一个dl节点
            Element dl = dd.select("dl").first();
            //   通过select选择器选择dl节点的第一个dd节点
            Element d1 = dl.select("dd").first();
            //   因为有些电影导演数据可能为空,为空(null)时会出现异常,所以在这里进行处理,将null转化为"";
            if (d1 != null) {
                //   获取d1的文本对象即为导演
                director = d1.text();
            } else {
                director = "";
            }
            //   将director(导演)添加进datalist集合
            datalist.add(director);
            //   通过select选择器选择dl节点的最后一个dd节点
            Element d2 = dl.select("dd").last();
            if (d2 != null) {
                actor = d2.text();
            } else {
                actor = "";
            }
            datalist.add(actor);
            //   通过getElementsByClass方法获取class为"star_clearfix"的节点对象
            Element foot = liNode.getElementsByClass("star_clearfix").first();
            //   通过select选择器选择foot的最后一个span对象
            Element span = foot.select("span").last();
            //   取得span里面的文本元素即为评论数量
            amount = span.text();
            datalist.add(amount);
            //   调用outputRs方法将datalist里面的每一条数据插入到数据库
            outputRs();
        }
    }
    /**
     * 输出到数据库
     *
     * @throws Exception
     */
    private static void outputRs() throws Exception {
        String strout = "";
        for (int i = 0; i < datalist.size(); i++) {
            //   获取datalist集合中的每一条数据,串成一个字符串
            strout = strout + datalist.get(i) + ",";
        }
        if (bfile) {
            //   实例化文件输出流
            FileWriter fw = new FileWriter(outfile, true);
            //   实例化打印流
            PrintWriter out = new PrintWriter(fw);
            if (countrs == 0){
                //   输出头标题
                out.println(headtitle);
            }

            //   输出刚刚串起来的strout字符串
            out.println(strout);
            //   关闭打印流
            out.close();
            //   关闭输出流
            fw.close();
        }
        countrs = countrs + 1;

//   在命令行打印数据
        System.out.println(countrs + "    " + strout);
// 插入数据库
        if (bdb) {
            CrawlDatatoBase.InsertProduct(datalist);
        }
    }
    /**
     * 翻页爬取
     * @param surl
     * @throws Exception
     */
    public static void skipPage(String surl) throws Exception {
//        下载页面 保存到html文件中 并返回了html的代码
        String html = CrawlPage.downloadPage(surl);
//        Jsoup处理HTML文件是,是将用户输入的HTML文档,解析转换成一个Document对象进行处理。
        Document doc = Jsoup.parse(html);
        //   获取页码部分的div对象  获取paginator"分页器"的div对象
        Element footDiv = doc.getElementsByClass("paginator").first();
        //   获取class为"next"的节点对象用footSpan表示  获取分页器中的next对象
        Element footSpan = footDiv.getElementsByClass("next").first();
        //   选择footSpan中第一个带有href属性的a节点对象,并用footA表示
        Element footA = footSpan.select("a[href]").first();
        //   获得footA中href属性中的内容href
        String href = footA.attr("href");
//   将"http://movie.douban.com/celebrity/1054424/movies"和href拼接即为下一页面的url
       String http = "http://movie.douban.com/celebrity/1054424/movies" + href;
//   获取当前页码节点
        Element thispage = doc.getElementsByClass("thispage").first();
//   获取当前页码中的数字元素(String类型),并转化为int类型
        int end = Integer.parseInt(thispage.text());
        if (end == 1) {
            getDouBanList(surl);
            System.out.println("==========================" + 1 + "===================");
        }
        //   爬取下一页面
        try {
            getDouBanList(http);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //   打印一行页面分隔符
        System.out.println("==========================" + (end + 1) + "===================");
        //   由于一共是19页,所以end小于19的时候循环爬取
        if (end < 19) {
            skipPage(http);
        } else {
            System.out.println("页面已爬完");
        }
    }

    /**
     * 主方法
     * @param args
     */
    public static void main(String[] args) {
     //   String strURL = "https://movie.douban.com/celebrity/1054424/movies?sortby=time&amp;format=pic&amp;&amp;qq-pf-to=pcqq.group";
        try {
            CrawlDatatoBase.setConn();
            //   翻页爬取
            skipPage(strURL);
            //   关闭数据库
            CrawlDatatoBase.closeConn();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.3数据库表

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for film
-- ----------------------------
DROP TABLE IF EXISTS `film`;
CREATE TABLE `film`  (
  `title` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `time` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `director` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `actor` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `amount` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `redate` date DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

3.运行展示

3.1. 存储到csv文件的图片

在这里插入图片描述

3.2.数据库的内容

请添加图片描述

3.3.ip被禁后的html内容,应该是正常的那种哈哈,现在成了登陆跳转页面

请添加图片描述

4.代码分析

  • jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。 这次学习的重点就是使用jsoup对网站页面的解析接下来是对网页的具体解析分析.

4.1.html代码解析(重点)

//   通过url下载页面
        String html = CrawlPage.downloadPage(surl);
  • 得到了整个页面的html文档,然后截取部分分析
<li class="">
            <dl>
                <dt>
                    <a class="nbg" href="https://movie.douban.com/subject/30171424/">
                        <img src="https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2621379901.webp" alt="拆弹专家2" title="拆弹专家2" class="">
                    </a>
                </dt>
                <dd>
                    <h6>
                        <a href="https://movie.douban.com/subject/30171424/" class="">拆弹专家2</a>
                            &nbsp;&nbsp;<span class="">(2020)</span>
                        &nbsp;&nbsp;<span class="">[ 演员 (饰 潘乘风) / 制片人 - 监制 ]</span>
                    </h6>
			<dl>
                <dt>导演:&nbsp;</dt>
                <dd>邱礼涛 Herman Yau</dd>
            
                <dt>主演:&nbsp;</dt>
                <dd>刘德华 Andy Lau / 刘青云 Sean Lau / 倪妮 Ni Ni / 谢君豪 Kwan-Ho Tse / 姜...</dd>
			</dl>
                        <div class="star clearfix">
                                <span class="allstar40"></span>
                                <span>7.5</span>&nbsp;/&nbsp;<span>521694人评价</span>
                        </div>
    <p>
        <span class="gact">
            <a href="https://movie.douban.com/wish/255581384/update?add=30171424" target="_blank" class="j a_collect_btn" name="sbtn-30171424-wish" rel="nofollow">想看</a>
        </span>&nbsp;&nbsp;
        <span class="gact">
            <a href="https://movie.douban.com/collection/255581384/update?add=30171424" target="_blank" class="j a_collect_btn" name="sbtn-30171424-collection" rel="nofollow">看过</a>
        </span>&nbsp;&nbsp;
    </p>
                </dd>
			</dl>
		</li>
//   用"star_clearfix"替代"star clearfix"
        html = html.replace("star clearfix", "star_clearfix");
  • 这是替换评论数字段,方便下一步的字符串分析
// 解析获取Document对象
        Document doc = Jsoup.parse(html);
        //   通过getElementsByClass方法获取class为"grid_view"的div节点对象 网格视图
        Element divNode = doc.getElementsByClass("grid_view").first();
  • 得到一整个页面的数据网格面如图在这里插入图片描述
//   通过select选择器选择有class属性的li标签节点,返回Element元素的集合
        Elements liTag = divNode.select("li[class]");
  • 得到li标签 有class属性的所有元素代码如图在这里插入图片描述
//   选择dd节点里面的第一个h6节点对象
            Element h6 = dd.select("h6").first();
            //   进一步选择h6节点对象的第一个span节点对象
            Element a = h6.select("span").first();
            //   取得第一个span节点对象的文本内容,初步取出时间
            time = a.text();
            //   进一步处理文本内容,去掉左括号
            time = time.replace("(", "");
            //   进一步处理文本内容,去掉右括号
            time = time.replace(")", "");
            //   将time(上映时间)添加进datalist集合
            datalist.add(time);
  • 获取时间在这里插入图片描述
  • 大概就是跟json一个理论,俄罗斯套娃,一层一层的打开拿到数据,进行字符串处理

4.2.分页爬取问题(重点)

  • 既然是分页爬取,那重点肯定是翻页了
//        下载页面 保存到html文件中 并返回了html的代码
        String html = CrawlPage.downloadPage(surl);
//        Jsoup处理HTML文件是,是将用户输入的HTML文档,解析转换成一个Document对象进行处理。
        Document doc = Jsoup.parse(html);
        //   获取页码部分的div对象  获取paginator"分页器"的div对象
        Element footDiv = doc.getElementsByClass("paginator").first();
        //   获取class为"next"的节点对象用footSpan表示  获取分页器中的next对象
        Element footSpan = footDiv.getElementsByClass("next").first();
        //   选择footSpan中第一个带有href属性的a节点对象,并用footA表示
        Element footA = footSpan.select("a[href]").first();
        //   获得footA中href属性中的内容href
        String href = footA.attr("href");
//   将"http://movie.douban.com/celebrity/1054424/movies"和href拼接即为下一页面的url
        String http = "http://movie.douban.com/celebrity/1054424/movies" + href;
  • 如图所示在这里插入图片描述

小结

  • 由于某瓣的分爬取机制,会出现
<h1>登录跳转</h1>
            <div><p>有异常请求从你的 IP 发出,请

的问题,这个个人实测等待一天,可以继续爬取.

  • 还有一个问题,需加上请求头
//        设置下请求头
        request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36");

就ok了!!