爬虫——java的实现(1)

1. 网络爬虫

网络爬虫(Web crawler),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本

1.1. 爬虫入门程序

1.1.1. 环境

 JDK1.8
 IntelliJ IDEA
 IDEA自带的Maven

1.1.2. 环境准备

创建Maven工程itcast-crawler-first并给pom.xml加入依赖:

<?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>

    <groupId>com.xxx</groupId>
    <artifactId>crawler_first</artifactId>
    <version>1.0-SNAPSHOT</version>


    <dependencies>

        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
            <scope>test</scope>
        </dependency>

    </dependencies>


</project>

1.1.3. java代码编写:

public class CrawlerFirst {

    public static void main(String[] args) throws Exception {
        //1.打开浏览器,(创建HttpClient对象)
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //2.输入网址(输入网址,发起get请求,创建HttpGet对象)
        HttpGet httpGet = new HttpGet("http://www.itcast.cn");
        //3.按回车,发起请求,返回响应,使用HttpClient对象发起请求
        CloseableHttpResponse response = httpClient.execute(httpGet);
        //4.解析响应,获取数据
        //判断状态码是否为200(成功)
        if(response.getStatusLine().getStatusCode() == 200){
            HttpEntity httpEntity = response.getEntity();
            String content = EntityUtils.toString(httpEntity, "utf8");
            System.out.println(content);
        }
    }
}

测试结果(获得相应页面的html文本数据):

测试结果部分截图

2. 网络爬虫

2.1. 网络爬虫介绍

在大数据时代,信息的采集是一项重要的工作,而互联网中的数据是海量的,如果单纯靠人力进行信息采集,不仅低效繁琐,搜集的成本也会提高。如何自动高效地获取互联网中我们感兴趣的信息并为我们所用是一个重要的问题,而爬虫技术就是为了解决这些问题而生的。

网络爬虫(Web crawler)也叫做网络机器人,可以代替人们自动地在互联网中进行数据信息的采集与整理。它是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本,可以自动采集所有其能够访问到的页面内容,以获取相关数据。

从功能上来讲,爬虫一般分为数据采集,处理,储存三个部分。爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL,在抓取网页的过程中,不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。

3. HttpClient

网络爬虫就是用程序帮助我们访问网络上的资源,我们一直以来都是使用HTTP协议访问互联网的网页,网络爬虫需要编写程序,在这里使用同样的HTTP协议访问网页。
这里我们使用Java的HTTP协议客户端 HttpClient这个技术,来实现抓取网页数据。

3.1. GET请求

访问百度官网,请求url地址:
http://www.baidu.com/

