MiniCPM-o-4.5-nvidia-FlagOS入门实践:使用Python爬虫构建模型微调数据集

MiniCPM-o-4.5-nvidia-FlagOS入门实践:使用Python爬虫构建模型微调数据集 MiniCPM-o-4.5-nvidia-FlagOS入门实践使用Python爬虫构建模型微调数据集想给MiniCPM-o-4.5-nvidia-FlagOS模型做微调让它更懂你的专业领域第一步也是最关键的一步就是准备一份高质量的数据集。数据质量直接决定了模型微调后的表现但高质量、结构化的数据往往不易获得。今天我们就来聊聊一个非常实用的方法用Python爬虫从互联网的公开资源里自己动手构建一个专属的微调数据集。这听起来有点技术含量但别担心我会用最直白的方式带你一步步走完从数据采集、清洗到格式化的全过程。整个过程就像做一道菜找到好食材爬取数据、清洗处理数据清洗、最后装盘格式化输出每一步都有讲究。1. 准备工作明确目标与搭建环境在开始写代码之前我们需要先想清楚两件事要爬什么以及用什么工具。1.1 明确数据需求与来源微调数据集不是数据越多越好而是越“对”越好。你需要先想清楚微调目标是什么是想让模型学会写某个领域的专业文章如科技评论、产品评测还是想让它精通某种对话风格如客服问答、创意写作目标决定了你需要什么样的文本。数据从哪里来选择公开、合法、且内容质量较高的网站。例如知识类维基百科、特定领域的百科站点。社区问答Stack Overflow、知乎需注意其API或Robots协议。新闻资讯各大新闻网站的科技、财经等垂直频道。开源数据集Hugging Face Datasets、Kaggle等平台上的相关文本数据集。电子书与论文Project Gutenberg、arXiv等。重要提示务必遵守目标网站的robots.txt协议尊重版权仅爬取允许爬取的内容并控制请求频率避免对目标网站服务器造成压力。本教程旨在学习技术流程请务必用于合法的个人学习与研究目的。1.2 搭建Python爬虫环境我们需要几个得力的Python库。打开你的终端或命令提示符创建一个新的项目目录然后安装它们# 创建项目目录并进入 mkdir minicpm_finetune_data cd minicpm_finetune_data # 创建虚拟环境推荐 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # Mac/Linux: source venv/bin/activate # 安装必要的库 pip install requests beautifulsoup4 pandas tqdm简单介绍一下这几个“帮手”requests用来向网站发送请求获取网页的HTML代码。beautifulsoup4用来解析HTML代码像一把手术刀精准地从中提取出我们需要的文字、标题、链接等信息。pandas数据处理和分析的瑞士军刀清洗、去重、保存数据到文件都非常方便。tqdm一个进度条库在爬取大量页面时能让你清晰地看到进度避免感觉程序“卡死了”。2. 动手编写爬虫以博客文章为例理论说再多不如动手试一次。我们假设要爬取某个技术博客例如一个模拟的、结构清晰的博客站上的文章来构建一个“技术教程”风格的文本数据集。2.1 分析网页结构首先你需要用浏览器如Chrome打开目标网站的一篇文章页面。按下F12打开开发者工具使用“元素选择”工具箭头图标点击文章标题和正文。你会发现网页内容是被包裹在各种HTML标签里的比如h1是标题article或某个特定的div classcontent里是正文。我们的任务就是告诉程序“去找到这个标签把里面的文本拿出来。”假设目标博客的文章页结构如下标题位于h1 classpost-title标签内。正文内容位于div classarticle-content标签内。2.2 编写单页爬取函数现在我们来写第一个函数它的任务是抓取一篇文章的标题和内容。import requests from bs4 import BeautifulSoup import time import pandas as pd from tqdm import tqdm def fetch_article(url): 抓取单篇文章的标题和正文。 参数: url (str): 文章页面的URL 返回: dict: 包含标题和正文的字典如果失败则返回None headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 } try: # 发送请求设置超时时间 response requests.get(url, headersheaders, timeout10) response.raise_for_status() # 如果状态码不是200抛出异常 response.encoding response.apparent_encoding # 自动识别编码 except requests.RequestException as e: print(f请求失败 {url}: {e}) return None # 使用BeautifulSoup解析HTML soup BeautifulSoup(response.text, html.parser) # 根据之前分析的网页结构提取数据 # 这里的选择器需要根据实际网站调整 title_elem soup.find(h1, class_post-title) content_elem soup.find(div, class_article-content) if not title_elem or not content_elem: print(f未在 {url} 中找到所需内容可能结构已变化。) return None title title_elem.get_text(stripTrue) # 获取所有段落文本并用换行符连接保持一定格式 content \n.join([p.get_text(stripTrue) for p in content_elem.find_all(p) if p.get_text(stripTrue)]) if content: # 确保内容不为空 return {title: title, content: content, url: url} else: return None # 测试单页爬取 test_url https://example-blog.com/post/123 # 请替换为真实的测试URL # article_data fetch_article(test_url) # if article_data: # print(f标题: {article_data[title]}) # print(f内容预览: {article_data[content][:200]}...) # 打印前200字符关键点设置Headers模拟浏览器访问避免被一些简单的反爬机制拦截。异常处理网络请求可能失败代码必须能妥善处理这些情况避免程序崩溃。动态调整选择器find方法里的标签名和类名 (class_) 必须根据你实际要爬的网站来修改。这是爬虫编写中最需要灵活变通的部分。2.3 获取文章列表并批量爬取通常我们需要从一个文章列表页如博客首页、分类页获取所有文章的链接然后逐个爬取。def fetch_article_list(list_url, max_pages5): 从列表页获取文章链接。 参数: list_url (str): 列表页URL可能支持分页如 https://example-blog.com/page/1 max_pages (int): 最多爬取多少页列表 返回: list: 文章URL列表 article_urls [] for page in range(1, max_pages 1): # 构造分页URL根据网站实际规则调整 if page 1: current_url list_url else: current_url f{list_url.rstrip(/)}/page/{page} # 假设分页规则如此 print(f正在抓取列表页: {current_url}) try: resp requests.get(current_url, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) # 假设文章链接在 a classpost-link 标签的 href 属性里 # 同样选择器需要根据实际网站调整 link_elements soup.find_all(a, class_post-link, hrefTrue) for link in link_elements: article_url link[href] # 确保是完整的URL处理相对链接 if not article_url.startswith(http): article_url requests.compat.urljoin(list_url, article_url) if article_url not in article_urls: article_urls.append(article_url) # 简单判断是否还有下一页这里可以根据网站情况优化 time.sleep(1) # 礼貌性延迟避免请求过快 except Exception as e: print(f抓取列表页 {current_url} 时出错: {e}) break return article_urls def batch_fetch_articles(url_list, output_fileraw_articles.jsonl): 批量爬取文章并保存。 参数: url_list (list): 文章URL列表 output_file (str): 输出文件名 articles_data [] print(f开始批量爬取 {len(url_list)} 篇文章...) for url in tqdm(url_list, desc爬取进度): article fetch_article(url) if article: articles_data.append(article) time.sleep(2) # 非常重要的延迟尊重网站避免被封IP # 使用pandas保存为JSON Lines格式便于后续处理 if articles_data: df pd.DataFrame(articles_data) df.to_json(output_file, orientrecords, linesTrue, force_asciiFalse) print(f数据已保存至 {output_file}共 {len(df)} 条记录。) return df else: print(未爬取到有效数据。) return None # 模拟工作流程 # 1. 获取文章链接列表 # list_url https://example-blog.com # all_urls fetch_article_list(list_url, max_pages3) # print(f获取到 {len(all_urls)} 个文章链接。) # # 2. 批量爬取文章内容 # if all_urls: # raw_data_df batch_fetch_articles(all_urls[:10]) # 先测试前10篇核心技巧延迟 (time.sleep)在请求之间加入几秒的延迟这是网络爬虫的“礼仪”能极大降低被封IP的风险。进度条 (tqdm)处理大量URL时进度条能让你安心。增量保存对于海量数据可以考虑每爬取一定数量就保存一次防止程序意外中断导致前功尽弃。3. 数据清洗与格式化打造高质量数据集爬下来的原始数据是“毛坯房”里面可能有很多“垃圾”广告文本、导航栏内容、无关的脚本、重复的空白字符等等。我们需要把它变成“精装房”。3.1 基础清洗与去重def clean_text(text): 清洗单段文本。 if not isinstance(text, str): return # 移除多余的空白字符包括换行符、制表符等 import re # 将多个连续空白字符包括换行替换为单个空格 text re.sub(r\s, , text) # 移除首尾空格 text text.strip() # 这里可以添加更多清洗规则例如 # - 移除特定的广告关键词 # - 过滤掉过短的句子可能是导航项 # - 使用正则表达式移除HTML实体如 nbsp; return text def clean_and_deduplicate_data(input_fileraw_articles.jsonl, output_filecleaned_articles.jsonl): 加载原始数据进行清洗和去重。 try: df pd.read_json(input_file, linesTrue) except FileNotFoundError: print(f文件 {input_file} 不存在。) return None print(f原始数据共 {len(df)} 条。) # 1. 清洗标题和内容 df[title] df[title].apply(clean_text) df[content] df[content].apply(clean_text) # 2. 过滤掉内容为空或过短的文章例如长度小于100字符 df df[df[content].str.len() 100] print(f过滤掉空/过短内容后剩余 {len(df)} 条。) # 3. 基于内容去重简单基于内容字符串完全匹配 df df.drop_duplicates(subset[content]) print(f去重后剩余 {len(df)} 条。) # 4. 保存清洗后的数据 df.to_json(output_file, orientrecords, linesTrue, force_asciiFalse) print(f清洗后的数据已保存至 {output_file}) return df # 执行清洗 # cleaned_df clean_and_deduplicate_data()3.2 转换为模型微调格式不同的模型微调框架需要不同的数据格式。常见的有JSON Lines (.jsonl)和纯文本 (.txt)。对于像MiniCPM-o这类模型通常需要将文本组织成特定的对话或指令格式。假设我们采用简单的“指令-输出”格式来构建数据def convert_to_instruction_format(df, output_filefinetune_data.jsonl): 将清洗后的数据转换为指令微调格式。 这里采用一个简单的格式将文章标题作为指令instruction文章内容作为输出output。 formatted_data [] for _, row in df.iterrows(): # 构建一条训练样本 # 你可以根据需求设计更复杂的指令例如“请写一篇关于{主题}的技术文章。” instruction f请根据以下主题撰写一篇详细的技术文章{row[title]} output row[content] # 构建符合常见微调框架如 transformers 的 SFTTrainer的格式 # 这里是一个通用格式示例具体需参考MiniCPM-o官方微调脚本的要求 formatted_entry { instruction: instruction, input: , # 本例中没有额外的输入上下文故为空 output: output, # 可以添加其他元数据 source: row[url] } formatted_data.append(formatted_entry) # 保存为JSONL import json with open(output_file, w, encodingutf-8) as f: for entry in formatted_data: f.write(json.dumps(entry, ensure_asciiFalse) \n) print(f已转换并保存 {len(formatted_data)} 条指令数据至 {output_file}) # 执行转换 # if cleaned_df is not None: # convert_to_instruction_format(cleaned_df)关键点convert_to_instruction_format函数中的格式是示例。在开始大规模爬取和清洗之前务必先查阅MiniCPM-o-4.5-nvidia-FlagOS模型的官方文档或微调示例确认其要求的具体数据格式例如是否需要特定的字段名如conversations列表是否需要添加system提示词等。格式不对后续训练就无法进行。4. 完整流程回顾与实用建议走完一遍流程你可能已经成功构建了一个小型的、干净的数据集。整个过程可以总结为以下几个核心步骤规划与探查想清楚要什么数据找到合适的、合法的来源并用浏览器工具分析网站结构。单点突破编写能成功爬取单篇文章标题和正文的函数。这是基础务必测试通过。批量抓取编写从列表页获取所有文章链接的逻辑并整合单页爬取函数进行批量作业。切记添加延迟保持礼貌。深度清洗对爬下来的原始文本进行清洗去空白、去广告、过滤垃圾内容和去重提升数据质量。格式转换将清洗后的数据按照目标模型微调要求的具体格式进行转换和保存。这里还有一些实用的建议能让你少走弯路从简单网站开始找结构清晰、没有复杂反爬机制的网站练手比如一些静态博客GitHub Pages, Hugo等生成的。处理反爬机制如果遇到403错误或需要登录可能需要处理Cookies、Session甚至使用更高级的库如selenium来模拟浏览器。但始终优先考虑遵守网站规则。数据质量优先不要盲目追求数据量。1000条高质量、相关性强的数据远胜于10万条充满噪声的垃圾数据。在清洗阶段可以多花些心思。备份与版本控制保存原始数据 (raw_)、清洗后数据 (cleaned_) 和最终格式数据 (finetune_)。使用Git来管理你的代码和数据清洗脚本。伦理与法律再次强调仅爬取公开且允许爬取的数据用于个人学习与研究。不要爬取个人隐私信息、受版权严格保护的内容或用于任何商业牟利及侵害他人权益的用途。构建数据集是模型微调中既基础又充满挑战的一环。通过Python爬虫你获得了一种主动获取特定领域知识的能力。虽然过程中会遇到网站结构变化、反爬策略等问题但解决问题的过程本身也是宝贵的学习经验。当你用自己亲手构建的数据集成功微调出一个更“懂你”的模型时那种成就感会告诉你这一切都是值得的。接下来你就可以拿着这份格式正确的finetune_data.jsonl文件去探索MiniCPM-o-4.5-nvidia-FlagOS的微调脚本了。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。