Java爬虫入门案例,Java爬虫爬取豆瓣图书教程并存储到数据库中(HttpClient+Jsoup+Jackson+Mybatis)

Java简单爬虫入门案例

所需知识: HttpClient + [Jsoup本次接口返回JSON不需要Jsoup解析页面],[Jackson解析JSON,大家也可以用其他工具解析JSON] HttpClient用于模拟发起请求,Jsoup用于解析,虽然Jsoup可以发起请求,但HttpClient拥有线程池等,可以自定义配置,因此我们一般采用HttpClient发起请求,Jsoup仅用于解析html页面。

一、业务需求

二、思路分析

三、代码实现

  1. 数据库表创建
  2. 环境搭建
  3. 创建实体类
  4. 创建BookMapper操作数据库
  5. 核心代码编写

四、常见问题


业务需求

简单爬取到豆瓣图书的选影视模块前100本,并将图书的基本信息爬取下来存储中数据库中(mysql)
https://movie.douban.com/tag/#/ 目标网址


思路分析

打开这个网址我们可以看到 在这里插入图片描述
页面展示了一些电影信息,为简单起,我们直接就爬取默认分类。
可以看到页面有20条电影信息,但是这些信息怎么来的呢?让我们右键检查打开Chrome浏览器的开发者模式,点击Network,选择 XHR类型,然后F5重新加载页面,我们可以看到,出现了一条异步请求。
在这里插入图片描述
我们点击这条请求,然后点response,可以看到,请求直接返回了JSON数据,看来我们已经找到了要爬取的地址了。

接下来我们分析url

我们在页面上点击加载更多,在观察XHR,能够发现又发起了一次请求,我们多点几次对比来看。

https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=0
https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=20
https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=40
https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start=60

通过这四个请求的url我们可以发现,只有start在变,并且每次请求会多出20条数据,那么这个start就代表了起始的第几条。然后请求过来20条数据。

分析到这里,我们已经清楚如何获取到所需的数据了,下面我们开始编写代码。


代码实现

  1. 数据库表创建
    我们选择接口返回数据中的一条查看。
    在这里插入图片描述
    因此,我们将如图这些数据存入数据库即可,对于图片,我们将图片存入本地,将图片名称存入数据库中即可。
    建表语句如下(在navicat中选择数据库,新建查询,粘贴进去执行):
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for book
-- ----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `directors` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `rate` double NULL DEFAULT NULL,
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `casts` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
  1. 环境搭建
    创建一个maven项目,使用骨架,

    我们只是为了演示爬取并存储数据,与在web项目中核心代码相同。

    导入坐标 (我为了便于理解,我使用尽量少的jar包,所以代码不是特别整洁)

       <dependencies>
       <dependency>
           <groupId>org.apache.httpcomponents</groupId>
           <artifactId>httpclient</artifactId>
           <version>4.5.7</version>
       </dependency>
    
       <dependency>
           <groupId>org.jsoup</groupId>
           <artifactId>jsoup</artifactId>
           <version>1.10.3</version>
       </dependency>
    
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>8.0.15</version>
       </dependency>
    
       <dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis</artifactId>
           <version>3.4.6</version>
       </dependency>
               <!--解析JSON-->
       <dependency>
           <groupId>com.fasterxml.jackson.core</groupId>
           <artifactId>jackson-core</artifactId>
           <version>2.10.2</version>
       </dependency>
    
       <dependency>
           <groupId>com.fasterxml.jackson.core</groupId>
           <artifactId>jackson-databind</artifactId>
           <version>2.10.2</version>
       </dependency>
    
       <dependency>
           <groupId>com.fasterxml.jackson.core</groupId>
           <artifactId>jackson-annotations</artifactId>
           <version>2.10.2</version>
       </dependency>
    </dependencies>
    

配置Mybatis

这里我使用的是Mybatis,因为在一般web项目中都使用mybatis,大家也可以自行使用其他(jdbc),我们只涉及简单的sql语句。
我使用的是Mysql8版本,Mybatis使用的是注解开发

在Resources下创建sqlMapConfig.xml文件,注意我使用的Mysql8版本,如果你们使用的是5版本需要更改下driver和url

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <!-- 数据源环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/db1?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT&amp;allowPublicKeyRetrieval=true" />
                <property name="username" value="root" />
                <property name="password" value="jiajie123" />
            </dataSource>
        </environment>
    </environments>

    <!-- 配置映射关系-->
    <mappers>
        <package name="cn.jayjia.mapper" />
    </mappers>
</configuration>
  1. 创建实体类
public class Book {

    private Integer id;
    private String directors;
    private Double rate;
    private String url;
    private String casts;
    private String cover;
    private String title;
	添加getter/setter方法,我就不写了,大家注意添加
}

  1. 创建BookMapper用于操作数据库
package cn.jayjia.mapper;
import cn.jayjia.pojo.Book;
import org.apache.ibatis.annotations.Insert;

public interface BookMapper {

    @Insert("insert into book values(null, #{directors}, #{rate}, #{url}, #{casts}, #{cover}, #{title})")
    void save(Book book);
}
  1. 编写核心代码

注释比较清晰,大家可以直接从main方法入手
代码重点主要在于 doGetContent() 和 doGetImage()
连接池管理器和RequestConfig都可以不配置,我在代码中注释了,如果大家觉得影响阅读可以不适用。但User-Agent头信息必须添加

public class Main {
	// HttpClient连接池管理器
    private static PoolingHttpClientConnectionManager manager;
    // 解析JSON
    private static ObjectMapper mapper = new ObjectMapper();