public class HttpGetTest {
    public static void main(String[] args) {
        //1.创建一个新的HTTPClient对象(打开浏览器)
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //2.创建一个HttpGet对象(输入网址)
        HttpGet httpGet = new HttpGet("http://www.baidu.com");
        //3.执行发起http请求
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpGet);
            //4.得到响应的结果,判断响应的状态码是否为200,如果是的话就打印响应的数据
            if(response.getStatusLine().getStatusCode() == 200){
                String content = EntityUtils.toString(response.getEntity(), "utf8");
                System.out.println(content.length());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

结果:
在这里插入图片描述

3.2带参数的GET请求

在传智中搜索学习视频,地址为:
http://yun.itheima.com/search?keys=Java

public class HttpGetParamTest {
    public static void main(String[] args) throws Exception {
        //1.创建一个新的HTTPClient对象(打开浏览器)
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //设置请求地址为:http://yun.itheima.com/search?keys=Java
        //创建URIBuilder
        URIBuilder builder = new URIBuilder("http://yun.itheima.com/search");
        //设置参数(多个参数的时候可以多次 .setParameter)
        builder.setParameter("keys","java");
        //2.创建一个HttpGet对象(输入网址)
        HttpGet httpGet = new HttpGet(builder.build());
        //3.执行发起http请求
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpGet);
            //4.得到响应的结果,判断响应的状态码是否为200,如果是的话就打印响应的数据
            if(response.getStatusLine().getStatusCode() == 200){
                String content = EntityUtils.toString(response.getEntity(), "utf8");
                System.out.println(content.length());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

结果:在这里插入图片描述

3.3POST请求

与上边的GET请求差别就在于 HttpPost 这里:

public class HttpPostTest {
    public static void main(String[] args) {
        //1.创建一个新的HTTPClient对象(打开浏览器)
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //2.创建一个HttpGet对象(输入网址)
        HttpPost httpPost = new HttpPost("http://www.itcast.cn");
        //3.执行发起http请求
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpPost);
            //4.得到响应的结果,判断响应的状态码是否为200,如果是的话就打印响应的数据
            if(response.getStatusLine().getStatusCode() == 200){
                String content = EntityUtils.toString(response.getEntity(), "utf8");
                System.out.println(content.length());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

3.4带参数的POST请求

public class HttpPostParamTest {
    public static void main(String[] args) throws Exception{
        //1.创建一个新的HTTPClient对象(打开浏览器)
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //2.创建一个HttpGet对象(输入网址)
        HttpPost httpPost = new HttpPost("http://yun.itheima.com/search");
        //声明List集合,封装表单中的参数
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        //设置请求地址为:http://yun.itheima.com/search?keys=Java
        params.add(new BasicNameValuePair("keys","Java"));
        //创建表单的entity对象
        UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(params,"utf8");
        //设置表单的Entity对象到Post请求中
        httpPost.setEntity(formEntity);

        //3.执行发起http请求
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpPost);
            //4.得到响应的结果,判断响应的状态码是否为200,如果是的话就打印响应的数据
            if(response.getStatusLine().getStatusCode() == 200){
                String content = EntityUtils.toString(response.getEntity(), "utf8");
                System.out.println(content.length());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

3.5连接池

如果每次请求都要创建HttpClient,会有频繁创建和销毁的问题,可以使用连接池来解决这个问题。
测试以下代码,并断点查看每次获取的HttpClient都是不一样的。

public class HttpClientPoolTest {

    //为避免浏览器对象(httpClient)反复关闭打开,创建一个连接池对象来管理
    public static void main(String[] args) {
    //创建连接池管理器
    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
    //设置最大连接数
    cm.setMaxTotal(100);
    //设置每个主机的最大连接数
    cm.setDefaultMaxPerRoute(10);
    //使用连接池管理器发起请求
    doGet(cm);
    doGet(cm);
    }

    private static void doGet(PoolingHttpClientConnectionManager cm) {
        //不是每次创建新的HttpClient,而是从连接池中获取HttpClient对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();

        HttpGet httpGet = new HttpGet("http://www.itcast.cn");

        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpGet);

            if (response.getStatusLine().getStatusCode() == 200) {
                String content = EntityUtils.toString(response.getEntity(), "utf8");

                System.out.println(content.length());
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //不能关闭HttpClient,由连接池管理HttpClient
                //httpClient.close();
            }
        }

    }

}

3.6 请求参数

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

public class HttpConfigTest {
    public static void main(String[] args)  {
        //创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //创建HttpGet对象,设置url访问地址
        HttpGet httpGet = new HttpGet("http://www.itcast.cn");
        //配置请求信息
        RequestConfig config = RequestConfig.custom().setConnectTimeout(1000)   //创建连接的最长时间,单位是毫秒
                .setConnectionRequestTimeout(500)   //设置获取连接的最长时间,单位是毫秒
                .setSocketTimeout(10*1000)      //设置数据传输的最长时间,单位是毫秒
                .build();
        //给请求设置请求信息
        httpGet.setConfig(config);

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

            //解析响应
            if (response.getStatusLine().getStatusCode() == 200) {
                String content = EntityUtils.toString(response.getEntity(), "utf8");
                System.out.println(content.length());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关闭response
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

4. Jsoup

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

4.1. jsoup介绍

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

jsoup的主要功能如下:

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

使用:
先添加jar包的坐标依赖(就在上边那个工程基础上添加):

<dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.11.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>

4.2 jsoup解析

4.2.1 解析url

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

public class JsoupTest {
    @Test
    public void testJsoupUrl() throws Exception{
        //解析url地址
        Document doc = Jsoup.parse(new URL("http://www.itcast.cn"), 1000);
        //获取html文档中title的内容
        Element title = doc.getElementsByTag("title").first();
        System.out.println(title.text());
    }

}

4.2.2解析字符串

先准备html文件:

<html>
 <head> 
  <title>白百度中心</title> 
 </head> 
 <body>
	<div class="city">
		<h3 id="city_bj">北京中心</h3>
		<fb:img src="/2018czgw/images/slogan.jpg" class="slogan"/>
		<div class="city_in">
			<div class="city_con" style="display: none;">
				<ul>
					<li id="test" class="class_a class_b">
						<a href="http://www.itcast.cn" target="_blank">
							<span class="s_name">北京</span>
						</a>
					</li>
					<li>
							<a href="http://sh.itcast.cn" target="_blank">
							<span class="s_name">上海</span>
						</a>
					</li>
					<li>
						<a href="http://gz.itcast.cn" target="_blank">
							<span abc="123" class="s_name">广州</span>
						</a>
					</li>
					<ul>
						<li>深圳</li>
					</ul>					
				</ul>
			</div>
		</div>
	</div>
 </body>
</html>

在JsoupTest文件中进行测试读取:

public void testJsoupString() throws Exception{
        //使用工具类读取文件,获取字符串
        String content = FileUtils.readFileToString(new File("C:\\Users\\24848\\Desktop\\text.html"), "utf8");
        //解释字符串
        Document document = Jsoup.parse(content);
        //获取title的内容
        Element title = document.getElementsByTag("title").first();
        System.out.println(title.text());
    }

4.2.3解析文件

准备的html文件和上边的是一样的。
测试:

public void testFile() throws Exception{
        //直接使用 jsoup 解释文件
        Document document = Jsoup.parse(new File("C:\\Users\\24848\\Desktop\\text.html"), "utf8");
        //获取title的内容
        Element title = document.getElementsByTag("title").first();
        System.out.println(title.text());
    }

4.2.4使用dom方式遍历文档

1)元素获取
  1. 根据id查询元素getElementById
  2. 根据标签获取元素getElementsByTag
  3. 根据class获取元素getElementsByClass
  4. 根据属性获取元素getElementsByAttribute
public void testDom() throws Exception{
        //直接使用 jsoup 解释文件
        Document document = Jsoup.parse(new File("C:\\Users\\24848\\Desktop\\text.html"), "utf8");
        //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();
        System.out.println(element.text());
    }
2)元素中获取数据
  1. 从元素中获取id
  2. 从元素中获取className
  3. 从元素中获取属性的值attr
  4. 从元素中获取所有属性attributes
  5. 从元素中获取文本内容text
public void testData() throws Exception {
        //解析文件,获取Document
        Document doc = Jsoup.parse(new File("C:\\Users\\tree\\Desktop\\test.html"), "utf8");

        //根据id获取元素
        Element element = doc.getElementById("test");

        String str = "";

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

        //2.	从元素中获取className
        str = element.className();
        //Set<String> classSet = element.classNames();
        //for (String s : classSet ) {
        //    System.out.println(s);
        //}

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

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

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

        //打印获取到的内容
        System.out.println("获取到的数据是:" + str);

    }
3)使用选择器语法查找元素

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]
public void testSelector() throws Exception {
        //解析html文件,获取Document对象
        Document doc = Jsoup.parse(new File("C:\\Users\\tree\\Desktop\\test.html"), "utf8");
        //tagname: 通过标签查找元素,比如:span
        Elements elements = doc.select("span");
        //for (Element element : elements) {
        //    System.out.println(element.text());
        //}
        //#id: 通过ID查找元素,比如:#city_bj
        //Element element = doc.select("#city_bj").first();
        //.class: 通过class名称查找元素,比如:.class_a
        //Element element = doc.select(".class_a").first();
        //[attribute]: 利用属性查找元素,比如:[abc]
        Element element = doc.select("[abc]").first();

        //[attr=value]: 利用属性值来查找元素,比如:[class=s_name]
        Elements elements1 = doc.select("[class=s_name]");
        for (Element element1 : elements1) {
            System.out.println(element1.text());
        }
        
        //打印结果
        System.out.println("获取到的结果是:" + element.text());
    }
4)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 > *: 查找某个父元素下所有直接子元素

public void testSelector2()throws Exception{
        //解析html文件,获取Document对象
        Document doc = Jsoup.parse(new File("C:\\Users\\24848\\Desktop\\text.html"), "utf8");

        //el#id: 元素+ID,比如: h3#city_bj
        Element element = doc.select("h3#city_bj").first();

        //el.class: 元素+class,比如: li.class_a
        element = doc.select("li.class_a").first();

        //el[attr]: 元素+属性名,比如: span[abc]
        element = doc.select("span[abc]").first();

        //任意组合: 比如:span[abc].s_name
        element = doc.select("span[abc].s_name").first();

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

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

        //parent > *: 查找某个父元素下所有直接子元素
        elements = doc.select(".city_con > ul > *");

        System.out.println("获取到的内容是:"+element.text());

        for (Element element1 : elements) {
            System.out.println("遍历的结果:"+element1.text());
        }
    }

5.案例综合

目标:抓取京东上的手机页面搜索出来的信息,分三步走:
1)使用HttpClient获取到页面的html文本信息;
2)使用Jsoup解释html文本信息;
3)将所获得的数据进行存储,放到数据库中。

详细步骤:

1. 需求分析

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

1.1. SPU和SKU

除了以上四个属性以外,我们发现上图中的苹果手机有四种产品,我们应该每一种都要抓取。那么这里就必须要了解spu和sku的概念

SPU = Standard Product Unit (标准产品单位)
SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。

例如上图中的苹果手机就是SPU,包括红色、深灰色、金色、银色

SKU=stock keeping unit(库存量单位)
SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。

例如上图中的苹果手机有几个款式,红色苹果手机,就是一个sku

2. 数据库表分析

根据需求分析,我们创建的表如下:

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='京东商品表';

3.创建maven工程,添加依赖

使用Spring Boot+Spring Data JPA和定时任务进行开发,需要创建Maven工程并添加以下依赖

3.1添加jar包依赖:

<?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>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
    </parent>
    <groupId>cn.itcast.crawler</groupId>
    <artifactId>itcast-crawler-jd</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!--SpringMVC-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--SpringData Jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!--MySQL连接包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- HttpClient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

