Qwen2-VL视觉大模型微调实战:LaTex公式OCR识别任务(完整代码)
《SwanLab机器学习实战教程》是一个主打「开箱即用」的AI训练系列教程,我们致力于提供完善的数据集、源代码、实验记录以及环境安装方式,手把手帮助你跑起训练,解决问题。
Qwen2-VL是通义千问团队最近开源的大语言模型,由阿里云通义实验室研发。
以Qwen2-VL作为基座多模态大模型,通过指令微调的方式实现特定场景下的OCR,是学习多模态LLM微调的入门任务。

本文我们将简要介绍基于 transformers、peft 等框架,使用 Qwen2-VL-2B-Instruct 模型在LaTeX_OCR 上进行Lora微调训练,同时使用 SwanLab 监控训练过程与评估模型效果。
- 训练过程:ZeyiLin/Qwen2-VL-ft-latexocr
- 代码:Zeyi-Lin/Qwen2-VL-finetune-LatexOCR
- 数据集:LaTeX_OCR
- 模型:Qwen2-VL-2B-Instruct
- 在线LaTex公式预览网站:latexlive
- 显存占用:约20GB,如显存不足,请调低per_device_train_batch_size
目录
- 环境配置
- 准备数据集
- 模型下载与加载
- 集成SwanLab
- 开始微调(完整代码)
- 训练结果演示
- 推理LoRA微调后的模型
- 补充
📖 知识点:视觉大模型微调的场景与用法
视觉大模型是指能够支持图片/视频输入的大语言模型,能够极大丰富与LLM的交互方式。
对视觉大模型做微调的一个典型场景,是让它特化成一个更强大、更智能的计算机视觉模型,执行图像分类、目标检测、语义分割、OCR、图像描述任务等等。
并且由于视觉大模型强大的基础能力,所以训练流程变得非常统一——无论是分类、检测还是分割,只需要构建好数据对(图像 -> 文本),都可以用同一套代码完成,相比以往针对不同任务就要构建迥异的训练代码而言,视觉大模型微调要简单粗暴得多,而且效果还更好。
当然,硬币的另一面是要承担更高的计算开销,但在大模型逐渐轻量化的趋势下,可以预想这种训练范式将逐渐成为主流。
🌍 环境配置
环境配置分为三步:
- 确保你的电脑上至少有一张英伟达显卡,并已安装好了CUDA环境。
- 安装Python(版本>=3.8)以及能够调用CUDA加速的PyTorch。
- 安装与Qwen2-VL微调相关的第三方库,可以使用以下命令:
python -m pip install --upgrade pip
# 更换 pypi 源,加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simplepip install modelscope==1.18.0
pip install transformers==4.46.2
pip install sentencepiece==0.2.0
pip install accelerate==1.1.1
pip install datasets==2.18.0
pip install peft==0.13.2
pip install swanlab==0.3.27
pip install qwen-vl-utils==0.0.8
pip install pandas==2.2.2
📚 准备数据集
本节使用的是 LaTex_OCR 数据集,这个数据集包含了大量的数学公式图片,以及对应的LaTex语法字符串。可以看到,下图中的image就是学术公式图,text就是对应的LaTex语法字符串:

将这些LaTex语法字符串粘贴到latexlive中,可以预览对应的数学公式:

了解了数据集结构之后,我们需要做的是将这些数据整理成Qwen2-VL需要的json格式,下面是目标的格式:
[{"id": "identity_1","conversations": [{"role": "user","value": "图片路径"},{"role": "assistant","value": "LaTex公式"}] },
...
]
我们来解读一下这个json:
- id:数据对的编号
- conversations:人类与LLM的对话,类型是列表
- role:角色,user代表人类,assistant代表模型
- content:聊天发送的内容,其中user的value是图片路径,assistant的回复是LaTex公式
接下来让我们下载数据集并进行处理:
- 我们需要做四件事情:
- 通过Modelscope下载LaTex_OCR数据集
- 加载数据集,将图像保存到本地
- 将图像路径和描述文本转换为一个csv文件
- 将csv文件转换为json文件,包含1个训练集和验证集
- 使用下面的代码完成从数据下载到生成csv的过程:
data2csv.py:
# 导入所需的库
from modelscope.msdatasets import MsDataset
import os
import pandas as pdMAX_DATA_NUMBER = 1000
dataset_id = 'AI-ModelScope/LaTeX_OCR'
subset_name = 'default'
split = 'train'dataset_dir = 'LaTeX_OCR'
csv_path = './latex_ocr_train.csv'# 检查目录是否已存在
if not os.path.exists(dataset_dir):# 从modelscope下载COCO 2014图像描述数据集ds = MsDataset.load(dataset_id, subset_name=subset_name, split=split)print(len(ds))# 设置处理的图片数量上限total = min(MAX_DATA_NUMBER, len(ds))# 创建保存图片的目录os.makedirs(dataset_dir, exist_ok=True)# 初始化存储图片路径和描述的列表image_paths = []texts = []for i in range(total):# 获取每个样本的信息item = ds[i]text = item['text']image = item['image']# 保存图片并记录路径image_path = os.path.abspath(f'{dataset_dir}/{i}.jpg')image.save(image_path)# 将路径和描述添加到列表中image_paths.append(image_path)texts.append(text)# 每处理50张图片打印一次进度if (i + 1) % 50 == 0:print(f'Processing {i+1}/{total} images ({(i+1)/total*100:.1f}%)')# 将图片路径和描述保存为CSV文件df = pd.DataFrame({'image_path': image_paths,'text': texts,})# 将数据保存为CSV文件df.to_csv(csv_path, index=False)print(f'数据处理完成,共处理了{total}张图片')else: print(f'{dataset_dir}目录已存在,跳过数据处理步骤')
3. 在同一目录下,用以下代码,将csv文件转换为json文件(训练集+验证集):
csv2json.py:
import pandas as pd
import jsoncsv_path = './latex_ocr_train.csv'
train_json_path = './latex_ocr_train.json'
val_json_path = './latex_ocr_val.json'
df = pd.read_csv(csv_path)
# Create conversation format
conversations = []# Add image conversations
for i in range(len(df)):conversations.append({"id": f"identity_{i+1}","conversations": [{"role": "user","value": f"{df.iloc[i]['image_path']}"},{"role": "assistant", "value": str(df.iloc[i]['text'])}]})# print(conversations)
# Save to JSON
# Split into train and validation sets
train_conversations = conversations[:-4]
val_conversations = conversations[-4:]# Save train set
with open(train_json_path, 'w', encoding='utf-8') as f:json.dump(train_conversations, f, ensure_ascii=False, indent=2)# Save validation set
with open(val_json_path, 'w', encoding='utf-8') as f:json.dump(val_conversations, f, ensure_ascii=False, indent=2)
此时目录下会多出3个文件:
- latex_ocr_train.csv
- latex_ocr_train.json
- latex_ocr_val.json
至此,我们完成了数据集的准备。
🤖 模型下载与加载
这里我们使用modelscope下载Qwen2-VL-2B-Instruct模型,然后把它加载到Transformers中进行训练:
from modelscope import snapshot_download, AutoTokenizer
from transformers import TrainingArguments, Trainer, DataCollatorForSeq2Seq, Qwen2VLForConditionalGeneration, AutoProcessor
import torch# 在modelscope上下载Qwen2-VL模型到本地目录下
model_dir = snapshot_download("Qwen/Qwen2-VL-2B-Instruct", cache_dir="./", revision="master")# 使用Transformers加载模型权重
tokenizer = AutoTokenizer.from_pretrained("./Qwen/Qwen2-VL-2B-Instruct/", use_fast=False, trust_remote_code=True)
# 特别的,Qwen2-VL-2B-Instruct模型需要使用Qwen2VLForConditionalGeneration来加载
model = Qwen2VLForConditionalGeneration.from_pretrained("./Qwen/Qwen2-VL-2B-Instruct/", device_map="auto", torch_dtype=torch.bfloat16, trust_remote_code=True,)
model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法
模型大小为 4.5GB,下载模型大概需要 5 分钟。
🐦 集成SwanLab

