RPA迁移场景批量数据录入与表单自动化

一、批量数据录入场景

图表

二、表单自动化架构

2.1 表单自动化架构

图表

2.2 RPA表单自动化流程

图表

三、数据准备阶段

3.1 数据源类型

图表

3.2 数据读取代码示例

Excel数据读取

import pandas as pd

class ExcelDataReader:
    def __init__(self, file_path):
        self.file_path = file_path
    
    def read(self, sheet_name=0):
        df = pd.read_excel(self.file_path, sheet_name=sheet_name)
        return df.to_dict('records')
    
    def read_with_validation(self, sheet_name=0, required_columns=None):
        df = pd.read_excel(self.file_path, sheet_name=sheet_name)
        records = df.to_dict('records')
        
        if required_columns:
            valid_records = []
            errors = []
            
            for i, record in enumerate(records):
                missing_columns = [col for col in required_columns if col not in record or pd.isna(record[col])]
                
                if missing_columns:
                    errors.append({
                        'row': i + 2,
                        'record': record,
                        'errors': f"Missing required columns: {', '.join(missing_columns)}"
                    })
                else:
                    valid_records.append(record)
            
            return valid_records, errors
        
        return records, []
    
    def read_chunked(self, sheet_name=0, chunk_size=1000):
        for chunk in pd.read_excel(self.file_path, sheet_name=sheet_name, chunksize=chunk_size):
            yield chunk.to_dict('records')

CSV数据读取

import csv

class CSVDataReader:
    def __init__(self, file_path, delimiter=','):
        self.file_path = file_path
        self.delimiter = delimiter
    
    def read(self):
        with open(self.file_path, 'r', encoding='utf-8') as f:
            reader = csv.DictReader(f, delimiter=self.delimiter)
            return list(reader)
    
    def read_with_validation(self, required_columns=None):
        with open(self.file_path, 'r', encoding='utf-8') as f:
            reader = csv.DictReader(f, delimiter=self.delimiter)
            records = list(reader)
        
        if required_columns:
            valid_records = []
            errors = []
            
            for i, record in enumerate(records):
                missing_columns = [col for col in required_columns if col not in record or not record[col].strip()]
                
                if missing_columns:
                    errors.append({
                        'row': i + 2,
                        'record': record,
                        'errors': f"Missing required columns: {', '.join(missing_columns)}"
                    })
                else:
                    valid_records.append(record)
            
            return valid_records, errors
        
        return records, []
    
    def read_chunked(self, chunk_size=1000):
        with open(self.file_path, 'r', encoding='utf-8') as f:
            reader = csv.DictReader(f, delimiter=self.delimiter)
            chunk = []
            
            for row in reader:
                chunk.append(row)
                if len(chunk) >= chunk_size:
                    yield chunk
                    chunk = []
            
            if chunk:
                yield chunk

四、数据验证阶段

4.1 数据验证规则

图表

4.2 验证规则配置

{
    "validation_rules": {
        "email": {
            "required": true,
            "type": "string",
            "format": "email",
            "max_length": 100
        },
        "phone": {
            "required": false,
            "type": "string",
            "format": "phone",
            "max_length": 20
        },
        "age": {
            "required": false,
            "type": "integer",
            "min": 0,
            "max": 150
        },
        "salary": {
            "required": true,
            "type": "number",
            "min": 0,
            "max": 1000000
        },
        "join_date": {
            "required": true,
            "type": "date",
            "format": "YYYY-MM-DD"
        }
    }
}

4.3 数据验证代码示例

import re
from datetime import datetime