        <!--Jsoup-->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.10.3</version>
        </dependency>

        <!--工具包-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>
</project>

3.2 添加配置文件依赖

#DB Configuration:
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/crawler
spring.datasource.username=root
spring.datasource.password=root

#JPA Configuration:
spring.jpa.database=MySQL
spring.jpa.show-sql=true 

4.编写pojo对象,dao,service

根据数据库表编写pojo:

@Entity
@Table(name = "jd_item")
public class Item implements Serializable {
    //主键
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    //标准产品单位(商品集合)
    private Long spu;
    //库存量单位(最小品类单元)
    private Long sku;
    //商品标题
    private String title;
    //商品价格
    private Double price;
    //商品图片
    private String pic;
    //商品详情地址
    private String url;
    //创建时间
    private Date created;
    //更新时间
    private Date updated;
	//省略了get/set,toString
}

dao:

public interface ItemDao extends JpaRepository<Item,Long> {
}

service:

public interface IItemService {
    /**
     * 保存商品
     */
    public void save(Item item);

    /**
     * 根据条件查询商品
     * @param item
     * @return
     */
    public List<Item> findAll(Item item);
}

service实现类:

@Service
public class ItemServiceImpl implements IItemService {

    @Autowired
    private ItemDao itemDao;


    @Override
    @Transactional
    public void save(Item item) {
        itemDao.save(item);
    }

