Platform hardening. Measured on 30 companies: 30/30 succeeded, 0 failed, 156s, 0.043
compute units - roughly a third of the cost per result the Actor used to run at.
- Dataset writes are serialised. Called concurrently from every worker,
Actor.pushData stopped settling for the later companies. The request handler waited on
a write that never returned, was killed by Crawlee at its timeout, and a company that had
been enriched successfully was lost. Writes now queue one at a time, are time-boxed, and
are retried once against a fresh handle. A company whose row cannot be stored is reported
rather than dropped, and is never counted as billable.
- A company is bounded by one deadline shared across all providers, each receiving only
the time that remains. Previously each provider had its own timeout and the worst case
summed to more than the request handler allowed, so a slow company was killed and lost
everything it had gathered instead of finishing with partial data.
- Companies are never retried. Providers already retry their own requests; re-running a
whole enrichment costs twice and risked charging twice.
- Redundant DOM parsing removed. Each page was parsed about four times, including a
full serialise-and-reparse per call in
visibleText. Now one parse per page.
- Chromium is installed into a writable path in the image, so the browser fallback
actually works on the platform instead of silently degrading to HTTP-only.
- Status messages are throttled and never awaited, and named-dataset access is time-boxed
and self-disabling. Both could previously stall a company for minutes through client
retry backoff.
- Domains are checked for an A or MX record before any page is fetched, so a dead domain
no longer costs four browser page loads.
Fixes for three failures that only appeared once the Actor ran on the Apify platform.
- Chromium was missing from the image.
playwright resolved to 1.58.2, which looks
for a browser revision the base image does not ship, so every browser fallback failed
with "Executable doesn't exist at /pw-browsers/...". The browser is now installed
explicitly during the build. Before this fix the Actor silently degraded to HTTP-only
and returned no data for JavaScript-rendered sites.
- Writing to the auxiliary
errors dataset could stall a run. The call returned
"Insufficient permissions" and the Apify client retried it with exponential backoff for
roughly two minutes, pushing the request handler past its 120-second timeout. Crawlee
then retried the request and the company was enriched and pushed a second time, billing
one input row twice. Named-dataset access is now time-boxed, and the first failure
disables it for the rest of the run and falls back to logging.
- A retried request can no longer re-push a company that was already pushed, whatever
the cause of the retry.
Cost and data-quality rework, and a move to pay-per-result pricing.
- The Actor is now billed once per company enriched. One input row produces exactly
one dataset row regardless of how many contacts are found, so run cost is predictable
from the CSV alone.
- Renamed from "B2B Lead Enrichment (Free)" to "B2B Lead Enrichment".
- Output is now one row per company instead of one row per contact. Company fields are
no longer duplicated across rows.
person* columns are replaced by primaryContact* columns carrying the best contact
found. The full list moved to the contacts array with a contactCount alongside it.
techStack no longer contains SSL issuers, GitHub repository topics or source
languages. These moved to sslIssuer, githubTopics and programmingLanguages.
- The mail host moved out of
techStack into emailProvider.
- Pages are fetched over plain HTTP first; a browser is launched only for pages that
render entirely in JavaScript. Previously every page load ran through Chromium.
- Removed a fixed 1.5-second wait on every page load, replaced by waiting for content to
actually appear.
- Chromium is now launched lazily and shared, so runs that never meet a JavaScript-only
site never start a browser at all.
- Browsers block images, fonts, media and stylesheets — only the DOM is ever read.
- Default concurrency raised from 3 to 10.
- Provider time budgets are now enforced by the waterfall, so one hung request can no
longer hold a company open until the request handler times out.
Measured on a 12-company sample, per-company wall time fell from 2.55 s to 0.74 s and
browser page loads fell from 100% of fetches to roughly 10%.
- Sub-pages are discovered from the site's own navigation instead of guessed from a
fixed path list. The previous defaults never reached the contact page at all.
- Documentation placeholders are rejected. Addresses and phone numbers inside code
samples are excluded, along with known example identities. Previously a Stripe API
sample address and the placeholder number
1000000000 were published as real contacts.
- Shared inboxes are surfaced as labelled contacts and in
companyEmails, ranked by
sales relevance. They were previously collected and then discarded.
- Contacts now carry names, titles, seniority and department read from team and
contact page cards, or derived from unambiguous
first.last@ addresses.
- Locations are parsed rather than passed through. Office lists, countries in the city
field, combined "City, ST" values and non-places like "Remote" are handled correctly.
- Added
emailProvider, emailSecurity, emailMarketingTools, dnsProvider,
sslIssuer, sslValidFrom, sslValidTo, companyLogoUrl, companyGithubUrl,
companyEmails, companyPhone, programmingLanguages and githubTopics.
- Added
enrichmentScore and fieldsFilled for filtering thin records.
- Industry classification reads page headings as well as the description, weighted lower
so marketing copy cannot outvote it, and covers 18 non-technology sectors.
- Homepages are retried on the
www host and over HTTP before being given up on.
- schema.org parsing now walks
@graph structures and picks the richest organization node.
- DNS falls back to DNS-over-HTTPS when the system resolver is unreachable.
"San Francisco, CA" resolved to country Canada, because CA is both a US state code
and an ISO country code.
- A short page carrying schema.org or a meta description was treated as a JavaScript
shell and needlessly re-fetched in a browser.
contact-us@ style addresses were treated as individuals, and a nearby office heading
could be published as that person's name.
- Hyphenated surnames were dropped when deriving names from email addresses.
- "Chief Executive Officer" mapped to no department.
- Unit test suite covering email validity, contact extraction, name derivation, phone
validation, location normalisation, JavaScript-shell detection and sub-page discovery.