ZoomInfo zu scrapen, eine B2B-Datenplattform, bedeutet, die Firmenprofile zu sammeln, die ohne Login angezeigt werden. Vertriebs- und Datenteams nutzen ZoomInfo-Unternehmensdaten, um CRM-Konten mit Branche, Mitarbeiterzahl, Umsatz und Technologiefeldern anzureichern.
Ohne Referrer von einer anderen Seite erhielten alle 32 ersten Anfragen, die wir von neuen Residential-IPs sendeten, ZoomInfos “Press & Hold”-Challenge-Seite. In unseren Tests wurden IPs, die eine erste Seite erhielten, nach einem Median von 3 Seiten blockiert.
Dieser Leitfaden zeigt Ihnen, wie Sie Firmenseiten im großen Maßstab abrufen, Konten mit Profilen abgleichen, Daten extrahieren und entscheiden, wann Sie einen ZoomInfo Scraper kaufen sollten.
Kurzfassung
- Wir maßen etwa 3 Seiten pro IP, daher würde das Sammeln von 100.000 ZoomInfo-Seiten mehr als 33.000 Sitzungen erfordern. Bei der 5. Anfrage erhielten 12 von 14 Sitzungen einen Cloudflare-429-Fehler.
- ZoomInfo enthielt doppelte Firmenprofile. Eine Suche nach einer Domain ergab 3 Firmen-IDs, und ein Duplikat zeigte eine andere Mitarbeiterzahl und einen anderen Umsatz an.
- Das eingebettete JSON von ZoomInfo enthielt ungerundete Zahlen. Web Unlocker lieferte Aviagens Seite mit 6.776 Mitarbeitern im JSON, während das Profil 5K bis 10K anzeigte.
- Die ZoomInfo Scraper API von Bright Data übernimmt die Entsperrung für Sie. Aus 1 Verzeichnis-URL lieferte sie 45 strukturierte Firmendatensätze.
Was ZoomInfo ohne Login anzeigt
ZoomInfos öffentliche Seiten lieferten detaillierte Firmendaten und grundlegende Personendaten. E-Mails, direkte Telefonnummern, vollständige Kontaktlisten und die vollständige Liste der Technologien erforderten ein Konto. Unser ausgeloggter Scraper sammelte firmographische Daten wie Branche und Mitarbeiterzahl. Die öffentliche Seite hatte diese Haupttypen von Seiten:
| Seitentyp | URL-Muster | Was sie anzeigen kann |
|---|---|---|
| Firmenprofil | /c/{slug}/{id} |
Branche, Mitarbeiterzahl, Umsatzschätzung, Hauptsitz, Telefon, Website, SIC- und NAICS-Codes, einige Technologien, Finanzierung und Mitarbeiterzahlen nach Managementebene |
| Firmenverzeichnis | /companies-search/{filters} |
9 Unternehmen pro Seite, gefiltert nach Standort und Branche |
| Personenprofil | /p/{name}/{id} |
Ein Name, eine Berufsbezeichnung und ein Arbeitgeber |
| Andere Seiten | /pic/, /hq/, /tech/, /executives/, /competitors/ |
Mitarbeiterverzeichnisse, Hauptsitzdetails, Unternehmen, die eine Technologie nutzen, Führungskräfte und Wettbewerber |
Personenseiten, Mitarbeiterverzeichnisse und die Listen von Personen auf Firmenseiten enthalten personenbezogene Daten, die unter die GDPR-Konformität fallen können. Eingeloggte Seiten unterliegen den Nutzungsbedingungen von ZoomInfo, denen Kontoinhaber zustimmen. Unsere Skripte sammeln nur öffentliche Firmendatensätze und lassen Personenfelder weg.
Warum Ihre erste Anfrage an ZoomInfo blockiert wird
ZoomInfo betrieb 2 Anti-Bot-Systeme vor seinen Seiten, und jede blockierte Antwort zeigte, welches System sie gesendet hatte. Ein Proxy zwischen Ihrem Scraper und ZoomInfo kann auch den TLS-Fingerabdruck ändern, den diese Systeme sehen. In unseren Tests benötigte die erste Anfrage einer Sitzung einen externen Referrer, aber spätere Anfragen, die die Cookies der Sitzung trugen, benötigten keinen.
Jedes Anti-Bot-System hat seine eigene Signatur
Anfragen an zoominfo.com-Seiten, einschließlich Verzeichnisseiten, wurden über Cloudflare geleitet und von HUMAN Security überprüft, das 2022 mit PerimeterX fusionierte. Beide Systeme markierten ihre blockierten Antworten mit eindeutigen Signaturen, und eine echte Firmenseite hatte ebenfalls eine Signatur:
| Antwort von | Status und Seite | Signal zur Unterscheidung |
|---|---|---|
| HUMAN Security | 403, “Access to this page has been denied” mit einer “Press & Hold”-Schaltfläche | Der 403-Status und px-captcha im Body |
| Cloudflare | 429, “Just a moment…” | Der cf-mitigated: challenge Response-Header |
| ZoomInfo-Ursprung | 200, die Firmenseite | <script id="ng-state"> mit Firmendaten |
Unterscheiden Sie anhand dieser Signale, nicht anhand von Seitentiteln. Firmenseiten enthielten auch _pxAppId, daher bedeutet dessen Vorhandensein nicht, dass HUMAN die Anfrage blockiert hat. Die Cloudflare-Dokumentation besagt, dass jeder Typ von Challenge-Seite den cf-mitigated: challenge Header trägt.
Das eingebettete JSON im ng-state-Skript markierte echte Firmenseiten am zuverlässigsten. Behandeln Sie eine Antwort nur dann als erfolgreich, wenn sie dieses JSON trägt.
Was Ihr Proxy mit Ihrem TLS-Fingerabdruck macht
Wir verwendeten eine Bright Data Residential-Proxy-Zone, eine benannte Proxy-Konfiguration. Die Zone öffnete eine eigene TLS-Verbindung zur Seite. Wir sendeten Anfragen von 3 Clients an tls.peet.ws, einen TLS-Fingerabdruck-Checker, zunächst ohne Proxy und dann über diese Zone:
| Client | JA4 ohne Proxy | JA4 über den Proxy |
|---|---|---|
| curl_cffi als Chrome 150 | t13d1516h2_8daaf6152771_806a8c22fdea |
t13d1516h2_8daaf6152771_d8a2da3f94cd |
| curl_cffi als Firefox 147 | t13d1717h2_5b57614c22b0_3cbfd9057e0d |
t13d1516h2_8daaf6152771_02713d6af862 |
| System curl 8.7.1 (LibreSSL) | t13d497h2_0d8feac7bc37_7395dae3b2f3 |
t13d1516h2_8daaf6152771_02713d6af862 |
Über den Proxy begannen alle 3 JA4-Fingerabdrücke mit t13d1516h2_8daaf6152771, denselben ersten 2 Teilen wie curl_cffi, das Chrome imitiert. Diese Teile kodieren TLS 1.3, 15 Cipher-Suiten, 16 Erweiterungen und HTTP/2. Der Proxy präsentierte also für alle 3 Clients einen Chrome-ähnlichen TLS-Fingerabdruck. Die Chrome-Imitation behielt über den Proxy auch ihren HTTP/2-Fingerabdruck bei, nutzen Sie diese also, um sowohl den TLS- als auch den HTTP/2-Fingerabdruck wie den von Chrome aussehen zu lassen.
Prüfen Sie, welcher Hop TLS terminiert, bevor Sie Zeit in TLS-Imitation investieren. Wenn der Proxy Ihre Verbindung nur tunnelt, sieht die Seite den eigenen Handshake Ihres Clients. In beiden Fällen beginnt die TCP-Verbindung am Exit-Node, dessen SYN-Paket seine eigene TTL, Fenstergröße und Optionsreihenfolge trägt. Ein macOS-User-Agent kann über einen Windows-TCP-Stack ankommen, und ein Anti-Bot-System, das TCP-Fingerabdrücke liest, kann diese Diskrepanz melden.
Wie ZoomInfo die erste Anfrage bewertet
HUMANs Challenge-Seite führte ein Sensor-Skript im Browser aus, und das Skript setzte Risiko-Cookies wie _px3. Die erste Anfrage eines Scrapers trägt keines dieser Cookies, daher kann die Seite nur die Anfrage und die Verbindung, über die sie ankommt, bewerten.
Wir sendeten 32 cookielose Firmenseiten-Anfragen ohne Referrer von neuen US-Residential-IPs, wobei jeder der 4 Clients 8 sendete. Die Clients waren System-curl, curl mit einem vollständigen Satz von Chrome-Headern, curl_cffi als Chrome-Imitation und httpx über HTTP/2. Alle 32 erhielten HUMANs 403-Fehler und diese “Press & Hold”-Seite:

