문서

문서

ActiCrawl을 사용하여 웹 스크래핑 워크플로를 자동화하는 방법을 알아보세요

배치 처리

ActiCrawl의 배치 처리 기능으로 여러 URL을 효율적으로 처리하세요. 수천 개의 페이지를 스크래핑하고, 속도 제한을 처리하며, 대규모 성능을 최적화하는 방법을 알아보세요.

개요

배치 처리를 통해 다음을 수행할 수 있습니다:
- 여러 URL을 동시에 스크래핑
- 속도 제한 자동 관리
- 실패를 우아하게 처리
- 실시간 진행 상황 모니터링
- 리소스 사용 최적화

기본 배치 스크래핑

간단한 배치 요청

단일 요청으로 여러 URL 처리:

javascript
const results = await client.batchScrape({
  urls: [
    'https://example.com/page1',
    'https://example.com/page2',
    'https://example.com/page3'
  ],
  format: 'markdown'
});

// 결과 배열은 URL 순서를 유지합니다
results.forEach((result, index) => {
  console.log(`URL ${index + 1}:`, result.url);
  console.log('상태:', result.status);
  console.log('콘텐츠:', result.content);
});

추출 기능이 있는 배치

여러 URL에 동일한 추출 규칙 적용:

python
urls = [
    'https://shop.com/product/1',
    'https://shop.com/product/2',
    'https://shop.com/product/3'
]

results = client.batch_scrape(
    urls=urls,
    extract={
        'name': 'h1.product-name',
        'price': '.price',
        'availability': '.stock-status',
        'image': 'img.main-image@src'
    }
)

# 결과 처리
for result in results:
    if result['status'] == 'success':
        print(f"제품: {result['extracted']['name']}")
        print(f"가격: {result['extracted']['price']}")

고급 배치 구성

동시성 제어

동시 요청으로 성능 최적화:

javascript
const results = await client.batchScrape({
  urls: productUrls,
  concurrency: 5,  // 5개 URL 동시 처리
  delay: 1000,     // 요청 사이 1초 지연
  retries: 3,      // 실패한 요청 최대 3회 재시도
  timeout: 30000   // URL당 30초 타임아웃
});

속도 제한

대상 웹사이트 제한 준수:

python
results = client.batch_scrape(
    urls=urls,
    rate_limit={
        'requests_per_second': 2,
        'burst': 5,  # 5개 요청의 버스트 허용
        'per_domain': True  # 도메인별 제한 적용
    }
)

헤더가 있는 배치

모든 요청에 대한 사용자 정의 헤더:

javascript
const results = await client.batchScrape({
  urls: urls,
  headers: {
    'User-Agent': 'MyBot/1.0',
    'Accept-Language': 'ko-KR',
    'X-Custom-Header': 'value'
  },
  cookies: {
    'session': 'abc123',
    'preferences': 'lang=ko'
  }
});

배치 크롤링

여러 도메인 크롤링

전체 웹사이트를 병렬로 처리:

python
crawl_configs = [
    {
        'start_url': 'https://site1.com',
        'max_pages': 100,
        'include_patterns': ['/products/*']
    },
    {
        'start_url': 'https://site2.com',
        'max_pages': 50,
        'include_patterns': ['/blog/*']
    }
]

results = client.batch_crawl(crawl_configs)

동적 URL 생성

프로그래밍 방식으로 URL 생성:

javascript
// 페이지네이션 URL 생성
const baseUrl = 'https://example.com/products';
const urls = Array.from({length: 50}, (_, i) => 
  `${baseUrl}?page=${i + 1}`
);

// 카테고리 URL 생성
const categories = ['전자제품', '도서', '의류'];
const categoryUrls = categories.flatMap(cat => 
  Array.from({length: 10}, (_, i) => 
    `${baseUrl}/${cat}?page=${i + 1}`
  )
);

const results = await client.batchScrape({
  urls: [...urls, ...categoryUrls],
  concurrency: 10
});

오류 처리 및 재시도

포괄적인 오류 관리

javascript
const results = await client.batchScrape({
  urls: urls,
  retry: {
    attempts: 3,
    delay: 2000,  // 지수 백오프
    on: ['timeout', 'network', '5xx']  // 재시도 조건
  },
  onError: 'continue'  // 오류 시 중단하지 않음
});

// 오류 처리와 함께 결과 처리
results.forEach(result => {
  if (result.status === 'success') {
    processSuccessfulResult(result);
  } else {
    logError({
      url: result.url,
      error: result.error,
      attempts: result.attempts
    });
  }
});

부분 결과 처리

python
def process_batch_with_fallback(urls):
    results = client.batch_scrape(
        urls=urls,
        on_partial_success=True  # 부분 결과 반환
    )

    successful = []
    failed = []

    for result in results:
        if result['status'] == 'success':
            successful.append(result)
        else:
            failed.append(result['url'])

    # 실패한 URL을 다른 설정으로 재시도
    if failed:
        retry_results = client.batch_scrape(
            urls=failed,
            javascript=True,  # JS 렌더링으로 시도
            timeout=60000
        )
        successful.extend(retry_results)

    return successful

진행 상황 모니터링

실시간 진행 상황 추적

javascript
const batch = client.createBatch({
  urls: urls,
  onProgress: (progress) => {
    console.log(`진행: ${progress.completed}/${progress.total}`);
    console.log(`성공률: ${progress.successRate}%`);
    console.log(`예상 남은 시간: ${progress.estimatedTimeRemaining}초`);
  }
});

// 배치 처리 시작
const results = await batch.run();

웹훅 알림

