通义千问1.5-1.8B-Chat-GPTQ-Int4 WebUI实战:爬虫数据清洗与信息摘要生成

你是不是也遇到过这种情况?辛辛苦苦写了个爬虫,吭哧吭哧跑了一晚上,结果拿到的数据乱七八糟——正文里混着广告、重复内容一大堆、关键信息散落在各个角落,整理起来比爬数据还累。

我之前处理一个新闻网站的数据,爬下来几千条,光是人工筛选和整理摘要就花了两天时间,眼睛都快看花了。后来我发现,其实这种重复性高、规则性强的文本处理工作,交给合适的大语言模型来做,效率能提升几十倍。

今天我就跟你分享一下,怎么用通义千问1.5-1.8B-Chat这个轻量级模型,通过WebUI来搞定爬虫数据的清洗和摘要生成。这个模型虽然参数不大,但处理这类结构化信息提取任务特别合适,而且经过GPTQ-Int4量化后,对硬件要求很低,普通电脑就能跑起来。

1. 为什么用大模型处理爬虫数据?

你可能觉得,爬虫数据清洗用正则表达式或者写点规则不就行了吗?确实,对于结构非常规整的网站,传统方法很有效。但现实情况往往更复杂。

我最近处理过一个电商商品页面,不同商家的描述格式千差万别。有的把价格放在“售价:”后面,有的写“价格:”,还有的直接写“¥99”。用规则去匹配,得写一堆正则,还经常漏掉一些变体。更麻烦的是,有些页面会有“推荐商品”、“看了又看”这种模块,爬下来的数据里混着不相关的内容。

这时候大模型的优势就体现出来了。它不需要你告诉它价格可能出现在哪些关键词后面,它自己能从上下文中理解什么是价格。你只需要告诉它:“从这段文本里提取商品名称、价格和主要特点”,它就能给你整理得明明白白。

通义千问1.5-1.8B-Chat这个版本,在信息提取和摘要生成上表现不错,关键是它比较“听话”——你让它输出JSON格式,它基本上就会按JSON格式来,这对于后续的数据入库特别友好。

2. 环境准备与WebUI部署

先说说怎么把这个模型跑起来。我选择的是GPTQ-Int4量化版本,这个版本在几乎不损失精度的情况下,把模型大小压缩了很多,运行起来内存占用小,速度也快。

2.1 基础环境搭建

如果你已经有Python环境,安装起来很简单。我建议用conda创建一个独立环境,避免包冲突。

# 创建并激活环境
conda create -n qwen_clean python=3.10
conda activate qwen_clean

# 安装基础依赖
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118  # 根据你的CUDA版本调整
pip install transformers accelerate

2.2 WebUI部署与模型加载

现在有很多现成的WebUI框架可以用,我比较喜欢用Gradio,它简单直观,部署也方便。

# webui_demo.py
import gradio as gr
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

# 加载模型和分词器
model_name = "Qwen/Qwen1.5-1.8B-Chat-GPTQ-Int4"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    device_map="auto"
)

def chat_with_model(message, history):
    # 构建对话格式
    messages = [
        {"role": "system", "content": "你是一个专业的数据处理助手,擅长从文本中提取结构化信息和生成简洁摘要。"},
        {"role": "user", "content": message}
    ]
    
    # 应用聊天模板
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    
    # 生成回复
    inputs = tokenizer(text, return_tensors="pt").to(model.device)
    with torch.no_grad():
        outputs = model.generate(**inputs, max_new_tokens=512)
    
    response = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
    return response

# 创建Gradio界面
demo = gr.ChatInterface(
    fn=chat_with_model,
    title="通义千问数据清洗助手",
    description="输入爬虫获取的原始文本,我会帮你提取关键信息和生成摘要"
)

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)

运行这个脚本,在浏览器打开 http://localhost:7860 就能看到Web界面了。界面很简洁,左边是聊天区域,右边可以调整一些参数。

第一次运行会下载模型,大概需要3-4GB的磁盘空间。下载完成后,后续启动就很快了。在我的GTX 3060笔记本上,生成一段摘要大概需要2-3秒,完全能满足批量处理的需求。

3. 设计有效的Prompt模板

用大模型处理数据,最关键的就是Prompt设计。设计得好,模型输出稳定可靠;设计得不好,每次输出格式都不一样,后续处理就麻烦了。

3.1 信息提取Prompt设计

