监听mac价格变动,降价了就通知。
场景
想买二手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("\n", "").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() + "\n"; } simpleMailMessage.setText(text); javaMailSender.send(simpleMailMessage); open = false; } System.out.println("\n\n\n"+LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"))); Thread.sleep(5000); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律