class DataValidator:
    def __init__(self, rules):
        self.rules = rules
    
    def validate(self, record):
        errors = {}
        
        for field, rule in self.rules.items():
            value = record.get(field)
            
            if rule.get('required') and (value is None or value == '' or value == 'nan'):
                errors[field] = f"{field} is required"
                continue
            
            if value is None or value == '' or value == 'nan':
                continue
            
            if rule.get('type') == 'string':
                if not isinstance(value, str):
                    errors[field] = f"{field} must be a string"
                else:
                    if rule.get('max_length') and len(value) > rule['max_length']:
                        errors[field] = f"{field} exceeds maximum length of {rule['max_length']}"
                    if rule.get('min_length') and len(value) < rule['min_length']:
                        errors[field] = f"{field} must be at least {rule['min_length']} characters"
                
                if rule.get('format') == 'email':
                    if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', value):
                        errors[field] = f"{field} is not a valid email address"
                
                if rule.get('format') == 'phone':
                    if not re.match(r'^\+?[\d\s-]{7,20}$', value):
                        errors[field] = f"{field} is not a valid phone number"
            
            elif rule.get('type') == 'integer':
                try:
                    int_value = int(value)
                except ValueError:
                    errors[field] = f"{field} must be an integer"
                    continue
                
                if rule.get('min') and int_value < rule['min']:
                    errors[field] = f"{field} must be at least {rule['min']}"
                if rule.get('max') and int_value > rule['max']:
                    errors[field] = f"{field} must be at most {rule['max']}"
            
            elif rule.get('type') == 'number':
                try:
                    float_value = float(value)
                except ValueError:
                    errors[field] = f"{field} must be a number"
                    continue
                
                if rule.get('min') and float_value < rule['min']:
                    errors[field] = f"{field} must be at least {rule['min']}"
                if rule.get('max') and float_value > rule['max']:
                    errors[field] = f"{field} must be at most {rule['max']}"
            
            elif rule.get('type') == 'date':
                date_format = rule.get('format', '%Y-%m-%d')
                try:
                    datetime.strptime(str(value), date_format)
                except ValueError:
                    errors[field] = f"{field} must be in format {date_format}"
        
        return {
            'is_valid': len(errors) == 0,
            'errors': errors
        }
    
    def validate_batch(self, records):
        valid_records = []
        invalid_records = []
        
        for record in records:
            validation = self.validate(record)
            if validation['is_valid']:
                valid_records.append(record)
            else:
                invalid_records.append({
                    'record': record,
                    'errors': validation['errors']
                })
        
        return {
            'valid': valid_records,
            'invalid': invalid_records,
            'total': len(records),
            'valid_count': len(valid_records),
            'invalid_count': len(invalid_records)
        }

五、表单填充阶段

5.1 表单元素定位

图表

5.2 表单填充代码示例

Playwright表单填充

from playwright.sync_api import sync_playwright

class FormFiller:
    def __init__(self):
        self.playwright = None
        self.browser = None
        self.page = None
    
    def init(self, headless=True):
        self.playwright = sync_playwright().start()
        self.browser = self.playwright.chromium.launch(headless=headless)
        self.page = self.browser.new_page()
    
    def login(self, url, username, password):
        self.page.goto(url)
        self.page.fill('#username', username)
        self.page.fill('#password', password)
        self.page.click('#loginBtn')
        self.page.wait_for_load_state('networkidle')
    
    def fill_form(self, form_data):
        for field, value in form_data.items():
            self._fill_field(field, value)
    
    def _fill_field(self, field, value):
        selectors = [
            f'#{field}',
            f'[name="{field}"]',
            f'[data-field="{field}"]',
            f'//input[@placeholder="*{field}"]',
            f'//input[contains(@id, "{field}")]'
        ]
        
        for selector in selectors:
            try:
                element = self.page.query_selector(selector)
                if element:
                    element.fill(str(value))
                    return
            except:
                continue
        
        print(f"Warning: Could not find field '{field}'")
    
    def select_option(self, field, option_text):
        selectors = [
            f'select#{field}',
            f'select[name="{field}"]',
            f'[data-field="{field}"]'
        ]
        
        for selector in selectors:
            try:
                element = self.page.query_selector(selector)
                if element:
                    element.select_option(label=option_text)
                    return
            except:
                continue
        
        print(f"Warning: Could not find select field '{field}'")
    
    def click_button(self, button_text):
        xpath = f'//button[contains(text(), "{button_text}")]'
        try:
            self.page.click(xpath)
            return True
        except:
            print(f"Warning: Could not find button '{button_text}'")
            return False
    
    def submit(self):
        return self.click_button('Submit') or self.click_button('Save') or self.click_button('提交')
    
    def close(self):
        if self.browser:
            self.browser.close()
        if self.playwright:
            self.playwright.stop()

5.3 复杂表单处理