对于爬虫数据清洗,我通常设计两种类型的Prompt:一种是提取特定字段,另一种是自由提取关键信息。

先看个商品信息提取的例子:

def extract_product_info(raw_text):
    prompt = f"""
请从以下商品描述文本中提取结构化信息,并以JSON格式输出。

要求:
1. 提取商品名称、价格、主要特点、适用场景
2. 价格统一转换为数字格式(如:99.00)
3. 主要特点提取3-5个关键点
4. 如果某个信息不存在,对应字段值为null

文本内容:
{raw_text}

请输出JSON格式:
{{
  "product_name": "商品名称",
  "price": 价格数字,
  "features": ["特点1", "特点2", "特点3"],
  "scenarios": ["场景1", "场景2"]
}}
"""
    return prompt

这个Prompt有几个设计要点:

  • 明确输出格式:直接告诉模型要输出JSON,并给出具体格式
  • 字段说明清晰:每个字段要提取什么内容都说清楚
  • 处理边界情况:明确说明信息不存在时怎么处理
  • 格式要求具体:比如价格要转成数字,特点要列表形式

实际使用时,你可以根据你的数据特点调整字段。比如处理新闻数据时,字段可能是“标题”、“发布时间”、“作者”、“正文摘要”、“关键词”。

3.2 摘要生成Prompt设计

对于长文本摘要,Prompt设计要引导模型抓住重点,而不是简单截取开头几句。

def generate_summary(long_text, summary_length="medium"):
    length_map = {
        "short": "50字左右",
        "medium": "100-150字", 
        "long": "200-300字"
    }
    
    prompt = f"""
请为以下文本生成一个简洁的摘要。

要求:
1. 摘要长度:{length_map[summary_length]}
2. 抓住核心内容和关键信息
3. 保持客观,不要添加个人观点
4. 语言流畅,逻辑连贯

文本内容:
{long_text}

摘要:
"""
    return prompt

我发现在摘要生成时,明确指定字数范围很重要。只说“简洁摘要”,模型可能生成30字也可能生成300字。指定了字数范围,输出就稳定多了。

3.3 批量处理与格式统一

爬虫数据往往是成批的,我们需要确保每一条数据的处理结果格式一致。这里有个小技巧:在Prompt里加入示例。

def batch_extract_template():
    prompt = """
你是一个数据提取专家。请从每条文本中提取公司名称、成立时间和主营业务。

输出格式要求:
每条结果以JSON格式输出,多个结果用两个换行符分隔。

示例:
输入:阿里巴巴集团成立于1999年,主要业务包括电子商务、云计算、数字媒体等。
输出:{"company_name": "阿里巴巴集团", "found_year": 1999, "main_business": ["电子商务", "云计算", "数字媒体"]}

输入:腾讯公司于1998年成立,是一家互联网综合服务提供商。
输出:{"company_name": "腾讯公司", "found_year": 1998, "main_business": ["互联网综合服务"]}

现在请处理以下文本:
"""
    return prompt

给模型一两个示例,它就能很好地理解你想要什么格式。这对于批量处理特别有用,能保证所有输出都是统一的JSON,方便后续用Python直接解析成字典列表。

4. 实战:新闻数据清洗与摘要

我最近用这个流程处理了一批科技新闻数据,效果很不错。跟你分享一下具体怎么操作。

4.1 原始数据示例

爬虫抓取的数据通常长这样:

【最新消息】苹果公司今日凌晨发布全新iPhone 16系列手机,起售价7999元。这款手机搭载了最新的A18芯片,性能提升30%。同时,电池续航也有显著改善,据称可支持全天候使用。发布时间:2024年9月10日。作者:科技快讯。标签:苹果, iPhone, 手机发布。

相关阅读:安卓手机市场最新动态...(这里是无关的推荐内容)

网友评论:我觉得价格太贵了...(这里是评论内容)

我们需要从这里面提取出核心信息,并过滤掉无关内容。

4.2 完整处理流程

我写了一个完整的处理脚本,你可以参考这个思路:

import json
import re
from typing import List, Dict
import requests