python
# 배치 완료를 위한 웹훅 구성
batch_job = client.batch_scrape(
    urls=urls,
    webhook={
        'url': 'https://myapp.com/webhook/batch-complete',
        'events': ['complete', 'error', 'progress'],
        'headers': {
            'Authorization': 'Bearer token123'
        }
    },
    async=True  # 논블로킹 실행
)

print(f"배치 작업 ID: {batch_job['id']}")
print(f"상태 URL: {batch_job['status_url']}")

성능 최적화

메모리 효율적인 처리

javascript
// 메모리에 모두 로드하는 대신 스트림 결과
const stream = client.batchStream({
  urls: urls,
  concurrency: 20
});

stream.on('data', (result) => {
  // 각 결과가 도착할 때 처리
  saveToDatabase(result);
});

stream.on('end', () => {
  console.log('배치 처리 완료');
});

stream.on('error', (error) => {
  console.error('배치 오류:', error);
});

청크 처리

python
def process_large_batch(urls, chunk_size=100):
    """대량 URL 목록을 청크로 처리"""
    total_results = []

    for i in range(0, len(urls), chunk_size):
        chunk = urls[i:i + chunk_size]

        print(f"청크 {i//chunk_size + 1} 처리 중")

        results = client.batch_scrape(
            urls=chunk,
            concurrency=10
        )

        total_results.extend(results)

        # 중간 결과 저장
        save_checkpoint(total_results, i)

        # 청크 사이 속도 제한
        time.sleep(5)

    return total_results

데이터 내보내기 옵션

다양한 형식으로 내보내기

javascript
const results = await client.batchScrape({
  urls: urls,
  export: {
    format: 'csv',  // 'json', 'csv', 'excel', 'parquet'
    path: './exports/batch_results.csv',
    fields: ['url', 'title', 'price', 'timestamp'],
    compress: true  // .csv.gz 생성
  }
});

스트리밍 내보내기

python
# 결과를 파일로 직접 스트리밍
with client.batch_scrape_stream(urls) as stream:
    with open('results.jsonl', 'w') as f:
        for result in stream:
            f.write(json.dumps(result) + '\n')

모범 사례

1. URL 중복 제거

javascript
// 처리 전 중복 URL 제거
const uniqueUrls = [...new Set(urls)];

// 또는 정규화와 함께
const normalizedUrls = urls
  .map(url => new URL(url).href)
  .filter((url, index, self) => self.indexOf(url) === index);

2. 배치 크기 최적화

python
def optimal_batch_size(total_urls):
    """전체 URL 수에 따라 최적 배치 크기 계산"""
    if total_urls < 100:
        return total_urls
    elif total_urls < 1000:
        return 50
    elif total_urls < 10000:
        return 100
    else:
        return 200

3. 리소스 관리

javascript
// 배치 처리 후 리소스 정리
const batch = client.createBatch({ urls });

try {
  const results = await batch.run();
  return results;
} finally {
  await batch.cleanup();  // 리소스 해제
}

4. 모니터링 및 알림

python
def batch_with_monitoring(urls):
    """모니터링이 있는 배치 처리"""
    start_time = time.time()

    results = client.batch_scrape(
        urls=urls,
        on_complete=lambda r: send_metrics({
            'total_urls': len(urls),
            'successful': len([x for x in r if x['status'] == 'success']),
            'duration': time.time() - start_time,
            'success_rate': calculate_success_rate(r)
        })
    )

    # 낮은 성공률에 대한 알림
    success_rate = calculate_success_rate(results)
    if success_rate < 0.8:
        send_alert(f"낮은 성공률: {success_rate}")

    return results

일반적인 사용 사례

이커머스 가격 모니터링

javascript
async function monitorPrices(productUrls) {
  const results = await client.batchScrape({
    urls: productUrls,
    extract: {
      name: 'h1.product-title',
      price: '.current-price',
      originalPrice: '.original-price',
      availability: '.stock-status'
    },
    concurrency: 10
  });

  // 이전 가격과 비교
  const priceChanges = results
    .filter(r => r.status === 'success')
    .map(r => ({
      ...r.extracted,
      url: r.url,
      priceChange: calculatePriceChange(r.extracted.price)
    }))
    .filter(p => p.priceChange !== 0);

  return priceChanges;
}

콘텐츠 집계

python
def aggregate_news_articles(news_sites):
    """여러 뉴스 사이트에서 기사 집계"""

    urls = []
    for site in news_sites:
        # 각 사이트에서 기사 URL 가져오기
        homepage = client.scrape(site['url'])
        article_urls = extract_article_urls(homepage, site['selector'])
        urls.extend(article_urls[:10])  # 각각 상위 10개

    # 모든 기사 배치 스크래핑
    articles = client.batch_scrape(
        urls=urls,
        extract={
            'title': 'h1',
            'author': '.author-name',
            'date': 'time@datetime',
            'content': 'article.content',
            'category': '.category'
        }
    )

    return process_articles(articles)

문제 해결

일반적인 문제

  1. 대량 배치의 메모리 문제

    • 메모리에 모든 결과를 로드하는 대신 스트리밍 사용
    • 청크로 처리
    • Node.js 메모리 증가: node --max-old-space-size=4096
  2. 속도 제한 오류

    • 동시성 감소
    • 요청 사이 지연 추가
    • 지수 백오프 구현
  3. 타임아웃 문제

    • 타임아웃 값 증가
    • 배치 크기 감소
    • 네트워크 연결 확인
  4. 일관되지 않은 결과

    • 동적 콘텐츠에 대해 JavaScript 렌더링 활성화
    • 탐지를 피하기 위해 무작위 지연 추가
    • 사용자 에이전트 순환

다음 단계