ZoomInfo gab diese Challenge-Seite als Antwort auf eine cookielose Anfrage ohne Referrer zurück.
Über die Proxy-Zone sah Cloudflare den TLS-Handshake des Proxys für alle 4 Clients. Von einer Heimverbindung aus sah Cloudflare den eigenen Handshake jedes Clients. Die ersten Anfragen von allen 6 Clients, die wir bei dieser Verbindung testeten, erhielten denselben 403-Fehler. Bei unseren ersten Anfragen ohne Referrer veränderte der TLS-Handshake also nicht das Ergebnis.
Ein externer Referrer veränderte das Ergebnis. Cookielose Anfragen von curl_cffi mit Chrome-Imitation waren in 16 von 18 Fällen erfolgreich, wenn ihr Referer-Header eine andere Seite benannte. Die Referrer waren Google, Bing, DuckDuckGo, LinkedIn und example.com. Von den 2 fehlgeschlagenen Anfragen erhielt 1 einen HTTP-502-Fehler.
Cookielose Anfragen mit einem sec-fetch-site: cross-site-Header, aber ohne Referer-Header, wurden in 3 von 3 Fällen blockiert. Cookielose Anfragen mit einem Same-Origin-Referrer wurden ebenfalls in 3 von 3 Fällen blockiert. In unseren Tests musste eine erste Anfrage wie ein Klick von einer anderen Seite aussehen, zum Beispiel aus einem Suchergebnis.
Mit einem Google-Referrer änderte sich die Erfolgsrate auch mit der Parallelität und dem User-Agent:
- Bei 6 gleichzeitigen Anfragen über rotierende IPs waren 14 von 45 curl_cffi-Anfragen erfolgreich.
- Bei 1 Anfrage gleichzeitig mit einer 6-Sekunden-Pause nach jeder waren 13 von 20 Anfragen erfolgreich, und 3 erhielten eine Weiterleitung.
- Anfragen mit einem
curl– oderpython-requests-User-Agent wurden in 29 von 29 Fällen blockiert. - Bei 6 gleichzeitigen Anfragen waren 4 von 15 System-curl-Anfragen mit einem Chrome-User-Agent erfolgreich.
Selbst bei 1 Anfrage gleichzeitig schlugen 7 von 20 Anfragen fehl, daher benötigt ein selbst erstellter Scraper dennoch Wiederholungsversuche, neue Sitzungen und Überwachung.
Wie viele ZoomInfo-Seiten jede IP erhält
Sobald eine Sitzung ihre erste Seite erhielt, lieferte ZoomInfo nur noch wenige weitere Seiten an dieselbe IP. Jede Sitzung behielt 1 Residential-IP durch die Sticky-Session-Einstellung des Proxys bei und sendete bei der ersten Anfrage einen Google-Referrer. Dann zählten wir Firmenseiten bis zur ersten blockierten Anfrage.
Über 14 Sitzungen, die eine erste Seite erhielten, lud die mittlere Sitzung 3 Firmenseiten, bevor ihre erste blockierte Anfrage auftrat. Bei der 5. Anfrage hatten 12 der 14 einen Cloudflare-429-Fehler erhalten. Nach dem ersten 429-Fehler erhielt auch jede spätere Anfrage in dieser Sitzung einen 429-Fehler. Keiner der untersuchten 429-Fehler trug einen Retry-After-Header.
In den untenstehenden Sitzungen veränderte eine Verzögerung von 2 oder 15 Sekunden zwischen den Seiten die Zählung nicht:
| Client | Verzögerung zwischen Seiten | Ergebnis pro Anfrage |
|---|---|---|
| HTTP-Client | 15 Sekunden | OK, OK, dann 429 bei den nächsten 8 |
| HTTP-Client | 15 Sekunden | OK, OK, OK, dann 429 bei den nächsten 7 |
| HTTP-Client | 2 Sekunden | OK, OK, OK, dann 429 bei den nächsten 7 |
| Echtes Chrome | Seitenladezeit | OK, OK, OK, dann “Just a moment…” bei den nächsten 5 |
In der Chrome-Sitzung lief ein sichtbares Chrome mit verstecktem navigator.webdriver, über eine separate Sticky-Session. Es lud die ersten 3 Seiten jeweils in 6 bis 9 Sekunden. Bei jeder der nächsten 5 Seiten zeigte Cloudflares Challenge nach etwa 15 Sekunden immer noch an, obwohl der Browser ein Clearance-Cookie, cf_clearance, besaß.
In Sitzungen, die die Cookies der ersten Antwort beibehielten, benötigten spätere Anfragen keinen externen Referrer. Eine spätere Anfrage konnte die vorherige ZoomInfo-Seite als ihren Referrer benennen, wie es ein Browser tut.
Das Löschen der Cookies setzte die Zählung nicht zurück. Von 4 Sitzungen, die sie vor jeder Anfrage löschten und jedes Mal einen Google-Referrer sendeten, erhielten 3 nach 3 Seiten einen 429-Fehler. Das Limit war also an die IP gebunden, nicht an die Cookies.
Foren-Threads behaupten, Anti-Bot-Cookies seien an die Exit-IP gebunden, aber wir konnten dies mit den Cookies, die unser HTTP-Client erhielt, nicht reproduzieren. Wir verschoben ein Cookie-Jar mit _pxhd, _cfuvid und contextual_signup_test von einer IP auf eine neue, und 4 von 5 Anfragen waren erfolgreich. Cookielose Anfragen von anderen neuen IPs zur gleichen Zeit waren in 2 von 5 Fällen erfolgreich. Beide Gruppen sendeten einen Google-Referrer.
Dieses Diagramm zeigt den Pfad von einem Scraper zu einer Firmenseite und die Antwort jedes Anti-Bot-Systems:

Eine blockierte Anfrage liefert keine Firmendaten, daher bedeutet eine höhere Blockierungsrate mehr Anfragen pro Datensatz.
Bei etwa 3 Seiten pro IP würde das Sammeln von 100.000 Firmenseiten pro Monat mehr als 33.000 Sitzungen erfordern, die eine erste Seite erhalten. Jede blockierte erste Anfrage bedeutet eine weitere zu startende Sitzung. Bright Datas Web Unlocker übernimmt diese Sitzungen für Sie.
Firmendaten aus einer ZoomInfo-Seite extrahieren
ZoomInfos Firmenseiten waren serverseitig gerenderte Angular-Anwendungen. Der Server schrieb die Daten der Seite in <script id="ng-state" type="application/json">. Dieses Skript stammt von Angulars TransferState-API, die es dem Browser erlaubt, die Daten wiederzuverwenden, ohne sie erneut abzurufen. Für Walmart enthielt das Skript 84 KB JSON, und sein pageData-Objekt hatte 42 Schlüssel.
Das eingebettete JSON enthielt präzisere Zahlen als die sichtbare Seite. Aviagens Profil zeigte seine Mitarbeiterzahl als Bereich und seinen Umsatz auf 1 Nachkommastelle gerundet an:

Aviagens sichtbares Profil zeigte “5K – 10K” Mitarbeiter und “2,4 Milliarden Dollar” Umsatz.
Das JSON derselben Seite enthielt die ungerundeten Zahlen:
<script id="ng-state" type="application/json">
{"pageData": {
"companyId": 9047889,
"name": "Aviagen",
"numberOfEmployees": "6776",
"about": {"revenue": "2413885", ...},
...
}}
</script>
ZoomInfo speicherte den Umsatz in Tausenden von US-Dollar, daher bedeutet Aviagens 2413885 2.413.885.000 Dollar. Vergleichen Sie das eingebettete JSON eines Unternehmens mit seinem sichtbaren Profil, um den Multiplikator zu bestätigen.
Andere Firmenseiten zeigten ebenfalls Bereiche, während ihr eingebettetes JSON ungerundete Zahlen enthielt. Der FAQ-Block auf der Walmart-Seite nannte “10K+” Mitarbeiter, während das JSON 2.100.000 enthielt. Bei 5 kleinen Unternehmen, die als “<5 Millionen Dollar" Umsatz angezeigt wurden, reichten die JSON-Schätzungen von 366.000 bis 4.474.000 Dollar.
Einige Felder änderten sich zwischen den Abrufen, selbst wenn die Firmendaten gleich blieben. Bei 2 Abrufen der Walmart-Seite, Minuten voneinander entfernt, unterschieden sich genau 3 pageData-Schlüssel:
picenthielt eine teilweise andere Gruppe von 9 Mitarbeitern.copilotContententhielt dieselben 10 Marketing-Vorlagen in neuer Reihenfolge.intentsenthielt 5 Einträge, von denen 2 einenrandomLocation-Wert trugen, und beide Werte änderten sich.
Mit diesen Feldern in der Seite kann das Hashing ganzer Seiten unveränderte Unternehmen bei jedem Crawl als verändert erscheinen lassen. Dieser Parser, gespeichert als parse_zoominfo.py, liest das eingebettete JSON, konvertiert die Einheiten, behält IDs als Strings und hasht nur Firmenfelder:
import hashlib
import json
import re
from urllib.parse import urljoin
STATE = re.compile(r'<script[^>]*id="ng-state"[^>]*>(.*?)</script>', re.S)
LD_JSON = re.compile(
r'<script[^>]*type="application/ld\+json"[^>]*>(.*?)</script>', re.S
)
def _state(html: str) -> dict | None:
match = STATE.search(html)
return json.loads(match.group(1)) if match else None
def _int(value) -> int | None:
return int(value) if value and str(value).isdigit() else None
def _zi_rank(html: str) -> int | None:
for block in LD_JSON.findall(html):
data = json.loads(block)
if isinstance(data, dict) and data.get("@type") == "Organization":
for prop in data.get("additionalProperty", []):
if prop.get("name") == "ZI Rank":
return prop.get("value")
return None
def parse_company(html: str) -> dict | None:
state = _state(html)
page = (state or {}).get("pageData") or {}
if not page.get("companyId"):
return None # eine Challenge-Seite, eine leere Hülle oder eine andere Vorlage
about = page.get("about") or {}
overview = page.get("overview") or {}
revenue_k = _int(about.get("revenue")) # in Tausend USD gespeichert
domain = (page.get("url") or "").lower().removeprefix("www.")
return {
"id": str(page["companyId"]), # Strings: Personen-IDs überlaufen int32
"name": page.get("name"),
"domain": domain or None,
"employees": _int(page.get("numberOfEmployees")), # ungerundete Zahl
"revenue_usd": revenue_k * 1000 if revenue_k is not None else None,
"founded": _int(page.get("foundingYear")),
"ticker": overview.get("ticker"),
"sic": overview.get("sic") or [],
"naics": overview.get("naics") or [],
"industries": [i["name"] for i in about.get("industries") or []],
"hq": page.get("address"),
"phone": about.get("phone"),
"tech_used": sorted(t["name"] for t in page.get("techUsed") or []),
"management_levels": {
m["title"]: m["value"] for m in page.get("mgtl") or []
},
"zi_rank": _zi_rank(html),
# Personenfelder (executives, pic, hierarchyOrgChart) werden ausgeschlossen.
}
# intents, pic und copilotContent änderten sich zwischen 2 Abrufen der
# gleichen Seite, daher schließt der Hash sie aus.
STABLE = ("name", "domain", "employees", "revenue_usd", "industries",
"hq", "phone", "sic", "naics", "ticker", "tech_used")
def content_hash(record: dict) -> str:
stable = json.dumps({k: record.get(k) for k in STABLE}, sort_keys=True)
return hashlib.sha256(stable.encode()).hexdigest()[:16]
parse_company gibt None für jede Seite ohne Firmendaten zurück, etwa eine Challenge-Seite oder eine leere Seite. Bei den 2 Walmart-Abrufen lieferte content_hash denselben Wert, b4ffe1810bbb1c8c, während sich der SHA-256-Hash ihres Roh-HTML unterschied.
ZoomInfo-Firmen-URLs im großen Maßstab finden
ZoomInfos robots.txt listete 1 Sitemap auf, die nur Marketing-Seiten enthielt. Wir fanden keine Sitemap für Firmen- oder Personenseiten, daher können Sie die URL-Liste aus 3 anderen Quellen aufbauen.
Verzeichnisseiten
Jede /companies-search/-URL listete die Unternehmen auf, die ihren Standort- und Branchenfiltern entsprachen, und ein pageNum-Abfrageparameter wählte die Seite aus. Die Alabama-Landwirtschaftsliste zeigte insgesamt 1.962 Unternehmen. Die Seiten 1 bis 5 lieferten jeweils die angeforderte Seite zurück. Anfragen für die Seiten 6 und 10 lieferten HTTP 200 mit den Unternehmen von Seite 1 und pageNum: 1 im eingebetteten JSON zurück.
Hier ist die erste Seite dieser Liste:

Die Seitennummerierung zeigte 5 Seiten. Die Umsatzspalte war in Tausenden von Dollar angegeben, daher entsprechen Aviagens 2,4M den 2,4 Milliarden Dollar in seinem Profil.
Ein Paginator, der nur Statuscodes prüft, würde die Unternehmen von Seite 1 erneut als neue Ergebnisse sammeln. Fügen Sie diese Absicherung zu parse_zoominfo.py hinzu. Sie vergleicht die Seitennummer im eingebetteten JSON mit der angeforderten:
def directory_page(html: str, requested: int) -> list[str] | None:
state = _state(html) or {}
if state.get("pageNum") != requested:
return None # jenseits der erreichbaren Seiten liefert ZoomInfo erneut Seite 1
rows = (state.get("aiSearchResults") or {}).get("data") or []
return [urljoin("https://www.zoominfo.com", row["companyUrl"])
for row in rows]
directory_page lieferte 9 Firmen-URLs pro Seite und gab für Seite 6 None zurück. Nur 5 Seiten dieses Filters waren also erreichbar, und sie enthielten etwa 45 der 1.962 Unternehmen. Um mehr Unternehmen zu erreichen, teilen Sie einen großen Filter nach US-Bundesstaat oder Teilbranche auf, bis jeder kleinere Filter 45 oder weniger Unternehmen enthält. Die ZoomInfo Scraper API von Bright Data kann auch ZoomInfo-Verzeichnisseiten crawlen und die darauf gelisteten Unternehmen als strukturierte Datensätze zurückgeben.
Suchergebnisse für bereits bekannte Unternehmen
Eine Abfrage wie site:zoominfo.com/c example.com, gesendet über Bright Datas SERP-API, kann Kandidatenprofile für eine Domain finden. Für brightdata.com lieferte die Abfrage 3 verschiedene Firmen-IDs. Behalten Sie den Kandidaten, dessen Website mit der gesuchten Domain übereinstimmt.
Links zwischen Firmenseiten
Viele Profile listeten ähnliche Unternehmen auf, und die ZoomInfo Scraper API von Bright Data lieferte diese in einem similar_companies-Feld zurück. Ein Crawler kann mit einer Liste von Seed-URLs beginnen und diesen Links folgen, um weitere Unternehmen zu finden.
Doppelte Profile und stabile IDs
Duplikate existierten in den eigenen Daten von ZoomInfo, sodass jede der 3 Quellen sie zurückgeben konnte. Für brightdata.com listete ein zusätzliches Profil die Straßenadresse des Hauptprofils, aber eine andere Website-Domain auf. Auch seine Schätzungen für Mitarbeiterzahl und Umsatz unterschieden sich vom Hauptprofil.
Ein Join auf Domain übersieht dieses Duplikat, und ein Join auf Name kann auch nicht verwandte Unternehmen zuordnen. Eine Suche nach minimax.com lieferte 9 Profile mit Minimax im Namen, und mehrere waren nicht verwandte Unternehmen. Gleichen Sie Entitäten anhand der Domain oder der Hauptsitz-Straßenadresse ab und überprüfen Sie nur die Profile, die bei einem, aber nicht beim anderen übereinstimmen. Behalten Sie die numerische ID von ZoomInfo als Primärschlüssel bei.
In einer Firmen-URL konnte sich der Slug ändern, aber die von uns verfolgte Weiterleitung behielt die numerische ID bei. In einer Stichprobe lieferten 3 von 20 Firmen-URLs eine 301-Weiterleitung, sodass jede eine weitere Anfrage über dieselben Anti-Bot-Systeme benötigte. Für die Eingabe /c/walmart-inc/155353090 lieferte die Scraper API die kanonische URL /c/walmart/155353090, die dieselbe ID beibehielt. Web Unlocker folgte derselben Weiterleitung und lieferte Walmarts Seite zurück.
3 Möglichkeiten, ZoomInfo-Daten im großen Maßstab zu sammeln
Bei 100.000 Firmenseiten pro Monat und etwa 3 Seiten pro IP würde eine selbst erstellte Pipeline mehr als 33.000 Sitzungen und laufende technische Arbeit erfordern. Bright Datas ZoomInfo-Datenextraktionsoptionen übernehmen unterschiedliche Mengen dieser Arbeit, von entsperrten Seiten bis zu fertigen Datensätzen:
| Web Unlocker | ZoomInfo Scraper API | ZoomInfo-Datensatz | |
|---|---|---|---|
| Sie senden | Eine Seiten-URL | Firmen- oder Verzeichnis-URLs | Einen Filter |
| Sie erhalten | Das vollständige Seiten-HTML | Strukturierte JSON-Datensätze | Fertige Datensätze in einer Datei |
| In unseren Tests | Firmenseiten kamen mit intaktem eingebettetem JSON an | Firmendatensätze kamen an, und Verzeichnis-URLs wurden für uns paginiert | Die Beispieldatei zeigte das vollständige Schema |
| Lieferung | Synchrone oder asynchrone Anfragen | Snapshots über API, Cloud-Speicher oder Webhook | Dateien bei geplanter Aktualisierung |
| Parsing | Ihres | Enthalten | Enthalten |
Die richtige Option hängt von der benötigten Ausgabe ab:
- Strukturierte Datensätze für Ihre eigene URL-Liste. Nutzen Sie die Scraper API.
- Ungerundete Mitarbeiterzahl und Umsatz. Nutzen Sie Web Unlocker mit
parse_company. - Breite Abdeckung, bereits gesammelt. Nutzen Sie den Datensatz.
Für Seiten, die Klicks und Scrollen erfordern, betreibt Bright Datas Browser API ein gehostetes Chrome, das Sie mit Playwright oder Puppeteer steuern. Für Firmenseiten, die keine Klicks benötigen, ist Web Unlocker die einfachere Option, da es das HTML direkt zurückgibt, ohne Browser-Steuerung. Für KI-Agenten ermöglicht Bright Datas ZoomInfo MCP-Server Clients wie Claude Desktop und Cursor, dieselben öffentlichen Firmenprofile zu sammeln.
Um zu beginnen, erstellen Sie ein Bright Data-Konto und einen API-Schlüssel. Für Web Unlocker erstellen Sie auch eine Zone, eine benannte Web Unlocker-Konfiguration, im Kontrollfeld. Neue Konten erhalten kostenlose Credits, um Bright Datas APIs zu testen.
Die Skripte benötigen Python 3.10 oder höher. Die vollständigen Dateien befinden sich in Bright Datas zoominfo-scraper-Repository auf GitHub. Setzen Sie den Schlüssel und die Zone einmal:
pip install httpx
export BRIGHTDATA_API_KEY="your_api_key"
export BRIGHTDATA_UNLOCKER_ZONE="your_web_unlocker_zone"
Jedes Skript liest den Schlüssel beim Start ein, daher stoppt ein Skript mit fehlendem Schlüssel, bevor es irgendeine Anfrage sendet.
Web Unlocker liefert Seiten, die Sie parsen
Web Unlocker ist Bright Datas Entsperrungs-API. Sie senden eine URL, und Web Unlocker übernimmt Proxys, Browser-Rendering und Challenges, bevor es die Seite zurückgibt. In unseren Tests entsperrte es ZoomInfo-Firmenseiten und lieferte das HTML mit intaktem eingebettetem JSON zurück. parse_company funktioniert also mit diesem HTML ohne Änderungen.
Web Unlocker meldet den eigenen Status des Ziels im x-brd-status-code-Response-Header und Details in x-brd-error, sodass eine Pipeline anhand des Ergebnisses unterscheiden kann. Standardmäßig werden nur erfolgreiche Anfragen berechnet, und die Dokumentation listet die Einstellungen, die diesen Standard ändern.
Dieser Fetcher, gespeichert als fetch_pages.py, verarbeitet 5 URLs gleichzeitig, versucht jede bis zu 3 Mal und speichert die Datensätze in companies.jsonl:
import json
import os
import sys
import time
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timezone
import httpx
from parse_zoominfo import content_hash, parse_company
API = "https://api.brightdata.com/request"
KEY = os.environ["BRIGHTDATA_API_KEY"]
ZONE = os.environ["BRIGHTDATA_UNLOCKER_ZONE"]
def fetch(url: str, attempts: int = 3) -> dict:
error = None
for attempt in range(1, attempts + 1):
try:
resp = httpx.post(
API,
headers={"Authorization": f"Bearer {KEY}"},
json={"zone": ZONE, "url": url, "format": "raw"},
timeout=180,
)
# Die API meldet das eigene Ergebnis des Ziels in diesen 2 Headern.
target = resp.headers.get("x-brd-status-code")
record = parse_company(resp.text) if target == "200" else None
except (httpx.HTTPError, ValueError) as exc: # Timeout oder fehlerhaftes JSON
error = f"{type(exc).__name__}: {exc}"
else:
if record:
record["fetched_at"] = datetime.now(timezone.utc).isoformat()
record["hash"] = content_hash(record)
return {"url": url, "ok": True, "attempts": attempt,
"record": record}
error = resp.headers.get("x-brd-error") or f"target {target}"
if attempt < attempts:
time.sleep(2 ** attempt)
return {"url": url, "ok": False, "attempts": attempts, "error": error}
if __name__ == "__main__":
urls = [line.strip() for line in open(sys.argv[1]) if line.strip()]
with ThreadPoolExecutor(max_workers=5) as pool, \
open("companies.jsonl", "a") as good, \
open("failures.jsonl", "a") as bad:
for result in pool.map(fetch, urls):
(good if result["ok"] else bad).write(json.dumps(result) + "\n")
print("saved" if result["ok"] else "not saved", result["url"])
Mit 3 Firmen-URLs, je eine pro Zeile, in urls.txt gab python fetch_pages.py urls.txt aus:
saved https://www.zoominfo.com/c/bright-data/430599389
saved https://www.zoominfo.com/c/peco-foods-inc/82428957
saved https://www.zoominfo.com/c/aviagen-group/9047889
Alle 3 Unternehmen wurden gespeichert. Hier sind die Firmenfelder des Aviagen-Eintrags in companies.jsonl:
{
"id": "9047889",
"name": "Aviagen",
"domain": "aviagen.com",
"employees": 6776,
"revenue_usd": 2413885000,
"founded": 1923,
"ticker": null,
"sic": ["02", "025"],
"naics": ["11", "112"],
"industries": ["Agriculture", "Animals & Livestock"],
"hq": {
"street": "920 Explorer Blvd NW",
"city": "Huntsville",
"state": "Alabama",
"country": "United States",
"zip": "35806"
},
"phone": "(256) 890-3800",
"tech_used": ["ClickDimensions", "Freshservice", "PHP", "Zingfit"],
"management_levels": {
"C-Level": 14,
"VP-Level": 28,
"Director": 108,
"Manager": 998,
"Non-Manager": 1229,
"Top Contacts": 1316
},
"zi_rank": 65
}
Aviagens Profil zeigte diese employees– und revenue_usd-Werte als “5K – 10K” und “2,4 Milliarden Dollar” in seinem Header und seiner Übersicht an.
Die ZoomInfo Scraper API liefert Datensätze
Bright Datas ZoomInfo Scraper API übernimmt sowohl das Entsperren als auch das Parsen. Ihr Collect by URL-Endpunkt nimmt Firmen-URLs entgegen, und Discover by search_url nimmt Verzeichnis-URLs entgegen und sammelt die auf deren Seiten gelisteten Unternehmen. Das Kontrollfeld listete beide Endpunkte auf und bot synchrone und asynchrone Modi an:

