JAVA网络爬爬学习之HttpClient+Jsoup

2021-12-17 16:12:03 浏览数 (1)

JAVA网络爬爬学习

  • HttpClient用法简单整理
    • GET请求
      • 无参
      • 带参
    • POST请求
      • 无参
      • 带参
    • 连接池
    • 请求request的相关配置
    • httpclient用法详解
  • Jsoup用法简单整理
    • jsoup解析
      • 解析URL
      • 解析字符串
      • 解析文件
    • 使用dom方式遍历文档
    • 使用选择器语法查找元素
    • Selector选择器概述
    • Selector选择器组合使用
    • Jsoup参考资料
  • 爬虫案例
    • 开发准备
  • 封装HttpClient
  • 实现数据抓取
  • 爬虫演示
  • 错误记录
  • gitee源码链接

HttpClient用法简单整理

引入HttpClient和日志依赖

代码语言:javascript复制
    <dependencies>
<!--        HttpClient-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
        <!-- 日志 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>

GET请求

无参

代码语言:javascript复制
public class Main
{
    public static void main(String[] args)
    {
        //1.创建默认的HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //2.创建HttpGet请求
        HttpGet httpGet=new HttpGet("https://blog.csdn.net/m0_53157173");

        try(
                //3.使用HttpClient发请求
            CloseableHttpResponse  response = httpClient.execute(httpGet);)
        {
            //判断响应状态码是否为200
            if(response.getStatusLine().getStatusCode()==200)
            {
                //如果为200表示请求成功,获取返回数据
                HttpEntity entity = response.getEntity();
                //使用工具类
                String content = EntityUtils.toString(entity,"UTF-8");
                //打印数据内容
                System.out.println(content);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

带参

另一种写法:

代码语言:javascript复制
       //带参
        URIBuilder urIBuilder=new URIBuilder("https://blog.csdn.net/m0_53157173/article/details/121876392");
           urIBuilder.setParameter("name","大忽悠").setParameter("age","18");
        //2.创建HttpPOST请求
        HttpGet httpGet=new HttpGet(urIBuilder.build());

POST请求

无参


带参

代码语言:javascript复制
        //2.创建HttpPOST请求
        HttpPost httpPost=new HttpPost();
        //声明存放参数的List集合
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("keys", "java"));
        //创建表单数据Entity
        UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(params, "UTF-8");
        //设置表单Entity到httpPost请求对象中
        httpPost.setEntity(formEntity);

连接池

如果每次请求都要创建HttpClient,会有频繁创建和销毁的问题,可以使用连接池来解决这个问题。

代码语言:javascript复制
public class Main
{
    public static void main(String[] args) {
        //连接池管理器
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();

        //设置最大连接数
        cm.setMaxTotal(200);

        //设置每个主机的并发数
        cm.setDefaultMaxPerRoute(20);

        doGet(cm);

        doGet(cm);

    }

    private static void doGet(PoolingHttpClientConnectionManager cm) {
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();

        HttpGet httpGet = new HttpGet("https://www.baidu.com");

        CloseableHttpResponse response = null;

        try {
            response = httpClient.execute(httpGet);

            // 判断状态码是否是200
            if (response.getStatusLine().getStatusCode() == 200) {
                // 解析数据
                String content = EntityUtils.toString(response.getEntity(), "UTF-8");
                System.out.println(content.length());
            }


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放连接
            if (response == null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //不能关闭HttpClient
                //httpClient.close();
            }
        }
    }
}

请求request的相关配置

有时候因为网络,或者目标服务器的原因,请求需要更长的时间才能完成,我们需要自定义相关时间

代码语言:javascript复制
public static void main(String[] args) throws IOException {
    //创建HttpClient对象
 CloseableHttpClient httpClient = HttpClients.createDefault();

    //创建HttpGet请求
    HttpGet httpGet = new HttpGet("http://www.itcast.cn/");

    //设置请求参数
    RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(1000)//设置创建连接的最长时间
            .setConnectionRequestTimeout(500)//设置获取连接的最长时间
            .setSocketTimeout(10 * 1000)//设置数据传输的最长时间
            .build();

    httpGet.setConfig(requestConfig);

    CloseableHttpResponse response = null;
    try {
        //使用HttpClient发起请求
        response = httpClient.execute(httpGet);

        //判断响应状态码是否为200
        if (response.getStatusLine().getStatusCode() == 200) {
            //如果为200表示请求成功,获取返回数据
            String content = EntityUtils.toString(response.getEntity(), "UTF-8");
            //打印数据长度
            System.out.println(content);
        }

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //释放连接
        if (response == null) {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            httpClient.close();
        }
    }
}

Jsoup用法简单整理

我们抓取到页面之后,还需要对页面进行解析。可以使用字符串处理工具解析页面,也可以使用正则表达式,但是这些方法都会带来很大的开发成本,所以我们需要使用一款专门解析html页面的技术。

jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。

jsoup的主要功能如下:

  • 从一个URL,文件或字符串中解析HTML;
  • 使用DOM或CSS选择器来查找、取出数据;
  • 可操作HTML元素、属性、文本;

先加入依赖:

代码语言:javascript复制
<!--Jsoup-->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.10.3</version>
</dependency>
<!--测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<!--工具-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.7</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

jsoup解析

解析URL

Jsoup可以直接输入url,它会发起请求并获取数据,封装为Document对象

代码语言:javascript复制
public class Main
{
    public static void main(String[] args) throws IOException {
        //解析url地址
        Document document = Jsoup.parse(new URL("https://www.baidu.com/"), 1000);

        //获取title的内容
        Element title = document.getElementsByTag("title").first();
        System.out.println(title.text());
    }
}

解析字符串

先准备以下html文件

代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>测试文件</title>
    <link rel="stylesheet" href="css/sb-admin-2.css">
    <link rel="stylesheet" href="bootstrap-4.6.0-dist/css/bootstrap.min.css">

</head>

<body class="bg-gradient-primary">

</body>
</html>
代码语言:javascript复制
String fileToString = FileUtils.
readFileToString(new File("C:\\Users\\zdh\\Desktop\\test.html"), Charset.defaultCharset());
        Document document = Jsoup.parse(fileToString);

        System.out.println(document.body());

解析文件

代码语言:javascript复制
      //解析文件
        Document document = Jsoup.parse(new File("C:\Users\zdh\Desktop\test.html"), "UTF-8");

        String html = document.getElementsByTag("title").first().html();
        System.out.println(html);

使用dom方式遍历文档

元素获取

  • 1.根据id查询元素getElementById
  • 2.根据标签获取元素getElementsByTag
  • 3.根据class获取元素getElementsByClass
  • 4.根据属性获取元素getElementsByAttribute
代码语言:javascript复制
//1.    根据id查询元素getElementById
Element element = document.getElementById("city_bj");

//2.   根据标签获取元素getElementsByTag
element = document.getElementsByTag("title").first();

//3.   根据class获取元素getElementsByClass
element = document.getElementsByClass("s_name").last();

//4.   根据属性获取元素getElementsByAttribute
element = document.getElementsByAttribute("abc").first();
element = document.getElementsByAttributeValue("class", "city_con").first();

元素中获取数据

  • 1.从元素中获取id
  • 2.从元素中获取className
  • 3.从元素中获取属性的值attr
  • 4.从元素中获取所有属性attributes
  • 5.从元素中获取文本内容text
代码语言:javascript复制
//获取元素
Element element = document.getElementById("test");

//1.   从元素中获取id
String str = element.id();

//2.   从元素中获取className
str = element.className();

//3.   从元素中获取属性的值attr
str = element.attr("id");

//4.   从元素中获取所有属性attributes
str = element.attributes().toString();

//5.   从元素中获取文本内容text
str = element.text();

使用选择器语法查找元素

jsoup elements对象支持类似于CSS (或jquery)的选择器语法,来实现非常强大和灵活的查找功能。这个select 方法在Document, Element,或Elements对象中都可以使用。且是上下文相关的,因此可实现指定元素的过滤,或者链式选择访问。

Select方法将返回一个Elements集合,并提供一组方法来抽取和处理结果。


Selector选择器概述

  • tagname: 通过标签查找元素,比如:span
  • #id: 通过ID查找元素,比如:# city_bj
  • .class: 通过class名称查找元素,比如:.class_a
  • [attribute]: 利用属性查找元素,比如:[abc]
  • [attr=value]: 利用属性值来查找元素,比如:[class=s_name]
代码语言:javascript复制
//tagname: 通过标签查找元素,比如:span
Elements span = document.select("span");
for (Element element : span) {
    System.out.println(element.text());
}

//#id: 通过ID查找元素,比如:#city_bjj
String str = document.select("#city_bj").text();

//.class: 通过class名称查找元素,比如:.class_a
str = document.select(".class_a").text();

//[attribute]: 利用属性查找元素,比如:[abc]
str = document.select("[abc]").text();

//[attr=value]: 利用属性值来查找元素,比如:[class=s_name]
str = document.select("[class=s_name]").text();

Selector选择器组合使用

  • el#id: 元素 ID,比如: h3#city_bj
  • el.class: 元素 class,比如: li.class_a
  • el[attr]: 元素 属性名,比如: span[abc]
  • 任意组合: 比如:span[abc].s_name
  • ancestor child: 查找某个元素下子元素,比如:.city_con li 查找"city_con"下的所有li
  • parent > child: 查找某个父元素下的直接子元素,比如:
  • .city_con > ul > li 查找city_con第一级(直接子元素)的ul,再找所有ul下的第一级li
  • parent > *: 查找某个父元素下所有直接子元素
代码语言:javascript复制
//el#id: 元素 ID,比如: h3#city_bj
String str = document.select("h3#city_bj").text();

//el.class: 元素 class,比如: li.class_a
str = document.select("li.class_a").text();

//el[attr]: 元素 属性名,比如: span[abc]
str = document.select("span[abc]").text();

//任意组合,比如:span[abc].s_name
str = document.select("span[abc].s_name").text();

//ancestor child: 查找某个元素下子元素,比如:.city_con li 查找"city_con"下的所有li
str = document.select(".city_con li").text();

//parent > child: 查找某个父元素下的直接子元素,
//比如:.city_con > ul > li 查找city_con第一级(直接子元素)的ul,再找所有ul下的第一级li
str = document.select(".city_con > ul > li").text();

//parent > * 查找某个父元素下所有直接子元素.city_con > *
str = document.select(".city_con > *").text();

爬虫案例

首先访问京东,搜索手机,分析页面,我们抓取以下商品数据:商品图片、价格、标题、商品详情页

SPU和SKU 除了以上四个属性以外,我们发现上图中的苹果手机有四种产品,我们应该每一种都要抓取。那么这里就必须要了解spu和sku的概念 SPU = Standard Product Unit (标准产品单位) SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。 例如上图中的苹果手机就是SPU,包括红色、深灰色、金色、银色 SKU=stock keeping unit(库存量单位) SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。 例如上图中的苹果手机有几个款式,红色苹果手机,就是一个sku 查看页面的源码也可以看出区别


开发准备

根据需求,建立对应的数据库

代码语言:javascript复制
CREATE TABLE `jd_item` (
  `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `spu` bigint(15) DEFAULT NULL COMMENT '商品集合id',
  `sku` bigint(15) DEFAULT NULL COMMENT '商品最小品类单元id',
  `title` varchar(100) DEFAULT NULL COMMENT '商品标题',
  `price` bigint(10) DEFAULT NULL COMMENT '商品价格',
  `pic` varchar(200) DEFAULT NULL COMMENT '商品图片',
  `url` varchar(200) DEFAULT NULL COMMENT '商品详情地址',
  `created` datetime DEFAULT NULL COMMENT '创建时间',
  `updated` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `sku` (`sku`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='京东商品表';

依赖引入

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
<parent>
    <artifactId>spring-boot-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.0.4.RELEASE</version>
</parent>

    <groupId>org.example</groupId>
    <artifactId>Pa</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
<!--        spring web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        HttpClient-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
        <!-- 日志 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!--Jsoup-->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.10.3</version>
        </dependency>
        <!--测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--工具-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

<!--        mybaits-plus-->

        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--mybaits-plus第三方提供的启动器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3.1</version>
        </dependency>

        <!--        代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>

<!--        SpringBootTest-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

mybaits-plus相关配置

  • 配置数据源
代码语言:javascript复制
spring:
  datasource:                        #是否使用安全连接
    #mysal 8驱动不同com.mysql.cj.jdbc.Driver,还需要增加时区的配置 serverTimezone=GMT+8
    url: jdbc:mysql://localhost:3306/xxx?userSSL=false&useUnicode=true&characterEncoding=utf-8
    username: root
    password: xxx
    driver-class-name: com.mysql.jdbc.Driver
  • MP代码生成器
代码语言:javascript复制
//MP代码生成器
@SpringBootTest(classes = Main.class,webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ExtendWith(SpringExtension.class)
public class Generator
{
    //代码生成的文件路径                  当前系统的目录
    private final String outPutDir=System.getProperty("user.dir") "/src/main/java/dhy/com";
    @Autowired
    private HttpUtils httpUtils;
    //数据库驱动名
    @Value("${spring.datasource.driver-class-name}")
    private String dbDriverClassName;
    //数据库地址
    @Value("${spring.datasource.url}")
    private String dbUrl;
    //用户名
    @Value("${spring.datasource.username}")
    private String dbUsername;
    //密码
    @Value("${spring.datasource.password}")
    private String dbPassword;

    @Test
   public void CodeAutoGenerated()
   {


       //1.全局策略配置
       GlobalConfig config = new GlobalConfig();
       config.setAuthor("大忽悠")//作者
               .setOutputDir(outPutDir)//生成路径
               .setFileOverride(true)//文件覆盖
               .setIdType(IdType.AUTO)//主键策略
               .setServiceName("%sService")//设置生成service接口名字的首字母是否为I(默认会生成I开头的IStudentService)
               .setBaseResultMap(true)//自动SQL映射文件,生成基本的ResultMap
               .setBaseColumnList(true);//生成基本的SQL片段

       //2.数据源配置
       DataSourceConfig dataSourceConfig = new DataSourceConfig();
       dataSourceConfig.setDbType(DbType.MYSQL)//设置数据库类型
               .setDriverName(dbDriverClassName)
               .setUrl(dbUrl)//数据库地址
               .setUsername(dbUsername)//数据库名字
               .setPassword(dbPassword);//数据库密码


       //3.策略配置
       StrategyConfig strategy = new StrategyConfig();
       strategy.setCapitalMode(true)//全局大写命名
               .setNaming(NamingStrategy.underline_to_camel)//数据库表映射到实体的命名策略
               .setColumnNaming(NamingStrategy.underline_to_camel)//列的命名也支持驼峰命名规则
               .setTablePrefix("jd_")//数据库表的前缀
               .setInclude("jd_item")//设置要映射的表名,这里可以写多个
               .setEntityLombokModel(true)  //使用Lombok开启注解
               .setControllerMappingHyphenStyle(true);//controller层,开启下划线url : //localhost:8080/hello_id_2

       //4.包名策略
       PackageConfig packageConfig = new PackageConfig();
       packageConfig
              // .setModuleName("generator")
               .setParent("com")//所放置的包(父包)
               .setMapper("mapper")//Mapper包
               .setService("service")//服务层包
               .setController("controller")//控制层
               .setEntity("beans")//实体类
               .setXml("mapper");//映射文件
       //5.整合配置
       AutoGenerator autoGenerator = new AutoGenerator();
       autoGenerator.setGlobalConfig(config)
               .setDataSource(dataSourceConfig)
               .setStrategy(strategy)
               .setPackageInfo(packageConfig);
       //6.执行
       autoGenerator.execute();
   }
}
  • 实体类增加自动填充字段功能
  • 自定义填充策略
代码语言:javascript复制
@Component//填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component或@Bean注入
public class MyMetaObjectHandler implements MetaObjectHandler
{
    //插入时填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("created", LocalDateTime.now(),metaObject);
        this.setFieldValByName("updated",LocalDateTime.now(),metaObject);
    }

    //更新时填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updated",new Date(),metaObject);
    }
}

封装HttpClient

  • HttpClientUtils
代码语言:javascript复制
package dhy.com.utils;

import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
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.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;


import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.UUID;

@Component
public class HttpUtils {

    //连接池管理器
    private PoolingHttpClientConnectionManager cm;

    //输出的文件地址
    @Value("${image.file.path}")
    private String outputFilePath;

    public HttpUtils() {
        this.cm = new PoolingHttpClientConnectionManager();
        //设置最大连接数
        cm.setMaxTotal(200);
        //设置每个主机的并发数
        cm.setDefaultMaxPerRoute(20);
    }

    //获取内容
    public String getHtml(String url) {

        // 获取HttpClient对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();

        // 声明httpGet请求对象
        HttpGet httpGet = new HttpGet(url);
        // 设置请求参数RequestConfig
        httpGet.setConfig(this.getConfig());
       //模拟浏览器访问行为
        httpGet.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0");

        CloseableHttpResponse response = null;
        try {
            // 使用HttpClient发起请求,返回response
            response = httpClient.execute(httpGet);
            // 解析response返回数据
            if (response.getStatusLine().getStatusCode() == 200) {
                String html = "";

                // 如果response.getEntity获取的结果是空,在执行EntityUtils.toString会报错
                // 需要对Entity进行非空的判断
                if (response.getEntity() != null) {
                    html = EntityUtils.toString(response.getEntity(), "UTF-8");
                }

                return html;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    // 关闭连接
                    response.close();
                }
                // 不能关闭,现在使用的是连接管理器
                // httpClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    //获取图片
    public String getImage(String url) {
        // 获取HttpClient对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();

        // 声明httpGet请求对象
        HttpGet httpGet = new HttpGet(url);
        // 设置请求参数RequestConfig
        httpGet.setConfig(this.getConfig());

        CloseableHttpResponse response = null;
        try {
            // 使用HttpClient发起请求,返回response
            response = httpClient.execute(httpGet);
            // 解析response下载图片
            if (response.getStatusLine().getStatusCode() == 200&&response.getEntity()!=null)
            {

                Header contentType = response.getEntity().getContentType();
                String name = contentType.getName();//key: contentType
                String value = contentType.getValue();//value: MediaType类型
                //如果不是图片
                if(!value.equals(MediaType.IMAGE_JPEG_VALUE)&&!value.equals(MediaType.IMAGE_PNG_VALUE))
                {
                    return null;
                }
                // 获取文件类型
                String extName = url.substring(url.lastIndexOf("."));
                // 使用uuid生成图片名
                String imageName = UUID.randomUUID().toString()   extName;
                // 声明输出的文件
                File file = new File(outputFilePath   imageName);
                OutputStream outstream = new FileOutputStream(file);

                System.out.println(file.getAbsolutePath());
                System.out.println(file.exists());

                // 使用响应体输出文件
                response.getEntity().writeTo(outstream);

                // 返回生成的图片名
                return imageName;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    // 关闭连接
                    response.close();
                }
                // 不能关闭,现在使用的是连接管理器
                // httpClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    //获取请求参数对象
    private RequestConfig getConfig() {
        RequestConfig config = RequestConfig.custom().setConnectTimeout(1000)// 设置创建连接的超时时间
                .setConnectionRequestTimeout(500) // 设置获取连接的超时时间
                .setSocketTimeout(10000) // 设置连接的超时时间
                .build();

        return config;
    }

}

实现数据抓取

  • 使用定时任务,可以定时抓取最新的数据

商品定位分析:

获取到所有spu商品信息对应的代码为:

代码语言:javascript复制
     //获取商品数据
        Elements spus = document.select("div#J_goodsList > ul > li");

获取每一个商品对应的唯一spu标识:

代码语言:javascript复制
  //遍历商品spu数据
        for (Element spuEle : spus) {
            //获取商品spu
            Long spuId = Long.parseLong(spuEle.attr("data-spu"));
            .....
            }

获取商品sku数据

代码语言:javascript复制
      //获取商品sku数据
            Elements skus = spuEle.select("li.ps-item img");

通过上面获取到的当前img的dom对象,就可以获取到里面每个我们需要的属性了

完整代码:

代码语言:javascript复制
package dhy.com.sechudle;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import dhy.com.beans.Item;
import dhy.com.service.ItemService;
import dhy.com.utils.HttpUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class ItemTask {

    @Autowired
    private HttpUtils httpUtils;
    @Autowired
    private ItemService itemService;

    public static final ObjectMapper MAPPER = new ObjectMapper();

    //抓取页面的url
    @Value("${clawer.page.url}")
    private String pageUrl;
    //页码
    private final String pageNumStr="&page=";

    //设置定时任务执行完成后,再间隔100秒执行一次
    @Scheduled(fixedDelay = 1000 * 100)
    public void process() throws Exception {
        //对于京东来说,2个页面对应一页,即99和100都对应第50也
        //101对应第51页

        //遍历执行,获取所有的数据
        for (int i = 1; i < 6; i = i   2) {
            //发起请求进行访问,获取页面数据,先访问第一页
            String html = this.httpUtils.getHtml(pageUrl pageNumStr   i);
            //解析页面数据,保存数据到数据库中
            this.parseHtml(html);

        }
        System.out.println("执行完成");
    }


  //  解析页面,并把数据保存到数据库中
    private void parseHtml(String html) throws Exception {
        //使用jsoup解析页面
        Document document = Jsoup.parse(html);

        //获取商品数据
        Elements spus = document.select("div#J_goodsList > ul > li");

        //遍历商品spu数据
        for (Element spuEle : spus) {
            //获取商品spu
            String attr = spuEle.attr("data-spu");
            Long spuId = Long.parseLong(attr.equals("")?"0":attr);

            //获取商品sku数据
            Elements skus = spuEle.select("li.ps-item img");
            for (Element skuEle : skus)
            {
                //获取商品sku
                Long skuId = Long.parseLong(skuEle.attr("data-sku"));

                //判断商品是否被抓取过,可以根据sku判断
                Item param = new Item();
                param.setSku(skuId);
                List<Item> list = this.itemService.list(new QueryWrapper<Item>().eq("sku",skuId));
                //判断是否查询到结果
                if (list.size() > 0) {
                    //如果有结果,表示商品已下载,进行下一次遍历
                    continue;
                }

                //保存商品数据,声明商品对象
                Item item = new Item();
                //商品spu
                item.setSpu(spuId);
                //商品sku
                item.setSku(skuId);
                //商品url地址
                item.setUrl("https://item.jd.com/"   skuId   ".html");
                //获取商品标题
                String itemHtml = this.httpUtils.getHtml(item.getUrl());
                String title = Jsoup.parse(itemHtml).select("div.sku-name").text();
                item.setTitle(title);

                //获取商品价格
                String priceUrl = "https://p.3.cn/prices/mgets?skuIds=J_" skuId;

                String priceJson = this.httpUtils.getHtml(priceUrl);
                //解析json数据获取商品价格
                double price = MAPPER.readTree(priceJson).get(0).get("p").asDouble();
                item.setPrice(price);

                //获取图片地址
                String pic = "https:"   skuEle.attr("data-lazy-img").replace("/n9/","/n1/");
                System.out.println(pic);
                //下载图片
                String picName = this.httpUtils.getImage(pic);
                item.setPic(picName);

                //保存商品数据
                this.itemService.save(item);
            }
        }
    }
}

爬虫演示


错误记录

httpClient访问京东网站的时候,需要加上请求头,模拟是浏览器访问,否则京东默认会跳到登录页面

代码语言:javascript复制
 //模拟浏览器访问行为
        httpGet.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0");

gitee源码链接

gitee

0 人点赞