class NewsProcessor:
    def __init__(self, api_url="http://localhost:7860"):
        self.api_url = api_url
        
    def clean_raw_text(self, raw_text: str) -> str:
        """初步清洗,移除明显无关内容"""
        # 移除“相关阅读”之后的内容
        if "相关阅读:" in raw_text:
            raw_text = raw_text.split("相关阅读:")[0]
        
        # 移除“网友评论”之后的内容  
        if "网友评论:" in raw_text:
            raw_text = raw_text.split("网友评论:")[0]
            
        # 移除多余的空白字符
        raw_text = re.sub(r'\s+', ' ', raw_text).strip()
        
        return raw_text
    
    def build_extraction_prompt(self, text: str) -> str:
        """构建信息提取Prompt"""
        prompt = f"""
请从以下新闻文本中提取结构化信息。

要求:
1. 提取新闻标题、发布时间、作者、核心内容摘要
2. 摘要长度控制在100字左右
3. 识别并提取标签(最多5个)
4. 如果信息不存在,对应字段值为null

新闻文本:
{text}

请以JSON格式输出,包含以下字段:
- title: 新闻标题
- publish_time: 发布时间(格式:YYYY-MM-DD)
- author: 作者
- summary: 内容摘要
- tags: 标签列表
- has_price: 是否包含价格信息(true/false)
"""
        return prompt
    
    def call_model(self, prompt: str) -> str:
        """调用模型API"""
        # 这里简化了,实际使用时需要根据你的WebUI接口调整
        payload = {
            "message": prompt,
            "history": []
        }
        
        try:
            response = requests.post(
                f"{self.api_url}/chat",
                json=payload,
                timeout=30
            )
            response.raise_for_status()
            return response.json()["response"]
        except Exception as e:
            print(f"API调用失败: {e}")
            return None
    
    def parse_model_response(self, response: str) -> Dict:
        """解析模型返回的JSON"""
        # 尝试从响应中提取JSON部分
        json_match = re.search(r'\{.*\}', response, re.DOTALL)
        
        if json_match:
            try:
                return json.loads(json_match.group())
            except json.JSONDecodeError:
                print(f"JSON解析失败: {response}")
        
        # 如果提取失败,返回空结构
        return {
            "title": None,
            "publish_time": None,
            "author": None,
            "summary": None,
            "tags": [],
            "has_price": False
        }
    
    def process_news(self, raw_text: str) -> Dict:
        """处理单条新闻"""
        # 1. 初步清洗
        cleaned_text = self.clean_raw_text(raw_text)
        
        # 2. 构建Prompt
        prompt = self.build_extraction_prompt(cleaned_text)
        
        # 3. 调用模型
        response = self.call_model(prompt)
        
        if not response:
            return None
            
        # 4. 解析结果
        result = self.parse_model_response(response)
        
        # 5. 添加原始文本哈希(用于去重)
        import hashlib
        text_hash = hashlib.md5(cleaned_text.encode()).hexdigest()[:8]
        result["text_hash"] = text_hash
        
        return result
    
    def batch_process(self, news_list: List[str]) -> List[Dict]:
        """批量处理新闻"""
        results = []
        seen_hashes = set()
        
        for i, news_text in enumerate(news_list):
            print(f"处理第 {i+1}/{len(news_list)} 条新闻...")
            
            result = self.process_news(news_text)
            
            if result and result["text_hash"] not in seen_hashes:
                seen_hashes.add(result["text_hash"])
                results.append(result)
            
            # 简单限流,避免请求过快
            import time
            time.sleep(0.5)
        
        return results

# 使用示例
if __name__ == "__main__":
    processor = NewsProcessor()
    
    # 模拟一批新闻数据
    sample_news = [
        "【最新消息】苹果公司今日凌晨发布全新iPhone 16系列手机,起售价7999元。这款手机搭载了最新的A18芯片,性能提升30%。同时,电池续航也有显著改善,据称可支持全天候使用。发布时间:2024年9月10日。作者:科技快讯。标签:苹果, iPhone, 手机发布。",
        "特斯拉宣布新款Model 3降价,现价25.99万元起。此次降价旨在提升市场竞争力,同时推出了新的自动驾驶套餐。发布时间:2024年8月15日。作者:汽车之家。",
        # ... 更多新闻数据
    ]
    
    results = processor.batch_process(sample_news)
    
    # 保存结果
    with open("processed_news.json", "w", encoding="utf-8") as f:
        json.dump(results, f, ensure_ascii=False, indent=2)
    
    print(f"处理完成,共处理 {len(results)} 条有效新闻")

4.3 处理效果分析

运行这个脚本,原来杂乱的爬虫数据就被整理成了干净的结构化数据。以第一条新闻为例,处理后的结果大概是这样的:

