JAVA网络爬爬学习
- HttpClient用法简单整理
- GET请求
- 无参
- 带参
- POST请求
- 无参
- 带参
- 连接池
- 请求request的相关配置
- httpclient用法详解
- Jsoup用法简单整理
- jsoup解析
- 解析URL
- 解析字符串
- 解析文件
- 使用dom方式遍历文档
- 使用选择器语法查找元素
- Selector选择器概述
- Selector选择器组合使用
- Jsoup参考资料
- 爬虫案例
- 开发准备
- 封装HttpClient
- 实现数据抓取
- 爬虫演示
- 错误记录
- gitee源码链接
HttpClient用法简单整理
引入HttpClient和日志依赖
代码语言:javascript复制 <dependencies>
<!-- HttpClient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
GET请求
无参
代码语言:javascript复制public class Main
{
public static void main(String[] args)
{
//1.创建默认的HttpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.创建HttpGet请求
HttpGet httpGet=new HttpGet("https://blog.csdn.net/m0_53157173");
try(
//3.使用HttpClient发请求
CloseableHttpResponse response = httpClient.execute(httpGet);)
{
//判断响应状态码是否为200
if(response.getStatusLine().getStatusCode()==200)
{
//如果为200表示请求成功,获取返回数据
HttpEntity entity = response.getEntity();
//使用工具类
String content = EntityUtils.toString(entity,"UTF-8");
//打印数据内容
System.out.println(content);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
带参
另一种写法:
代码语言:javascript复制 //带参
URIBuilder urIBuilder=new URIBuilder("https://blog.csdn.net/m0_53157173/article/details/121876392");
urIBuilder.setParameter("name","大忽悠").setParameter("age","18");
//2.创建HttpPOST请求
HttpGet httpGet=new HttpGet(urIBuilder.build());
POST请求
无参
带参
代码语言:javascript复制 //2.创建HttpPOST请求
HttpPost httpPost=new HttpPost();
//声明存放参数的List集合
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("keys", "java"));
//创建表单数据Entity
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(params, "UTF-8");
//设置表单Entity到httpPost请求对象中
httpPost.setEntity(formEntity);
连接池
如果每次请求都要创建HttpClient,会有频繁创建和销毁的问题,可以使用连接池来解决这个问题。
代码语言:javascript复制public class Main
{
public static void main(String[] args) {
//连接池管理器
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
//设置最大连接数
cm.setMaxTotal(200);
//设置每个主机的并发数
cm.setDefaultMaxPerRoute(20);
doGet(cm);
doGet(cm);
}
private static void doGet(PoolingHttpClientConnectionManager cm) {
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
HttpGet httpGet = new HttpGet("https://www.baidu.com");
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpGet);
// 判断状态码是否是200
if (response.getStatusLine().getStatusCode() == 200) {
// 解析数据
String content = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(content.length());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放连接
if (response == null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
//不能关闭HttpClient
//httpClient.close();
}
}
}
}
请求request的相关配置
有时候因为网络,或者目标服务器的原因,请求需要更长的时间才能完成,我们需要自定义相关时间
代码语言:javascript复制public static void main(String[] args) throws IOException {
//创建HttpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建HttpGet请求
HttpGet httpGet = new HttpGet("http://www.itcast.cn/");
//设置请求参数
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(1000)//设置创建连接的最长时间
.setConnectionRequestTimeout(500)//设置获取连接的最长时间
.setSocketTimeout(10 * 1000)//设置数据传输的最长时间
.build();
httpGet.setConfig(requestConfig);
CloseableHttpResponse response = null;
try {
//使用HttpClient发起请求
response = httpClient.execute(httpGet);
//判断响应状态码是否为200
if (response.getStatusLine().getStatusCode() == 200) {
//如果为200表示请求成功,获取返回数据
String content = EntityUtils.toString(response.getEntity(), "UTF-8");
//打印数据长度
System.out.println(content);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放连接
if (response == null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
httpClient.close();
}
}
}
Jsoup用法简单整理
我们抓取到页面之后,还需要对页面进行解析。可以使用字符串处理工具解析页面,也可以使用正则表达式,但是这些方法都会带来很大的开发成本,所以我们需要使用一款专门解析html页面的技术。
jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
jsoup的主要功能如下:
- 从一个URL,文件或字符串中解析HTML;
- 使用DOM或CSS选择器来查找、取出数据;
- 可操作HTML元素、属性、文本;
先加入依赖:
代码语言:javascript复制<!--Jsoup-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.3</version>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--工具-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
jsoup解析
解析URL
Jsoup可以直接输入url,它会发起请求并获取数据,封装为Document对象
代码语言:javascript复制public class Main
{
public static void main(String[] args) throws IOException {
//解析url地址
Document document = Jsoup.parse(new URL("https://www.baidu.com/"), 1000);
//获取title的内容
Element title = document.getElementsByTag("title").first();
System.out.println(title.text());
}
}
解析字符串
先准备以下html文件
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>测试文件</title>
<link rel="stylesheet" href="css/sb-admin-2.css">
<link rel="stylesheet" href="bootstrap-4.6.0-dist/css/bootstrap.min.css">
</head>
<body class="bg-gradient-primary">
</body>
</html>
代码语言:javascript复制String fileToString = FileUtils.
readFileToString(new File("C:\\Users\\zdh\\Desktop\\test.html"), Charset.defaultCharset());
Document document = Jsoup.parse(fileToString);
System.out.println(document.body());
解析文件
代码语言:javascript复制 //解析文件
Document document = Jsoup.parse(new File("C:\Users\zdh\Desktop\test.html"), "UTF-8");
String html = document.getElementsByTag("title").first().html();
System.out.println(html);
使用dom方式遍历文档
元素获取
- 1.根据id查询元素getElementById
- 2.根据标签获取元素getElementsByTag
- 3.根据class获取元素getElementsByClass
- 4.根据属性获取元素getElementsByAttribute
//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();
元素中获取数据
- 1.从元素中获取id
- 2.从元素中获取className
- 3.从元素中获取属性的值attr
- 4.从元素中获取所有属性attributes
- 5.从元素中获取文本内容text
//获取元素
Element element = document.getElementById("test");
//1. 从元素中获取id
String str = element.id();
//2. 从元素中获取className
str = element.className();
//3. 从元素中获取属性的值attr
str = element.attr("id");
//4. 从元素中获取所有属性attributes
str = element.attributes().toString();
//5. 从元素中获取文本内容text
str = element.text();
使用选择器语法查找元素
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]
//tagname: 通过标签查找元素,比如:span
Elements span = document.select("span");
for (Element element : span) {
System.out.println(element.text());
}
//#id: 通过ID查找元素,比如:#city_bjj
String str = document.select("#city_bj").text();
//.class: 通过class名称查找元素,比如:.class_a
str = document.select(".class_a").text();
//[attribute]: 利用属性查找元素,比如:[abc]
str = document.select("[abc]").text();
//[attr=value]: 利用属性值来查找元素,比如:[class=s_name]
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 > *: 查找某个父元素下所有直接子元素
//el#id: 元素 ID,比如: h3#city_bj
String str = document.select("h3#city_bj").text();
//el.class: 元素 class,比如: li.class_a
str = document.select("li.class_a").text();
//el[attr]: 元素 属性名,比如: span[abc]
str = document.select("span[abc]").text();
//任意组合,比如:span[abc].s_name
str = document.select("span[abc].s_name").text();
//ancestor child: 查找某个元素下子元素,比如:.city_con li 查找"city_con"下的所有li
str = document.select(".city_con li").text();
//parent > child: 查找某个父元素下的直接子元素,
//比如:.city_con > ul > li 查找city_con第一级(直接子元素)的ul,再找所有ul下的第一级li
str = document.select(".city_con > ul > li").text();
//parent > * 查找某个父元素下所有直接子元素.city_con > *
str = document.select(".city_con > *").text();
爬虫案例
首先访问京东,搜索手机,分析页面,我们抓取以下商品数据:商品图片、价格、标题、商品详情页
SPU和SKU 除了以上四个属性以外,我们发现上图中的苹果手机有四种产品,我们应该每一种都要抓取。那么这里就必须要了解spu和sku的概念 SPU = Standard Product Unit (标准产品单位) SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。 例如上图中的苹果手机就是SPU,包括红色、深灰色、金色、银色 SKU=stock keeping unit(库存量单位) SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。 例如上图中的苹果手机有几个款式,红色苹果手机,就是一个sku 查看页面的源码也可以看出区别
开发准备
根据需求,建立对应的数据库
代码语言:javascript复制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='京东商品表';
依赖引入
代码语言:javascript复制<?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>
<artifactId>spring-boot-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.0.4.RELEASE</version>
</parent>
<groupId>org.example</groupId>
<artifactId>Pa</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- spring web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- HttpClient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<!--Jsoup-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.3</version>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--工具-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- mybaits-plus-->
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybaits-plus第三方提供的启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
<!-- 代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!-- SpringBootTest-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
mybaits-plus相关配置
- 配置数据源
spring:
datasource: #是否使用安全连接
#mysal 8驱动不同com.mysql.cj.jdbc.Driver,还需要增加时区的配置 serverTimezone=GMT+8
url: jdbc:mysql://localhost:3306/xxx?userSSL=false&useUnicode=true&characterEncoding=utf-8
username: root
password: xxx
driver-class-name: com.mysql.jdbc.Driver
- MP代码生成器
//MP代码生成器
@SpringBootTest(classes = Main.class,webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ExtendWith(SpringExtension.class)
public class Generator
{
//代码生成的文件路径 当前系统的目录
private final String outPutDir=System.getProperty("user.dir") "/src/main/java/dhy/com";
@Autowired
private HttpUtils httpUtils;
//数据库驱动名
@Value("${spring.datasource.driver-class-name}")
private String dbDriverClassName;
//数据库地址
@Value("${spring.datasource.url}")
private String dbUrl;
//用户名
@Value("${spring.datasource.username}")
private String dbUsername;
//密码
@Value("${spring.datasource.password}")
private String dbPassword;
@Test
public void CodeAutoGenerated()
{
//1.全局策略配置
GlobalConfig config = new GlobalConfig();
config.setAuthor("大忽悠")//作者
.setOutputDir(outPutDir)//生成路径
.setFileOverride(true)//文件覆盖
.setIdType(IdType.AUTO)//主键策略
.setServiceName("%sService")//设置生成service接口名字的首字母是否为I(默认会生成I开头的IStudentService)
.setBaseResultMap(true)//自动SQL映射文件,生成基本的ResultMap
.setBaseColumnList(true);//生成基本的SQL片段
//2.数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDbType(DbType.MYSQL)//设置数据库类型
.setDriverName(dbDriverClassName)
.setUrl(dbUrl)//数据库地址
.setUsername(dbUsername)//数据库名字
.setPassword(dbPassword);//数据库密码
//3.策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setCapitalMode(true)//全局大写命名
.setNaming(NamingStrategy.underline_to_camel)//数据库表映射到实体的命名策略
.setColumnNaming(NamingStrategy.underline_to_camel)//列的命名也支持驼峰命名规则
.setTablePrefix("jd_")//数据库表的前缀
.setInclude("jd_item")//设置要映射的表名,这里可以写多个
.setEntityLombokModel(true) //使用Lombok开启注解
.setControllerMappingHyphenStyle(true);//controller层,开启下划线url : //localhost:8080/hello_id_2
//4.包名策略
PackageConfig packageConfig = new PackageConfig();
packageConfig
// .setModuleName("generator")
.setParent("com")//所放置的包(父包)
.setMapper("mapper")//Mapper包
.setService("service")//服务层包
.setController("controller")//控制层
.setEntity("beans")//实体类
.setXml("mapper");//映射文件
//5.整合配置
AutoGenerator autoGenerator = new AutoGenerator();
autoGenerator.setGlobalConfig(config)
.setDataSource(dataSourceConfig)
.setStrategy(strategy)
.setPackageInfo(packageConfig);
//6.执行
autoGenerator.execute();
}
}
- 实体类增加自动填充字段功能
- 自定义填充策略
@Component//填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component或@Bean注入
public class MyMetaObjectHandler implements MetaObjectHandler
{
//插入时填充策略
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("created", LocalDateTime.now(),metaObject);
this.setFieldValByName("updated",LocalDateTime.now(),metaObject);
}
//更新时填充策略
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updated",new Date(),metaObject);
}
}
封装HttpClient
- HttpClientUtils
package dhy.com.utils;
import org.apache.http.Header;
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.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.UUID;
@Component
public class HttpUtils {
//连接池管理器
private PoolingHttpClientConnectionManager cm;
//输出的文件地址
@Value("${image.file.path}")
private String outputFilePath;
public HttpUtils() {
this.cm = new PoolingHttpClientConnectionManager();
//设置最大连接数
cm.setMaxTotal(200);
//设置每个主机的并发数
cm.setDefaultMaxPerRoute(20);
}
//获取内容
public String getHtml(String url) {
// 获取HttpClient对象
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
// 声明httpGet请求对象
HttpGet httpGet = new HttpGet(url);
// 设置请求参数RequestConfig
httpGet.setConfig(this.getConfig());
//模拟浏览器访问行为
httpGet.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0");
CloseableHttpResponse response = null;
try {
// 使用HttpClient发起请求,返回response
response = httpClient.execute(httpGet);
// 解析response返回数据
if (response.getStatusLine().getStatusCode() == 200) {
String html = "";
// 如果response.getEntity获取的结果是空,在执行EntityUtils.toString会报错
// 需要对Entity进行非空的判断
if (response.getEntity() != null) {
html = EntityUtils.toString(response.getEntity(), "UTF-8");
}
return html;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
// 关闭连接
response.close();
}
// 不能关闭,现在使用的是连接管理器
// httpClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
//获取图片
public String getImage(String url) {
// 获取HttpClient对象
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
// 声明httpGet请求对象
HttpGet httpGet = new HttpGet(url);
// 设置请求参数RequestConfig
httpGet.setConfig(this.getConfig());
CloseableHttpResponse response = null;
try {
// 使用HttpClient发起请求,返回response
response = httpClient.execute(httpGet);
// 解析response下载图片
if (response.getStatusLine().getStatusCode() == 200&&response.getEntity()!=null)
{
Header contentType = response.getEntity().getContentType();
String name = contentType.getName();//key: contentType
String value = contentType.getValue();//value: MediaType类型
//如果不是图片
if(!value.equals(MediaType.IMAGE_JPEG_VALUE)&&!value.equals(MediaType.IMAGE_PNG_VALUE))
{
return null;
}
// 获取文件类型
String extName = url.substring(url.lastIndexOf("."));
// 使用uuid生成图片名
String imageName = UUID.randomUUID().toString() extName;
// 声明输出的文件
File file = new File(outputFilePath imageName);
OutputStream outstream = new FileOutputStream(file);
System.out.println(file.getAbsolutePath());
System.out.println(file.exists());
// 使用响应体输出文件
response.getEntity().writeTo(outstream);
// 返回生成的图片名
return imageName;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
// 关闭连接
response.close();
}
// 不能关闭,现在使用的是连接管理器
// httpClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
//获取请求参数对象
private RequestConfig getConfig() {
RequestConfig config = RequestConfig.custom().setConnectTimeout(1000)// 设置创建连接的超时时间
.setConnectionRequestTimeout(500) // 设置获取连接的超时时间
.setSocketTimeout(10000) // 设置连接的超时时间
.build();
return config;
}
}
实现数据抓取
- 使用定时任务,可以定时抓取最新的数据
商品定位分析:
获取到所有spu商品信息对应的代码为:
代码语言:javascript复制 //获取商品数据
Elements spus = document.select("div#J_goodsList > ul > li");
获取每一个商品对应的唯一spu标识:
代码语言:javascript复制 //遍历商品spu数据
for (Element spuEle : spus) {
//获取商品spu
Long spuId = Long.parseLong(spuEle.attr("data-spu"));
.....
}
获取商品sku数据
代码语言:javascript复制 //获取商品sku数据
Elements skus = spuEle.select("li.ps-item img");
通过上面获取到的当前img的dom对象,就可以获取到里面每个我们需要的属性了
完整代码:
代码语言:javascript复制package dhy.com.sechudle;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import dhy.com.beans.Item;
import dhy.com.service.ItemService;
import dhy.com.utils.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.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class ItemTask {
@Autowired
private HttpUtils httpUtils;
@Autowired
private ItemService itemService;
public static final ObjectMapper MAPPER = new ObjectMapper();
//抓取页面的url
@Value("${clawer.page.url}")
private String pageUrl;
//页码
private final String pageNumStr="&page=";
//设置定时任务执行完成后,再间隔100秒执行一次
@Scheduled(fixedDelay = 1000 * 100)
public void process() throws Exception {
//对于京东来说,2个页面对应一页,即99和100都对应第50也
//101对应第51页
//遍历执行,获取所有的数据
for (int i = 1; i < 6; i = i 2) {
//发起请求进行访问,获取页面数据,先访问第一页
String html = this.httpUtils.getHtml(pageUrl pageNumStr i);
//解析页面数据,保存数据到数据库中
this.parseHtml(html);
}
System.out.println("执行完成");
}
// 解析页面,并把数据保存到数据库中
private void parseHtml(String html) throws Exception {
//使用jsoup解析页面
Document document = Jsoup.parse(html);
//获取商品数据
Elements spus = document.select("div#J_goodsList > ul > li");
//遍历商品spu数据
for (Element spuEle : spus) {
//获取商品spu
String attr = spuEle.attr("data-spu");
Long spuId = Long.parseLong(attr.equals("")?"0":attr);
//获取商品sku数据
Elements skus = spuEle.select("li.ps-item img");
for (Element skuEle : skus)
{
//获取商品sku
Long skuId = Long.parseLong(skuEle.attr("data-sku"));
//判断商品是否被抓取过,可以根据sku判断
Item param = new Item();
param.setSku(skuId);
List<Item> list = this.itemService.list(new QueryWrapper<Item>().eq("sku",skuId));
//判断是否查询到结果
if (list.size() > 0) {
//如果有结果,表示商品已下载,进行下一次遍历
continue;
}
//保存商品数据,声明商品对象
Item item = new Item();
//商品spu
item.setSpu(spuId);
//商品sku
item.setSku(skuId);
//商品url地址
item.setUrl("https://item.jd.com/" skuId ".html");
//获取商品标题
String itemHtml = this.httpUtils.getHtml(item.getUrl());
String title = Jsoup.parse(itemHtml).select("div.sku-name").text();
item.setTitle(title);
//获取商品价格
String priceUrl = "https://p.3.cn/prices/mgets?skuIds=J_" skuId;
String priceJson = this.httpUtils.getHtml(priceUrl);
//解析json数据获取商品价格
double price = MAPPER.readTree(priceJson).get(0).get("p").asDouble();
item.setPrice(price);
//获取图片地址
String pic = "https:" skuEle.attr("data-lazy-img").replace("/n9/","/n1/");
System.out.println(pic);
//下载图片
String picName = this.httpUtils.getImage(pic);
item.setPic(picName);
//保存商品数据
this.itemService.save(item);
}
}
}
}
爬虫演示
错误记录
httpClient访问京东网站的时候,需要加上请求头,模拟是浏览器访问,否则京东默认会跳到登录页面
代码语言:javascript复制 //模拟浏览器访问行为
httpGet.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0");
gitee源码链接
gitee