文件上传处理

class FileUploadHandler:
    def __init__(self, page):
        self.page = page
    
    def upload_file(self, field_name, file_path):
        selectors = [
            f'input[type="file"]#{field_name}',
            f'input[type="file"][name="{field_name}"]',
            f'input[type="file"][data-field="{field_name}"]'
        ]
        
        for selector in selectors:
            try:
                element = self.page.query_selector(selector)
                if element:
                    element.set_input_files(file_path)
                    return True
            except:
                continue
        
        return False
    
    def upload_files(self, field_name, file_paths):
        selectors = [
            f'input[type="file"]#{field_name}',
            f'input[type="file"][name="{field_name}"]'
        ]
        
        for selector in selectors:
            try:
                element = self.page.query_selector(selector)
                if element:
                    element.set_input_files(file_paths)
                    return True
            except:
                continue
        
        return False

六、表单提交阶段

6.1 提交策略

图表

6.2 提交结果处理

class SubmissionHandler:
    def __init__(self):
        self.success_count = 0
        self.fail_count = 0
        self.results = []
    
    def handle_result(self, record, success, message=''):
        if success:
            self.success_count += 1
        else:
            self.fail_count += 1
        
        self.results.append({
            'record': record,
            'success': success,
            'message': message,
            'timestamp': datetime.now().isoformat()
        })
    
    def get_summary(self):
        return {
            'total': self.success_count + self.fail_count,
            'success': self.success_count,
            'fail': self.fail_count,
            'success_rate': (self.success_count / (self.success_count + self.fail_count)) * 100
        }
    
    def save_results(self, output_file):
        with open(output_file, 'w', encoding='utf-8') as f:
            json.dump({
                'summary': self.get_summary(),
                'results': self.results
            }, f, ensure_ascii=False, indent=2)

七、批量数据录入实现

7.1 完整流程代码示例

import json
from datetime import datetime

class BatchDataEntry:
    def __init__(self, config):
        self.config = config
        self.reader = None
        self.validator = None
        self.filler = None
        self.handler = SubmissionHandler()
    
    def init_components(self):
        if self.config['data_source']['type'] == 'excel':
            self.reader = ExcelDataReader(self.config['data_source']['path'])
        elif self.config['data_source']['type'] == 'csv':
            self.reader = CSVDataReader(self.config['data_source']['path'])
        
        self.validator = DataValidator(self.config['validation_rules'])
        self.filler = FormFiller()
    
    def run(self):
        self.init_components()
        
        print("Reading data...")
        data, read_errors = self.reader.read_with_validation(
            required_columns=self.config.get('required_columns')
        )
        print(f"Read {len(data)} records, {len(read_errors)} errors")
        
        if read_errors:
            with open('read_errors.json', 'w', encoding='utf-8') as f:
                json.dump(read_errors, f, ensure_ascii=False, indent=2)
        
        print("Validating data...")
        validation_result = self.validator.validate_batch(data)
        print(f"Valid: {validation_result['valid_count']}, Invalid: {validation_result['invalid_count']}")
        
        if validation_result['invalid']:
            with open('validation_errors.json', 'w', encoding='utf-8') as f:
                json.dump(validation_result['invalid'], f, ensure_ascii=False, indent=2)
        
        print("Starting form filling...")
        self.filler.init(headless=self.config.get('headless', True))
        
        try:
            self.filler.login(
                self.config['login']['url'],
                self.config['login']['username'],
                self.config['login']['password']
            )
            
            for i, record in enumerate(validation_result['valid']):
                print(f"Processing record {i+1}/{validation_result['valid_count']}")
                
                try:
                    self.filler.page.goto(self.config['form_url'])
                    self.filler.fill_form(record)
                    
                    if self.filler.submit():
                        self.handler.handle_result(record, True, 'Submitted successfully')
                    else:
                        self.handler.handle_result(record, False, 'Submit button not found')
                except Exception as e:
                    self.handler.handle_result(record, False, str(e))
                    print(f"Error processing record {i+1}: {e}")
        finally:
            self.filler.close()
        
        print("Generating report...")
        self.handler.save_results('submission_results.json')
        
        summary = self.handler.get_summary()
        print(f"Batch entry complete: {summary['success']} success, {summary['fail']} fail")
        print(f"Success rate: {summary['success_rate']:.2f}%")
        
        return summary