SwanLab 是一个开源的模型训练记录工具,常被称为"中国版 Weights&Biases + Tensorboard"。SwanLab面向AI研究者,提供了训练可视化、自动日志记录、超参数记录、实验对比、多人协同等功能。在SwanLab上,研究者能基于直观的可视化图表发现训练问题,对比多个实验找到研究灵感,并通过在线链接的分享与基于组织的多人协同训练,打破团队沟通的壁垒。
SwanLab与Transformers已经做好了集成,用法是在Trainer的callbacks参数中添加SwanLabCallback实例,就可以自动记录超参数和训练指标,简化代码如下:
from swanlab.integration.transformers import SwanLabCallback
from transformers import Trainerswanlab_callback = SwanLabCallback()trainer = Trainer(...callbacks=[swanlab_callback],
)
首次使用SwanLab,需要先在官网注册一个账号,然后在用户设置页面复制你的API Key,然后在训练开始提示登录时粘贴即可,后续无需再次登录:

更多用法可参考快速开始、Transformers集成。
🚀 开始微调(完整代码)
查看可视化训练过程:ZeyiLin/Qwen2-VL-ft-latexocr
本节代码做了以下几件事:
- 下载并加载Qwen2-VL-2B-Instruct模型
- 加载数据集,取前996条数据参与训练,4条数据进行主观评测
- 配置Lora,参数为r=64, lora_alpha=16, lora_dropout=0.05
- 使用SwanLab记录训练过程,包括超参数、指标和最终的模型输出结果
- 训练2个epoch
开始执行代码时的目录结构应该是:
|———— train.py
|———— data2csv.py
|———— csv2json.py
|———— latex_ocr_train.csv
|———— latex_ocr_train.json
|———— latex_ocr_val.json
完整代码如下
train.py:
import torch
from datasets import Dataset
from modelscope import snapshot_download, AutoTokenizer
from swanlab.integration.transformers import SwanLabCallback
from qwen_vl_utils import process_vision_info
from peft import LoraConfig, TaskType, get_peft_model, PeftModel
from transformers import (TrainingArguments,Trainer,DataCollatorForSeq2Seq,Qwen2VLForConditionalGeneration,AutoProcessor,
)
import swanlab
import json
import osprompt = "你是一个LaText OCR助手,目标是读取用户输入的照片,转换成LaTex公式。"
model_id = "Qwen/Qwen2-VL-2B-Instruct"
local_model_path = "./Qwen/Qwen2-VL-2B-Instruct"
train_dataset_json_path = "latex_ocr_train.json"
val_dataset_json_path = "latex_ocr_val.json"
output_dir = "./output/Qwen2-VL-2B-LatexOCR"
MAX_LENGTH = 8192def process_func(example):"""将数据集进行预处理"""input_ids, attention_mask, labels = [], [], []conversation = example["conversations"]image_file_path = conversation[0]["value"]output_content = conversation[1]["value"]messages = [{"role": "user","content": [{"type": "image","image": f"{image_file_path}","resized_height": 500,"resized_width": 100,},{"type": "text", "text": prompt},],}]text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) # 获取文本image_inputs, video_inputs = process_vision_info(messages) # 获取数据数据(预处理过)inputs = processor(text=[text],images=image_inputs,videos=video_inputs,padding=True,return_tensors="pt",)inputs = {key: value.tolist() for key, value in inputs.items()} #tensor -> list,为了方便拼接instruction = inputsresponse = tokenizer(f"{output_content}", add_special_tokens=False)input_ids = (instruction["input_ids"][0] + response["input_ids"] + [tokenizer.pad_token_id])attention_mask = instruction["attention_mask"][0] + response["attention_mask"] + [1]labels = ([-100] * len(instruction["input_ids"][0])+ response["input_ids"]+ [tokenizer.pad_token_id])if len(input_ids) > MAX_LENGTH: # 做一个截断input_ids = input_ids[:MAX_LENGTH]attention_mask = attention_mask[:MAX_LENGTH]labels = labels[:MAX_LENGTH]input_ids = torch.tensor(input_ids)attention_mask = torch.tensor(attention_mask)labels = torch.tensor(labels)inputs['pixel_values'] = torch.tensor(inputs['pixel_values'])inputs['image_grid_thw'] = torch.tensor(inputs['image_grid_thw']).squeeze(0) #由(1,h,w)变换为(h,w)return {"input_ids": input_ids, "attention_mask": attention_mask, "labels": labels,"pixel_values": inputs['pixel_values'], "image_grid_thw": inputs['image_grid_thw']}def predict(messages, model):# 准备推理text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)image_inputs, video_inputs = process_vision_info(messages)inputs = processor(text=[text],images=image_inputs,videos=video_inputs,padding=True,return_tensors="pt",)inputs = inputs.to("cuda")# 生成输出generated_ids = model.generate(**inputs, max_new_tokens=MAX_LENGTH)generated_ids_trimmed = [out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)]output_text = processor.batch_decode(generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False)return output_text[0]# 在modelscope上下载Qwen2-VL模型到本地目录下
model_dir = snapshot_download(model_id, cache_dir="./", revision="master")# 使用Transformers加载模型权重
tokenizer = AutoTokenizer.from_pretrained(local_model_path, use_fast=False, trust_remote_code=True)
processor = AutoProcessor.from_pretrained(local_model_path)origin_model = Qwen2VLForConditionalGeneration.from_pretrained(local_model_path, device_map="auto", torch_dtype=torch.bfloat16, trust_remote_code=True,)
origin_model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法# 处理数据集:读取json文件
train_ds = Dataset.from_json(train_dataset_json_path)
train_dataset = train_ds.map(process_func)# 配置LoRA
config = LoraConfig(task_type=TaskType.CAUSAL_LM,target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],inference_mode=False, # 训练模式r=64, # Lora 秩lora_alpha=16, # Lora alaph,具体作用参见 Lora 原理lora_dropout=0.05, # Dropout 比例bias="none",
)# 获取LoRA模型
train_peft_model = get_peft_model(origin_model, config)# 配置训练参数
args = TrainingArguments(output_dir=output_dir,per_device_train_batch_size=4,gradient_accumulation_steps=4,logging_steps=10,logging_first_step=10,num_train_epochs=2,save_steps=100,learning_rate=1e-4,save_on_each_node=True,gradient_checkpointing=True,report_to="none",
)# 设置SwanLab回调
swanlab_callback = SwanLabCallback(project="Qwen2-VL-ft-latexocr",experiment_name="7B-1kdata",config={"model": "https://modelscope.cn/models/Qwen/Qwen2-VL-7B-Instruct","dataset": "https://modelscope.cn/datasets/AI-ModelScope/LaTeX_OCR/summary",# "github": "https://github.com/datawhalechina/self-llm","model_id": model_id,"train_dataset_json_path": train_dataset_json_path,"val_dataset_json_path": val_dataset_json_path,"output_dir": output_dir,"prompt": prompt,"train_data_number": len(train_ds),"token_max_length": MAX_LENGTH,"lora_rank": 64,"lora_alpha": 16,"lora_dropout": 0.1,},
)# 配置Trainer
trainer = Trainer(model=train_peft_model,args=args,train_dataset=train_dataset,data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),callbacks=[swanlab_callback],
)# 开启模型训练
trainer.train()# ====================测试===================
# 配置测试参数
val_config = LoraConfig(task_type=TaskType.CAUSAL_LM,target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],inference_mode=True, # 训练模式r=64, # Lora 秩lora_alpha=16, # Lora alaph,具体作用参见 Lora 原理lora_dropout=0.05, # Dropout 比例bias="none",
)# 获取测试模型,从output_dir中获取最新的checkpoint
load_model_path = f"{output_dir}/checkpoint-{max([int(d.split('-')[-1]) for d in os.listdir(output_dir) if d.startswith('checkpoint-')])}"
print(f"load_model_path: {load_model_path}")
val_peft_model = PeftModel.from_pretrained(origin_model, model_id=load_model_path, config=val_config)# 读取测试数据
with open(val_dataset_json_path, "r") as f:test_dataset = json.load(f)test_image_list = []
for item in test_dataset:image_file_path = item["conversations"][0]["value"]label = item["conversations"][1]["value"]messages = [{"role": "user", "content": [{"type": "image", "image": image_file_path,"resized_height": 100,"resized_width": 500, },{"type": "text","text": prompt,}]}]response = predict(messages, val_peft_model)print(f"predict:{response}")print(f"gt:{label}\n")test_image_list.append(swanlab.Image(image_file_path, caption=response))swanlab.log({"Prediction": test_image_list})# 在Jupyter Notebook中运行时要停止SwanLab记录,需要调用swanlab.finish()
swanlab.finish()
看到下面的进度条即代表训练开始:

💻 训练结果演示
详细训练过程请看这里:ZeyiLin/Qwen2-VL-ft-latexocr

从SwanLab图表中我们可以看到,学习率的下降策略是线性下降,loss随epoch呈现下降趋势,同时grad_norm也呈现下降趋势。这种形态反映了模型的训练效果是符合预期的。
在Prediction图表中记录着模型最终的输出结果,可以看到模型在回答的风格已经是标准的LaTex语法。

对这四个结果进行验证,跟输入图像完成一致。


那么与没有微调的模型进行效果对比,我们选择997.jpg:

没有微调:(10,10),(989,989)
微调后:\mathrm { t r i e s } \left( \vec { \Phi } _ { A } ^ { ( 3 ) } \right) = ( g h _ { 1 } \left( \Phi ^ { A } \right) + 1 , g h _ { 2 } \left( \Phi ^ { A } \right) + 1 , g h _ { 3 } \left( \Phi ^ { A } \right) )
可以看到没有微调的模型,对于这张图片的输出明显是错误的;
而微调后的模型,有着非常完美表现:

🧐 推理LoRA微调后的模型
加载lora微调后的模型,并进行推理:
from transformers import Qwen2VLForConditionalGeneration, AutoProcessor
from qwen_vl_utils import process_vision_info
from peft import PeftModel, LoraConfig, TaskTypeprompt = "你是一个LaText OCR助手,目标是读取用户输入的照片,转换成LaTex公式。"
local_model_path = "./Qwen/Qwen2-VL-2B-Instruct"
lora_model_path = "./output/Qwen2-VL-2B-LatexOCR/checkpoint-124"
test_image_path = "./LaTeX_OCR/997.jpg"config = LoraConfig(task_type=TaskType.CAUSAL_LM,target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],inference_mode=True,r=64, # Lora 秩lora_alpha=16, # Lora alaph,具体作用参见 Lora 原理lora_dropout=0.05, # Dropout 比例bias="none",
)# default: Load the model on the available device(s)
model = Qwen2VLForConditionalGeneration.from_pretrained(local_model_path, torch_dtype="auto", device_map="auto"
)model = PeftModel.from_pretrained(model, model_id=f"{lora_model_path}", config=config)
processor = AutoProcessor.from_pretrained(local_model_path)messages = [{"role": "user","content": [{"type": "image","image": test_image_path,"resized_height": 100,"resized_width": 500,},{"type": "text", "text": f"{prompt}"},],}
]# Preparation for inference
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True
)
image_inputs, video_inputs = process_vision_info(messages)
inputs = processor(text=[text],images=image_inputs,videos=video_inputs,padding=True,return_tensors="pt",
)
inputs = inputs.to("cuda")# Inference: Generation of the output
generated_ids = model.generate(**inputs, max_new_tokens=8192)
generated_ids_trimmed = [out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
]
output_text = processor.batch_decode(generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
)print(output_text[0])
补充
详细硬件配置和参数说明
使用4张A100 40GB显卡(总显存占用大约),batch size为4,gradient accumulation steps为4,训练2个epoch的用时为8分钟51秒。


