LinkedIn ist voll von nützlichen Daten. Von hochkarätigen Leads und qualifizierten Mitarbeiterkandidaten bis hin zu umfangreichen Stellenangeboten und Geschäftsmöglichkeiten.
Auf all diese Informationen kann man von Hand zugreifen, da sie für alle Nutzer und Nicht-Nutzer öffentlich zugänglich sind. Was aber, wenn wir auf diese Daten in größerem Umfang zugreifen möchten?
Heute möchten wir Ihnen zeigen, wie Sie die Macht des Web Scraping nutzen können, um Daten aus LinkedIn-Stellenangeboten zu ziehen.
Inhaltsübersicht
Ist es legal, LinkedIn-Daten zu scrapen?
Ja, das Scraping von LinkedIn-Seiten ist legal, wie der Fall LinkedIn vs. HiQ aus dem Jahr 2019 zeigt. LinkedIn hat jedoch beim Obersten Gerichtshof der USA (SCOTUS) Berufung gegen die Entscheidung eingelegt, ohne eine Antwort zu erhalten - soweit wir wissen. Bis wir vom SCOTUS eine Antwort erhalten, bleibt die Entscheidung des 9th Circuit geltendes Recht.
Wir werden diesen Fall im Auge behalten und diesen Artikel aktualisieren, sobald sich etwas ändert - und wir empfehlen Ihnen, dies ebenfalls zu tun.
Scraping von LinkedIn-Stellenangeboten mit JavaScript
Obwohl das Scraping von LinkedIn legal ist, ist uns klar, dass LinkedIn selbst nicht gescraped werden möchte, daher möchten wir beim Aufbau unseres Bots respektvoll vorgehen.
Eine Sache, die wir bei diesem Projekt vermeiden werden, ist die Verwendung eines Headless-Browsers, um sich bei einem Konto anzumelden und auf Daten zuzugreifen, die als privat gelten. Stattdessen werden wir uns darauf konzentrieren, öffentliche LinkedIn-Daten zu scrapen, bei denen wir keinen Anmeldebildschirm betreten müssen.
Wir gehen auf die Seite mit den öffentlichen Stellenangeboten von LinkedIn und verwenden Axios und Cheerio, um den HTML-Code herunterzuladen und zu analysieren, um die Stellenbezeichnung, das Unternehmen, den Ort und die URL des Angebots zu extrahieren.
1. Installieren Sie Node.js, Axios und Cheerio
Falls Sie das noch nicht getan haben, müssen Sie Node.js und NPM herunterladen und installieren. Letzteres wird uns helfen, den Rest unserer Abhängigkeiten zu installieren. Da wir unser Projekt auf einem M1 Mac erstellen, haben wir die ARM64-Version gewählt.
Nachdem wir diese installiert haben, erstellen wir einen Ordner für unser Projekt mit dem Namen "linkedin-scraper-project" und öffnen ihn in VScode (oder dem von Ihnen bevorzugten Editor). Rufen Sie das Terminal auf und erstellen Sie ein neues Projekt mit npm init -y
.
Anmerkung: Wenn Sie überprüfen möchten, ob die Installation gut verlaufen ist, können Sie mit node -v
und npm -v
.
Wir sind nun bereit, unsere Abhängigkeiten mit den folgenden Befehlen zu installieren:
- Axios:
npm install axios
- Auf Wiedersehen:
npm install cheerio
Oder wir könnten beide mit einem Befehl installieren: npm install axios cheerio
.
Um den Ball ins Rollen zu bringen, erstellen wir in unserem Ordner eine index.js-Datei und importieren unsere Abhängigkeiten am Anfang:
</p>
const axios = require('axios');
const cheerio = require('cheerio');
<p>
2. Chrome DevTools verwenden, um die Struktur der LinkedIn-Website zu verstehen
Bevor wir etwas anderes schreiben, müssen wir einen Plan erstellen, wie wir auf die Daten zugreifen wollen. Dazu öffnen wir https://www.linkedin.com/ in unserem Browser und sehen, was wir bekommen.
Anmerkung: Wenn Sie automatisch eingeloggt sind, melden Sie sich zunächst von Ihrem Konto ab und wechseln Sie dann auf die Homepage von LikedIn, um mitzumachen.
LinkedIn stellt uns sofort ein Suchformular zur Verfügung, mit dem wir genau die Stelle finden, die wir suchen, und unsere Suche auf einen Ort eingrenzen können. Lassen Sie uns als Beispiel nach Jobs für E-Mail-Entwickler suchen.
Auf den ersten Blick scheint jeder Auftrag eine in sich geschlossene Karte zu sein, die wir anvisieren können sollten, um alle benötigten Informationen zu extrahieren.
Nachdem wir die Seite inspiziert haben, können wir bestätigen, dass sich jeder Auftrag innerhalb einer <li>
Element:
Wenn wir einen genaueren Blick auf die <li>
Element finden wir die Stellenbezeichnung, den Firmennamen, den Standort und die URL des Angebots. Alles ist gut organisiert und klar strukturiert, so dass man sich leicht orientieren kann.
Allerdings gibt es einen Haken. Wenn Sie auf der Seite nach unten blättern, stellt sich heraus, dass LinkedIn das unendliche Scrollen verwendet, um die Seite mit weiteren Jobs aufzufüllen, anstatt die traditionelle nummerierte Paginierung zu verwenden.
Um mit der unendlichen Paginierung umzugehen, könnten wir einen Headless-Browser wie Puppeteer verwenden, um den ersten Stapel von Aufträgen zu scrapen, auf der Seite nach unten zu scrollen, zu warten, bis weitere Aufträge geladen sind, und die neuen Einträge zu scrapen.
Aber wenn Sie die Überschrift dieses Tutorials beachtet haben, werden wir keinen Headless Browser verwenden. Stattdessen werden wir versuchen, schlauer zu sein als die Seite.
3. Verwenden Sie die Registerkarte Netzwerk von DevTool
Eine Sache, die Sie beim Umgang mit endlos scrollenden Paginierungen beachten sollten, ist, dass die neuen Daten irgendwoher kommen müssen. Wenn wir also auf die Quelle zugreifen können, können wir auch auf die Daten zugreifen.
Gehen Sie bei geöffneten DevTools auf die Registerkarte Netzwerk und laden Sie die Seite neu:
Jetzt sehen wir alle verschiedenen Anfragen, die die Website stellt. Wir konzentrieren uns vor allem auf die Fetch/XHR-Anfragen, weil wir hier die neuen Daten abrufen können.
Nachdem Sie zum letzten Auftrag gescrollt haben, sendet die Seite eine neue Anfrage zum Abrufen der Daten an die URL im Screenshot unten. Wenn wir diese Anfrage imitieren, erhalten wir Zugriff auf dieselben Daten.
Testen wir es in unserem Browser, indem wir die URL kopieren und einfügen.
Großartig, diese Seite verwendet dieselbe Struktur, so dass es keine Probleme beim Scrapen der Daten geben sollte. Aber lassen Sie uns unsere Experimente noch ein wenig weiterführen. Diese URL enthält die Daten für die zweite Seite, aber wir möchten auch auf die erste Seite zugreifen.
Lassen Sie uns URL 1 mit URL 2 vergleichen, um zu sehen, wie sie sich verändern:
- URL 1:
https://it.linkedin.com/jobs/search?keywords=email%20developer&location=United%20States&geoId=103644278&trk=public_jobs_jobs-search-bar_search-submit&position=1&pageNum=0 - URL 2:
https://it.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=email%20developer&location=United%20States&geoId=103644278&trk=public_jobs_jobs-search-bar_search-submit&position=1&pageNum=0&start=25
In diesen URLs ist eine Menge los. Was uns jedoch am meisten interessiert, ist der letzte Teil. Versuchen wir es mit URL 2, aber mit dem Parameter start=0
in unserem Browser, um zu sehen, ob wir auf die Daten zugreifen können.
Das war's! Der erste Auftrag auf beiden Seiten ist derselbe.
Experimentieren ist beim Web-Scraping unerlässlich. Deshalb haben wir noch ein paar Dinge ausprobiert, bevor wir uns für diese Lösung entschieden haben:
- Ändern Sie die
pageNum
Parameter ändert nichts an der Seite. - Die
start
Parameter erhöht sich bei jeder neuen URL um 25. Wir haben dies herausgefunden, indem wir die Seite nach unten scrollten und die von der Website selbst gesendeten Abrufe verglichen. - Ändern Sie die
start
Parameter um 1 (alsostart=2
,start=3
usw.) verändert die resultierende Seite, indem sie die vorherigen Stellenanzeigen aus der Seite ausblendet - was nicht das ist, was wir wollen. - Die aktuell letzte Seite ist
start=975
. Wenn Sie auf 1000 drücken, wird eine 404-Seite angezeigt.
Wenn wir unsere erste URL haben, können wir zum nächsten Schritt übergehen.
4. Analysieren Sie LinkedIn mit Axios und Cheerio
Wir werden unsere ursprüngliche URL ein wenig ändern, um auf die US-Version der Website zuzugreifen.
</p>
const axios = require('axios');
const cheerio = require('cheerio');
const { html } = require('cheerio/lib/static');
let url="https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=email%2Bdeveloper&location=United%2BStates&geoId=103644278&trk=public_jobs_jobs-search-bar_search-submit¤tJobId=2931031787&position=1&pageNum=0&start=0"
axios(url)
.then (response => {
const html = response.data;
console.log(html)
})
<p>
Um zu überprüfen, ob unsere Axios-Anfrage funktioniert, werden wir console.log()
unsere html-Variable.
Großartig, wir konnten das rohe HTML herunterladen und können es nun an Cheerio zum Parsen übergeben. Ersetzen Sie die console.log()
Methode mit dem nächsten Schnipsel, um ein Cheerio-Objekt zu erstellen, das wir dann abfragen können, um unsere Zieldaten zu extrahieren.
</p>
const $ = cheerio.load(html);
<p>
5. Wählen Sie Ihre Selektoren
Cheerio verwendet eine JQuery-Implementierung, um Elemente auszuwählen. Wenn Sie also bereits mit der Syntax von JQuery vertraut sind, werden Sie sich gleich zu Hause fühlen.
Um die richtigen Selektoren auszuwählen, gehen wir zurück zur geöffneten URL und notieren uns die Attribute, die wir verwenden können. Wir wissen bereits, dass sich alle Daten, die wir benötigen, in den einzelnen <li>
Elemente, also zielen wir zuerst darauf ab und speichern es in einer constant
.
</p>
const jobs = $('li');
<p>
Da alle Auflistungen in Jobs gespeichert sind, können wir nun eine nach der anderen auswählen und die gewünschten Daten extrahieren. Wir können unsere Selektoren direkt in den DevTools testen, damit wir keine unnötigen Anfragen an den Server senden.
Wenn wir uns die Stellenbezeichnung ansehen, können wir feststellen, dass sie sich innerhalb eines <h3>
Element mit der Klasse base-search-card__title.
Wechseln Sie zur Registerkarte Konsole in DevTools und verwenden Sie die Option document.querySelectorAll()
Methode und übergeben Sie sie ‘.base-search-card__title’
- wobei Punkt für Klasse steht - als Argument, um alle Elemente mit dieser Klasse auszuwählen.
Er gibt eine NodeList von 25 zurück, was der Anzahl der Aufträge auf der Seite entspricht. Wir können das Gleiche für die übrigen Ziele tun. Hier sind unsere Ziele:
- Stellenbezeichnung: 'h3.base-search-card__title'
- Unternehmen: 'h4.base-search-card__subtitle'
- Ort: 'span.job-search-card__location'
- URL: 'a.base-card__full-link'
6. Iterieren Sie durch die Knotenliste mit .each()
Mit den definierten Selektoren können wir nun durch die in der Variablen jobs gespeicherte Liste iterieren, um alle Auftragsdetails mit der Methode .each()
Methode.
</p>
jobs.each((index, element) => {
const jobTitle = $(element).find('h3.base-search-card__title').text()
console.log(jobTitle)
})
<p>
Sie haben sicher erkannt, wie wichtig es für uns ist, alles zu testen. Bevor wir also versuchen, jedes Element zu erfassen, extrahieren wir zunächst nur den Jobtitel und protokollieren ihn in unserer Konsole. Geben Sie ein. node index.js
im Terminal, um es auszuführen.
Hm... es scheint, als gäbe es eine Menge Leerraum um den Titel herum und er wird auch von unserem .text()
Methode. Keine Sorge, wir können die .trim()
Methode, um es zu bereinigen.
Viel besser! Jetzt können wir sicher sein, dass wir den Rest der Selektoren in unser Skript einfügen können.
</p>
const company = $(element).find('h4.base-search-card__subtitle').text().trim()
const ort = $(element).find('span.job-suche-karte__ort').text().trim()
const link = $(element).find('a.base-card__full-link').attr('href')
<p>
Für den Link des Jobs sind wir eigentlich nicht daran interessiert, den Text innerhalb des Elements zu erfassen, sondern den Wert der Option href
Attribut - mit anderen Worten, die URL selbst. Dazu können wir einfach die Funktion .attr()
Methode und übergeben Sie ihr das Attribut, von dem wir den Wert haben möchten, als Argument.
7. Schieben Sie Ihre Daten zur Formatierung in ein Array
Im Moment protokolliert unser Skript in der Konsole eine Menge ungeordneter Zeichenketten, die es uns sehr schwer machen, es zu benutzen. Die gute Nachricht ist, dass wir eine einfache Methode verwenden können, um unsere Daten in ein leeres Array zu schieben und alles automatisch ordentlich zu formatieren.
Zunächst erstellen wir vor Axios ein leeres Array namens linkedinJobs:
</p>
linkedinJobs = [];
<p>
Them, fügen wir den folgenden Codeschnipsel direkt nach const link ein:
</p>
linkedinJobs.push({
'Titel': jobTitle,
'Unternehmen': Unternehmen,
'Ort': Ort,
'Link': link,
})
<p>
Und protokollieren Sie linkedinJobs in der Konsole.
Wie sieht es jetzt aus? Alle unsere Daten sind perfekt beschriftet und formatiert, so dass wir sie in eine JSON- oder CSV-Datei exportieren können.
Das sind jedoch noch nicht alle Daten, die wir benötigen. Unser nächster Schritt besteht darin, zu den übrigen Seiten zu navigieren und den gleichen Vorgang zu wiederholen.
8. Scraping aller Seiten mit einer for loop
Es gibt viele Möglichkeiten, zur nächsten Seite zu navigieren, aber mit unserem derzeitigen Wissen über die Website wäre der einfachste Weg, den Startparameter in der URL um 25 zu erhöhen, um die nächsten 25 Jobs anzuzeigen, bis es keine Ergebnisse mehr gibt und ein for loop
erfüllt diese Funktion perfekt.
Um Ihr Gedächtnis aufzufrischen, hier ist die for loop
’s
Syntax:
</p>
for (Anweisung 1; Anweisung 2; Anweisung 3) {
//auszuführender Codeblock
}
<p>
Lassen Sie uns zuerst diesen Teil erstellen, indem wir unsere benutzerdefinierten Anweisungen hinzufügen:
</p>
for (let pageNumber = 0; pageNumber < 1000; pageNumber += 25) {
}
<p>
- Unser Ausgangspunkt wird 0 sein, da dies der erste Wert ist, den wir an die
start
Parameter. - Da wir wissen, dass es beim Erreichen von 1000 keine weiteren Ergebnisse mehr geben wird, wollen wir, dass der Code so lange läuft, wie
pageNumber
weniger als 1000 beträgt. - Schließlich wollen wir nach jeder Iteration des Codes die
pageNumber
um 25, um zur nächsten Seite zu gelangen.
Bevor wir den Rest des Codes in die Schleife einfügen, müssen wir unsere Variable pageNumber in die URL einfügen:
</p>
let url = `https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=email%2Bdeveloper&location=United%2BStates&geoId=103644278&trk=public_jobs_jobs-search-bar_search-submit¤tJobId=2931031787&position=1&pageNum=0&start=${pageNumber}`
<p>
Und nun fügen wir den Rest unseres Codes in die Schleife ein, wobei wir nun die Variable pageNumber als Wert für die Option start
Parameter.
9. Schreiben Sie Ihre Daten in eine CSV-Datei
Es war ein langer Prozess, aber wir sind fast fertig mit unserem Scraper. Allerdings ist die Speicherung all dieser Daten in unserer Konsole nicht gerade der beste Weg.
Um den Aufwand zu verringern, verwenden wir das Paket Objects-to-CSV. Es gibt eine sehr ausführliche Dokumentation, wenn Sie sich näher mit dem Paket befassen möchten, aber in einfachen Worten konvertiert es unser Array von JavaScript-Objekten (linkedinJobs
) in ein CSV-Format, das wir auf unserem Rechner speichern können.
Zuerst installieren wir das Paket mit npm install objects-to-csv
und fügen Sie es an den Anfang unseres Projekts.
</p>
const ObjectsToCsv = require('objects-to-csv');
<p>
Wir können das Paket nun direkt nach dem Schließen unseres job.each()
Methode:
</p>
const csv = new ObjectsToCsv(linkedinJobs)
csv.toDisk('./linkedInJobs.csv', { append: true })
<p>
"Die Schlüssel im ersten Objekt des Arrays werden als Spaltennamen verwendet", also ist es wichtig, dass wir sie beschreibend machen, wenn wir die .push()
Methode.
Da wir mehrere Seiten in einer Schleife durchlaufen wollen, möchten wir außerdem nicht, dass unsere CSV-Datei jedes Mal überschrieben wird, sondern dass die neuen Daten unten hinzugefügt werden. Dazu müssen wir nur Folgendes festlegen append
zu true
. Es fügt die Kopfzeilen nur einmal hinzu und aktualisiert die Datei ständig mit den neuen Daten.
10. Führen Sie Ihren Code aus [Vollständiger LinkedIn Scraper Code]
Wenn Sie mitgemacht haben, sollte Ihr fertiger Code folgendermaßen aussehen:
</p>
const axios = require('axios');
const cheerio = require('cheerio');
const ObjectsToCsv = require('objects-to-csv');
linkedinJobs = [];
for (let pageNumber = 0; pageNumber {
const html = response.data;
const $ = cheerio.load(html);
const jobs = $('li')
jobs.each((index, element) => {
const jobTitle = $(element).find('h3.base-search-card__title').text().trim()
const Firma = $(element).find('h4.base-search-card__subtitle').text().trim()
const location = $(element).find('span.job-suche-karte__ort').text().trim()
const link = $(element).find('a.base-card__full-link').attr('href')
linkedinJobs.push({
'Titel': jobTitle,
'Unternehmen': Unternehmen,
'Ort': Ort,
'Link': link,
})
});
const csv = new ObjectsToCsv(linkedinJobs)
csv.toDisk('./linkedInJobs.csv', { append: true })
})
.catch(console.error);
}
<p>
Um es auszuführen, gehen Sie zu Ihrem Terminal und geben Sie node index.js
(oder den Namen Ihrer Datei):
Herzlichen Glückwunsch, mehr als 15.000 Stellenanzeigen in wenigen Sekunden gefunden. Für die nächsten Schritte exportieren Sie diese Daten nach Excel und filtern die Spalte Title
nur solche, die das Stichwort "E-Mail-Entwickler" enthalten, oder nach Ort.
Zum Abschluss: Hier ist eine Herausforderung für Sie
Wir haben in diesem Tutorial schon viel behandelt, aber es gibt noch ein paar weitere Dinge, die Sie ausprobieren sollten:
- Lassen Sie das Skript Aufträge nach Titel filtern, so dass Sie nur die Karten extrahieren, die das Schlüsselwort "E-Mail-Entwickler" oder "HTML-E-Mail" enthalten.
- Im Moment durchläuft das Skript die Seiten zu schnell. Fügen Sie einen Puffer zwischen den Anfragen ein, so dass es erst nach 5 Sekunden eine neue Anfrage sendet. So können Sie Ihre IP schützen.
Schicken Sie uns Ihren fertigen Code auf Twitter oder LinkedIn und wir werden Ihre Lösung in unseren sozialen Medien veröffentlichen und Sie in unserem nächsten Beitrag vorstellen.
Bis dahin, viel Spaß beim Schaben!