7.2 配置文件示例

{
    "data_source": {
        "type": "excel",
        "path": "data/users.xlsx"
    },
    "required_columns": ["username", "email", "department"],
    "validation_rules": {
        "username": {
            "required": true,
            "type": "string",
            "min_length": 3,
            "max_length": 50
        },
        "email": {
            "required": true,
            "type": "string",
            "format": "email",
            "max_length": 100
        },
        "department": {
            "required": true,
            "type": "string"
        },
        "age": {
            "required": false,
            "type": "integer",
            "min": 18,
            "max": 65
        }
    },
    "login": {
        "url": "https://example.com/login",
        "username": "admin",
        "password": "password"
    },
    "form_url": "https://example.com/users/add",
    "headless": true,
    "batch_size": 100
}

八、错误处理与重试机制

8.1 错误类型

图表

8.2 重试机制

class RetryHandler:
    def __init__(self, max_retries=3, delay=1, backoff=2):
        self.max_retries = max_retries
        self.delay = delay
        self.backoff = backoff
    
    def retry(self, func, *args, **kwargs):
        delay = self.delay
        
        for attempt in range(self.max_retries):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                print(f"Attempt {attempt + 1} failed: {e}")
                
                if attempt < self.max_retries - 1:
                    print(f"Retrying in {delay} seconds...")
                    time.sleep(delay)
                    delay *= self.backoff
        
        raise Exception(f"Failed after {self.max_retries} attempts")
    
    def retry_with_exponential_backoff(self, func, *args, **kwargs):
        delay = self.delay
        
        for attempt in range(self.max_retries):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                print(f"Attempt {attempt + 1} failed: {e}")
                
                if attempt < self.max_retries - 1:
                    jitter = random.uniform(0, delay)
                    sleep_time = delay + jitter
                    print(f"Retrying in {sleep_time:.2f} seconds...")
                    time.sleep(sleep_time)
                    delay *= self.backoff
        
        raise Exception(f"Failed after {self.max_retries} attempts")

九、性能优化

9.1 优化策略

图表

9.2 并行处理代码示例

from concurrent.futures import ThreadPoolExecutor

class ParallelDataEntry:
    def __init__(self, config):
        self.config = config
        self.max_workers = config.get('max_workers', 5)
    
    def process_record(self, record):
        filler = FormFiller()
        
        try:
            filler.init(headless=True)
            filler.login(
                self.config['login']['url'],
                self.config['login']['username'],
                self.config['login']['password']
            )
            
            filler.page.goto(self.config['form_url'])
            filler.fill_form(record)
            
            if filler.submit():
                return {'record': record, 'success': True}
            else:
                return {'record': record, 'success': False, 'message': 'Submit failed'}
        except Exception as e:
            return {'record': record, 'success': False, 'message': str(e)}
        finally:
            filler.close()
    
    def run(self, records):
        results = []
        
        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            futures = [executor.submit(self.process_record, record) for record in records]
            
            for future in futures:
                results.append(future.result())
        
        success_count = sum(1 for r in results if r['success'])
        
        return {
            'total': len(results),
            'success': success_count,
            'fail': len(results) - success_count,
            'results': results
        }

十、常见问题

10.1 表单元素定位失败

现象:无法找到表单元素

解决方案

方案说明
使用多种定位方式 尝试ID、Name、XPath等多种方式
等待元素加载 添加等待时间
动态定位 使用相对定位

10.2 数据验证失败

现象:数据验证不通过

解决方案

方案说明
检查验证规则 检查验证规则配置
数据预处理 提前清洗数据
错误日志 查看错误日志

10.3 提交超时

现象:表单提交超时

解决方案

方案说明
增加超时时间 增加等待时间
优化网络 检查网络连接
异步提交 使用异步提交方式

10.4 页面结构变化

现象:页面结构变化导致机器人失败

解决方案

方案说明
定期维护 定期检查页面结构
动态定位 使用动态定位策略
多选择器 使用多个选择器
posted @ 2026-07-10 20:17  ai755  阅读(2)  评论(0)    收藏  举报