    @Override
    public List<Item> findAll(Item item) {
        //声明查询条件
        Example<Item> example = Example.of(item);

        //根据查询条件进行查询数据
        List<Item> list = this.itemDao.findAll(example);

        return list;
    }
}

5.编写SpringBoot引导类:

@SpringBootApplication
//使用定时任务,需要先开启定时任务,需要添加注解
@EnableScheduling
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

6.编写请求数据类工具:

package com.xxx.util;

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.stereotype.Component;

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

@Component
public class HttpUtils {

    private PoolingHttpClientConnectionManager cm;

    public HttpUtils() {
        this.cm = new PoolingHttpClientConnectionManager();

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

        //设置每个主机的最大连接数
        this.cm.setDefaultMaxPerRoute(10);
    }

    /**
     * 根据请求地址下载页面数据
     *
     * @param url
     * @return 页面数据
     */
    public String doGetHtml(String url) {
        //获取HttpClient对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(this.cm).build();

        //创建httpGet请求对象,设置url地址
        HttpGet httpGet = new HttpGet(url);

        //设置请求信息
        httpGet.setConfig(this.getConfig());

        CloseableHttpResponse response = null;


        try {
            //使用HttpClient发起请求,获取响应
            response = httpClient.execute(httpGet);//发起响应的时候京东会进行拦截,直接返回一个登录页面给你···

            //解析响应,返回结果
            if (response.getStatusLine().getStatusCode() == 200) {
                //判断响应体Entity是否不为空,如果不为空就可以使用EntityUtils
                if (response.getEntity() != null) {
                    String content = EntityUtils.toString(response.getEntity(), "utf8");
                    return content;
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭response
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //返回空串
        return "";
    }


    /**
     * 下载图片
     *
     * @param url
     * @return 图片名称
     */
    public String doGetImage(String url) {
        //获取HttpClient对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(this.cm).build();

        //创建httpGet请求对象,设置url地址
        HttpGet httpGet = new HttpGet(url);

        //设置请求信息
        httpGet.setConfig(this.getConfig());

        CloseableHttpResponse response = null;


        try {
            //使用HttpClient发起请求,获取响应
            response = httpClient.execute(httpGet);

            //解析响应,返回结果
            if (response.getStatusLine().getStatusCode() == 200) {
                //判断响应体Entity是否不为空
                if (response.getEntity() != null) {
                    //下载图片
                    //获取图片的后缀
                    String extName = url.substring(url.lastIndexOf("."));

                    //创建图片名,重命名图片
                    String picName = UUID.randomUUID().toString() + extName;

                    //下载图片
                    //声明OutPutStream
                    OutputStream outputStream = new FileOutputStream(new File("C:\\Users\\24848\\Desktop\\images\\" +
                            picName));
                    response.getEntity().writeTo(outputStream);

                    //返回图片名称
                    return picName;
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭response
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //如果下载失败,返回空串
        return "";
    }

    //设置请求信息
    private RequestConfig getConfig() {
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(1000)    //创建连接的最长时间
                .setConnectionRequestTimeout(500)  // 获取连接的最长时间
                .setSocketTimeout(10000)    //数据传输的最长时间
                .build();

        return config;
    }
}

7.正式编写爬虫代码:

package com.xxx.task;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xxx.pojo.Item;
import com.xxx.service.IItemService;
import com.xxx.util.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.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;

/**
 * 
 * 在这实现定时获取最新数据的操作
 **/
@Component
public class ItemTask {

    @Autowired
    private HttpUtils httpUtils;
    @Autowired
    private IItemService itemService;

    private static final ObjectMapper MAPPER =  new ObjectMapper();


    //当下载任务完成后,间隔多长时间进行下一次的任务。
    @Scheduled(fixedDelay = 100 * 1000)
    public void itemTask() throws Exception {
        //声明需要解析的初始地址
        //京东的
        String url = "https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=" +
                "%E6%89%8B%E6%9C%BA&cid2=653&cid3=655&s=113&click=0&page=";
        //唯品会的
//        String url = "https://category.vip.com/suggest.php?keyword=手机" +
//                "&count=50&suggestType=brand&page=";
//        String lastStr = "#catPerPos";
        //按照页面对手机的搜索结果进行遍历解析
        for (int i = 1; i < 6; i = i + 2) {
            String html = httpUtils.doGetHtml(url + i);

            //解析页面,获取商品数据并存储
            this.parse(html);
        }


        System.out.println("手机数据抓取完成!");


    }

    //解析页面,获取商品数据并存储(京东的)
    private void parse(String html) throws Exception {
        //解析html获取Document
        Document doc = Jsoup.parse(html);

        //获取spu信息
        Elements spuEles = doc.select("div#J_goodsList > ul > li");

        if(spuEles.size() < 1){
            System.out.println("抓取不到数据!!!");
        }

        for (Element spuEle : spuEles) {
            //获取spu
            long spu = Long.parseLong(spuEle.attr("data-spu"));

            //获取sku信息
            Elements skuEles = spuEle.select("li.ps-item");

            for (Element skuEle : skuEles) {
                //获取sku
                long sku = Long.parseLong(skuEle.select("[data-sku]").attr("data-sku"));

                //根据sku查询商品数据
                Item item = new Item();
                item.setSku(sku);
                List<Item> list = this.itemService.findAll(item);

                if(list.size()>0) {
                    //如果商品存在,就进行下一个循环,该商品不保存,因为已存在
                    continue;
                }

                //设置商品的spu
                item.setSpu(spu);

                //获取商品的详情的url
                String itemUrl = "https://item.jd.com/" + sku + ".html";
                item.setUrl(itemUrl);


                //获取商品的图片
                String picUrl ="https:"+ skuEle.select("img[data-sku]").first().attr("data-lazy-img");
                picUrl = picUrl.replace("/n9/","/n1/");
                String picName = this.httpUtils.doGetImage(picUrl);
                item.setPic(picName);

                //获取商品的价格
                String priceJson = this.httpUtils.doGetHtml("https://p.3.cn/prices/mgets?skuIds=J_" + sku);
                double price = MAPPER.readTree(priceJson).get(0).get("p").asDouble();
                item.setPrice(price);


                //获取商品的标题
                String itemInfo = this.httpUtils.doGetHtml(item.getUrl());
                String title = Jsoup.parse(itemInfo).select("div.sku-name").text();
                item.setTitle(title);


                item.setCreated(new Date());
                item.setUpdated(item.getCreated());

                //保存商品数据到数据库中
                this.itemService.save(item);

            }
        }
    }


    //解释唯品会的数据
    private void parse2(String html) throws Exception {
        //解析html获取Document
        Document doc = Jsoup.parse(html);

        //获取spu信息
        Elements spuEles = doc.select("div.goods-list-item,.c-goods,.J_pro_items");

        if(spuEles.size() < 1){
            System.out.println("抓取不到数据!!!");
        }

        for (Element spuEle : spuEles) {
            //获取spu
            long spu = Long.parseLong(spuEle.attr("data-id"));

            //获取sku
            long sku = Long.parseLong(spuEle.attr("data-brand"));

            //根据sku查询商品数据
            Item item = new Item();
            item.setSku(sku);
            List<Item> list = this.itemService.findAll(item);

            if(list.size()>0) {
                //如果商品存在,就进行下一个循环,该商品不保存,因为已存在
                continue;
            }

            //设置商品的spu
            item.setSpu(spu);

            //获取商品的详情的url
//            String itemUrl = "https://item.jd.com/" + sku + ".html";
            //https://detail.vip.com/detail-100029428-994987853489684.html
            String itemUrl = "https://detail.vip.com/detail-" + sku + "-"+spu+".html";
            item.setUrl(itemUrl);


            //获取商品的图片
//            String picUrl ="https:"+ spuEle.select("img[data-sku]").first().attr("data-lazy-img");
//            picUrl = picUrl.replace("/n9/","/n1/");
//            String picName = this.httpUtils.doGetImage(picUrl);
//            item.setPic(picName);

            //获取商品的价格
            String priceJson = this.httpUtils.doGetHtml(item.getUrl());
            double price = MAPPER.readTree(priceJson).get(0).get(".J-price").asDouble();
            item.setPrice(price);


            //获取商品的标题
            String itemInfo = this.httpUtils.doGetHtml(item.getUrl());
            String title = Jsoup.parse(itemInfo).select("div.M-productInfo>.product-content-inner" +
                    ".pi-title-box+.pib-title>.pib-title-detail").text();
            item.setTitle(title);


            item.setCreated(new Date());
            item.setUpdated(item.getCreated());

            //保存商品数据到数据库中
            this.itemService.save(item);


        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值