基於SpringBoot的Java爬蟲-京東商品頁

本文是在學習java爬蟲時,跟着視頻做的一個小項目,本文會着重介紹其中會遇到的問題及重要代碼實現。

一. 爬取(部分)效果圖

具體信息:在這裏插入圖片描述
爬取圖片:在這裏插入圖片描述

二.遇到的各種BUG

在這裏插入圖片描述
導入到數據庫中的中文文字符亂碼:

解決方法:在數據庫名字後面加上:

?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=utf-8&nullCatalogMeansCurrent=true

在這裏插入圖片描述

在做debug測試時出現:
在這裏插入圖片描述
解決方法:
觀察項目的jdk版本和congratulation中的jdk版本是否一致
在這裏插入圖片描述
在這裏插入圖片描述
在導入依賴時,Sping的包版本要一致,不然會出現很多問題
在這裏插入圖片描述
數據庫版本不宜太高:
版本6以下可以用用5.1.8版本

在這裏插入圖片描述
高版本的話還要在application.properties中修改成com.mysql.cj.jdbc.Driver
在這裏插入圖片描述

在請求京東頁面時,最起碼要加個請求頭,不然也太看不起爬蟲了…

httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36");
最新的User-Agent去京東上看看

三. 項目目錄結構

在這裏插入圖片描述

四. 具體代碼詳解

4.1 配置文件添加依賴

在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>cn.itcast</groupId>
    <artifactId>itcast-crawler-jd</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
    <!--SpringMVC-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.2.2.RELEASE</version>
    </dependency>

    <!--SpringData Jpa-->
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        <version>2.2.2.RELEASE</version>
    </dependency>

    <!--MySQL連接包-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.8</version>
    </dependency>

    <!-- HttpClient -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.2</version>
    </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>
        <version>3.4</version>
    </dependency>
</dependencies>

</project>

4.2 application.properties文件

#DB Configuration:
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/day17?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=utf-8&nullCatalogMeansCurrent=true
spring.datasource.username=數據庫用戶名稱
spring.datasource.password=數據庫密碼

#JPA Configuration:
spring.jpa.database=MySQL
spring.jpa.show-sql=true
spring.jpa.open-in-view=false

4.3 dao目錄下ItemDao類

這個文件就是對數據庫進行操作的信息,具體的可以查看JpaRepository類源碼。
JpaRepository <Item,Long>中的Item就是需要存儲數據所放的參數,在這裏非常的方便,只需要放入一個對象就可以存入數據庫。

package cn.itcast.jd.dao;

import cn.itcast.jd.pojo.Item;
import org.springframework.data.jpa.repository.JpaRepository;

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

4.4 jd.pojo目錄下Item類

這個類其實就是用於存放在數據庫中

package cn.itcast.jd.pojo;

import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name = "jd_item")
public class Item {
    //主鍵
    @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;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getSpu() {
        return spu;
    }

    public void setSpu(Long spu) {
        this.spu = spu;
    }

    public Long getSku() {
        return sku;
    }

    public void setSku(Long sku) {
        this.sku = sku;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public String getPic() {
        return pic;
    }

    public void setPic(String pic) {
        this.pic = pic;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }
}

4.5 service目錄下的ItemServiceImpl和ItemService

ItemService接口:這裏定義了接口用於對數據庫進行操作。

package cn.itcast.jd.service;

import cn.itcast.jd.pojo.Item;

import java.util.List;

public interface ItemService {

    /**
     * 保存商品
     * @param item
     */
    public void save(Item item);

    /**
     * 根據條件查詢商品
     * @param item
     * @return
     */
    public List<Item> findAll(Item item);
}

ItemServiceImpl類:接口的實現類,這個類其實就是利用了ItemDao這個類的方法。

package cn.itcast.jd.service.impl;

import cn.itcast.jd.dao.ItemDao;
import cn.itcast.jd.pojo.Item;
import cn.itcast.jd.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class ItemServiceImpl implements ItemService {

    @Autowired
    private ItemDao itemDao;

    @Override
    @Transactional
    public void save(Item item) {
        this.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;
    }
}

4.6 工具類:HttpUtils

這個類包含了解析HTML和下載圖片的方法

package cn.itcast.jd.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.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36");
        //設置請求信息
        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.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36");
        //設置請求信息
        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\\張振東\\Pictures\\爬蟲\\" + 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;
    }
}

4.7 ItemTask類

實現爬取功能的主要類:

package cn.itcast.jd.task;

import cn.itcast.jd.pojo.Item;
import cn.itcast.jd.service.ItemService;
import cn.itcast.jd.util.HttpUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
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 ItemService 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=";

      //按照頁面對手機的搜索結果進行遍歷解析
      for (int i = 1; i < 10; 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");

      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/");
            if (picUrl.equals("https:")){
                break;
                }
            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);

         }
      }
   }

}

END!!! 長路漫漫,JAVA爲伴!!!
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章