How to Bulk Harvest¶
Use HarvesterAPI to create web harvest tasks (screenshots, asset downloads, video capture) and retrieve the results.
Use async for bulk operations
For harvesting multiple URLs, use the async methods shown below to run tasks concurrently. This is significantly faster than processing URLs one at a time.
Task Types¶
task_type |
Description | Params key |
|---|---|---|
"visual" |
Full-page screenshot, PDF, or MHTML capture | vis_params |
"asset" |
Recursive file/asset download via wget | wget_params |
"video" |
Video download and transcription via yt-dlp | vid_params |
"single" |
Single-URL non-recursive fetch; same wget_params as asset; available from all egress locations |
wget_params |
See the Web Harvesting API reference for all supported task_params keys per type.
Single Task (Synchronous)¶
from silo_sdk import HarvesterAPI, load_config
config = load_config("config/default.json")
harvester = HarvesterAPI(config)
# Visual capture — full-page image
task_id = harvester.create_harvest_task(
task_type="visual",
urls=["https://example.com"],
egress_info={"name": "New York, NY"},
dest_path="/screenshots/",
task_params={"output_image": True},
)
print(f"Task created: {task_id}")
# Poll until done
result = harvester.wait_for_completion(task_id, max_wait_time=300, check_interval=5)
if result and result.get("status") == "done":
print("Capture complete")
elif result and result.get("status") == "error":
print(f"Failed: {result.get('last_error')}")
Single Task (Async)¶
import asyncio
from silo_sdk import HarvesterAPI, load_config
config = load_config("config/default.json")
harvester = HarvesterAPI(config)
async def harvest_one(url: str) -> dict:
task_id = harvester.create_harvest_task(
task_type="visual",
urls=[url],
egress_info={"name": "New York, NY"},
dest_path="/screenshots/",
task_params={"output_pdf": True, "output_image": True},
)
return await harvester.wait_for_completion_async(
task_id, check_interval=5, max_wait_time=300
)
result = asyncio.run(harvest_one("https://example.com"))
print(result.get("status"))
Bulk Tasks (Async Concurrent)¶
Use async for bulk harvesting — tasks run concurrently:
import asyncio
from silo_sdk import HarvesterAPI, load_config
config = load_config("config/default.json")
harvester = HarvesterAPI(config)
urls = [
"https://site1.example.com",
"https://site2.example.com",
"https://site3.example.com",
]
async def harvest_all(urls: list[str]) -> None:
# Create all tasks first
task_ids = []
for url in urls:
task_id = harvester.create_harvest_task(
task_type="visual",
urls=[url],
egress_info={"name": "New York, NY"},
dest_path="/screenshots/",
task_params={"output_image": True},
)
task_ids.append(task_id)
print(f"Created: {task_id} → {url}")
# Poll all tasks concurrently
results = await asyncio.gather(*[
harvester.wait_for_completion_async(task_id, check_interval=5, max_wait_time=300)
for task_id in task_ids
])
for task_id, result in zip(task_ids, results):
status = result.get("status") if result else "timeout"
print(f"Task {task_id}: {status}")
asyncio.run(harvest_all(urls))
Scheduled Daily Captures (run_after)¶
Use run_after to queue tasks at a future time. Accepts a Unix epoch integer or ISO 8601 UTC string:
import time
task_id = harvester.create_harvest_task(
task_type="visual",
urls=["https://example.com"],
egress_info={"name": "London"},
dest_path="/daily/",
task_params={"output_pdf": True},
run_after=int(time.time()) + 3600, # 1 hour from now
)
For automated daily scheduling with jitter, use the scheduled_visual_harvest.py script:
# 7 days starting now, London egress, screenshot + image
python scripts/scheduled_visual_harvest.py https://example.com \
--egress London --output-image
# PDF capture, A4 landscape, specific start time
python scripts/scheduled_visual_harvest.py https://target.com \
--egress "New York, NY" --start 2026-03-01T09:00:00Z \
--days 7 --dest-path /daily-captures/ \
--output-pdf --paper A4 --landscape
# Preview the schedule without submitting
python scripts/scheduled_visual_harvest.py https://example.com \
--egress London --output-image --dry-run
Tasks enter wait status until their scheduled time, then activate automatically.
Task Status Values¶
| Status | Meaning |
|---|---|
wait |
Queued but not yet started (includes run_after deferred tasks) |
claimed |
Assigned to a Silo node |
running |
Actively executing |
done |
Completed successfully |
error |
Failed — check last_error field |
Asset Download Example¶
task_id = harvester.create_harvest_task(
task_type="asset",
urls=["https://example.com"],
egress_info={"name": "New York City", "categories": {"connectivity": "datacenter"}},
dest_path="/assets/",
task_params={
"recursive": "true",
"level": "2",
"page-requisites": "true",
"quota": "100m",
},
)
Egress Locations¶
egress_info={"name": "New York, NY"}
egress_info={"name": "London"}
egress_info={"name": "Dallas, TX"}
# Silo for Research (datacenter egress required for asset tasks)
egress_info={
"name": "New York, NY",
"categories": {"connectivity": "datacenter", "availability": "public"},
}
Use BrowsingAPI.get_egress_locations() to list available location names.
For enriched metadata including connectivity type, availability, and supported protocols per city,
call BrowsingAPI.get_egress_locations(include_details=True):
from silo_sdk.browsing import BrowsingAPI
locations = BrowsingAPI.get_egress_locations(include_details=True)
# Returns: {"North America": [{"name": "New York, NY", "connectivity": ["datacenter", "isp", "wireless-carrier"],
# "availability": ["public"], "protocol": ["direct"]}, ...], ...}
TOR Routing¶
TOR egress is available for visual, video, and single task types only (not asset).
Two cities support TOR: Sydney (direct + TOR) and Moncks Corner, SC (TOR only).
# TOR routing — random exit node (recommended)
egress_info={"name": "World", "categories": {"protocol": "tor"}}
# TOR routing — constrained to a specific TOR-capable city
egress_info={"name": "Sydney", "categories": {"protocol": "tor"}}
egress_info={"name": "Moncks Corner, SC", "categories": {"protocol": "tor"}}
# Geographic hierarchy also works (resolves to nearest TOR-capable city)
egress_info={"name": "Australia", "categories": {"protocol": "tor"}} # → Sydney
egress_info={"name": "United States", "categories": {"protocol": "tor"}} # → Moncks Corner
Download Results¶
Harvest results are stored in the bucket specified by BUCKET_ID in your config (or override per-task with dest_bucket_id). Download with FileAPI: