내 액터 만들기
원하는 액터가 목록에 없으면 직접 만듭니다. Apify 방식 코드(Apify SDK·Crawlee)를 그대로 올리면 격리된 컨테이너에서 돌고, 결과는 준비된 액터와 같은 곳에 쌓여 같은 방법으로 내려받습니다. 요금은 쓴 메모리와 시간만큼입니다.
실행 환경
필요한 도구가 미리 깔려 있습니다. 내 패키지는 아래 의존성 파일에 적으면 실행 전에 설치하고, 다음 실행부터는 저장해 둔 것을 씁니다.
| runtime | 구성 | 시작 파일 | 의존성 파일 | 판 |
|---|---|---|---|---|
node-playwright | Node.js 22 + Crawlee + Apify SDK + Playwright(Chromium) | main.js | package.json | Node.js 22 · apify@3 · crawlee@3 · playwright@1.48.2 |
python-playwright | Python 3.12 + Crawlee for Python + Apify SDK + Playwright(Chromium) | main.py | requirements.txt | Python 3.12 · apify >=4,<5 · crawlee >=1,<2 |
가장 짧은 액터
Actor.getInput() 으로 입력을 받고 Actor.pushData() 로 결과를 한 건씩 저장하면 됩니다. 실행 방식이 Apify 의 로컬 실행과 같아서, Apify SDK·Crawlee 로 쓴 코드를 그대로 올리면 됩니다.
// main.js
import { Actor } from "apify";
import { CheerioCrawler, ProxyConfiguration } from "crawlee";
await Actor.init();
const { startUrls = ["https://example.com"], maxPages = 10 } = (await Actor.getInput()) ?? {};
// 인터넷 요청은 FI_PROXY_URL 프록시로 나갑니다(실행마다 자동으로 들어 있음).
const proxyConfiguration = new ProxyConfiguration({ proxyUrls: [process.env.FI_PROXY_URL] });
const crawler = new CheerioCrawler({
proxyConfiguration,
maxRequestsPerCrawl: maxPages,
async requestHandler({ request, $, enqueueLinks }) {
await Actor.pushData({ url: request.loadedUrl, title: $("title").text().trim(), h1: $("h1").first().text().trim() });
await enqueueLinks({ strategy: "same-domain" });
},
});
await crawler.run(startUrls);
await Actor.exit();
# main.py (runtime: python-playwright)
import asyncio, os
from apify import Actor
from crawlee.crawlers import BeautifulSoupCrawler, BeautifulSoupCrawlingContext
from crawlee.proxy_configuration import ProxyConfiguration
async def main() -> None:
async with Actor:
inp = await Actor.get_input() or {}
crawler = BeautifulSoupCrawler(
proxy_configuration=ProxyConfiguration(proxy_urls=[os.environ["FI_PROXY_URL"]]),
max_requests_per_crawl=inp.get("maxPages", 10),
)
@crawler.router.default_handler
async def handler(ctx: BeautifulSoupCrawlingContext) -> None:
await ctx.push_data({"url": ctx.request.url, "title": ctx.soup.title.get_text(strip=True) if ctx.soup.title else None})
await crawler.run(inp.get("startUrls", ["https://example.com"]))
asyncio.run(main())
만들고 실행하기
콘솔에서는 스크래핑 › 액터 탭 아래 내 액터 › + 새 액터. 예제 코드 버튼으로 위 코드부터 시작할 수 있습니다.
POST /v1/scrape/my-actors
{
"name": "my-title-crawler",
"title": "제목 모으기",
"runtime": "node-playwright",
"files": [ { "path": "main.js", "content": "import { Actor } from \"apify\"; …" } ],
"inputSchema": { … 아래 "입력 형식" … },
"memoryMb": 1024, "timeoutSec": 600, "maxItems": 10000
}
# 실행 — 만들 때 받은 id 앞에 user: 를 붙입니다
POST /v1/scrape/runs
{ "actor": "user:<id>", "input": { "startUrls": ["https://example.com"] } }
- 고치기
PUT /v1/scrape/my-actors/<id>(고칠 때마다 버전이 1씩 오릅니다) · 목록GET /v1/scrape/my-actors· 지우기DELETE - 결과·상태·내려받기·여러 개 한꺼번에·웹훅은 준비된 액터와 같습니다
- AI 도구에서는
scrape.create_actor·scrape.update_actor로 AI 가 코드를 써서 올립니다
준비된 액터를 복사해서 고치기
비슷한 액터가 이미 있으면 처음부터 쓰지 말고 복사해서 고치세요. 코드·입력 형식·메모리 설정이 통째로 내 액터로 들어옵니다.
콘솔에서는 액터 카드의 복사해서 고치기. API 로는 이렇게 합니다.
POST /v1/scrape/store/naver-blog-search/fork
{ "name": "my-naver-blog-search" }
201 Created
{ "id": "…", "name": "my-naver-blog-search", "title": "…" }
코드는 GET /v1/scrape/store/<이름> 으로 먼저 읽어 볼 수 있습니다. 복사한 액터는 아래 「내 액터 요금」으로 셈합니다.
입력 형식 (input_schema)
Apify 의 input_schema.json 형식 그대로입니다. 콘솔은 이것으로 입력 칸을 그리고, 실행할 때 빠진 칸은 기본값으로 채웁니다.
{
"title": "입력",
"type": "object",
"schemaVersion": 1,
"properties": {
"startUrls": {
"title": "시작 주소",
"type": "array",
"editor": "stringList",
"prefill": [
"https://example.com"
]
},
"maxPages": {
"title": "최대 페이지 수",
"type": "integer",
"default": 10
}
},
"required": [
"startUrls"
]
}
type—string·integer·number·boolean·array·object. 모양이 다르면 실행 전에input_invalid로 알려 줍니다required에 적은 칸이 비면input_required— 칸 이름이 같이 옵니다default는 비었을 때 넣을 값,prefill은 콘솔 입력 칸에 미리 채울 값입니다
프록시 자동 연결
실행마다 그 실행만 쓰는 프록시 주소가 환경변수로 들어갑니다. 따로 계정을 만들 필요가 없습니다.
FI_PROXY_URL— Crawlee 의ProxyConfiguration에 그대로 넣습니다(위 예제 참고)HTTP_PROXY·HTTPS_PROXY에도 같은 값이 들어 있어, 이 값을 읽는 도구(requests·axios 등)는 설정 없이 프록시로 나갑니다- 패키지 설치(
package.json·requirements.txt)도 같은 길로 받아 옵니다
내 액터 요금
메모리 × 실제로 돈 시간으로 셉니다. 메모리 1GB로 1시간 돌면 400원(부가세 포함).
- 예) 메모리 1,024MB 로 10분 돌면 67원
- 시작할 때 "메모리 × 제한 시간" 최대치만큼 잔액이 있는지 보고, 끝나면 실제로 쓴 만큼만 빠집니다
maxCostKrw를 주면 그 금액 안에서 끝나도록 제한 시간을 맞춥니다
| 설정 | 고를 수 있는 범위 | 기본값 |
|---|---|---|
memoryMb 메모리 | 256~4,096MB | 1,024MB |
timeoutSec 제한 시간 | 60~3,600초 | 600초 |
maxItems 최대 결과 수 | 1~100,000건 | 10,000건 |
| 코드 파일 | 50개, 합쳐서 1MB 까지 | — |
| 내 액터 수 | 조직당 30개 | — |
결과와 파일
Actor.pushData()로 넣은 것 → 결과(데이터셋).GET /v1/scrape/runs/<id>/items?format=csv로 내려받습니다Actor.setValue("이름", 내용)으로 넣은 것 → 파일(스크린샷·PDF 등).GET /v1/scrape/runs/<id>/records에서 봅니다. 파일 하나 10MB, 실행당 100개, 합쳐서 200MB 까지- 로그(
log.info·print)는GET /v1/scrape/runs/<id>/log와 콘솔 실행 기록에서 봅니다