当前位置: 首页 > news >正文

正则采集器之五——商品匹配规则

需求设计 + 实现分析

系统通过访问URL得到html代码,通过正则表达式匹配html,通过反向引用来得到商品的标题、图片、价格、原价、id,这部分逻辑在java中实现。

匹配商品的正则做成可视化编辑,因为不同网站的结构不同,同一个网站的结构会随时间发生变化,为方便修改,做成可视化编辑。以九块邮为例分析匹配商品的正则:

由此图可见一个正则由多个单元项组成,每个单元项都是一个单独的正则(包括匹配商品的字段项和字段项前后的标志字符串),比如匹配价格的[\d\.]+,价格前面的html >¥ 。最终组合成的正则需要能够正确解析出一个个商品的标题、图片、价格、原价和id字段。

后端代码

匹配代码

package com.learn.reptile.utils;import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.learn.reptile.entity.po.Item;import cn.hutool.http.HttpUtil;public class ItemCraw {/*** 通过url获取html,然后解析出出商品* @param url* @param regexpStr 商品匹配正则表达式* @param startStr 开始匹配字符串* @param endStr 结束匹配字符串* @return*/public static List<Item> parseItemsFromUrl(String url, String regexpStr, String startStr, String endStr) {String html = HttpUtil.get(url);if(StringUtils.isNotBlank(endStr)) {html = html.substring(html.indexOf(startStr), html.lastIndexOf(endStr));} else {html = html.substring(html.indexOf(startStr));}List<Item> items = new ArrayList<>();Pattern pattern = Pattern.compile(regexpStr);Matcher matcher = pattern.matcher(html);// 每一个匹配整体while(matcher.find()) {Item item = new Item();item.setItemId(matcher.group("id"));item.setPic(matcher.group("pic"));item.setTitle(matcher.group("title"));item.setPrice(Double.parseDouble(matcher.group("price")));item.setPrePrice(Double.parseDouble(matcher.group("prePrice")));items.add(item);}return items;}}

匹配结果实体类

package com.learn.reptile.entity.po;import java.util.Date;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;import lombok.Data;@Data
public class Item {@TableId(type = IdType.AUTO)private Long id;// 淘宝商品idprivate String itemId;// 来源,匹配网站的编码private String source;private String title;private String pic;private double price;private double prePrice;// 采集时间private Date createTime;
}

controller类

package com.learn.reptile.web.controller;import java.util.List;import javax.annotation.Resource;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import com.learn.reptile.entity.po.Item;
import com.learn.reptile.entity.po.ItemWebsite;
import com.learn.reptile.entity.vo.R;
import com.learn.reptile.utils.ItemCraw;@RequestMapping("/item")
@RestController
public class ItemController {@PostMapping("test")public R<List<Item>> test(@RequestBody ItemWebsite itemWebsite) {return R.ok(ItemCraw.parseItemsFromUrl(itemWebsite.getUrl(), itemWebsite.getRegexpStr(), itemWebsite.getStartStr(), itemWebsite.getEndStr()));}
}

前端代码

添加router,位置:src/router/modules/home.js。router的path中增加了参数:id,即网站的id。

{path: '/item',component: Layout,name: 'item',meta: {title: '商品',},icon: 'icon-home',children: [{path: 'itemWebsite',name: 'itemWebiste',component: () => import('@/views/item_website/index.vue'),meta: {title: '网站',},},{path: 'itemRegexp/:id',name: 'itemRegexp',component: () => import('@/views/item_website/regexp.vue'),meta: {title: '商品匹配正则',},hidden: true,},],},

位置src/views/item_website/regexp.vue

分析

用regexpItems表示正则单元项列表,每个regexpItem包含三个字段:type 表示匹配商品的某个字段还是仅仅是分隔部分,matchType 表示该部分的正则模式,matchStr 表示该正则模式需要用到的字符串。

type 数据

key   value
id商品id
title标题
pic图片
price价格
prePrice原价
其他

matchType 数据

keyvalue
all任意字符串
exclude不包含 某些字符串 的字符串
fix固定字符串
number价格,[\d\.]+

正则单元项html:

 <divclass="regexp_item"v-for="(regexpItem, index) in regexpItems":key="index">{{ index + 1 }}<el-icon @click="regexpItems.splice(index, 1)"><CloseBold /></el-icon><div class="line"><div class="label">类型</div><div class="field"><el-selectv-model="regexpItem.type"@change="changeType(regexpItem)"><el-optionv-for="(name, code) in types":key="code":value="code":label="name">{{ name }}</el-option></el-select></div></div><div class="line"><div class="label">匹配类型</div><div class="field"><el-radio-group v-model="regexpItem.matchType"><el-radio value="number" label="number">数值</el-radio><el-radio value="all" label="all">任意字符</el-radio><el-radio value="exclude" label="exclude">除</el-radio><el-inputclass="match_input"v-if="regexpItem.matchType == 'exclude'"v-model="regexpItem.matchStr"/><el-radio value="fix" label="fix">固定</el-radio><el-inputv-if="regexpItem.matchType == 'fix'"v-model="regexpItem.matchStr"/></el-radio-group></div></div></div>

页面整体布局为左中右结构,左侧是正则单元项列表,中间是操作按钮,右边是测试匹配结果,完整html部分代码如下:

<template><div style="margin: 10px;">{{ itemWebsite.name }}匹配规则</div><div style="display: flex;"><div style="width: 60%"><div class="form"><div class="form_label">匹配开始字符串</div><div class="form_field"><el-input v-model="itemWebsite.startStr"></el-input></div><div class="form_label">匹配结束字符串</div><div class="form_field"><el-input v-model="itemWebsite.endStr"></el-input></div></div><divclass="regexp_item"v-for="(regexpItem, index) in regexpItems":key="index">{{ index + 1 }}<el-icon @click="regexpItems.splice(index, 1)"><CloseBold /></el-icon><div class="line"><div class="label">类型</div><div class="field"><el-selectv-model="regexpItem.type"@change="changeType(regexpItem)"><el-optionv-for="(name, code) in types":key="code":value="code":label="name">{{ name }}</el-option></el-select></div></div><div class="line"><div class="label">匹配类型</div><div class="field"><el-radio-group v-model="regexpItem.matchType"><el-radio value="number" label="number">数值</el-radio><el-radio value="all" label="all">任意字符</el-radio><el-radio value="exclude" label="exclude">除</el-radio><el-inputclass="match_input"v-if="regexpItem.matchType == 'exclude'"v-model="regexpItem.matchStr"/><el-radio value="fix" label="fix">固定</el-radio><el-inputv-if="regexpItem.matchType == 'fix'"v-model="regexpItem.matchStr"/></el-radio-group></div></div></div></div><div style="width: 180px; text-align: center;"><div style="margin-bottom: 10px;"><el-button round type="primary" @click="add">增加匹配项</el-button></div><div style="margin-bottom: 10px;"><el-button type="primary" round @click="save">保存</el-button></div><el-button type="primary" round @click="test">测试</el-button></div><div style="width: 40%;"><pre>{{ resultItems }}</pre></div></div>
</template>

javascript部分:

import {getCurrentInstance,reactive,toRefs,ref,computed,watch,onMounted,
} from 'vue'
import { getById, update } from '@/api/itemWebsite'
import { test } from '@/api/item'
import { ElMessageBox } from 'element-plus'export default {setup() {const { proxy: ctx } = getCurrentInstance()const state = reactive({id: '',itemWebsite: {},regexpItems: [],types: {title: '标题',pic: '图片',id: '商品id',price: '价格',prePrice: '原价','': '其他',},resultItems: '',add() {ElMessageBox.prompt('请输入添加的位置下标', '添加匹配项', {inputPattern: /\d+/,inputErrorMessage: '下标必须为正整数',}).then(({ value }) => {const index = parseInt(value)ctx.regexpItems.splice(index - 1, 0, {type: '',matchType: '',matchStr: '',})})},changeType(regexpItem) {switch (regexpItem.type) {case 'price':case 'prePrice':regexpItem.matchType = 'number'breakcase 'pic':case 'itemId':regexpItem.matchType = 'exclude'regexpItem.matchStr = '"'breakcase 'title':regexpItem.matchType = 'exclude'regexpItem.matchStr = '<'}},save() {var regexpStr = '' // 通过正则单元项列表生成正则字符串ctx.regexpItems.forEach(item => {var str = ''if (item.matchType == 'all') {str = '.+?'} else if (item.matchType == 'exclude') {str = '[^' + item.matchStr + ']+'} else if (item.matchType == 'fix') {str = item.matchStr} else if (item.matchType == 'number') {str = '[\\d\\.]+'}if (item.type) {regexpStr += '(?<' + item.type + '>' + str + ')'} else {regexpStr += str}})update({startStr: ctx.itemWebsite.startStr,endStr: ctx.itemWebsite.endStr,regexpContents: JSON.stringify(ctx.regexpItems), // 正则单元项列表以json字符串保存regexpStr: regexpStr,id: ctx.id,}).then(res => {ctx.$message.success('保存成功')})},test() {var regexpStr = ''ctx.regexpItems.forEach(item => {var str = ''if (item.matchType == 'all') {str = '.+?'} else if (item.matchType == 'exclude') {str = '[^' + item.matchStr + ']+'} else if (item.matchType == 'fix') {str = item.matchStr} else if (item.matchType == 'number') {str = '[\\d\\.]+'}if (item.type) {regexpStr += '(?<' + item.type + '>' + str + ')'} else {regexpStr += str}})test({url: ctx.itemWebsite.url,startStr: ctx.itemWebsite.startStr,endStr: ctx.itemWebsite.endStr,regexpStr: regexpStr,}).then(res => {ctx.$message.success('测试成功')ctx.resultItems = JSON.stringify(res.data,['itemId', 'title', 'pic', 'price', 'prePrice'],'\t')})},})onMounted(() => {ctx.id = ctx.$route.params.idgetById(ctx.id).then(res => {ctx.itemWebsite = res.dataif (ctx.itemWebsite.regexpContents) {ctx.regexpItems = eval('(' + ctx.itemWebsite.regexpContents + ')')}})})return {...toRefs(state),}},
}

样式部分:

<style>
.regexp_item {margin: 10px;border-top: 1px solid gray;border-right: 1px solid gray;position: relative;width: 100%;
}
.regexp_item .el-icon {position: absolute;right: -5px;top: -5px;color: red;cursor: pointer;
}
.line {display: flex;
}
.line > div {border-bottom: 1px solid gray;border-left: 1px solid gray;padding: 5px;
}
.label {width: 30%;
}
.field {width: 70%;
}
.match_input {width: 100px;margin-right: 15px;
}
.form {display: flex;align-items: center;margin: 10px;width: 100%;
}
.form_label {width: 20%;margin-left: 20px;
}
.form_field {width: 30%;
}
</style>

代码及演示网站见:正则采集器之一——需求说明-CSDN博客

相关文章:

正则采集器之五——商品匹配规则

需求设计 实现分析 系统通过访问URL得到html代码&#xff0c;通过正则表达式匹配html&#xff0c;通过反向引用来得到商品的标题、图片、价格、原价、id&#xff0c;这部分逻辑在java中实现。 匹配商品的正则做成可视化编辑&#xff0c;因为不同网站的结构不同&#xff0c;同…...

一键切换阿里yum源(包括其他系统repo镜像查找方法)

一键切换阿里yum源 示例命令其他系统repo镜像GitHub文档 示例命令 # 备份旧源 mv CentOS-Base.repo CentOS-Base.repo.bak # 添加新源(阿里镜像源) wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo其他系统repo镜像 这里的示例是用…...

Fiddler学习笔记

目录 前言 简介 原理 界面 前言 测试可以使用fiddler工具&#xff0c;通过抓包的方式修改前端参数和模拟后端返回&#xff0c;快速定位缺陷。 简介 Fiddler是HTTP协议调试代理工具&#xff0c;可以记录并检查所有客户端和服务器之间的HTTP和HTTPS请求&#xff0c;允许监视…...

【Vue3】watch 监视多种类型数据

【Vue3】watch 监视多种类型数据 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗…...

【C++入门】虚函数与多态

文章目录 前言虚函数是什么&#xff1f;如何使用虚函数&#xff1f; 纯虚函数是什么&#xff1f;虚函数与普通函数的区别虚表虚表是什么&#xff1f;含有虚表的类内存结构图如何找到虚表的地址&#xff1f;示例代码代码解释 多态是什么&#xff1f;如何使用多态&#xff1f;为什…...

wpf中轮询显示图片

本文的需求是&#xff0c;在一个文件夹中&#xff0c;放一堆图片的集合&#xff0c;然后在wpf程序中&#xff0c;按照定时的方式&#xff0c;循序显示照片。 全部代码 1.声明一个PictureInfo类 namespace WpfApp1 {public class PictureInfo{public string? FileName { get; …...

CSA笔记9-磁盘管理(2)

分区挂载 挂载&#xff1a;将该文件系统中的内容与指定的目录关联起来&#xff0c;使得你可以通过该目录来访问文件系统中的文件和目录。 mount 命令用来挂载文件系统 #挂载/dev/sda1和/dev/sda2 [rootlocalhost ~]# mkdir test{1..2} [rootlocalhost ~]# ll test1 te…...

Python入门第三课

# 入门第三课 # 关键字 if and or in not in ! car g print(car g) print(car dd) if car ! hh:print("wlcome to here ") age 33 print(age 33) print(age 44) age1 44 if age >0 and age1 > 0:print("nihao") if age >0 or age1 > …...

java计算器,输入公式和对应变量的值

目标&#xff1a;最近想写个东西&#xff0c;本质就是一个计算器&#xff0c;我们可以输入公式&#xff08;例如&#xff1a;ab&#xff09;&#xff0c;然后把公式的值&#xff08;a:10,b:20&#xff09;也输入进去。最后得到结果。核心&#xff1a;这个想法核心部分就是给一个…...

加密货币赋能跨境电商:PayPal供应链金融服务如何引领行业新趋势

跨境电商行业近年来呈现出爆发式增长&#xff0c;随着全球化贸易壁垒的降低和数字经济的快速发展&#xff0c;越来越多的商家和消费者跨越国界进行交易。根据eMarketer的数据&#xff0c;全球跨境电商交易额在2023年已超过4万亿美元&#xff0c;并预计在未来几年内仍将保持两位…...

redis面试(二)List链表数据

list 列表 我们总是说List为列表&#xff0c;其实在真正的数据结构来说&#xff0c;redis是自己基于c语言来实现的双向链表数据结构&#xff0c;主要的逻辑就是每个节点都可以指向下一个节点&#xff0c;这个结构就属于链表数组结构。 每个节点中的属性如下&#xff1a; type…...

SpringDataJPA(三):多表操作,复杂查询

一、Specifications动态查询 有时我们在查询某个实体的时候&#xff0c;给定的条件是不固定的&#xff0c;这时就需要动态构建相应的查询语句&#xff0c;在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。 import …...

嵌入式硬件面试题集萃:从基础到进阶

基础问题 问题: 解释什么是微控制器&#xff0c;以及它与微处理器的区别。 答案: 微控制器是具有集成内存和输入/输出外设的微型计算机。与通用微处理器相比&#xff0c;微控制器通常用于控制特定应用&#xff0c;而不是执行通用计算任务。 问题: 什么是数字逻辑门&#xff0c…...

easyui-datebox 只显示月份选择,默认开启月份,隐藏日期选择框

如果你使用 ​​easyui-datebox​​​ 并希望隐藏日期选择框,只显示月份选择,可以通过一些自定义代码来实现。虽然 EasyUI 没有直接提供这种功能,但可以通过自定义 ​​formatter​​​ 和 ​​parser​​​ 方法,以及修改 ​​onShowPanel​​ 事件来实现这个功能。 以下…...

【数据结构】队列(链表实现 + 力扣 + 详解 + 数组实现循环队列 )

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构 &#x1f4da;本系列文章为个人学…...

02 Go语言操作MySQL基础教程_20240729 课程笔记

概述 如果您没有Golang的基础&#xff0c;应该学习如下前置课程。 Golang零基础入门Golang面向对象编程Go Web 基础Go语言开发REST API接口_20240728 基础不好的同学每节课的代码最好配合视频进行阅读和学习&#xff0c;如果基础比较扎实&#xff0c;则阅读本教程巩固一下相…...

相交链表 - 力扣(LeetCode)C语言

160. 相交链表 - 力扣&#xff08;LeetCode&#xff09; (点击前面链接即可查看题目) 一、题目 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始…...

【Python】基础学习技能提升代码样例3:JSON文本处理

对json的处理&#xff0c;无非是编码和解码两部分 编码&#xff1a;将python数据结构转换为json字符串解码: 将json字符串转换为python数据结构 另外&#xff0c;还有.json文件的读写 一、编码 json.dumps(obj, *, skipkeysFalse, ensure_asciiTrue, check_circularTrue, a…...

最新Yiso智云搜索引擎系统源码/开源PHP源码/修复版

源码简介&#xff1a; 最新Yiso智云搜索引擎系统源码/开源PHP源码/修复版。Yiso 是一个性能非常好的搜索引擎&#xff0c;不仅免费开源&#xff0c;还能当作收录网址的平台来用呢&#xff01;只需要输入关键词&#xff0c;就能轻松找到相关的搜索结果内容。 1、Yiso 用的是自…...

Anconda 快速常用命令简洁版

目的&#xff1a;简单清楚的使用基本的conda 命令 可能需求 查看项目中的虚拟环境及依赖是否满足需求操作新环境来满足项目或者论文的实现 Anconda 常用命令 conda 查看基础命令1. 进入Anaconda 环境2. 查看版本3.查看有哪些虚拟环境4.激活虚拟环境5. 进入虚拟环境查看6. 退出…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

Vite中定义@软链接

在webpack中可以直接通过符号表示src路径&#xff0c;但是vite中默认不可以。 如何实现&#xff1a; vite中提供了resolve.alias&#xff1a;通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...

苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会

在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...

uni-app学习笔记三十五--扩展组件的安装和使用

由于内置组件不能满足日常开发需要&#xff0c;uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件&#xff0c;需要安装才能使用。 一、安装扩展插件 安装方法&#xff1a; 1.访问uniapp官方文档组件部分&#xff1a;组件使用的入门教程 | uni-app官网 点击左侧…...