java爬虫案例——SpringBoot使用HttpClient、Jsoup爬取京东手机数据

清疚 2022-12-15 14:11 619阅读 0赞

文章目录

  • 前言
  • 一、准备工作
  • 二、项目文件
    • 1.项目依赖
    • 2.项目配置文件
    • 3.pojo
    • 4.dao接口
    • 5.service接口及其实现类
    • 6.HttpClient封装工具类
    • 7.爬取任务实现
    • 8.启动类
  • 三、项目执行效果
  • 总结

前言

之前同事分享了一些关于Java爬虫的视频,其中有一个是用HttpClient及Jsoup爬取京东上的一些手机数据(如图片、标题、sku、spu等),同时参考几篇博客后基本实现目标,在此篇做个简单记录。


一、准备工作

由于需要将爬取到的数据的数据存储到数据库表中,因此需要建库建表。建库建表SQL如下:

  1. DROP DATABASE IF EXISTS `crawler`;
  2. CREATE DATABASE IF NOT EXISTS `crawler` DEFAULT CHARSET = `utf8`;
  3. USE `crawler`;
  4. SET FOREIGN_KEY_CHECKS = 0;
  5. DROP TABLE IF EXISTS `jd_item`;
  6. CREATE TABLE `jd_item` (
  7. `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  8. `spu` bigint(15) DEFAULT NULL COMMENT '商品集合id',
  9. `sku` bigint(15) DEFAULT NULL COMMENT '商品最小品类单元id',
  10. `title` varchar(100) DEFAULT NULL COMMENT '商品标题',
  11. `price` bigint(10) DEFAULT NULL COMMENT '商品价格',
  12. `pic` varchar(200) DEFAULT NULL COMMENT '商品图片',
  13. `url` varchar(200) DEFAULT NULL COMMENT '商品详情地址',
  14. `created` datetime DEFAULT NULL COMMENT '创建时间',
  15. `updated` datetime DEFAULT NULL COMMENT '更新时间',
  16. PRIMARY KEY (`id`),
  17. KEY `sku` (`sku`) USING BTREE
  18. ) ENGINE = InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET = utf8 COMMENT = '京东商品表';
  • 项目目录
    在这里插入图片描述

二、项目文件

1.项目依赖

pom.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <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">
  3. <modelVersion>4.0.0</modelVersion>
  4. <parent>
  5. <artifactId>spring-boot-starter-parent</artifactId>
  6. <groupId>org.springframework.boot</groupId>
  7. <version>2.3.4.RELEASE</version>
  8. </parent>
  9. <groupId>cn.mlnt</groupId>
  10. <artifactId>mlnt-crawler-jd</artifactId>
  11. <version>1.0-SNAPSHOT</version>
  12. <dependencies>
  13. <!--SpringMVC-->
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-web</artifactId>
  17. </dependency>
  18. <!--SpringData Jpa-->
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-data-jpa</artifactId>
  22. </dependency>
  23. <!--MySQL连接包-->
  24. <dependency>
  25. <groupId>mysql</groupId>
  26. <artifactId>mysql-connector-java</artifactId>
  27. <version>8.0.21</version>
  28. </dependency>
  29. <!--HttpClient-->
  30. <dependency>
  31. <groupId>org.apache.httpcomponents</groupId>
  32. <artifactId>httpclient</artifactId>
  33. </dependency>
  34. <!--Jsoup-->
  35. <dependency>
  36. <groupId>org.jsoup</groupId>
  37. <artifactId>jsoup</artifactId>
  38. <version>1.13.1</version>
  39. </dependency>
  40. <!--工具包-->
  41. <dependency>
  42. <groupId>org.apache.commons</groupId>
  43. <artifactId>commons-lang3</artifactId>
  44. </dependency>
  45. </dependencies>
  46. </project>

2.项目配置文件

application.properties(或使用.yml):

  1. #DB Configuration:
  2. spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
  3. spring.datasource.url=jdbc:mysql://127.0.0.1:3306/crawler?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
  4. spring.datasource.username=root
  5. spring.datasource.password=123456
  6. #JPA Configuration:
  7. spring.jpa.database=MySQL
  8. spring.jpa.show-sql=true

3.pojo

Item.java:

  1. package cn.mlnt.jd.pojo;
  2. import javax.persistence.*;
  3. import java.util.Date;
  4. @Entity
  5. @Table(name="jd_item")
  6. public class Item {
  7. // 主键
  8. @Id
  9. @GeneratedValue(strategy = GenerationType.IDENTITY)
  10. private Long id;
  11. // 标准产品单位(商品集合)
  12. private Long spu;
  13. // 库存量单位(最小品类单元)
  14. private Long sku;
  15. // 商品标题
  16. private String title;
  17. // 商品价格
  18. private Double price;
  19. // 商品图片
  20. private String pic;
  21. // 商品详情地址
  22. private String url;
  23. // 创建时间
  24. private Date created;
  25. // 更新时间
  26. private Date updated;
  27. public Long getId() {
  28. return id;
  29. }
  30. public void setId(Long id) {
  31. this.id = id;
  32. }
  33. public Long getSpu() {
  34. return spu;
  35. }
  36. public void setSpu(Long spu) {
  37. this.spu = spu;
  38. }
  39. public Long getSku() {
  40. return sku;
  41. }
  42. public void setSku(Long sku) {
  43. this.sku = sku;
  44. }
  45. public String getTitle() {
  46. return title;
  47. }
  48. public void setTitle(String title) {
  49. this.title = title;
  50. }
  51. public Double getPrice() {
  52. return price;
  53. }
  54. public void setPrice(Double price) {
  55. this.price = price;
  56. }
  57. public String getPic() {
  58. return pic;
  59. }
  60. public void setPic(String pic) {
  61. this.pic = pic;
  62. }
  63. public String getUrl() {
  64. return url;
  65. }
  66. public void setUrl(String url) {
  67. this.url = url;
  68. }
  69. public Date getCreated() {
  70. return created;
  71. }
  72. public void setCreated(Date created) {
  73. this.created = created;
  74. }
  75. public Date getUpdated() {
  76. return updated;
  77. }
  78. public void setUpdated(Date updated) {
  79. this.updated = updated;
  80. }
  81. }

4.dao接口

ItemDao.java:

  1. package cn.mlnt.jd.dao;
  2. import cn.mlnt.jd.pojo.Item;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. public interface ItemDao extends JpaRepository<Item, Long> {
  5. }

5.service接口及其实现类

ItemService.java:

  1. package cn.mlnt.jd.service;
  2. import cn.mlnt.jd.pojo.Item;
  3. import java.util.List;
  4. public interface ItemService {
  5. /** * 保存商品 * @param item */
  6. public void save(Item item);
  7. /** * 根据条件查询商品 * @param item * @return */
  8. public List<Item> findAll(Item item);
  9. }

ItemServiceImpl.java:

  1. package cn.mlnt.jd.service.impl;
  2. import cn.mlnt.jd.dao.ItemDao;
  3. import cn.mlnt.jd.pojo.Item;
  4. import cn.mlnt.jd.service.ItemService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.data.domain.Example;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. import java.util.List;
  10. @Service
  11. public class ItemServiceImpl implements ItemService {
  12. @Autowired
  13. private ItemDao itemDao;
  14. @Override
  15. @Transactional
  16. public void save(Item item) {
  17. this.itemDao.save(item);
  18. }
  19. @Override
  20. public List<Item> findAll(Item item) {
  21. // 声明查询条件
  22. Example<Item> example = Example.of(item);
  23. // 根据查询条件进行查询数据
  24. List<Item> list = this.itemDao.findAll(example);
  25. return list;
  26. }
  27. }

6.HttpClient封装工具类

HttpUtils.java:

  1. package cn.mlnt.jd.util;
  2. import org.apache.http.client.config.RequestConfig;
  3. import org.apache.http.client.methods.CloseableHttpResponse;
  4. import org.apache.http.client.methods.HttpGet;
  5. import org.apache.http.impl.client.CloseableHttpClient;
  6. import org.apache.http.impl.client.HttpClients;
  7. import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
  8. import org.apache.http.util.EntityUtils;
  9. import org.jsoup.Jsoup;
  10. import org.jsoup.nodes.Document;
  11. import org.springframework.stereotype.Component;
  12. import java.io.File;
  13. import java.io.FileOutputStream;
  14. import java.io.IOException;
  15. import java.io.OutputStream;
  16. import java.util.UUID;
  17. @Component
  18. public class HttpUtils {
  19. private PoolingHttpClientConnectionManager cm;
  20. public HttpUtils() {
  21. this.cm = new PoolingHttpClientConnectionManager();
  22. // 设置最大连接数
  23. this.cm.setMaxTotal(100);
  24. // 设置每个主机的最大连接数
  25. this.cm.setDefaultMaxPerRoute(10);
  26. }
  27. /** * 根据请求地址下载页面数据 * @param url * @return 页面数据 */
  28. public String doGetHtml(String url) {
  29. // 获取HttpClient对象
  30. CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(this.cm).build();
  31. // 创建httpGet对象,设置url地址
  32. HttpGet httpGet = new HttpGet(url);
  33. // 设置请求信息
  34. httpGet.setConfig(this.getConfig());
  35. // 设置请求Request Headers中的User-Agent,浏览器访问
  36. httpGet.addHeader("User-Agent","Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36");
  37. CloseableHttpResponse response = null;
  38. try {
  39. // 使用HttpClient发起请求,获取响应
  40. response = httpClient.execute(httpGet);
  41. // 解析响应,返回结果
  42. if(response.getStatusLine().getStatusCode() == 200) {
  43. String content = "";
  44. // 判断响应体Entity是否不为空,如果不为空就可以使用EntityUtils
  45. if(response.getEntity() != null) {
  46. content = EntityUtils.toString(response.getEntity(), "utf8");
  47. return content;
  48. }
  49. }
  50. } catch (IOException e) {
  51. e.printStackTrace();
  52. } finally {
  53. // 关闭response
  54. if(response != null) {
  55. try {
  56. response.close();
  57. } catch (IOException e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. }
  62. // 返回空字符串
  63. return "";
  64. }
  65. /** * 下载图片 * @param url * @return 图片名称 */
  66. public String doGetImage(String url) {
  67. // 获取HttpClient对象
  68. CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(this.cm).build();
  69. // 创建httpGet对象,设置url地址
  70. HttpGet httpGet = new HttpGet(url);
  71. // 设置请求信息
  72. httpGet.setConfig(this.getConfig());
  73. CloseableHttpResponse response = null;
  74. try {
  75. // 使用HttpClient发起请求,获取响应
  76. response = httpClient.execute(httpGet);
  77. // 解析响应,返回结果
  78. if(response.getStatusLine().getStatusCode() == 200) {
  79. // 判断响应体Entity是否不为空
  80. if(response.getEntity() != null) {
  81. // 下载图片
  82. // 获取图片的后缀
  83. String extName = url.substring(url.lastIndexOf("."));
  84. // 创建图片名,重命名图片
  85. String picName = UUID.randomUUID().toString()+extName;
  86. // 下载图片
  87. // 声明OutPutStream,下载图片存储路径
  88. OutputStream outputStream = new FileOutputStream(new File("E:\\images\\"+picName));
  89. response.getEntity().writeTo(outputStream);
  90. // 返回图片名称
  91. return picName;
  92. }
  93. }
  94. } catch (IOException e) {
  95. e.printStackTrace();
  96. } finally {
  97. // 关闭response
  98. if(response != null) {
  99. try {
  100. response.close();
  101. } catch (IOException e) {
  102. e.printStackTrace();
  103. }
  104. }
  105. }
  106. // 如果下载失败,返回空字符串
  107. return "";
  108. }
  109. /** * 设置请求信息 * @return */
  110. private RequestConfig getConfig() {
  111. RequestConfig config = RequestConfig.custom()
  112. // 创建链接的最长时间
  113. .setConnectTimeout(1000)
  114. // 获取连接到最长时间
  115. .setConnectionRequestTimeout(500)
  116. // 数据传输的最长时间
  117. .setSocketTimeout(10000)
  118. .build();
  119. return config;
  120. }
  121. public static void main(String[] args) throws IOException {
  122. HttpUtils httpUtils = new HttpUtils();
  123. String itemInfo = httpUtils.doGetHtml("https://item.jd.com/100009082466.html");
  124. String title = Jsoup.parse(itemInfo).select("div#itemName").text();
  125. System.out.println(Jsoup.parse(itemInfo).select("div#itemName"));
  126. System.out.println(title);
  127. }
  128. }

7.爬取任务实现

ItemTask.java:

  1. package cn.mlnt.jd.task;
  2. import cn.mlnt.jd.pojo.Item;
  3. import cn.mlnt.jd.service.ItemService;
  4. import cn.mlnt.jd.util.HttpUtils;
  5. import com.fasterxml.jackson.databind.ObjectMapper;
  6. import org.jsoup.Jsoup;
  7. import org.jsoup.nodes.Document;
  8. import org.jsoup.nodes.Element;
  9. import org.jsoup.select.Elements;
  10. import org.springframework.scheduling.annotation.Scheduled;
  11. import org.springframework.stereotype.Component;
  12. import javax.annotation.Resource;
  13. import java.util.Date;
  14. import java.util.List;
  15. @Component
  16. public class ItemTask {
  17. @Resource
  18. private HttpUtils httpUtils;
  19. @Resource
  20. private ItemService itemService;
  21. private static final ObjectMapper MAPPER = new ObjectMapper();
  22. /** * 当下载任务完成后,间隔多长时间进行下一次任务 * @throws Exception */
  23. @Scheduled(fixedDelay = 100*1000)
  24. public void itemTask() throws Exception {
  25. // 声明需要解析的初始地址
  26. String url = "https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&enc=utf-8&pvid=1e449f956a3b49319117b81bbde91f3c";
  27. // 按照页面对手机的搜索结果进行遍历解析
  28. for (int i = 1; i < 10; i=i+2) {
  29. String html = httpUtils.doGetHtml(url + i);
  30. // 解析页面,获取商品数据并存储
  31. if (html != null) {
  32. this.parse(html);
  33. }
  34. }
  35. System.out.println("手机数据抓取完成!");
  36. }
  37. /** * 解析页面,获取商品数据并存储 * @param html * @throws Exception */
  38. private void parse(String html) throws Exception {
  39. // 解析html获取Document对象
  40. Document document = Jsoup.parse(html);
  41. // 获取spu
  42. Elements spuEles = document.select("div#J_goodsList > ul > li");
  43. for (Element spuEle : spuEles) {
  44. // 获取spu
  45. String attr = spuEle.attr("data-spu");
  46. long spu = Long.parseLong(attr.equals("") ? "0" : attr);
  47. // 获取sku
  48. Elements skuEles = spuEle.select("li.ps-item");
  49. for (Element skuELe : skuEles) {
  50. // 获取sku
  51. long sku = Long.parseLong(skuELe.select("[data-sku]").attr("data-sku"));
  52. // 根据sku查询商品数据
  53. Item item = new Item();
  54. item.setSku(sku);
  55. List<Item> list = this.itemService.findAll(item);
  56. if(list.size() > 0) {
  57. // 如果商品存在,就进行下一个循环,该商品不保存,因为已存在
  58. continue;
  59. }
  60. // 设置商品的spu
  61. item.setSpu(spu);
  62. // 获取商品详情的url
  63. String itemUrl = "https://item.jd.com/" + sku + ".html";
  64. item.setUrl(itemUrl);
  65. // 获取商品的图片
  66. String picUrl = "https:" + skuELe.select("img[data-sku]").first().attr("data-lazy-img");
  67. //图片路径可能会为空的情况:一下为两种解决方式,第一种会让数据不全,第二种任会报错
  68. if(picUrl.equals("https:")){
  69. break;
  70. }
  71. picUrl = picUrl.replace("/n9/", "/n1/");
  72. String picName = this.httpUtils.doGetImage(picUrl);
  73. item.setPic(picName);
  74. // 获取商品的价格
  75. String priceJson = this.httpUtils.doGetHtml("https://p.3.cn/prices/mgets?skuIds=J_" + sku);
  76. double price = MAPPER.readTree(priceJson).get(0).get("p").asDouble();
  77. item.setPrice(price);
  78. //获取商品的标题
  79. String itemInfo = this.httpUtils.doGetHtml(item.getUrl());
  80. // String title = Jsoup.parse(itemInfo).select("div.sku-name").text();
  81. String title = Jsoup.parse(itemInfo).select("div#itemName").text();
  82. item.setTitle(title);
  83. item.setCreated(new Date());
  84. item.setUpdated(item.getCreated());
  85. // 保存商品数据到数据库中
  86. this.itemService.save(item);
  87. }
  88. }
  89. }
  90. }

8.启动类

Application.java:

  1. package cn.mlnt.jd;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.scheduling.annotation.EnableScheduling;
  5. @SpringBootApplication
  6. /** * 使用定时任务,需要先开启定时任务,需添加注解 */
  7. @EnableScheduling
  8. public class Application {
  9. public static void main(String[] args) {
  10. SpringApplication.run(Application.class, args);
  11. }
  12. }

三、项目执行效果

在这里插入图片描述

  • 爬取到的图片
    在这里插入图片描述
  • 存储到数据库中的记录
    在这里插入图片描述

总结

参照视频敲完后,执行项目并没有爬到数据,因为视频中没有提及要添加header,声明为浏览器访问。后来参考网上的博客后,遇到的问题基本解决。

  1. //设置请求Request Headers中的User-Agent,告诉京东说这是浏览器访问
  2. httpGet.addHeader("User-Agent","Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36");

参考文章地址:

  • https://blog.csdn.net/hellowork10/article/details/106292150
  • https://blog.csdn.net/weixin_44505194/article/details/106634835

发表评论

表情:
评论列表 (有 0 条评论,619人围观)

还没有评论,来说两句吧...

相关阅读