Startseite BlogWeb-Scraping 15k Stellenanzeigen in Sekundenschnelle

15k Stellenanzeigen in Sekundenschnelle

von Kadek

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.

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.

LinkedIn scrapen

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.

Laden Sie den Knoten herunter

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.

DevTools zum Verstehen der LinkedIn-Seitenstruktur

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.

DevTools zum Verstehen der LinkedIn-Seitenstruktur

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.

E-Mail-Entwickler

Nachdem wir die Seite inspiziert haben, können wir bestätigen, dass sich jeder Auftrag innerhalb einer <li> Element:

Schaber

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.

Schaber

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:

DevTool's Registerkarte Netzwerk

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.

DevTool's Registerkarte Netzwerk

Testen wir es in unserem Browser, indem wir die URL kopieren und einfügen.

DevTool's Registerkarte Netzwerk

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.

E-Mail-Entwickler

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 (also start=2, start=3usw.) 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&currentJobId=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.

Axios Entwickler

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.

Wählen Sie Ihre Selektoren

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.

Wählen Sie Ihre Selektoren

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) =&gt; {
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.

Wählen Sie Ihre Selektoren E-Mail-Entwickler

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.

Linkedin Scraper Projekt

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.

inkedin Jobs auf 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 &lt; 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&currentJobId=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) =&gt; {
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):

inkedin Jobs

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!

Verwandte Beiträge

Einen Kommentar hinterlassen