    public static void main(String[] args) throws JsonProcessingException {
        SqlSession sqlSession = getSqlSession();
        // 获取操作数据库对象
        BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);

        initPoolingManager(); // 初始化连接池管理器并设置参数

        String url = "https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=&start="; // url后start变化
        for (int i = 0; i < 100; i += 20) {
            String content = doGetContent(url + i); // 请求接口返回JSON数据
            JsonNode jsonNode = mapper.readTree(content);
            // 我们观察返回的json数据,数据是在data下的,我们要注意,先获取data才能获取想要的数据
            jsonNode = jsonNode.get("data");
            for (JsonNode node : jsonNode) {
                Book book = new Book();
                book.setDirectors(node.get("directors").asText());
                book.setRate(node.get("rate").asDouble());
                book.setUrl(node.get("url").asText());
                book.setCasts(node.get("casts").asText());
                book.setTitle(node.get("title").asText());
                // 下载图片并将图片名存储到数据库中
                book.setCover(doGetImage(node.get("cover").asText()));

                // 将图书信息存储到数据库中
                bookMapper.save(book);
            }
        }
        System.out.println("爬取完成!");
    }

    // 请求图片下载到本地并返回图片名
    private static String doGetImage(String url) {
        // 创建HttpClient对象,大家也可以不创建管理器 直接 CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(manager).build();

        // 创建Get请求
        HttpGet httpGet = new HttpGet(url);
        // 设置请求头信息,User-Agent欺骗网址是浏览器访问
        httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36");
        httpGet.setConfig(getConfig()); // 设置get请求的一些参数,如连接时间,可以不设置

        CloseableHttpResponse response = null; // 返回结果
        // 前面都一样,只是处理结果不一样
        try {
            response = httpClient.execute(httpGet);
            if (response.getStatusLine().getStatusCode() == 200) { // 如果请求成功再处理数据
                String picName = UUID.randomUUID().toString(); // 使用UUID生成随即名称防止重复
                picName += ".png";//别忘记加上文件后缀,我们统一存成png格式
                OutputStream os = new FileOutputStream("F:/img/douban/" + picName + ".png");
                response.getEntity().writeTo(os); // 使用writeTo将图片通过字节流传输
                return picName;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (response != null) { // 要记得关闭reponse,不需要关闭HttpClient因为我们使用了连接池
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return ""; // 请求失败
    }

    public static String doGetContent(String url) {
        // 创建HttpClient对象,大家也可以不创建管理器 直接 CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(manager).build();

        // 创建Get请求
        HttpGet httpGet = new HttpGet(url);
        // 设置请求头信息,User-Agent欺骗网址是浏览器访问
        httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36");
        httpGet.setConfig(getConfig()); // 设置get请求的一些参数,如连接时间,可以不设置

        CloseableHttpResponse response = null; // 返回结果
        try {
            response = httpClient.execute(httpGet);
            if (response.getStatusLine().getStatusCode() == 200) { // 如果请求成功再处理数据
                return EntityUtils.toString(response.getEntity(), "utf8");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (response != null) { // 要记得关闭reponse,不需要关闭HttpClient因为我们使用了连接池
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return ""; // 请求失败
    }

    private static void initPoolingManager() {
        // 创建HttpClient连接池管理器, 并设置连接数
        manager = new PoolingHttpClientConnectionManager();
        manager.setMaxTotal(100);
        manager.setDefaultMaxPerRoute(10);
    }

    private static SqlSession getSqlSession(){
        // 加载配置文件
        InputStream resourceAsStream = Main.class.getClassLoader().getResourceAsStream("sqlMapConfig.xml");
        // 获取sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获取session对象并设置事务自动提交
        return sqlSessionFactory.openSession(true);
    }

    private static RequestConfig getConfig() {
        RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(2000) // 创建时间
        .setConnectTimeout(4000) // 获取数据连接时间
        .setSocketTimeout(10*1000) // 数据传输时间
        .build();
        return config;
    }
}

至此,代码编写完成,运行我们就可以看到数据库的数据保存成功并且图片也下载成功了。


常见问题

  • javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    解决方案:SSLHandshakeException解决方案
  • 我们访问接口获取的reponse和我们在页面上的reponse不一样,拿不到想想要的数据。
    解决方案:打开浏览器的network,找到请求,将请求中的Request headers都添加到我们的HttpGet/或HttpPost中即可。
  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要使用Jsoup获取图片,可以按照以下步骤进行操作: 1. 使用Jsoup.connect()方法连接到指定的URL。 2. 使用Jsoup.parse()方法解析HTML文档。 3. 使用doc.select()方法选择包含图片的元素。 4. 使用element.attr()方法获取图片的URL。 5. 使用Jsoup.connect()方法连接到图片的URL。 6. 使用Response.bodyAsBytes()方法获取图片的字节数组。 下面是一个示例代码,可以获取指定网页的所有图片: ```java import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.jsoup.Connection.Response; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class ImageDownloader { public static void main(String[] args) throws IOException { String url = "https://www.example.com"; Document doc = Jsoup.connect(url).get(); Elements imgs = doc.select("img[src~=(?i)\\.(png|jpe?g|gif)]"); for (Element img : imgs) { String imgUrl = img.attr("abs:src"); Response resultImageResponse = Jsoup.connect(imgUrl) .ignoreContentType(true).execute(); InputStream inputStream = resultImageResponse.bodyStream(); OutputStream outputStream = new FileOutputStream( "image_" + System.currentTimeMillis() + ".jpg"); int bytesRead = -1; byte[] buffer = new byte[4096]; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.close(); inputStream.close(); } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值