项目结构图

配置信息
-
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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> </parent> <groupId>com.xiaoge</groupId> <artifactId>xiaoge-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> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> </project> -
application.properties
# 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=123456 # JPA Configuration spring.jpa.database=mysql spring.jpa.show-sql=true
启动类
-
ItemApplication
package com.xiaoge.jd; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; /** * @Author: 潇哥 * @DateTime: 2020/9/29 下午9:07 * @Description: TODO */ @SpringBootApplication // 使用定时任务, 需要先开启定时任务, 需要添加注解 @EnableScheduling public class ItemApplication { public static void main(String[] args) { SpringApplication.run(ItemApplication.class); } }
实体类
-
Item
package com.xiaoge.jd.pojo; import lombok.Data; import javax.persistence.*; import java.util.Date; /** * @Author: 潇哥 * @DateTime: 2020/9/29 下午8:39 * @Description: TODO */ @Data @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; }
持久层
-
ItemDao
package com.xiaoge.jd.dao; import com.xiaoge.jd.pojo.Item; import org.springframework.data.jpa.repository.JpaRepository; /** * @Author: 潇哥 * @DateTime: 2020/9/29 下午8:53 * @Description: TODO */ // JpaRepository泛型 第一个泛型为实体类名称, 第二个泛型为id类型 public interface ItemDao extends JpaRepository<Item, Long> { }
业务层
-
ItemService
package com.xiaoge.jd.service; import com.xiaoge.jd.pojo.Item; import java.util.List; /** * @Author: 潇哥 * @DateTime: 2020/9/29 下午8:56 * @Description: TODO */ public interface ItemService { /** * 保存商品 */ public void save(Item item); /** * 根据条件查询商品 * @param item * @return */ public List<Item> findAll(Item item); } -
ItemServiceImpl
package com.xiaoge.jd.service.impl; import com.xiaoge.jd.dao.ItemDao; import com.xiaoge.jd.pojo.Item; import com.xiaoge.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; /** * @Author: 潇哥 * @DateTime: 2020/9/29 下午8:57 * @Description: TODO */ @Service("itemService") public class ItemServiceImpl implements ItemService { @Autowired private ItemDao itemDao; /** * 保存商品 */ @Transactional @Override public void save(Item item) { itemDao.save(item); } /** * 根据条件查询商品 * @param item * @return */ @Override public List<Item> findAll(Item item) { // 声明查询条件 Example<Item> example = Example.of(item); // 根据查询条件进行查询数据 List<Item> list = itemDao.findAll(example); return list; } }
工具类
-
HttpUtils
package com.xiaoge.jd.util; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; 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; /** * @Author: 潇哥 * @DateTime: 2020/9/29 下午10:00 * @Description: TODO */ @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(); // 创建get请求, 访问url地址 HttpGet httpGet = new HttpGet(url); // 添加头信息, 让京东知道你是普通用户, 用电脑设备访问的, 而不是用程序访问的 httpGet.addHeader( "user-agent", "Mozilla/5.0" ); // 设置请求信息 httpGet.setConfig(this.config()); 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 { 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.addHeader( "user-agent", "Mozilla/5.0" ); // 设置请求信息 httpGet.setConfig(this.config()); CloseableHttpResponse response = null; try { // 使用HttpClient发请请求, 获取响应 response = httpClient.execute(httpGet); // 解析响应, 返回结果 if (response.getStatusLine().getStatusCode() == 200) { // 判断响应体Entity是否不为空, 如果不为空可以使用EntityUtils if (response.getEntity() != null) { // 获取图片后缀 String extName = StringUtils.substringAfterLast(url, "."); // 创建图片, 重命名图片 String picName = UUID.randomUUID().toString().concat(".").concat(extName); // 声明OutputStream OutputStream outputStream = new FileOutputStream(new File("/Users/xiaoge/Desktop/images/" + picName)); // 把响应体内容写入到这个流中 response.getEntity().writeTo(outputStream); // 返回图片名 return picName; } } } catch (IOException e) { e.printStackTrace(); } finally { if (response != null) { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } } // 响应体为空, 返回空字符串 return ""; } /** * 设置请求信息 * @return */ private RequestConfig config() { RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(1000) // 创建连接的最长时间 .setConnectionRequestTimeout(500) // 获取链接的最长时间 .setSocketTimeout(10000) // 数据传输的最长时间 .build(); return requestConfig; } }
定时任务
-
ItemTask
package com.xiaoge.jd.task; import com.fasterxml.jackson.databind.ObjectMapper; import com.xiaoge.jd.pojo.Item; import com.xiaoge.jd.service.ItemService; import com.xiaoge.jd.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 org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.Date; import java.util.List; /** * @Author: 潇哥 * @DateTime: 2020/9/29 下午10:37 * @Description: TODO */ @Component public class ItemTask { @Autowired private HttpUtils httpUtils; @Autowired private ItemService itemService; private static final ObjectMapper MAPPER = new ObjectMapper(); // 当下载任务完成后, 间隔多长时间进行下一次的任务. 这里 单位/毫秒 100*1000=100秒 @Scheduled(fixedDelay = 100 * 1000) public void itemTask() throws Exception { String url = "https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&wq=%E6%89%8B%E6%9C%BA&s=51&click=0&page="; // 按照页面对手机的搜索结果进行遍历解析 for(int i = 1; i < 10; i += 2) { String html = this.httpUtils.doGetHtml(url + i); // 解析页面, 获取商品数据并保存 this.parse(html); } System.out.println("页面爬取完成!"); } // 解析页面, 获取商品数据并保存 public void parse(String html) throws Exception { // 获取document对象 Document document = Jsoup.parse(html); // 获取spu信息 Elements spuEles = document.select("div#J_goodsList > ul > li"); for (Element spuEle : spuEles) { // 获取spu String spuStr = spuEle.attr("data-spu"); long spu = StringUtils.isEmpty(spuStr) == true ? 0: Long.parseLong(spuStr); // 获取sku信息 Elements skuEles = spuEle.select("li.ps-item"); for (Element skuEle : skuEles) { // 获取sku long sku = Long.parseLong(skuEle.select("img[data-sku]").attr("data-sku")); // 根据sku查询商品数据 Item item = new Item(); item.setSku(sku); // 查询数据库是否有该商品, 有跳过, 没有保存进数据库 List<Item> itemList = this.itemService.findAll(item); if(!CollectionUtils.isEmpty(itemList)) { continue; } // 设置商品的spu item.setSpu(spu); // 获取商品的详细链接 String itemUrl = "https://item.jd.com/"+ sku +".html"; item.setUrl(itemUrl); // 获取商品图片 String picUrl = "https:" + skuEle.select("img[data-sku]").attr("data-lazy-img"); picUrl = picUrl.replace("/n7/", "/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()); Document itemDocument = Jsoup.parse(itemInfo); String title = itemDocument.select("div.sku-name").first().text(); item.setTitle(title); // 商品创建时间 item.setCreated(new Date()); // 商品更新时间 item.setUpdated(item.getCreated()); // 保存进数据库 this.itemService.save(item); } } } }
浙公网安备 33010602011771号