{
  "title": "苹果发布全新iPhone 16系列手机",
  "publish_time": "2024-09-10",
  "author": "科技快讯",
  "summary": "苹果公司今日发布iPhone 16系列手机,起售价7999元。新品搭载A18芯片,性能提升30%,电池续航有显著改善,支持全天候使用。",
  "tags": ["苹果", "iPhone", "手机发布", "科技", "新品"],
  "has_price": true,
  "text_hash": "a1b2c3d4"
}

可以看到,无关的“相关阅读”和“网友评论”被过滤掉了,关键信息都被提取出来,并且格式统一。这样的数据直接就能导入数据库或者用于进一步分析。

5. 处理电商商品数据的技巧

新闻数据相对规整,电商商品页面就更复杂了。不同平台、不同商家的描述格式千差万别。我总结了一些处理这类数据的经验。

5.1 多轮对话提取

对于特别复杂的商品描述,可以用多轮对话的方式,让模型一步步提取信息。

def extract_product_details(raw_description):
    """多轮对话提取商品详情"""
    
    # 第一轮:提取基本信息
    prompt1 = f"""
请从以下商品描述中提取基本信息:

{raw_description}

请提取:
1. 商品名称
2. 品牌
3. 主要类别(如:电子产品、服装、食品等)
"""
    
    # 假设第一轮回复是 basic_info
    basic_info = call_model(prompt1)
    
    # 第二轮:提取规格参数
    prompt2 = f"""
基于之前的分析,现在请提取详细规格:

商品描述:{raw_description}

已提取的基本信息:{basic_info}

请提取:
1. 颜色/款式选项
2. 尺寸/规格参数  
3. 材质/成分信息
4. 特殊功能特点
"""
    
    # 第三轮:提取价格库存
    prompt3 = f"""
现在请提取商业信息:

商品描述:{raw_description}

请提取:
1. 价格信息(当前价格、原价、折扣等)
2. 库存状态(有货/缺货/预售等)
3. 配送信息
4. 促销活动
"""
    
    # 最后整合所有信息
    final_prompt = f"""
请将以下信息整合为完整的商品数据结构:

基本信息:{basic_info}
规格参数:{specs_info}
商业信息:{commerce_info}

请输出统一的JSON格式。
"""
    
    return call_model(final_prompt)

这种方法虽然调用次数多,但提取更准确,特别适合那些信息分散在不同段落的情况。

5.2 处理图片描述文本

很多商品页面除了文字描述,还有图片的alt文本或者图片描述。这些文本往往包含重要信息,但格式很不规范。

def process_image_descriptions(img_texts):
    """处理图片描述文本"""
    prompt = f"""
以下是商品页面的图片描述文本,请从中提取有用的商品信息:

图片描述列表:
{chr(10).join(f'- {text}' for text in img_texts)}

请提取:
1. 商品的不同角度展示信息
2. 商品的实际使用场景
3. 包装或配件信息
4. 尺寸对比或参考信息

注意:忽略纯装饰性或重复的描述。
"""
    return call_model(prompt)

5.3 价格信息规范化

价格信息是最重要的,但也是最混乱的。不同商家写法各异:

def normalize_price_info(price_text):
    """规范化价格信息"""
    prompt = f"""
请从以下价格相关文本中提取规范化信息:

文本:{price_text}

请识别:
1. 当前售价(取最低价格)
2. 原价(如果有)
3. 折扣力度(如果有)
4. 价格单位(元/美元等)
5. 是否包邮

输出要求:
- 价格统一转换为数字(如:99.99)
- 货币单位统一为CNY
- 包邮信息转换为布尔值
"""
    return call_model(prompt)

6. 性能优化与批量处理建议

当你需要处理成千上万条数据时,性能就很重要了。我总结了一些优化经验。

6.1 批量请求策略

不要一条一条地请求,可以适当批量处理:

def batch_process_texts(texts, batch_size=5):
    """批量处理文本"""
    results = []
    
    for i in range(0, len(texts), batch_size):
        batch = texts[i:i+batch_size]
        
        # 构建批量处理的Prompt
        batch_prompt = "请分别处理以下文本:\n\n"
        for j, text in enumerate(batch):
            batch_prompt += f"文本{j+1}:\n{text}\n\n"
        
        batch_prompt += "请为每个文本提取关键信息,输出格式为JSON列表。"
        
        batch_result = call_model(batch_prompt)
        results.extend(parse_batch_result(batch_result))
        
        # 添加延迟,避免请求过快
        time.sleep(1)
    
    return results

