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

news2024/9/21 10:52:03

需求设计 + 实现分析

系统通过访问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;
	
	// 淘宝商品id
	private 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:

 <div
        class="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-select
              v-model="regexpItem.type"
              @change="changeType(regexpItem)"
            >
              <el-option
                v-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-input
                class="match_input"
                v-if="regexpItem.matchType == 'exclude'"
                v-model="regexpItem.matchStr"
              />
              <el-radio value="fix" label="fix">固定</el-radio>
              <el-input
                v-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>
      <div
        class="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-select
              v-model="regexpItem.type"
              @change="changeType(regexpItem)"
            >
              <el-option
                v-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-input
                class="match_input"
                v-if="regexpItem.matchType == 'exclude'"
                v-model="regexpItem.matchStr"
              />
              <el-radio value="fix" label="fix">固定</el-radio>
              <el-input
                v-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'
            break
          case 'pic':
          case 'itemId':
            regexpItem.matchType = 'exclude'
            regexpItem.matchStr = '"'
            break
          case '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.id
      getById(ctx.id).then(res => {
        ctx.itemWebsite = res.data
        if (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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1957068.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

24小时在线的仪控专家

近年来&#xff0c;随着流程行业自动化水平的不断提高&#xff0c;仪表、阀门等设备在生产装置中的数量也越来越多&#xff0c;扮演着“眼睛”、“双手”和“神经”等角色&#xff0c;与生产过程的安全平稳息息相关&#xff0c;对企业追求效益最大化起着举足轻重的作用。 但仪控…

视频汇聚/安防监控/视频云存储EasyCVR平台实际通道数和授权数不一致的原因排查与解决

多协议接入/GB28181安防综合管理系统EasyCVR视频汇聚平台能在复杂的网络环境中&#xff0c;将前端设备统一集中接入与汇聚管理。智慧安防/视频存储/视频监控/视频汇聚EasyCVR平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘…

NOILinux2.0安装

NOI官方已发布NOILinux2.0&#xff0c;可是如何安装使用呢&#xff1f;我来教你。 首先下载VMWare和NOILinux2.0的ios&#xff0c;当然你用什么虚拟机软件都可以。这里我用的是VMware。 NOIlinux2.0的下载链接&#xff1a; NOI Linux 2.0发布&#xff0c;将于9月1日起正式启用…

2024电赛H题参考方案——自动行使小车

目录 一、题目要求 二、参考资源获取 三、参考方案 1、环境搭建及工程移植 2、移植MPU6050模块 3、移植TB6612电机驱动模块 其他模块根据需要移植 总结 一、题目要求 小编自认为&#xff1a;此次H题属于控制类题目&#xff0c;相较于往年较为简单&#xff0c;功能也算单一&…

hadoop学习(一)

一.hadoop概述 1.1hadoop优势 1&#xff09;高可靠性&#xff1a;Hadoop底层维护多个数据副本&#xff0c;即使Hadoop某个计算元素或存储出现故障&#xff0c;也不会导致数据的丢失。 2&#xff09;高扩展性&#xff1a;在集群间分配任务数据&#xff0c;可方便扩展数以千计…

开放式耳机和骨传导耳机哪个好?教你选择最好的开放式耳机!

​蓝牙耳机几乎成为和手机相同的数码设备&#xff0c;无论是在工作还是通勤过程&#xff0c;无论是娱乐还是线上办公&#xff0c;随身携带的蓝牙耳机都能提供更舒适、更便捷的听觉和通话体验。随着蓝牙耳机种类层出不穷&#xff0c;新型开放式耳机的加入&#xff0c;让更多消费…

DM数据库配置登录基于操作系统的身份验证

达梦数据库登录基于操作系统的身份验证 DM提供数据库身份验证模式和外部身份验证模式来保护对数据库访问的安全。数据库身份验证模式需要利用数据库口令&#xff0c;即在创建或修改用户时指定用户口令&#xff0c;用户在登录时输入对应口令进行身份验证&#xff1b;外部身份验…

React 和 Vue _使用区别

目录 一、框架介绍 1.Vue 2.React 二、框架结构 1.创建应用 2.框架结构 三、使用区别 1.单页面组成 2.样式 3.显示响应式数据 4.响应式html标签属性 5.控制元素显隐 6.条件渲染 7.渲染列表 react和vue是目前前端比较流行的两大框架&#xff0c;前端程序员应该将两…

鸿蒙HarmonyOS开发:如何灵活运用服务卡片提升用户体验

文章目录 一、ArkTS卡片相关模块二、卡片事件能力说明三、卡片事件的主要使用场景3.1、使用router事件跳转到指定UIAbility3.1.1、卡片内按钮跳转到应用的不同页面3.1.2、服务卡片的点击跳转事件 3.2、通过message事件刷新卡片内容3.2.1、在卡片页面调用postCardAction接口触发…

【Redis】Centos7 安装 redis(详细教程)

查看当前 Redis 版本&#xff1a; 当前的 redis 版本太老了&#xff0c;选择安装 Redis5。 一、使用 yum 安装 1、首先安装 scl 源 yum install centos-release-scl-rh 由于我之前已经安装过了&#xff0c;所以加载速度比较快&#xff0c;且显示已经安装成功&#xff0c;是最…

go-kratos 学习笔记(8) redis的使用

redis的在项目中的使用是很常见的&#xff0c;前面有了mysql的使用redis的也差不多&#xff1b;也是属于在data层的操作&#xff0c;所以需要新建一个 NewRedisCmd方法 在internal/data/data.go中新增NewRedisCmd 方法&#xff0c;注入到ProviderSet package dataimport (&quo…

【Java】类与对象、封装(008)

目录 类与对象 ♦️什么类与对象❓ &#x1f38f;类的定义 &#x1f383;定义一个类 &#x1f383;成员变量 &#x1f383;成员方法 &#x1f38f;对象的创建使用和引用传递 &#x1f383;对象的创建 &#x1f383;对象的引用 封装 ♦️什么是封装❓ ♦️实现封装 …

太阳伴星2600万年回转周期,或许正是它,导致地球生物周期性灭绝?!

我们知道地球已经有46亿年的寿命了&#xff0c;这相比人类生存的时间是极其漫长的。在地球历史中&#xff0c;恐龙在这里生活了1.6亿年&#xff0c;这是地球上相对独特的存在。当然&#xff0c;在恐龙的一生中&#xff0c;它们绝对是地球的统治者。当时&#xff0c;现在统治地球…

stm32入门-----DMA直接存储器存取(上——理论篇)

目录 前言 DMA 1.简介 2.存储器映像 3.DMA结构 4.数据宽度与对齐 5.DMA工作示例 前言 本期我们就开始学习DMA直接存储器存取&#xff0c;DMA是一个数据装运的小助手&#xff0c;执行数据的搬运处理&#xff0c;减少了CPU的负担&#xff0c;在stm32中担当重要的工作。在前…

《Milvus Cloud向量数据库指南》——不同开源向量数据库的适用数据规模及其技术特点深度剖析

在探讨向量数据库领域时,我们不得不提及多个备受瞩目的开源项目,它们各自以其独特的技术优势和适用场景赢得了广泛的关注。本文将深入剖析Milvus Cloud、Chroma、Weaviate、以及Qdrant这几个开源向量数据库在不同数据规模下的应用表现,以及它们各自的技术特点和优势。 引言…

SS9283403 开发环境搭建(二)

1.序 在前一篇“SS928&3403K开发环境搭建&#xff08;一&#xff09;”中已经借助Ebaina搭建好的ubuntu对开发板做了测试&#xff0c;这篇记录从零开始搭建SS928&3403K的开发环境&#xff1b; 2.开发前准备 下载VMware Workstation 16 Pro 16.1.0版本 下载ubuntu18.04…

封装导出功能(export)

业务描述: 通过一个button按钮, 实现导出功能, 导出后文件保存到电脑上 目录 一. file-saver 介绍 二. 项目中应用 1. 安装 file-saver库 2.创建 util / exportExcel.js 3. 页面内引入, 使用 4. 页面反馈 展示 一. file-saver 介绍 file-saver是一个用于在前端导出文件…

基于VueCli自定义创建Vue项目架子

基于VueCli自定义创建Vue项目架子 一、VueCli 自定义创建项目1.1安装脚手架 (已安装)1.2.创建项目1.2.1选项1.2.2手动选择功能&#xff08;按空格可选中&#xff09;1.2.3选择vue的版本1.2.4是否使用history模式1.2.5选择css预处理1.2.6选择eslint的风格 &#xff08;eslint 代…

正点原子imx6ull-mini-Linux驱动LED(新字符设备驱动)(3)

经过前几节实验的实战操作&#xff0c;我们已经掌握了 Linux 字符设备驱动开发的基本步骤&#xff0c;字符 设备驱动开发重点是使用 register_chrdev 函数注册字符设备&#xff0c;当不再使用设备的时候就使用 unregister_chrdev 函数注销字符设备&#xff0c;驱动模块加载成功…