Discover by search_url nimmt Verzeichnis-URLs wie diese entgegen und liefert die auf deren Seiten gelisteten Unternehmen zurück.
Für Batches nutzen Sie den asynchronen /trigger-Endpunkt, der den Job im Hintergrund ausführt und eine Snapshot-ID zurückgibt. Der synchrone /scrape-Endpunkt liefert Datensätze direkt für kleine Tests zurück und verschiebt längere Jobs nach seinem Antwortzeitlimit in einen Snapshot.
In unserem 40-URL-Batch trafen die ersten Datensätze innerhalb einer Minute ein. Lassen Sie die API bei großen Batches Ergebnisse an Cloud-Speicher oder einen Webhook liefern. Sie sendet jeden Snapshot, wenn die letzte Eingabe abgeschlossen ist, sodass Sie die API nicht abfragen müssen.
Discover by search_url übernimmt die Seitennummerierung für Sie. Allein aus der Alabama-Verzeichnis-URL lieferte es 45 Firmendatensätze zurück. Jeder Datensatz trug die Verzeichnis-URL, aus der er stammte. Für einen größeren Filter senden Sie eine Verzeichnis-URL für jeden US-Bundesstaat oder jede Teilbranche.
Der scraper_api.py-Client löst einen Job aus und fragt ab, bis der Snapshot bereit ist, und lädt ihn dann herunter. Seine clean-Funktion wandelt den Mitarbeiterbereich in employees_min und employees_max um und markiert Umsatzbereiche wie “<5 Millionen Dollar". Sie entfernt auch die Felder für Führungsebene, Organigramm und CEO, die Personendaten enthalten. Mit –discover sendet der Client Verzeichnis-URLs an Discover by search_url:
import json
import os
import re
import sys
import time
import httpx
BASE = "https://api.brightdata.com/datasets/v3"
DATASET_ID = "gd_m0ci4a4ivx3j5l6nx" # Zoominfo-Unternehmensinformationen
HEADERS = {"Authorization": f"Bearer {os.environ['BRIGHTDATA_API_KEY']}"}
def trigger(urls: list[str], discover: bool = False, limit: int = 50) -> str:
params = {"dataset_id": DATASET_ID, "include_errors": "true"}
if discover: # Verzeichnis-URLs: der Job übernimmt deren Seitennummerierung
params |= {"type": "discover_new", "discover_by": "search_url",
"limit_per_input": limit}
resp = httpx.post(f"{BASE}/trigger", params=params, headers=HEADERS,
json=[{"url": u} for u in urls], timeout=60)
resp.raise_for_status()
return resp.json()["snapshot_id"]
def wait(snapshot_id: str, every: int = 30) -> dict:
while True:
try:
progress = httpx.get(f"{BASE}/progress/{snapshot_id}",
headers=HEADERS, timeout=60).json()
except (httpx.HTTPError, ValueError): # Wiederholung nach Netzwerkfehler
progress = {}
if progress.get("status") in ("ready", "failed"):
return progress
time.sleep(every)
def download(snapshot_id: str, tries: int = 30) -> list[dict]:
for _ in range(tries):
try:
resp = httpx.get(f"{BASE}/snapshot/{snapshot_id}",
params={"format": "json"}, headers=HEADERS,
timeout=300)
if resp.status_code == 200 and resp.content.strip():
return resp.json()
except (httpx.HTTPError, ValueError): # Timeout oder unvollständige Datei
pass
time.sleep(10) # warten, bis die Snapshot-Datei verfügbar ist
raise RuntimeError(f"snapshot {snapshot_id} is not available yet")
def employee_range(text: str | None) -> tuple[int | None, int | None]:
# "11-50" -> (11, 50), "1K - 5K" -> (1000, 5000), "10K+" -> (10000, None)
if not text:
return None, None
nums = [int(float(n) * (1000 if k else 1)) for n, k
in re.findall(r"([\d.]+)\s*(K?)", text.replace(",", ""))]
if not nums:
return None, None
if len(nums) == 1:
return nums[0], None if "+" in text else nums[0]
return nums[0], nums[1]
def clean(row: dict) -> dict:
row["employees_min"], row["employees_max"] = employee_range(
row.get("employees_text"))
# Die Mitarbeiterzahl stammt aus dem employees_text-Bereich.
row["employees"] = row["total_employees"] = None
row["revenue_is_range"] = (row.get("revenue_text") or "").startswith("<")
row["stock_symbol"] = (row.get("stock_symbol") or "").strip() or None
row["id"] = str(row["id"])
for field in ("leadership", "org_chart", "ceo"): # Personendaten
row.pop(field, None)
return row
if __name__ == "__main__":
urls = [a for a in sys.argv[1:] if a.startswith("http")]
snapshot = trigger(urls, discover="--discover" in sys.argv)
progress = wait(snapshot)
summary = {k: progress.get(k) for k in ("status", "records", "errors")}
print(json.dumps(summary))
rows = download(snapshot)
# Zeilen mit einer ID und ohne Fehler behalten.
good = [clean(r) for r in rows if r.get("id") and not r.get("error")]
bad = [r for r in rows if r.get("error") or not r.get("id")]
json.dump(good, open("records.json", "w"), indent=2)
json.dump(bad, open("errors.json", "w"), indent=2)
print(f"{len(good)} records saved to records.json")
Mit den 3 Firmen-URLs aus urls.txt als Argumente gab der Client aus:
{"status": "ready", "records": 3, "errors": 0}
3 records saved to records.json
Der Snapshot enthielt alle 3 Datensätze, und der Client speicherte sie in records.json. Jeder Datensatz trug Firmenfelder wie Umsatz, Branche, Technologien, Website, Hauptsitz und Telefonnummer.
Der ZoomInfo-Datensatz liefert bereits gesammelte Datensätze
Der ZoomInfo-Datensatz liefert Datensätze mit denselben Feldern, bereits gesammelt und nach Zeitplan aktualisiert. Prüfen Sie die Datensatzseite auf die aktuelle Datensatzanzahl, Aktualisierungshäufigkeit und Preise, und sehen Sie sich das Beispiel an, bevor Sie kaufen. Um zwischen diesem und einem ZoomInfo-Abonnement zu entscheiden, lesen Sie unseren Vergleich von Bright Data und ZoomInfo.
Filtern Sie vor dem Kauf. Die Filter des Datensatzes lassen Sie nur die benötigten Datensätze auswählen, etwa Firmenseiten mit URLs, die /c/ enthalten. Lassen Sie die Spalten für Führungsebene und Organigramm weg, die Personendaten enthalten, es sei denn, Sie haben eine rechtmäßige Grundlage dafür.
Ein Datensatz enthält Datensätze, wie sie zum Zeitpunkt der Erfassung waren, und jede Aktualisierung bringt sie auf den neuesten Stand. Um eine Aktualisierungshäufigkeit festzulegen, sammeln Sie dieselbe Stichprobe von Unternehmen zweimal im Abstand von einem Monat und zählen Sie die veränderten Datensätze.
CRM-Domains mit ZoomInfo-Profilen abgleichen
Ein CRM enthält Domains statt ZoomInfo-URLs. Dieses Skript, gespeichert als find_profiles.py, sucht bei Google für jede Domain über Bright Data. Es ruft die Top-3-Profile mit fetch aus fetch_pages.py ab und behält den ersten Datensatz, dessen domain mit der CRM-Domain übereinstimmt. Findet die Suche ohne Anführungszeichen keine Übereinstimmung, sucht das Skript erneut mit der Domain in Anführungszeichen:
import json
import os
import re
import sys
from concurrent.futures import ThreadPoolExecutor
from urllib.parse import quote_plus
import httpx
from fetch_pages import fetch
API = "https://api.brightdata.com/request"
KEY = os.environ["BRIGHTDATA_API_KEY"]
# Eine SERP-API-Zone, falls vorhanden, ansonsten die Web Unlocker-Zone.
ZONE = (os.environ.get("BRIGHTDATA_SERP_ZONE")
or os.environ["BRIGHTDATA_UNLOCKER_ZONE"])
PROFILE = re.compile(r"https://www\.zoominfo\.com/c/[^/?#]+/\d+")
# Jede Abfrage fand Profile, die die andere übersah, daher beide versuchen.
QUERIES = ("site:zoominfo.com/c {}", 'site:zoominfo.com/c "{}"')
def candidates(query: str, top: int = 3) -> list[str]:
url = f"https://www.google.com/search?q={quote_plus(query)}&brd_json=1"
for _ in range(3):
try:
resp = httpx.post(
API,
headers={"Authorization": f"Bearer {KEY}"},
json={"zone": ZONE, "url": url, "format": "raw"},
timeout=120,
)
results = resp.json().get("organic", []) # geparste Ergebnisse
except (httpx.HTTPError, ValueError): # Timeout oder leerer Body
continue
links = [row.get("link", "") for row in results]
found = (PROFILE.match(link) for link in links)
return list(dict.fromkeys(m.group(0) for m in found if m))[:top]
return []
def find_profile(domain: str) -> dict | None:
for query in QUERIES:
for url in candidates(query.format(domain)):
result = fetch(url)
# Ein Profil nur behalten, wenn seine Website der CRM-Domain entspricht.
if result["ok"] and result["record"]["domain"] == domain:
return result["record"]
return None
if __name__ == "__main__":
domains = [d.strip().lower().removeprefix("www.") for d in sys.argv[1:]]
with ThreadPoolExecutor(max_workers=5) as pool, \
open("matches.jsonl", "a") as out:
records = pool.map(find_profile, domains)
for domain, record in zip(domains, records, strict=True):
out.write(json.dumps({"domain": domain, "record": record}) + "\n")
print(domain, record["id"] if record else "no match")
Für 4 CRM-Domains gab python find_profiles.py aviagen.com pecofoods.com brightdata.com walmart.com aus:
aviagen.com 9047889
pecofoods.com 82428957
brightdata.com 430599389
walmart.com no match
Für jede Übereinstimmung lieferte das Skript einen vollständigen Firmendatensatz zurück. Die Suche ohne Anführungszeichen fand Aviagen und Bright Data, und die Suche mit Anführungszeichen fand Peco Foods. Walmarts Profil erschien in keiner der beiden Suchergebnisse, daher lieferte das Skript keine Übereinstimmung statt eines falschen Unternehmens zurück. Senden Sie nicht übereinstimmende Domains an eine Namenssuche oder eine Überprüfungswarteschlange.
Jeden Datensatz validieren, bevor er Ihr CRM erreicht
Jede Antwort, die wir von ZoomInfo und von Bright Datas APIs sahen, erfordert eine eigene Aktion:
| Antwort | Bedeutung | Aktion |
|---|---|---|
200 mit Firmendaten in ng-state |
Eine echte Firmenseite | Parsen und speichern |
| 301-Weiterleitung | Die URL hat sich geändert | Die neue URL speichern und die ID als Schlüssel verwenden |
403 mit px-captcha im Body |
HUMAN hat die Anfrage blockiert | Mit neuer Sitzung wiederholen |
429 mit cf-mitigated: challenge |
Das Cloudflare-Anfragelimit pro IP wurde erreicht | Die IP rotieren und dort nicht mehr wiederholen |
| 429 ohne Cloudflare-Header | Bright Datas Auto-Drosselung, ausgelöst durch niedrige Erfolgsrate | Anfragerate senken und fehlschlagende Anfragen beheben |
| 502 ohne Anti-Bot-Signatur | Ein vorübergehender Upstream-Fehler | Mit Backoff wiederholen |
Web Unlocker x-brd-status-code-Header |
Der eigene Status der Zielseite | 200-Ergebnisse speichern und die anderen eine begrenzte Anzahl von Malen wiederholen |
Scraper API 202 von /scrape |
Der Job wurde in einen Snapshot verschoben | Die Snapshot-ID abfragen |
Verzeichnisseite, deren pageNum niedriger als angefordert ist |
Jenseits der letzten erreichbaren Seite | Seitennummerierung stoppen und Filter eingrenzen |
Statusprüfungen fangen fehlgeschlagene Seiten ab, und Feldprüfungen fangen fehlerhafte Datensätze ab:
- IDs als Strings beibehalten. Die von uns untersuchten ZoomInfo-Personen-IDs reichten von -2.135.143.530 bis 17.773.369.573, sodass sie weder in einen vorzeichenbehafteten noch in einen vorzeichenlosen 32-Bit-Integer passen.
- Mitarbeiterzahl aus Bereichen oder dem eingebetteten JSON lesen. Nutzen Sie
employees_minundemployees_maxausclean, odernumberOfEmployeesaus dem JSON. - Umsatzbereiche markieren. Ein Wert wie “<5 Millionen Dollar" ist ein Bereich, und das JSON enthielt die Schätzung.
- Branchencodes aufteilen. Werte wie “54,541” trugen einen Sektorcode und einen Teilsektorcode.
- Leere CEO-Felder erwarten. ZoomInfo ließ das CEO-Feld auf jeder von uns geprüften öffentlichen Seite leer.
- Füllraten pro Batch verfolgen. Ein plötzlicher Rückgang der Füllrate eines Felds kann bedeuten, dass ZoomInfo eine Vorlage geändert hat.
Unser Leitfaden zum Beheben ungenauer Web-Scraping-Daten erklärt die Datenbereinigung ausführlicher.
Was ZoomInfo-Scraping im großen Maßstab kostet
Die Optionen haben unterschiedliche Abrechnungseinheiten, und jede Preisseite listet die aktuellen Tarife auf:
| Option | Abgerechnet nach | Weitere Kosten |
|---|---|---|
| Web Unlocker und Ihr Parser | Erfolgreiche Anfragen, mit Standardeinstellungen | URL-Erkennung, Parsing, Deduplizierung und Hosting |
| ZoomInfo Scraper API | Gelieferte Datensätze | Deduplizierung und Speicherung |
| ZoomInfo-Datensatz | Datensätze in der Datei | Deduplizierung und Speicherung |
| Residential-Proxys und eigener Stack | Bandbreite | Sitzungen, Wiederholungsversuche, Parsing, Deduplizierung und die Entwicklungsarbeit, um sie zu betreiben |
Die von uns gemessenen Firmenseiten waren 42 bis 55 KB komprimiert, sodass 100.000 Seiten etwa 4 bis 6 GB Bandbreite nutzen. Bei größeren Mengen prüfen Sie jede Preisseite auf Mengentarife.
Residential-Proxys geben Ihnen die meiste Kontrolle. Bei dieser Seitengröße ist ihre Bandbreitenrechnung im Vergleich zu den technischen Kosten gering. Bei einem selbst erstellten Stack zahlen Sie auch für blockierte erste Anfragen, Wiederholungsversuche und die zusätzlichen Sitzungen bei etwa 3 Seiten pro IP. Sie benötigen auch einen Entwickler, der den Stack pflegt, wenn ZoomInfo seine Anti-Bot-Regeln oder Seitenstruktur ändert.
In einem IsraelTech-Podcast-Interview sagte Bright Data CEO Or Lenchner: “Sie brauchen uns, wenn Sie wiederholt und ständig Skalierung benötigen.” Für kleine Datenmengen riet Lenchner Teams, sie selbst zu sammeln. Unser Vergleich von Managed- und DIY-Scraping listet die Kosten jedes Ansatzes auf.
Abschließende Gedanken
ZoomInfos Seiten enthielten ungerundete firmographische Daten als eingebettetes JSON, aber in unseren Tests prüften Anti-Bot-Systeme jede erste Anfrage und blockierten IPs nach etwa 3 Seiten. Das JSON machte die Extraktion zur kleineren Aufgabe, daher benötigen im großen Maßstab Sitzungen, URL-Erkennung und Deduplizierung die meiste Arbeit. Unsere Skripte sammeln nur öffentliche Firmendaten, da Kontaktdetails ein Konto erforderten und Personendaten eine rechtmäßige Grundlage erfordern können.
Bright Datas Web Unlocker und ZoomInfo Scraper API übernehmen die Sitzungen, und der ZoomInfo-Datensatz liefert Datensätze, die Bright Data bereits gesammelt hat. Testen Sie die ZoomInfo Scraper API an einer Stichprobe Ihrer eigenen Konten und vergleichen Sie die Datensätze mit Ihrem CRM.
FAQ
Kann man ZoomInfo scrapen?
Ja, für die Firmenseiten, die ohne Login geladen werden. Kontaktdetails erforderten ein Konto. Die öffentlichen Firmenseiten trugen ihre Daten als eingebettetes JSON. In unseren Tests blockierte ZoomInfo alle 32 ersten Anfragen ohne Referrer von einer anderen Seite, starten Sie also jede neue Sitzung mit einem solchen.
Wie scrapt man Daten von ZoomInfo?
Finden Sie die Profil-URL jedes Unternehmens, rufen Sie die öffentliche Seite ab und lesen Sie die Daten aus ihrem eingebetteten JSON. IPs erhielten in unseren Tests einen Median von 3 Seiten, planen Sie also viele Sitzungen ein. Bright Datas ZoomInfo Scraper API übernimmt die Sitzungen und das Parsing für Sie.
Gibt es eine ZoomInfo-API?
Ja. ZoomInfo verkauft Zugang zur eigenen Datenbank über eine API. Für öffentliche Firmenprofile sammelt Bright Datas ZoomInfo Scraper API diese Seiten und liefert strukturierte Datensätze zurück. Bright Datas ZoomInfo-Datensatz liefert Datensätze mit denselben Feldern, bereits gesammelt und nach Zeitplan aktualisiert.
Ist ZoomInfo veraltet?
Einige seiner Datensätze können veraltet sein, prüfen Sie sie also, bevor sie ein CRM erreichen. Unsere Suche nach einer Domain lieferte 3 ZoomInfo-IDs zurück, und eine war ein Duplikat mit anderer Mitarbeiterzahl. Deduplizieren Sie, vergleichen Sie Füllraten und aktualisieren Sie so oft, wie sich Ihre Felder ändern.
Kann ich ZoomInfo kostenlos nutzen?
Teilweise. Firmenprofilseiten auf zoominfo.com luden ohne Konto, obwohl ihr HTML Kontaktdetails und einige Abschnitte als kostenpflichtig markierte. ZoomInfo bot auch einen kostenlosen Lite-Plan mit begrenzten monatlichen Credits an. Neue Bright Data-Konten erhalten kostenlose Credits, um die ZoomInfo Scraper API an diesen Seiten zu testen.
Gibt es einen kostenlosen ZoomInfo-Scraper?
Ja. Bright Datas Open-Source-Repository zoominfo-scraper auf GitHub enthält Python-Skripte, die Bright Datas APIs nutzen. In unseren Tests wurden Anfragen mit einem curl- oder python-requests-User-Agent in 29 von 29 Fällen blockiert, selbst mit einem Google-Referrer. Bright Datas APIs übernehmen das Entsperren für diese Skripte.