Skip to content

Commit 3508d8b

Browse files
docs: Pay per result PR comments fixes
1 parent 5280723 commit 3508d8b

File tree

1 file changed

+65
-22
lines changed

1 file changed

+65
-22
lines changed

sources/platform/actors/publishing/monetize/pay_per_event.mdx

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,17 @@ Set memory limits using `minMemoryMbytes` and `maxMemoryMbytes` in your [`actor.
6666
"actorSpecification": 1,
6767
"name": "name-of-my-scraper",
6868
"version": "0.0",
69-
"minMemoryMbytes": 256,
70-
"maxMemoryMbytes": 4096,
69+
"minMemoryMbytes": 512,
70+
"maxMemoryMbytes": 1024,
7171
}
7272
```
7373

74+
:::note Memory requirements for browser-based scraping
75+
76+
When using browser automation tools like Puppeteer or Playwright for web scraping, increase the memory limits to accommodate the browser's memory usage.
77+
78+
:::
79+
7480
### Charge for "Actor start"
7581

7682
Charge for "Actor start" to prevent users from running your Actor for free.
@@ -134,36 +140,51 @@ async def main():
134140
:::note Actor migrations and charging
135141

136142
Actors can migrate between servers during execution, which restarts the process and clears memory. When using PPE charging, avoid charging the start event multiple times after a migration by checking your charging state.
143+
137144
:::
138145

139146
### Charge for invalid input
140147

141-
Charge for invalid input or empty search to prevent users from running your Actor for free.
148+
Charge for things like URLs that appear valid but lead to errors (like 404s) since you actually had to open the page to discover the error. Return error items with proper error codes and messages instead of failing the entire Actor run.
142149

143150
<Tabs groupId="main">
144151
<TabItem value="JavaScript" label="JavaScript">
145152

146153
```js
147154
import { Actor } from 'apify';
148155

149-
const parseInput = async (input) => {
150-
if (!input.searchStringsArray && !input.startUrls) {
156+
const processUrl = async (url) => {
157+
const response = await fetch(url);
158+
159+
if (response.status === 404) {
160+
// Charge for the work done (opening the page)
151161
await Actor.charge({
152-
eventName: "invalid-input",
162+
eventName: "scraped-result",
163+
});
164+
165+
// Return error item instead of failing
166+
await Actor.pushData({
167+
url: url,
168+
error: "404",
169+
errorMessage: "Page not found"
153170
});
154171

155-
throw await Actor.fail('INVALID INPUT: You must provide either search terms, or start URLs!');
172+
return;
156173
}
157174

158-
return input;
175+
// Rest of the Actor logic
159176
};
160177

161178
await Actor.init();
162179

163180
const main = async () => {
164181
const input = await Actor.getInput();
165-
const parsedInput = await parseInput(input);
182+
const { urls } = input;
166183

184+
for (const url of urls) {
185+
await processUrl(url);
186+
}
187+
167188
// Rest of the Actor logic
168189
};
169190

@@ -177,23 +198,37 @@ await Actor.exit();
177198

178199
```python
179200
from apify import Actor
201+
import requests
180202

181-
async def parse_input(input_data):
182-
if not input_data.get('searchStringsArray') and not input_data.get('startUrls'):
183-
await Actor.charge(event_name='invalid-input')
203+
async def process_url(url):
204+
response = requests.get(url)
205+
206+
if response.status_code == 404:
207+
# Charge for the work done (opening the page)
208+
await Actor.charge(event_name='scraped-result')
209+
210+
# Return error item instead of failing
211+
await Actor.push_data({
212+
'url': url,
213+
'error': '404',
214+
'errorMessage': 'Page not found'
215+
})
184216

185-
raise await Actor.fail('INVALID INPUT: You must provide either search terms, or start URLs!')
217+
return
186218

187-
return input_data
219+
# Rest of the Actor logic
188220

189221
async def main():
190222
await Actor.init()
191223

192224
input_data = await Actor.get_input()
193-
parsed_input = await parse_input(input_data)
225+
urls = input_data.get('urls', [])
226+
227+
for url in urls:
228+
await process_url(url)
194229

195230
# Rest of the Actor logic
196-
231+
197232
await Actor.exit()
198233
```
199234

@@ -204,6 +239,8 @@ async def main():
204239

205240
Finish the Actor run once charging reaches user-configured Maximum cost per run. Apify SDKs (JS and Python) return ChargeResult that helps determine when to finish.
206241

242+
The `eventChargeLimitReached` property checks if the current event type can be charged more. If you have multiple event types, analyze the `chargeableWithinLimit` property to see if other events can still be charged before stopping the Actor.
243+
207244
<Tabs groupId="main">
208245
<TabItem value="JavaScript" label="JavaScript">
209246

@@ -266,9 +303,11 @@ async def main():
266303
</TabItem>
267304
</Tabs>
268305

269-
### Use idempotency keys to prevent double charges
306+
:::note Crawlee integration and spending limits
270307

271-
If you're not using the Apify SDKs (JS/Python), you need to handle idempotency (ensuring the same operation produces the same result when called multiple times) manually to prevent charging the same event multiple times.
308+
When using [Crawlee](https://crawlee.dev/), use `crawler.autoscaledPool.abort()` instead of `Actor.exit()` to gracefully finish the crawler and allow the rest of your code to process normally.
309+
310+
:::
272311

273312
### Keep pricing simple with fewer events
274313

@@ -292,21 +331,25 @@ Avoid charging for:
292331

293332
This helps users understand exactly what they're paying for and builds trust in your pricing model.
294333

334+
### Use idempotency keys to prevent double charges
335+
336+
If you're not using the Apify SDKs (JS/Python), you need to handle idempotency (ensuring the same operation produces the same result when called multiple times) manually to prevent charging the same event multiple times.
337+
295338
## Example of a pay-per-event pricing model
296339

297340
You make your Actor pay-per-event and set the following pricing:
298341

299342
- _"actor-start" event_: $0.10 per start
300343
- _"scraped-product" event_: $0.01 per product
301-
- _"api-call" event_: $0.05 per API call
344+
- _"scraped-product-detail" event_: $0.05 per detail
302345

303346
During the first month, three users use your Actor:
304347

305-
- _User 1 (paid plan)_: Starts Actor 5 times, scrapes 1,000 products, makes 50 API calls
348+
- _User 1 (paid plan)_: Starts Actor 5 times, scrapes 1,000 products, makes 50 product details
306349
- Charges: 5 × $0.10 + 1,000 × $0.01 + 50 × $0.05 = $0.50 + $10.00 + $2.50 = $13.00
307-
- _User 2 (paid plan)_: Starts Actor 2 times, scrapes 500 products, makes 20 API calls
350+
- _User 2 (paid plan)_: Starts Actor 2 times, scrapes 500 products, makes 20 product details
308351
- Charges: 2 × $0.10 + 500 × $0.01 + 20 × $0.05 = $0.20 + $5.00 + $1.00 = $6.20
309-
- _User 3 (free plan)_: Starts Actor 1 time, scrapes 100 products, makes 5 API calls
352+
- _User 3 (free plan)_: Starts Actor 1 time, scrapes 100 products, makes 5 product details
310353
- Charges: 1 × $0.10 + 100 × $0.01 + 5 × $0.05 = $0.10 + $1.00 + $0.25 = $1.35
311354

312355
Let's say the underlying platform usage for the first user is $2.50, for the second $1.20, and for the third $0.30.

0 commit comments

Comments
 (0)