场景
想买二手mac,但又需要他降价,给自己一个安慰说是降价买的,其实还是那么贵。
第一步,确认接口与参数,
确认价格获取接口地址,mac找了半天,发现他是通过url直接拼接参数的,而且还很独特,具体的自己试着点两下。
我是选择了条件后再从url复制地址的,如果当前无法选择你所需要的配置,你也可以先自己在url里面拼接上具体条件。
https://www.apple.com.cn/cn-k12/shop/refurbished/mac/1tb-13-英寸-macbook-air-macbook-pro-16gb
第二步,确认返回数据格式
他这个数据是直接在script脚本里面声明一个全局变量,后面页面渲染应该是从这个window变量渲染的,我们不管他怎么渲染的,直接从这里拿到数据就行
第三步,写代码
这个就简单了,简单的一个页面爬取,获取数据,添加满足就发邮件通知自己。
<?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 https://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.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--邮箱-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!--httpclient引用 用于辅助配置restTemplate连接池-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!--WebMagic引用-->
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-core</artifactId>
<version>0.7.3</version>
</dependency>
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-extension</artifactId>
<version>0.7.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
# 使用 smtp 协议
spring.mail.protocol=smtp
spring.mail.port=465
# 邮箱服务器host,不同的邮箱厂商,这个地址不同。
spring.mail.host=smtp.qq.com
spring.mail.username=自己的邮箱账号
# 这个密码填的不是邮箱登录密码,而是邮箱smtp服务授权码
spring.mail.password=自己的邮箱授权码
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
# SSL Config
spring.mail.default-encoding=UTF-8
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.socketFactory.port=465
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
open=false
项目启动运行类CustomRunner.java
package com.example.demo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
* @author ListJiang
* @since 2021/9/22 10:52
* description 自定义延迟执行
*/
@Component
public class CustomRunner implements ApplicationRunner {
@Autowired
JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
String username;
@Value("${open}")
boolean open;
@Override
public void run(ApplicationArguments args) throws Exception {
while (open) {
Document document = Jsoup.connect("http://www.apple.com.cn/cn-k12/shop/refurbished/mac/" +
"1tb-macbook-air-macbook-pro-16gb-32gb")
.userAgent("Mozilla/5.0 (Windows NT 6.1; rv:30.0) Gecko/20100101 Firefox/30.0").get();
Elements scripts = document.select("script");
List<JSONObject> tiles = scripts.stream()
.filter(s -> s.data().contains("window.REFURB_GRID_BOOTSTRAP"))
.flatMap(s -> {
// 获取数据
String replace = s.data().replace("window.REFURB_GRID_BOOTSTRAP = ", "")
.replace("
", "").trim();
String substring = replace.substring(0, replace.length() - 1);
JSONObject parse = (JSONObject) JSONObject.parse(substring);
JSONArray title = parse.getJSONArray("tiles");
return title.stream();
})
.map(o -> (JSONObject) o)
.filter(jsonObject -> {
// 配置信息筛选
JSONObject json1 = jsonObject.getJSONObject("filters").getJSONObject("dimensions");
// 固态
boolean bool1 = Arrays.asList("1tb", "512gb").contains(json1.getString("dimensionCapacity"));
// 内存
boolean bool2 = Arrays.asList("16gb", "32gb").contains(json1.getString("tsMemorySize"));
// 出场年限
boolean bool3 = Arrays.asList("2019", "2020", "2021")
.contains(json1.getString("dimensionRelYear"));
// 价格,
boolean bool4 = jsonObject.getJSONObject("price").getJSONObject("currentPrice")
.getFloat("raw_amount") > 10000L;
return bool1 && bool2 && bool3 && bool4;
})
.map(json -> {
// 构建短信需要的json
JSONObject j = new JSONObject();
JSONObject dimensions = json.getJSONObject("filters").getJSONObject("dimensions");
j.put("tsMemorySize", dimensions.getString("tsMemorySize"));
j.put("dimensionCapacity", dimensions.getString("dimensionCapacity"));
j.put("dimensionScreensize", dimensions.getString("dimensionScreensize"));
j.put("version", dimensions.getString("refurbClearModel") + dimensions.getString("dimensionRelYear"));
j.put("dimensionColor", dimensions.getString("dimensionColor"));
j.put("remark", json.getString("title"));
j.put("price", json.getJSONObject("price").getJSONObject("currentPrice")
.getFloat("raw_amount"));
return j;
})
.collect(Collectors.toList());
tiles.sort((a, b) -> a.getFloat("price") > b.getFloat("price") ? -1 : 1);
tiles.stream().forEach(System.out::println);
// 筛选除超过预期的价格
tiles = tiles.stream().filter(t -> {
if (t.getString("tsMemorySize").equals("16gb")
&& t.getString("dimensionCapacity").equals("1tb")
&& t.getString("version").equals("macbookpro2020")
&& t.getFloat("price") < 12319L) {
return true;
}
if (t.getString("tsMemorySize").equals("16gb")
&& t.getString("dimensionCapacity").equals("1tb")
&& t.getString("version").equals("macbookair2020")
&& t.getString("remark").contains("8 核图形处理器")
&& t.getFloat("price") < 10939L) {
return true;
}
if (t.getString("tsMemorySize").equals("16gb")
&& t.getString("dimensionCapacity").equals("1tb")
&& t.getString("version").equals("macbookair2020")
&& t.getString("remark").contains("7 核图形处理器")
&& t.getFloat("price") < 10619L) {
return true;
}
if (t.getString("tsMemorySize").equals("16gb")
&& t.getString("dimensionCapacity").equals("512gb")
&& t.getFloat("price") < 11049L) {
return true;
}
return false;
}).collect(Collectors.toList());
if (tiles.size() > 0) {
String text = "";
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
// 发件人,这里的值必须是配置的发送邮箱账号的可用账号名,例如qq邮箱可以配置多个账号,这里是其中任意一个即可
simpleMailMessage.setFrom(username);
// 收件人
simpleMailMessage.setTo("2754005464@qq.com");
// 邮件主题
simpleMailMessage.setSubject("降价啦");
// 邮件内容
for (JSONObject jsonObject : tiles) {
text += jsonObject.toJSONString() + "
";
}
simpleMailMessage.setText(text);
javaMailSender.send(simpleMailMessage);
open = false;
}
System.out.println("
"+LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
Thread.sleep(5000);
}
}
}