注意
- 在微调脚本中,
val_peft_model加载的是一共固定的checkpoint文件,如果你添加了数据或超参数,请根据实际情况修改checkpoint文件路径。
相关文章:
Qwen2-VL视觉大模型微调实战:LaTex公式OCR识别任务(完整代码)
《SwanLab机器学习实战教程》是一个主打「开箱即用」的AI训练系列教程,我们致力于提供完善的数据集、源代码、实验记录以及环境安装方式,手把手帮助你跑起训练,解决问题。 Qwen2-VL是通义千问团队最近开源的大语言模型,由阿里云通…...
「Mac玩转仓颉内测版42」小学奥数篇5 - 圆和矩形的面积计算
本篇将通过 Python 和 Cangjie 双语解决简单的几何问题:计算圆的面积和矩形的面积。通过这道题,学生将掌握如何使用公式解决几何问题,并学会用编程实现数学公式。 关键词 小学奥数Python Cangjie几何计算 一、题目描述 编写一个程序&#…...
Groom Blender to UE5
Groom Blender to UE5 - Character & Animation - Epic Developer Community Forums Hello, 你好, While exporting my “groom” from blender to UE5, I notice that the curves have a minimal resolution in Unreal. However I would like to get the same …...
开发一套ERP 第十弹 图片作为配置文件,本地读取图片,定时更新图片类型
echo Hello World在同一数据库中在建一个图床数据表,产品一,一对应,图片命名 最优的方案,使用 rust 在构建一个 http server 用于管理非数据库资源,也可以将来对接不同的图床,部署方便 考虑到数据库资源和图片资源,都可以被远程访问这种方法最佳...
第七十六条:努力保持故障的原子性
当对象抛出异常之后,通常我们期望这个对象仍然保持在一种定义良好的可用状态之中,即使失败是发生在执行某个操作的过程中间。对于受检的异常而言,这尤为重要,因为调用者期望能从这种异常中进行恢复。一般而言,失败的方…...
Word分栏后出现空白页解决方法
Word分栏后出现空白页解决方法 只需要在后面的空白页设置相同的页面布局(分栏格式),然后按Ctrl backspace即可删除该空白页。 参考文章:Word分栏出现空白怎么解决。...
基于HTML和CSS的校园网页设计与实现
摘要 随着计算机、互联网与通信技术的进步,Internet在人们的学习、工作和生活中的地位也变得越来越高,校园网站已经成为学校与学生,学生与学生之间交流沟通的重要平台,对同学了解学校内发生的各种事情起到了重要的作用。学校网站…...
【算法day7】字符串:反转与替换
题目引用 反转字符串反转字符串II替换数字 1.反转字符串 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 示例 1&am…...
分布式存储厂商
分布式存储 以下是对分布式存储厂商XSKY星辰天合、IOMesh(SmartX)、SmartX的深度对比: 1. XSKY星辰天合 产品与服务:XSKY星辰天合提供统一存储平台,支持块、文件和对象存储服务。已为近2400家大型政企机构实施部署&…...
合合信息扫描全能王线下体验活动:科技与人文的完美交融
文章目录 前言签到欢迎仪式产品体验智能高清滤镜去除透字效果照片高清修复 破冰行动会议感受 前言 作为合合信息旗下扫描全能王的忠实粉丝,上周,我很荣幸参与了扫描全能王“扫出你的能量buff”快闪活动及技术交流会。这次活动的不仅让我对这款强大的文档…...
单链表在Go语言中的实现与操作
简介 单链表是一种基本的线性数据结构,由节点组成,每个节点存储数据和指向下一个节点的指针。今天,我们将深入探讨如何在Go语言中实现和操作单链表。 单链表的优缺点 优点: 动态内存分配,灵活性高。插入和删除节点操…...
网关整合sentinel无法读取nacos配置问题分析
sentinel无法读取nacos配置问题分析 1.spring-cloud-gateway整合sentinel2.问题现象3.原因猜测4.源码分析4. 结语 最近公司需要上线一个集约项目,虽然为内网项目,但曾经有过内网被攻破,导致内部系统被攻击的案例,且集约系统同时在…...
简化XPath表达式的方法与实践
XPath表达式用于在XML或HTML文档中定位元素。有时候,XPath表达式可能会变得非常冗长和复杂,这不仅难以阅读和维护,而且也可能影响性能。因此,学会如何简化XPath表达式是非常重要的。本文将介绍几种简化XPath表达式的方法ÿ…...
【文件下载】接口传递文件成功和失败时,前端的处理方式
问题 使用bold类型从后端接口获取文件流,获取成功的时候通过a标签下载;失败的时候,后端返回的是json,这个时候就无法向用户展示后端返回的错误提示信息。 思路 根据返回类型是否为 application/json 区分是否返回成功ÿ…...
html+css网页设计马林旅行社移动端4个页面
htmlcss网页设计马林旅行社移动端4个页面 网页作品代码简单,可使用任意HTML辑软件(如:Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作)。 获取源码 1&#…...
视频 的 音频通道提取 以及 视频转URL 的在线工具!
视频 的 音频通道提取 以及 视频转URL 的在线工具! 工具地址: https://www.lingyuzhao.top/toolsPage/VideoTo.html 它提供了便捷的方法来处理视频文件,具体来说是帮助用户从视频中提取音频轨道,并将视频转换为可以通过网络访问的URL链接。无…...
容易被遗忘的测试用例
网络服务器启动了吗?应用程序服务器启动了吗?数据库上线了吗?测试数据是否预先加载到数据库中?每当我们准备开始测试应用程序时,一切都应该已经准备妥当。 然而,当测试开始后,我们可能会漏掉一些…...
uni-app写的微信小程序如何实现账号密码登录后获取token,并且每天的第一次登录后都会直接获取参数而不是耀重新登录(2)
接uni-app写的微信小程序如何实现账号密码登录后获取token,并且每天的第一次登录后都会直接获取参数而不是耀重新登录(1), 在main.js中 import App from ./App// #ifndef VUE3 import Vue from vue import ./uni.promisify.adap…...
统计中间件稳定性指标
目前订单业务域涉及中间件:MySQL、Redis、TiDB、MQ、ES。(遗漏项请补充) 一、RDS 资源使用率 实例ID实例名称规格maxCPUavgCPUmaxDISKmaxIOPSavgIOPS活跃会话maxTPSavgTPSmaxQPSavgQPS实例风险 慢查询 慢查询会消耗大量的系统资源&#x…...
移动端使用REM插件postcss之postcss-px2rem
目录 一、概念 二、核心特性 三、功能 四、插件模块 注意事项: 五、使用 安装: 配置 一、概念 工具类型:PostCSS是一个基于JavaScript的工具,用于转换CSS的工作流。核心理念:PostCSS的核心理念是“转换而非替…...
相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
Linux系统部署KES
1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘…...
《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...
前端工具库lodash与lodash-es区别详解
lodash 和 lodash-es 是同一工具库的两个不同版本,核心功能完全一致,主要区别在于模块化格式和优化方式,适合不同的开发环境。以下是详细对比: 1. 模块化格式 lodash 使用 CommonJS 模块格式(require/module.exports&a…...
python基础语法Ⅰ
python基础语法Ⅰ 常量和表达式变量是什么变量的语法1.定义变量使用变量 变量的类型1.整数2.浮点数(小数)3.字符串4.布尔5.其他 动态类型特征注释注释是什么注释的语法1.行注释2.文档字符串 注释的规范 常量和表达式 我们可以把python当作一个计算器,来进行一些算术…...
【threejs】每天一个小案例讲解:创建基本的3D场景
代码仓 GitHub - TiffanyHoo/three_practices: Learning three.js together! 可自行clone,无需安装依赖,直接liver-server运行/直接打开chapter01中的html文件 运行效果图 知识要点 核心三要素 场景(Scene) 使用 THREE.Scene(…...
break 语句和 continue 语句
break语句和continue语句都具有跳转作用,可以让代码不按既有的顺序执行 break break语句用于跳出代码块或循环 1 2 3 4 5 6 for (var i 0; i < 5; i) { if (i 3){ break; } console.log(i); } continue continue语句用于立即终…...