6.2 缓存与去重

很多爬虫数据会有重复内容,可以先做一轮去重:

def deduplicate_texts(texts):
    """基于内容相似度去重"""
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.metrics.pairwise import cosine_similarity
    
    # 计算文本相似度
    vectorizer = TfidfVectorizer()
    tfidf_matrix = vectorizer.fit_transform(texts)
    similarity_matrix = cosine_similarity(tfidf_matrix)
    
    # 找出相似度高的文本
    duplicates = set()
    for i in range(len(texts)):
        for j in range(i+1, len(texts)):
            if similarity_matrix[i][j] > 0.8:  # 相似度阈值
                duplicates.add(j)
    
    # 返回去重后的文本
    return [texts[i] for i in range(len(texts)) if i not in duplicates]

6.3 错误处理与重试

网络请求总会有失败的时候,要做好错误处理:

def robust_model_call(prompt, max_retries=3):
    """带重试的模型调用"""
    for attempt in range(max_retries):
        try:
            response = call_model(prompt)
            if response and validate_response(response):
                return response
        except Exception as e:
            print(f"第{attempt+1}次尝试失败: {e}")
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # 指数退避
                time.sleep(wait_time)
    
    print("所有重试均失败")
    return None

def validate_response(response):
    """验证响应是否有效"""
    # 检查响应是否包含必要信息
    required_fields = ["title", "summary"]  # 根据你的需求调整
    try:
        data = json.loads(response)
        for field in required_fields:
            if field not in data:
                return False
        return True
    except:
        return False

7. 实际应用中的注意事项

在实际项目中用了一段时间,我总结了一些需要注意的地方。

7.1 Prompt设计的经验

保持一致性很重要。如果你这次让模型输出“价格”,下次让输出“售价”,虽然意思差不多,但解析起来就麻烦。最好在项目开始时就确定好字段命名规范。

给模型一些思考空间。有时候在Prompt里加一句“请仔细分析文本内容”,效果会比直接下指令好。模型需要“理解”文本,而不是简单地“匹配”关键词。

处理不确定性。爬虫数据里经常有缺失信息,要在Prompt里明确告诉模型怎么处理:“如果找不到XX信息,就输出null或空字符串”。这样能保证输出结构的一致性。

7.2 模型输出的稳定性

通义千问1.5-1.8B-Chat在格式遵循上表现不错,但也不是100%稳定。我建议:

  1. 后处理校验:对模型输出做一次格式校验,比如检查JSON是否能正常解析,必要字段是否存在。
  2. 备用方案:对于特别重要的字段,可以准备正则表达式作为备用提取方案。
  3. 人工审核样本:定期抽样检查,看看模型有没有什么奇怪的输出模式。

7.3 成本与效率平衡

这个模型本地部署,其实没有API调用成本,主要成本是电费和硬件折旧。但处理大量数据时,时间成本也要考虑。

我的经验是:

  • 对于实时性要求不高的后台任务,可以慢慢处理
  • 对于需要快速响应的场景,可以考虑先用规则过滤掉简单情况,复杂的再交给模型
  • 定期评估效果,如果某些类型的文本模型处理不好,可以考虑优化爬虫,让原始数据更干净

8. 总结

用通义千问1.5-1.8B-Chat处理爬虫数据,给我的感觉是“刚刚好”。它没有大到需要昂贵的GPU,也没有小到处理不了复杂任务。对于大多数爬虫数据清洗和摘要生成的需求,它都能很好地满足。

实际用下来,最深的体会是Prompt设计真的需要花心思。同样的模型,不同的Prompt设计,效果能差好几倍。开始的时候多花点时间设计好的Prompt模板,后面批量处理时就轻松多了。

另一个感受是,这种方案特别适合处理“半结构化”数据——就是那种有点规律但又不太规整的数据。完全规整的数据用正则表达式更快,完全无结构的数据需要更大的模型,而这种半结构化的文本,用这个大小的模型处理性价比最高。

如果你也在做爬虫相关的数据清洗工作,真的建议试试这个方案。从配置环境到跑通第一个例子,一两个小时就够了。一旦流程跑通,后面就是批量处理的事情,能省下大量的手工整理时间。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