python 自动化测试框架

8 阅读4分钟

本文档介绍如何开发一个针对政务网站新闻栏目的自动化爬虫工具。该工具基于 Playwright 框架,能够模拟浏览器行为,抓取指定栏目的新闻列表和详情内容,并导出为结构化数据文件。

🎯 核心技术方案

技术选型

技术组件选择理由
Playwright支持动态页面渲染,可模拟真实浏览器行为,绕过简单的反爬机制
Python开发效率高,生态丰富,易于打包分发
正则表达式轻量级解析,无需额外依赖
PyInstaller打包为独立可执行文件,方便非技术用户使用

核心设计思路

  1. 浏览器模拟:使用无头浏览器模拟真实用户访问,避免被识别为爬虫
  2. 路径自适应:支持开发环境和打包后环境的路径自动切换
  3. 模块化设计:将功能拆分为独立模块,便于维护和扩展
  4. 容错机制:请求失败自动跳过,避免单点故障影响整体

🏗️ 架构设计

text

┌─────────────────────────────────────┐
│           主控制流程                 │
├─────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  │
│  │ 浏览器管理器 │  │ 页面请求模块 │  │
│  └─────────────┘  └─────────────┘  │
│  ┌─────────────┐  ┌─────────────┐  │
│  │ 列表页解析器 │  │ 详情页解析器 │  │
│  └─────────────┘  └─────────────┘  │
│  ┌─────────────┐  ┌─────────────┐  │
│  │ 数据存储模块 │  │ 分页处理器 │  │
│  └─────────────┘  └─────────────┘  │
└─────────────────────────────────────┘

💻 关键技术实现

1. 打包环境路径处理

问题:打包后的程序需要正确找到 Playwright 浏览器驱动

解决方案

python

if getattr(sys, 'frozen', False):
    # 打包后的运行环境
    base_path = sys._MEIPASS
else:
    # 开发环境
    base_path = os.path.dirname(os.path.abspath(__file__))

# 设置浏览器路径环境变量
os.environ['PLAYWRIGHT_BROWSERS_PATH'] = os.path.join(base_path, "ms-playwright")

原理sys.frozen 在 PyInstaller 打包后为 True,sys._MEIPASS 指向临时解压目录

2. 浏览器反检测

关键配置

python

self.browser = self.p.chromium.launch(
    headless=True,
    args=['--disable-blink-features=AutomationControlled']  # 禁用自动化特征
)

self.context = self.browser.new_context(
    user_agent='Mozilla/5.0',  # 模拟真实浏览器UA
    viewport={'width': 1920, 'height': 1080}  # 设置窗口大小
)

3. 分页URL构造模式

通用方案:分析网站分页参数规律

python

def build_page_url(base_url, page, page_size):
    """
    构造分页URL
    常见参数名:page, p, pageNo, pageNum
    常见参数名:pageSize, limit, rows
    """
    return f"{base_url}?page={page}&pageSize={page_size}"

4. 列表页解析策略

使用正则表达式提取链接

python

def parse_list(html):
    # 匹配a标签中的href和文本内容
    pattern = r'<a\s+href="([^"]+)"[^>]*>([^<]+)</a>'
    matches = re.findall(pattern, html)
    
    # 去重处理
    seen = set()
    results = []
    for href, title in matches:
        if href not in seen:
            seen.add(href)
            results.append({
                'title': title.strip(),
                'url': normalize_url(href)  # 相对路径转绝对路径
            })
    return results

注意:政务网站常使用 .shtml 扩展名,可作为特征匹配

5. 详情页信息提取

python

def parse_detail(html):
    data = {}
    
    # 标题提取(常见标签:h1, .title, .news-title)
    title = re.search(r'<h1[^>]*>([\s\S]*?)</h1>', html)
    data['title'] = title.group(1).strip() if title else ""
    
    # 时间提取(正则匹配日期格式)
    time_match = re.search(r'(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})', html)
    data['time'] = time_match.group(1) if time_match else ""
    
    # 来源提取(常见标识:来源、责任编辑、信息来源)
    source_match = re.search(r'来源[::]\s*([^<>\n]+)', html)
    data['source'] = source_match.group(1).strip() if source_match else ""
    
    # 内容提取(定位到正文容器)
    content_match = re.search(r'<div\s+class="[^"]*content[^"]*">(.*?)</div>', html, re.DOTALL)
    data['content'] = content_match.group(1).strip() if content_match else ""
    
    return data

6. 数据存储设计

python

def save_csv(data, filename):
    """使用CSV格式存储,UTF-8-BOM编码支持中文"""
    with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
        writer = csv.writer(f)
        # 写入表头
        writer.writerow(['标题', '发布时间', '来源', '链接', '正文内容'])
        # 写入数据
        for item in data:
            writer.writerow([
                item.get('title'),
                item.get('time'),
                item.get('source'),
                item.get('url'),
                item.get('content')
            ])

📦 打包部署方案

PyInstaller 打包命令

bash

pyinstaller --onefile \
  --name "新闻爬虫工具" \
  --icon "icon.ico" \
  --add-data "浏览器驱动路径;ms-playwright" \
  --hidden-import "playwright" \
  --hidden-import "playwright.sync_api" \
  main.py

路径配置说明

  • --add-data:将 Playwright 浏览器驱动打包进程序
  • 源路径:%LOCALAPPDATA%\ms-playwright(Windows)
  • 目标路径:ms-playwright(程序内引用路径)

打包后文件结构

text

dist/
└── 新闻爬虫工具.exe    # 可执行文件

用户无需安装 Python 环境即可运行

🔧 常见问题解决方案

1. 浏览器驱动自动安装

python

def auto_install_browser():
    """首次运行时自动安装浏览器驱动"""
    try:
        # 尝试启动浏览器,失败则安装
        with sync_playwright() as p:
            p.chromium.launch(headless=True)
    except:
        # 调用Playwright内置安装命令
        subprocess.run(["playwright", "install", "chromium"])

2. 请求频率控制

python

import time
import random

# 随机延迟,模拟人类行为
time.sleep(random.uniform(1, 3))

3. 异常重试机制

python

def fetch_with_retry(page, url, max_retries=3):
    for i in range(max_retries):
        try:
            page.goto(url, timeout=30000)
            return page.content()
        except Exception as e:
            if i == max_retries - 1:
                raise
            time.sleep(2 ** i)  # 指数退避

📊 数据清洗建议

常见数据问题处理

python

def clean_text(text):
    """清洗文本中的特殊字符"""
    if not text:
        return ""
    # 去除空白字符
    text = re.sub(r'\s+', ' ', text)
    # 去除HTML标签
    text = re.sub(r'<[^>]+>', '', text)
    # 去除控制字符
    text = re.sub(r'[\x00-\x1f\x7f]', '', text)
    return text.strip()