JAVA网络爬爬学习之HttpClient+Jsoup

x33g5p2x  于2021-12-15 转载在 Java  
字(22.9k)|赞(0)|评价(0)|浏览(309)

HttpClient用法简单整理

引入HttpClient和日志依赖

  1. <dependencies>
  2. <!-- HttpClient-->
  3. <dependency>
  4. <groupId>org.apache.httpcomponents</groupId>
  5. <artifactId>httpclient</artifactId>
  6. <version>4.5.13</version>
  7. </dependency>
  8. <!-- 日志 -->
  9. <dependency>
  10. <groupId>org.slf4j</groupId>
  11. <artifactId>slf4j-log4j12</artifactId>
  12. <version>1.7.25</version>
  13. </dependency>
  14. </dependencies>

GET请求

无参

  1. public class Main
  2. {
  3. public static void main(String[] args)
  4. {
  5. //1.创建默认的HttpClient对象
  6. CloseableHttpClient httpClient = HttpClients.createDefault();
  7. //2.创建HttpGet请求
  8. HttpGet httpGet=new HttpGet("https://blog.csdn.net/m0_53157173");
  9. try(
  10. //3.使用HttpClient发请求
  11. CloseableHttpResponse response = httpClient.execute(httpGet);)
  12. {
  13. //判断响应状态码是否为200
  14. if(response.getStatusLine().getStatusCode()==200)
  15. {
  16. //如果为200表示请求成功,获取返回数据
  17. HttpEntity entity = response.getEntity();
  18. //使用工具类
  19. String content = EntityUtils.toString(entity,"UTF-8");
  20. //打印数据内容
  21. System.out.println(content);
  22. }
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. }finally {
  26. try {
  27. httpClient.close();
  28. } catch (IOException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }
  33. }

带参

另一种写法:

  1. //带参
  2. URIBuilder urIBuilder=new URIBuilder("https://blog.csdn.net/m0_53157173/article/details/121876392");
  3. urIBuilder.setParameter("name","大忽悠").setParameter("age","18");
  4. //2.创建HttpPOST请求
  5. HttpGet httpGet=new HttpGet(urIBuilder.build());

POST请求

无参

带参

  1. //2.创建HttpPOST请求
  2. HttpPost httpPost=new HttpPost();
  3. //声明存放参数的List集合
  4. List<NameValuePair> params = new ArrayList<NameValuePair>();
  5. params.add(new BasicNameValuePair("keys", "java"));
  6. //创建表单数据Entity
  7. UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(params, "UTF-8");
  8. //设置表单Entity到httpPost请求对象中
  9. httpPost.setEntity(formEntity);

连接池

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

  1. public class Main
  2. {
  3. public static void main(String[] args) {
  4. //连接池管理器
  5. PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
  6. //设置最大连接数
  7. cm.setMaxTotal(200);
  8. //设置每个主机的并发数
  9. cm.setDefaultMaxPerRoute(20);
  10. doGet(cm);
  11. doGet(cm);
  12. }
  13. private static void doGet(PoolingHttpClientConnectionManager cm) {
  14. CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
  15. HttpGet httpGet = new HttpGet("https://www.baidu.com");
  16. CloseableHttpResponse response = null;
  17. try {
  18. response = httpClient.execute(httpGet);
  19. // 判断状态码是否是200
  20. if (response.getStatusLine().getStatusCode() == 200) {
  21. // 解析数据
  22. String content = EntityUtils.toString(response.getEntity(), "UTF-8");
  23. System.out.println(content.length());
  24. }
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. } finally {
  28. //释放连接
  29. if (response == null) {
  30. try {
  31. response.close();
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. //不能关闭HttpClient
  36. //httpClient.close();
  37. }
  38. }
  39. }
  40. }

请求request的相关配置

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

  1. public static void main(String[] args) throws IOException {
  2. //创建HttpClient对象
  3. CloseableHttpClient httpClient = HttpClients.createDefault();
  4. //创建HttpGet请求
  5. HttpGet httpGet = new HttpGet("http://www.itcast.cn/");
  6. //设置请求参数
  7. RequestConfig requestConfig = RequestConfig.custom()
  8. .setConnectTimeout(1000)//设置创建连接的最长时间
  9. .setConnectionRequestTimeout(500)//设置获取连接的最长时间
  10. .setSocketTimeout(10 * 1000)//设置数据传输的最长时间
  11. .build();
  12. httpGet.setConfig(requestConfig);
  13. CloseableHttpResponse response = null;
  14. try {
  15. //使用HttpClient发起请求
  16. response = httpClient.execute(httpGet);
  17. //判断响应状态码是否为200
  18. if (response.getStatusLine().getStatusCode() == 200) {
  19. //如果为200表示请求成功,获取返回数据
  20. String content = EntityUtils.toString(response.getEntity(), "UTF-8");
  21. //打印数据长度
  22. System.out.println(content);
  23. }
  24. } catch (Exception e) {
  25. e.printStackTrace();
  26. } finally {
  27. //释放连接
  28. if (response == null) {
  29. try {
  30. response.close();
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. }
  34. httpClient.close();
  35. }
  36. }
  37. }

httpclient用法详解

HttpClient详细使用示例

111

HttpClient用法–这一篇全了解(内含例子)

HttpClient高并发-httpClient连接池

httpclient架构原理介绍 & 连接池详解

Jsoup用法简单整理

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

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

jsoup的主要功能如下:

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

先加入依赖:

  1. <!--Jsoup-->
  2. <dependency>
  3. <groupId>org.jsoup</groupId>
  4. <artifactId>jsoup</artifactId>
  5. <version>1.10.3</version>
  6. </dependency>
  7. <!--测试-->
  8. <dependency>
  9. <groupId>junit</groupId>
  10. <artifactId>junit</artifactId>
  11. <version>4.12</version>
  12. </dependency>
  13. <!--工具-->
  14. <dependency>
  15. <groupId>org.apache.commons</groupId>
  16. <artifactId>commons-lang3</artifactId>
  17. <version>3.7</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>commons-io</groupId>
  21. <artifactId>commons-io</artifactId>
  22. <version>2.6</version>
  23. </dependency>

jsoup解析

解析URL

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

  1. public class Main
  2. {
  3. public static void main(String[] args) throws IOException {
  4. //解析url地址
  5. Document document = Jsoup.parse(new URL("https://www.baidu.com/"), 1000);
  6. //获取title的内容
  7. Element title = document.getElementsByTag("title").first();
  8. System.out.println(title.text());
  9. }
  10. }

解析字符串

先准备以下html文件

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  7. <meta name="description" content="">
  8. <meta name="author" content="">
  9. <title>测试文件</title>
  10. <link rel="stylesheet" href="css/sb-admin-2.css">
  11. <link rel="stylesheet" href="bootstrap-4.6.0-dist/css/bootstrap.min.css">
  12. </head>
  13. <body class="bg-gradient-primary">
  14. </body>
  15. </html>
  1. String fileToString = FileUtils.
  2. readFileToString(new File("C:\\\\Users\\\\zdh\\\\Desktop\\\\test.html"), Charset.defaultCharset());
  3. Document document = Jsoup.parse(fileToString);
  4. System.out.println(document.body());

解析文件

  1. //解析文件
  2. Document document = Jsoup.parse(new File("C:\\Users\\zdh\\Desktop\\test.html"), "UTF-8");
  3. String html = document.getElementsByTag("title").first().html();
  4. System.out.println(html);

使用dom方式遍历文档

元素获取

  • 1.根据id查询元素getElementById
  • 2.根据标签获取元素getElementsByTag
  • 3.根据class获取元素getElementsByClass
  • 4.根据属性获取元素getElementsByAttribute
  1. //1. 根据id查询元素getElementById
  2. Element element = document.getElementById("city_bj");
  3. //2. 根据标签获取元素getElementsByTag
  4. element = document.getElementsByTag("title").first();
  5. //3. 根据class获取元素getElementsByClass
  6. element = document.getElementsByClass("s_name").last();
  7. //4. 根据属性获取元素getElementsByAttribute
  8. element = document.getElementsByAttribute("abc").first();
  9. element = document.getElementsByAttributeValue("class", "city_con").first();

元素中获取数据

  • 1.从元素中获取id
  • 2.从元素中获取className
  • 3.从元素中获取属性的值attr
  • 4.从元素中获取所有属性attributes
  • 5.从元素中获取文本内容text
  1. //获取元素
  2. Element element = document.getElementById("test");
  3. //1. 从元素中获取id
  4. String str = element.id();
  5. //2. 从元素中获取className
  6. str = element.className();
  7. //3. 从元素中获取属性的值attr
  8. str = element.attr("id");
  9. //4. 从元素中获取所有属性attributes
  10. str = element.attributes().toString();
  11. //5. 从元素中获取文本内容text
  12. str = element.text();

使用选择器语法查找元素

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

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

Selector选择器概述

  • tagname: 通过标签查找元素,比如:span
  • #id: 通过ID查找元素,比如:# city_bj
  • .class: 通过class名称查找元素,比如:.class_a
  1. //tagname: 通过标签查找元素,比如:span
  2. Elements span = document.select("span");
  3. for (Element element : span) {
  4. System.out.println(element.text());
  5. }
  6. //#id: 通过ID查找元素,比如:#city_bjj
  7. String str = document.select("#city_bj").text();
  8. //.class: 通过class名称查找元素,比如:.class_a
  9. str = document.select(".class_a").text();
  10. //[attribute]: 利用属性查找元素,比如:[abc]
  11. str = document.select("[abc]").text();
  12. //[attr=value]: 利用属性值来查找元素,比如:[class=s_name]
  13. 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 > *: 查找某个父元素下所有直接子元素
  1. //el#id: 元素+ID,比如: h3#city_bj
  2. String str = document.select("h3#city_bj").text();
  3. //el.class: 元素+class,比如: li.class_a
  4. str = document.select("li.class_a").text();
  5. //el[attr]: 元素+属性名,比如: span[abc]
  6. str = document.select("span[abc]").text();
  7. //任意组合,比如:span[abc].s_name
  8. str = document.select("span[abc].s_name").text();
  9. //ancestor child: 查找某个元素下子元素,比如:.city_con li 查找"city_con"下的所有li
  10. str = document.select(".city_con li").text();
  11. //parent > child: 查找某个父元素下的直接子元素,
  12. //比如:.city_con > ul > li 查找city_con第一级(直接子元素)的ul,再找所有ul下的第一级li
  13. str = document.select(".city_con > ul > li").text();
  14. //parent > * 查找某个父元素下所有直接子元素.city_con > *
  15. str = document.select(".city_con > *").text();

Jsoup参考资料

Jsoup

2

开源中国

Jsoup学习总结

Jsoup详解

【Java爬虫】Jsoup

爬虫案例

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

开发准备

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

  1. CREATE TABLE `jd_item` (
  2. `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  3. `spu` bigint(15) DEFAULT NULL COMMENT '商品集合id',
  4. `sku` bigint(15) DEFAULT NULL COMMENT '商品最小品类单元id',
  5. `title` varchar(100) DEFAULT NULL COMMENT '商品标题',
  6. `price` bigint(10) DEFAULT NULL COMMENT '商品价格',
  7. `pic` varchar(200) DEFAULT NULL COMMENT '商品图片',
  8. `url` varchar(200) DEFAULT NULL COMMENT '商品详情地址',
  9. `created` datetime DEFAULT NULL COMMENT '创建时间',
  10. `updated` datetime DEFAULT NULL COMMENT '更新时间',
  11. PRIMARY KEY (`id`),
  12. KEY `sku` (`sku`) USING BTREE
  13. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='京东商品表';

依赖引入

  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-parent</artifactId>
  6. <groupId>org.springframework.boot</groupId>
  7. <version>2.0.4.RELEASE</version>
  8. </parent>
  9. <groupId>org.example</groupId>
  10. <artifactId>Pa</artifactId>
  11. <version>1.0-SNAPSHOT</version>
  12. <properties>
  13. <maven.compiler.source>8</maven.compiler.source>
  14. <maven.compiler.target>8</maven.compiler.target>
  15. </properties>
  16. <dependencies>
  17. <!-- spring web-->
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-web</artifactId>
  21. </dependency>
  22. <!-- HttpClient-->
  23. <dependency>
  24. <groupId>org.apache.httpcomponents</groupId>
  25. <artifactId>httpclient</artifactId>
  26. <version>4.5.13</version>
  27. </dependency>
  28. <!-- 日志 -->
  29. <dependency>
  30. <groupId>org.slf4j</groupId>
  31. <artifactId>slf4j-log4j12</artifactId>
  32. <version>1.7.25</version>
  33. </dependency>
  34. <!--Jsoup-->
  35. <dependency>
  36. <groupId>org.jsoup</groupId>
  37. <artifactId>jsoup</artifactId>
  38. <version>1.10.3</version>
  39. </dependency>
  40. <!--测试-->
  41. <dependency>
  42. <groupId>junit</groupId>
  43. <artifactId>junit</artifactId>
  44. <version>4.12</version>
  45. </dependency>
  46. <!--工具-->
  47. <dependency>
  48. <groupId>org.apache.commons</groupId>
  49. <artifactId>commons-lang3</artifactId>
  50. <version>3.7</version>
  51. </dependency>
  52. <dependency>
  53. <groupId>commons-io</groupId>
  54. <artifactId>commons-io</artifactId>
  55. <version>2.6</version>
  56. </dependency>
  57. <!-- mybaits-plus-->
  58. <!--mysql驱动-->
  59. <dependency>
  60. <groupId>mysql</groupId>
  61. <artifactId>mysql-connector-java</artifactId>
  62. <scope>runtime</scope>
  63. </dependency>
  64. <!--mybaits-plus第三方提供的启动器-->
  65. <dependency>
  66. <groupId>com.baomidou</groupId>
  67. <artifactId>mybatis-plus-boot-starter</artifactId>
  68. <version>3.4.3.1</version>
  69. </dependency>
  70. <!-- 代码生成器-->
  71. <dependency>
  72. <groupId>com.baomidou</groupId>
  73. <artifactId>mybatis-plus-generator</artifactId>
  74. <version>3.4.1</version>
  75. </dependency>
  76. <dependency>
  77. <groupId>org.apache.velocity</groupId>
  78. <artifactId>velocity-engine-core</artifactId>
  79. <version>2.0</version>
  80. </dependency>
  81. <!-- SpringBootTest-->
  82. <dependency>
  83. <groupId>org.springframework.boot</groupId>
  84. <artifactId>spring-boot-starter-test</artifactId>
  85. <scope>test</scope>
  86. <exclusions>
  87. <exclusion>
  88. <groupId>org.junit.vintage</groupId>
  89. <artifactId>junit-vintage-engine</artifactId>
  90. </exclusion>
  91. </exclusions>
  92. </dependency>
  93. <dependency>
  94. <groupId>org.junit.jupiter</groupId>
  95. <artifactId>junit-jupiter</artifactId>
  96. <version>RELEASE</version>
  97. <scope>test</scope>
  98. </dependency>
  99. <dependency>
  100. <groupId>org.projectlombok</groupId>
  101. <artifactId>lombok</artifactId>
  102. </dependency>
  103. </dependencies>
  104. </project>

mybaits-plus相关配置

  • 配置数据源
  1. spring:
  2. datasource: #是否使用安全连接
  3. #mysal 8驱动不同com.mysql.cj.jdbc.Driver,还需要增加时区的配置 serverTimezone=GMT%2B8
  4. url: jdbc:mysql://localhost:3306/xxx?userSSL=false&useUnicode=true&characterEncoding=utf-8
  5. username: root
  6. password: xxx
  7. driver-class-name: com.mysql.jdbc.Driver
  • MP代码生成器
  1. //MP代码生成器
  2. @SpringBootTest(classes = Main.class,webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
  3. @ExtendWith(SpringExtension.class)
  4. public class Generator
  5. {
  6. //代码生成的文件路径 当前系统的目录
  7. private final String outPutDir=System.getProperty("user.dir")+"/src/main/java/dhy/com";
  8. @Autowired
  9. private HttpUtils httpUtils;
  10. //数据库驱动名
  11. @Value("${spring.datasource.driver-class-name}")
  12. private String dbDriverClassName;
  13. //数据库地址
  14. @Value("${spring.datasource.url}")
  15. private String dbUrl;
  16. //用户名
  17. @Value("${spring.datasource.username}")
  18. private String dbUsername;
  19. //密码
  20. @Value("${spring.datasource.password}")
  21. private String dbPassword;
  22. @Test
  23. public void CodeAutoGenerated()
  24. {
  25. //1.全局策略配置
  26. GlobalConfig config = new GlobalConfig();
  27. config.setAuthor("大忽悠")//作者
  28. .setOutputDir(outPutDir)//生成路径
  29. .setFileOverride(true)//文件覆盖
  30. .setIdType(IdType.AUTO)//主键策略
  31. .setServiceName("%sService")//设置生成service接口名字的首字母是否为I(默认会生成I开头的IStudentService)
  32. .setBaseResultMap(true)//自动SQL映射文件,生成基本的ResultMap
  33. .setBaseColumnList(true);//生成基本的SQL片段
  34. //2.数据源配置
  35. DataSourceConfig dataSourceConfig = new DataSourceConfig();
  36. dataSourceConfig.setDbType(DbType.MYSQL)//设置数据库类型
  37. .setDriverName(dbDriverClassName)
  38. .setUrl(dbUrl)//数据库地址
  39. .setUsername(dbUsername)//数据库名字
  40. .setPassword(dbPassword);//数据库密码
  41. //3.策略配置
  42. StrategyConfig strategy = new StrategyConfig();
  43. strategy.setCapitalMode(true)//全局大写命名
  44. .setNaming(NamingStrategy.underline_to_camel)//数据库表映射到实体的命名策略
  45. .setColumnNaming(NamingStrategy.underline_to_camel)//列的命名也支持驼峰命名规则
  46. .setTablePrefix("jd_")//数据库表的前缀
  47. .setInclude("jd_item")//设置要映射的表名,这里可以写多个
  48. .setEntityLombokModel(true) //使用Lombok开启注解
  49. .setControllerMappingHyphenStyle(true);//controller层,开启下划线url : //localhost:8080/hello_id_2
  50. //4.包名策略
  51. PackageConfig packageConfig = new PackageConfig();
  52. packageConfig
  53. // .setModuleName("generator")
  54. .setParent("com")//所放置的包(父包)
  55. .setMapper("mapper")//Mapper包
  56. .setService("service")//服务层包
  57. .setController("controller")//控制层
  58. .setEntity("beans")//实体类
  59. .setXml("mapper");//映射文件
  60. //5.整合配置
  61. AutoGenerator autoGenerator = new AutoGenerator();
  62. autoGenerator.setGlobalConfig(config)
  63. .setDataSource(dataSourceConfig)
  64. .setStrategy(strategy)
  65. .setPackageInfo(packageConfig);
  66. //6.执行
  67. autoGenerator.execute();
  68. }
  69. }
  • 实体类增加自动填充字段功能

  • 自定义填充策略
  1. @Component//填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component或@Bean注入
  2. public class MyMetaObjectHandler implements MetaObjectHandler
  3. {
  4. //插入时填充策略
  5. @Override
  6. public void insertFill(MetaObject metaObject) {
  7. this.setFieldValByName("created", LocalDateTime.now(),metaObject);
  8. this.setFieldValByName("updated",LocalDateTime.now(),metaObject);
  9. }
  10. //更新时填充策略
  11. @Override
  12. public void updateFill(MetaObject metaObject) {
  13. this.setFieldValByName("updated",new Date(),metaObject);
  14. }
  15. }

封装HttpClient

  • HttpClientUtils
  1. package dhy.com.utils;
  2. import org.apache.http.Header;
  3. import org.apache.http.client.config.RequestConfig;
  4. import org.apache.http.client.methods.CloseableHttpResponse;
  5. import org.apache.http.client.methods.HttpGet;
  6. import org.apache.http.impl.client.CloseableHttpClient;
  7. import org.apache.http.impl.client.HttpClients;
  8. import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
  9. import org.apache.http.util.EntityUtils;
  10. import org.springframework.beans.factory.annotation.Value;
  11. import org.springframework.http.MediaType;
  12. import org.springframework.stereotype.Component;
  13. import java.io.File;
  14. import java.io.FileOutputStream;
  15. import java.io.OutputStream;
  16. import java.util.UUID;
  17. @Component
  18. public class HttpUtils {
  19. //连接池管理器
  20. private PoolingHttpClientConnectionManager cm;
  21. //输出的文件地址
  22. @Value("${image.file.path}")
  23. private String outputFilePath;
  24. public HttpUtils() {
  25. this.cm = new PoolingHttpClientConnectionManager();
  26. //设置最大连接数
  27. cm.setMaxTotal(200);
  28. //设置每个主机的并发数
  29. cm.setDefaultMaxPerRoute(20);
  30. }
  31. //获取内容
  32. public String getHtml(String url) {
  33. // 获取HttpClient对象
  34. CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
  35. // 声明httpGet请求对象
  36. HttpGet httpGet = new HttpGet(url);
  37. // 设置请求参数RequestConfig
  38. httpGet.setConfig(this.getConfig());
  39. //模拟浏览器访问行为
  40. httpGet.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0");
  41. CloseableHttpResponse response = null;
  42. try {
  43. // 使用HttpClient发起请求,返回response
  44. response = httpClient.execute(httpGet);
  45. // 解析response返回数据
  46. if (response.getStatusLine().getStatusCode() == 200) {
  47. String html = "";
  48. // 如果response.getEntity获取的结果是空,在执行EntityUtils.toString会报错
  49. // 需要对Entity进行非空的判断
  50. if (response.getEntity() != null) {
  51. html = EntityUtils.toString(response.getEntity(), "UTF-8");
  52. }
  53. return html;
  54. }
  55. } catch (Exception e) {
  56. e.printStackTrace();
  57. } finally {
  58. try {
  59. if (response != null) {
  60. // 关闭连接
  61. response.close();
  62. }
  63. // 不能关闭,现在使用的是连接管理器
  64. // httpClient.close();
  65. } catch (Exception e) {
  66. e.printStackTrace();
  67. }
  68. }
  69. return null;
  70. }
  71. //获取图片
  72. public String getImage(String url) {
  73. // 获取HttpClient对象
  74. CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
  75. // 声明httpGet请求对象
  76. HttpGet httpGet = new HttpGet(url);
  77. // 设置请求参数RequestConfig
  78. httpGet.setConfig(this.getConfig());
  79. CloseableHttpResponse response = null;
  80. try {
  81. // 使用HttpClient发起请求,返回response
  82. response = httpClient.execute(httpGet);
  83. // 解析response下载图片
  84. if (response.getStatusLine().getStatusCode() == 200&&response.getEntity()!=null)
  85. {
  86. Header contentType = response.getEntity().getContentType();
  87. String name = contentType.getName();//key: contentType
  88. String value = contentType.getValue();//value: MediaType类型
  89. //如果不是图片
  90. if(!value.equals(MediaType.IMAGE_JPEG_VALUE)&&!value.equals(MediaType.IMAGE_PNG_VALUE))
  91. {
  92. return null;
  93. }
  94. // 获取文件类型
  95. String extName = url.substring(url.lastIndexOf("."));
  96. // 使用uuid生成图片名
  97. String imageName = UUID.randomUUID().toString() + extName;
  98. // 声明输出的文件
  99. File file = new File(outputFilePath + imageName);
  100. OutputStream outstream = new FileOutputStream(file);
  101. System.out.println(file.getAbsolutePath());
  102. System.out.println(file.exists());
  103. // 使用响应体输出文件
  104. response.getEntity().writeTo(outstream);
  105. // 返回生成的图片名
  106. return imageName;
  107. }
  108. } catch (Exception e) {
  109. e.printStackTrace();
  110. } finally {
  111. try {
  112. if (response != null) {
  113. // 关闭连接
  114. response.close();
  115. }
  116. // 不能关闭,现在使用的是连接管理器
  117. // httpClient.close();
  118. } catch (Exception e) {
  119. e.printStackTrace();
  120. }
  121. }
  122. return null;
  123. }
  124. //获取请求参数对象
  125. private RequestConfig getConfig() {
  126. RequestConfig config = RequestConfig.custom().setConnectTimeout(1000)// 设置创建连接的超时时间
  127. .setConnectionRequestTimeout(500) // 设置获取连接的超时时间
  128. .setSocketTimeout(10000) // 设置连接的超时时间
  129. .build();
  130. return config;
  131. }
  132. }

实现数据抓取

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

商品定位分析:

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

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

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

  1. //遍历商品spu数据
  2. for (Element spuEle : spus) {
  3. //获取商品spu
  4. Long spuId = Long.parseLong(spuEle.attr("data-spu"));
  5. .....
  6. }

获取商品sku数据

  1. //获取商品sku数据
  2. Elements skus = spuEle.select("li.ps-item img");

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

完整代码:

  1. package dhy.com.sechudle;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import dhy.com.beans.Item;
  5. import dhy.com.service.ItemService;
  6. import dhy.com.utils.HttpUtils;
  7. import org.jsoup.Jsoup;
  8. import org.jsoup.nodes.Document;
  9. import org.jsoup.nodes.Element;
  10. import org.jsoup.select.Elements;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.beans.factory.annotation.Value;
  13. import org.springframework.scheduling.annotation.Scheduled;
  14. import org.springframework.stereotype.Component;
  15. import java.util.List;
  16. @Component
  17. public class ItemTask {
  18. @Autowired
  19. private HttpUtils httpUtils;
  20. @Autowired
  21. private ItemService itemService;
  22. public static final ObjectMapper MAPPER = new ObjectMapper();
  23. //抓取页面的url
  24. @Value("${clawer.page.url}")
  25. private String pageUrl;
  26. //页码
  27. private final String pageNumStr="&page=";
  28. //设置定时任务执行完成后,再间隔100秒执行一次
  29. @Scheduled(fixedDelay = 1000 * 100)
  30. public void process() throws Exception {
  31. //对于京东来说,2个页面对应一页,即99和100都对应第50也
  32. //101对应第51页
  33. //遍历执行,获取所有的数据
  34. for (int i = 1; i < 6; i = i + 2) {
  35. //发起请求进行访问,获取页面数据,先访问第一页
  36. String html = this.httpUtils.getHtml(pageUrl+pageNumStr + i);
  37. //解析页面数据,保存数据到数据库中
  38. this.parseHtml(html);
  39. }
  40. System.out.println("执行完成");
  41. }
  42. // 解析页面,并把数据保存到数据库中
  43. private void parseHtml(String html) throws Exception {
  44. //使用jsoup解析页面
  45. Document document = Jsoup.parse(html);
  46. //获取商品数据
  47. Elements spus = document.select("div#J_goodsList > ul > li");
  48. //遍历商品spu数据
  49. for (Element spuEle : spus) {
  50. //获取商品spu
  51. String attr = spuEle.attr("data-spu");
  52. Long spuId = Long.parseLong(attr.equals("")?"0":attr);
  53. //获取商品sku数据
  54. Elements skus = spuEle.select("li.ps-item img");
  55. for (Element skuEle : skus)
  56. {
  57. //获取商品sku
  58. Long skuId = Long.parseLong(skuEle.attr("data-sku"));
  59. //判断商品是否被抓取过,可以根据sku判断
  60. Item param = new Item();
  61. param.setSku(skuId);
  62. List<Item> list = this.itemService.list(new QueryWrapper<Item>().eq("sku",skuId));
  63. //判断是否查询到结果
  64. if (list.size() > 0) {
  65. //如果有结果,表示商品已下载,进行下一次遍历
  66. continue;
  67. }
  68. //保存商品数据,声明商品对象
  69. Item item = new Item();
  70. //商品spu
  71. item.setSpu(spuId);
  72. //商品sku
  73. item.setSku(skuId);
  74. //商品url地址
  75. item.setUrl("https://item.jd.com/" + skuId + ".html");
  76. //获取商品标题
  77. String itemHtml = this.httpUtils.getHtml(item.getUrl());
  78. String title = Jsoup.parse(itemHtml).select("div.sku-name").text();
  79. item.setTitle(title);
  80. //获取商品价格
  81. String priceUrl = "https://p.3.cn/prices/mgets?skuIds=J_"+skuId;
  82. String priceJson = this.httpUtils.getHtml(priceUrl);
  83. //解析json数据获取商品价格
  84. double price = MAPPER.readTree(priceJson).get(0).get("p").asDouble();
  85. item.setPrice(price);
  86. //获取图片地址
  87. String pic = "https:" + skuEle.attr("data-lazy-img").replace("/n9/","/n1/");
  88. System.out.println(pic);
  89. //下载图片
  90. String picName = this.httpUtils.getImage(pic);
  91. item.setPic(picName);
  92. //保存商品数据
  93. this.itemService.save(item);
  94. }
  95. }
  96. }
  97. }

爬虫演示

错误记录

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

  1. //模拟浏览器访问行为
  2. httpGet.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0");

gitee源码链接

gitee

相关文章