Inhaltverzeichnis
Bekanntlich ist es sinnvoll eine Website während der Umsetzung des SEO-Konzepts sowie im Rahmen der Betreuung genaustens im Auge zu behalten. Eine der mächtigsten Instrumente um immer auf dem neusten Stand zu sein, ist das zeitversetzte Crawlen und Abgleichen der Website basierend auf der URL Basis. Um den regelmäßigen Zeitaufwand dafür auf ein Minimum zu reduzieren, werden wir in diesem Beitrag alles vom Crawling bis zum Output-Excel automatisieren! Anders ausgedrückt, das Einzige was zu tun ist neben der initialen Einrichtung, ist die intellektuelle Analyse des Ausgabe-Excel.
Da unser letzter Blog-Beitrag zu diesem Thema schon einige Zeit her ist und wir uns bei [Get:Traction] natürlich auch ständig weiterentwickeln, ist es an der Zeit, mal wieder ein Update zu lancieren! Optimierung ist schließlich der Inbegriff unserer Kernkompetenz.
Um abzuklären, ob der Crawlabgleich etwas für Dich ist, ist es sinnvoll zuerst einmal einen Blick auf den Output zu werfen:
Das Endergebnis ist nach wie vor eine Excel Datei, welche in einem Reiter für jede Metrik die Unterschiede zum vorherigen Crawl aufzeichnet. Dabei sind folgende Punkte abgedeckt:
Grundsätzlich haben wir 3 Schritte während des gesamten Prozesses:
– Wir crawlen die Website in Zeitabständen
– Wir vergleichen die Crawls miteinander
– Wir analysieren das Endergebnis
Für diesen Arbeitsschritt benötigen wir die Screaming-Frog SEO Spider.
Um unseren Export nach beliebigen Zeitabständen zu automatisieren und damit regelmäßig viel Zeit zu sparen, legen wir eine standardisierte Ordnerstruktur an und erstellen im Windows Aufgabenplaner Jobs für das Crawling.
Initial benötigen wir 4 Ordner, die alle im gleichen Verzeichnis abzulegen sind:
Die Struktur sieht folgendermaßen aus:
Alle fett geschriebenen Ordner in der obigen Darstellung müssen einmal initial angelegt werden. Der Rest wird automatisch von unserem Skript und dem SF generiert.
–project-name Kunden_Crawlabgleiche –task-name 1month_tue_1200_kunde –crawl „https://www.beispiel.de/“ –config „C:\03_sf_configs\kunde.seospiderconfig“ –headless –output-folder „C:\02_sf_exports\kunde“ –timestamped-output –save-crawl –export-tabs Internal%3AAll –export-format csv
Das Endergebnis sieht dann so aus:
Jetzt könnt Ihr auf OK klicken und den Job speichern. Damit haben wir unseren automatisierten Crawl!
Für diesen Arbeitsschritt benötigen wir R Studio. Hier eine kurze Anleitung und Erklärung zur Installation.
Jetzt müssen wir nur noch 2 Pfade im R Skript anpassen und schon haben wir alles für unseren Windows Job vorbereitet!
Navigiert hierzu in R Studio in eurem Projekt in die Datei „website_change_monitor.R“ und passt folgende Variablen an:
PATH_TO_XLSX_EXPORTS ist der Pfad in euren /01_crawl_abgleiche Ordner.
PATH_TO_SF_EXPORTS ist der Pfad in euren /02_sf_exports Ordner.
(Tipp: Um auf der sicheren Seite zu sein könnt ihr „C:/“ eingeben und dann mit TAB und den Pfeiltasten bis in den jeweiligen Ordner navigieren)
Speichert das Skript jetzt mit „STRG & S“.
Damit das Skript funktioniert müsst ihr ein paar R Packages installieren. Das geht ganz einfach indem ihr am oberen Rand in R Studio auf den hints „install“ durchklickt. Sollten die Meldungen nicht aufpoppen, könnt ihr alle Packages folgendermaßen installieren:
1 |
install.packages(„[package]“) |
Als Beispiel einmal:
1 |
install.packages(„tidyverse“) |
Die Namen aller benötigten Packages findet ihr unter #1 Libraries (alle die mit „library()“ geladen werden) im Skript.Die Namen aller benötigten Packages findet ihr unter #1 Libraries (alle die mit „library()“ geladen werden) im Skript.
Das wars! Jetzt haben wir einen völlig automatisierten Crawlabgleich. Zur Einrichtung eines weiteren Abgleichs, müsst ihr nur einen Ordner für den neuen Kunden in 02_sf_exports erstellen und einen Crawl-Job im Windows Aufgabenplaner erstellen. Das Skript registriert den neuen Kunden von alleine. Eure Output-Excels findet ihr nun im Ordner /01_Crawl_Abgleiche/.
Hier werden die zum Ausführen des Skripts benötigten R-Packages geladen. Um diese beim Ausführen des Skripts nutzen zu können, müsst ihr diese vorher installieren (Beschrieben in Punkt Anlegen des R-Skripts).
1 2 3 4 5 6 7 8 |
# Libraries ------------------------------------------- options(java.parameters = "-Xmx8g") library(tidyverse) library(xlsx) library(slackr) library(lubridate) source("FUNS.R") |
Hier werden die Pfade für die Imports und Exports festgelegt. Zusätzlich legen wir hier unseren Slack API Key als Variable (Auskommentiert da nicht essenziell) an, um erfolgreich ausgeführte Scripts sowie Fehlermeldungen in unseren Scripts- Slack Channel automatisiert zu pushen! Damit hat man also auch ein Warnsystem implementiert. Die Variable „#TESTING_WEBSITE“ ist standardmäßig auskommentiert mit einem #. Sollte man Adhoc nur eine einzige Website vergleichen wollen, kann man hier das # entfernen und in die Gänsefüßchen die Website schreiben. Dabei einfach den Namen des Ordners verwenden, wie ihr ihn im sf_exports Verzeichnis erstellt habt.
1 2 3 4 5 6 7 8 |
# VARS ------------------------------------------------ PATH_TO_XLSX_EXPORTS <- "C:/01_crawl_abgleiche" PATH_TO_SF_EXPORTS <- "C:/02_sf_exports" #TESTING_WEBSITE <- "" #SLACK_API_KEY <- "" |
Hier wird dem „slackR“ Package die nötige Information mitgegeben. Den API Key, in welchen Channel soll gepusht werden? Unter welchem Namen sollen die Nachrichten dort auftauchen?
Die Sektion ist im download-Skript ebenfalls auskommentiert, da die Nutzung von SlackR natürlich nicht obligatorisch ist.
1 2 3 4 5 6 |
# Slack ----------------------------------------------- #slackr_setup(api_token = SLACK_API_KEY, # channel = "#scripts", # username = "[R] Crawl-Abgleich", # echo = FALSE) |
Hier werden alle Dateien mit .csv-Endung rekursiv aus dem in der Variable PATH_TO_SF_EXPORTS enthaltenen Pfad aufgelistet.
1 2 3 4 5 6 7 |
# Get-files ------------------------------------------- ## Find all CSVs files <- list.files(PATH_TO_SF_EXPORTS, full.names = TRUE, recursive = TRUE, pattern = "\\.csv") |
Nun splitten wir die Pfade der aufgelisteten Dateien in dem Dataframe „df_file_paths“ in einzelne Spalten auf.
1 2 3 4 5 6 7 8 |
## Split paths df_file_paths <- tibble(path = files) %>% splitstackshape::cSplit(splitCols = "path", drop = FALSE, sep = "/", direction = "wide", type.convert = FALSE) %>% as_tibble() |
Hier determinieren wir, welche Teile der in „df_file_paths“ enthaltenen Pfade welches Element sind und geben den entsprechenden Spalten die Namen.
1 2 3 4 5 6 |
## Extract website, datetime select_cols <- c(1, ncol(df_file_paths) - 2, ncol(df_file_paths) - 1) df_file_paths <- df_file_paths[ , select_cols] names(df_file_paths) <- c("path", "website", "datetime") |
Hier konvertieren wir das bisher als Character zu verstehende Datum in der Spalte „datetime“ in ein richtiges Datums-Datenformat. Dazu nutze ich die äußerst nutzvolle Library Lubridate.
1 2 3 4 |
## Convert to date df_file_paths <- df_file_paths %>% mutate(datetime = ymd_hms(datetime), date = date(datetime)) |
Wir gruppieren nach der Spalte „website“ und zählen, ob die Anzahl an Elementen pro Gruppe mehr als 1 beträgt. Anschließend lösen wir die Gruppierung wieder auf.
1 2 3 4 5 |
## Check if two crawls exist df_file_paths <- df_file_paths %>% group_by(website) %>% filter(n() > 1) %>% ungroup() |
Wir gruppieren abermals nach „website“ und fügen dem Dataframe die Spalte „date_rank“ hinzu. Dieser wird mithilfe der rank() Funktion absteigend durch die Spalte „datetime“ berechnet. Abschliessend filtern wir auf Einträge mit „date_rank“ 1 & 2 und lösen die Gruppierung wieder auf.
1 2 3 4 5 6 |
## Get last two crawls df_file_paths <- df_file_paths %>% group_by(website) %>% mutate(date_rank = rank(desc(date))) %>% filter(date_rank %in% c(1, 2)) %>% ungroup() |
Um für jede Website einen einzelnen Crawl Abgleich bauen zu können, müssen wir jede Website als einzelnes Objekt einer Liste betrachten und die gleichen Aktionen für jedes Objekt in dieser Liste iterativ durchführen. Hierzu bedienen wir uns eines einfachen for()-loops. Diese Liste bereiten wir nun mithilfe der unique-Funktion vor. Diese gibt uns, nach Einspeisung der Spalte „websites“ aus „df_file_paths“, einen Vector ohne doppelte Werte zurück. Sprich – wir haben eine Liste aller unserer Websites, die als Iterationsobjekt benutzt werden können.
1 2 |
## Get websites for iteration websites <- unique(df_file_paths$website) |
Bevor wir mit dem eigentlichen Abgleich loslegen, gucken wir hier nach, ob die vorher angesprochene Variable „TESTING_WEBSITE“ besteht und wenn ja, filtern nur auf die dort enthaltene Website.
1 2 |
## Filter for testing if (exists("TESTING_WEBSITE")) websites <- TESTING_WEBSITE |
Wir iterieren über unseren „websites“ vector und führen für jede enthaltene Website folgende Aktionen aus:
Am Anfang des Loops reduzieren wir das in dieser Iteration behandelte Datenset auf die Daten einer einzigen Website und setzen temporäre Variablen für das neue und alte Crawldatum. Wir geben eine Message in der Console aus und lesen beide Crawls, mithilfe der in „FUNS.R“ definierten Funktion „read_crawl“, ein. Dazu fügen wir eine Spalte mit neuem/altem Datum an.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
for (WEBSITE in websites) { try_res <- tryCatch({ df_website_file_paths <- df_file_paths %>% filter(website == WEBSITE) CRAWL_NEW_DATE <- max(df_website_file_paths$date) CRAWL_OLD_DATE <- min(df_website_file_paths$date) message("\n\n[", WEBSITE, "] ", strrep("*", 40), "\n\n~ Read crawls\n") crawl_new <- read_crawl(df_website_file_paths[df_website_file_paths$date == CRAWL_NEW_DATE, "path"][[1,1]]) %>% mutate(date = CRAWL_NEW_DATE) crawl_old <-read_crawl(df_website_file_paths[df_website_file_paths$date == CRAWL_OLD_DATE, "path"][[1,1]]) %>% mutate(date = CRAWL_OLD_DATE) |
Der Kommentar ist ziemlich treffend – wir kreieren (wenn nicht vorhanden) ein Verzeichnis mithilfe des Iterationsobjektes sowie des Pfads aus PATH_TO_XLSX_EXPORTS.
1 2 3 |
## Create website-dir if not exists dir.create(file.path(PATH_TO_XLSX_EXPORTS, WEBSITE), showWarnings = FALSE) |
Abermals eine Message in die Console. Hier filtern wir innerhalb eines Anti-Joins vom neuen auf den alten Crawl, auf URLs, in dessen „content“ Spalte „html“ vorkommt oder die „status“ Spalte dem String „Connection Timeout“ entspricht.
Dabei bleiben nur noch die im neuen Crawl enthaltenen URLs zurück, die im alten nicht mehr existieren. Wir wählen per „select“ aus, welche Spalten wir haben wollen und konvertieren das ganze in ein Dataframe, da die XLSX Library kein Tibble versteht.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
## Get new (linked) URLs --------------------------- message("~ Get new (linked) URLs\n") new_urls <- crawl_new %>% filter(str_detect(content, "html") | status == "Connection Timeout") %>% anti_join(crawl_old %>% filter(str_detect(content, "html") | status == "Connection Timeout"), by = "address") %>% mutate(is_canonical = (address == canonical_link_element_1 | canonical_link_element_1 == "")) %>% select(date, address, status_code, status, indexability_status, meta_robots_1, canonical_link_element_1, title_1, meta_description_1, is_canonical) %>% mutate(date = as.character(date)) %>% as.data.frame() # convert to DataFrame as xlsx:: cannot handle tibble() |
Message in die Console. Gleiches Vorgehen wie bei Punkt 4.5.2 Nur hier Anti-joinen wir vom alten auf den Neuen Crawl.
1 2 3 4 5 6 7 8 |
## Get not longer / deleted URLs ------------------- message("~ Get not longer / deleted URLs\n") no_longer_linked_urls <- crawl_old %>% filter(str_detect(content, "html") | status == "Connection Timeout") %>% anti_join(crawl_new %>% filter(str_detect(content, "html") | status == "Connection Timeout"), by = "address") |
Message. Hier prüfen wir den Status Code der nicht mehr verlinkten Seiten, da wir ihn ja über den Crawl nicht kennen. Der Grund ist, dass wir prüfen wollen, ob eine Seite korrekterweise nicht mehr verlinkt ist. Antwortet die URL mit „404 Not Found“, ergibt es vollkommen Sinn, sie nicht mehr zu verlinken. Antwortet die Seite hingegen mit „200 OK“, stellt sich die Frage, warum sie nicht mehr verlinkt ist, wenn sie doch erreichbar ist. Im Hintergrund rennt hier ein parallelisierter Status-Code-Fetcher los. Je nachdem, wie viele URLs nicht mehr verlinkt sind, kann die Abfrage durchaus länger dauern. Auf der Console seht ihr aber die gerade abgefragten URLs, sodass ihr zumindest einen groben Überblick habt, dass sich noch etwas tut und dass das Skript nicht hängen geblieben ist. (Anhand dieses Punktes ist unter anderem erkennbar, dass es sinnvoll ist, die Crawls jeweils mit gleicher Konfiguration durchzuführen).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
## get Status Code of not linked / deleted URLs message("~ Get Status Code of not linked / deleted URLs\n") urls <- no_longer_linked_urls$address if (length(urls) > 0) { r <- fetch_multi_urls(urls) r_df <- tibble( address = unlist(lapply(r, `[[`, "url")), status_code_current = unlist(lapply(r, `[[`, "status_code")) ) no_longer_linked_urls <- no_longer_linked_urls %>% left_join(r_df, by = "address") %>% select(date, address, status_code, status, indexability_status, status_code_current, meta_robots_1, canonical_link_element_1, title_1, meta_description_1) %>% rename(status_code_old = status_code) %>% mutate(date = as.character(date)) %>% as.data.frame() } |
Was wohl 😉. Wir filtern auf HTML URLs beider Crawls und machen uns eines inner-Joins habhaft, um die identischen URLs des Abgleichs zu erhalten. Dazu fügen wir im Datenset Spalten für jedes Detail einer URL an, welche angeben, ob sich der entsprechende Wert geändert hat.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
message("~ Get identical URLs\n") identical_urls <- crawl_new %>% filter(str_detect(content, "html")) %>% inner_join(crawl_old %>% filter(str_detect(content, "html")), by = "address", suffix = c("_new", "_old")) %>% mutate(change_status_code = status_code_new != status_code_old, change_canonical = canonical_link_element_1_new != canonical_link_element_1_old, change_index = meta_robots_1_new != meta_robots_1_old, change_title = title_1_new != title_1_old, change_description = meta_description_1_new != meta_description_1_old, change_content_type = content_new != content_old, change_crawl_depth = crawl_depth_new != crawl_depth_old, change_h1 = h1_1_new != h1_1_old, change_word_count = word_count_new != word_count_old, change_indexability = indexability_new != indexability_old, change_indexability_status = indexability_status_old != indexability_status_new, change_orphan_pages = (is.na(crawl_depth_old) & !is.na(crawl_depth_new)) | (!is.na(crawl_depth_old) & is.na(crawl_depth_new))) |
Da wir nun das Datenset an URLs definiert haben, welches in beiden Crawls enthalten ist, können wir nun die einzelnen Bestandteile dieses Datensets betrachten und vergleichen.
Verwaiste Seiten:
Hier definieren wir, je nach dem, was in unserer Spalte change_orphan_pages, die wir in Punkt 4.5.5 gebildet haben, steht, ob es sich um eine nicht mehr oder neu verwaiste Seite handelt. Dies speichern wir als Dataframe, um es später in der Excel abbilden zu können.
Dieses Vorgehen bleibt für jede Metrik das gleiche. Darum werde ich hier nicht auf jeden Unterpunkt eingehen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
## Get identical URLs with changes ----------------- message("~ Get identical URLs with changes\n") ## orphane pages message("\tOrphan pages") change_orphan_pages <- identical_urls %>% filter(change_orphan_pages == TRUE) %>% mutate(change = case_when(!is.na(crawl_depth_old) & is.na(crawl_depth_new) ~ "neu verwaist", is.na(crawl_depth_old) & !is.na(crawl_depth_new) ~ "nicht mehr verwaist")) %>% select(change, address, status_code_old, status_code_new, status_old, status_new, title_1_old, title_1_new) %>% as.data.frame() |
Hier sortieren wir bei Images, JavaScript und CSS, absteigend nach der Größe (size_bytes) und speichern diese als DataFrame.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
## Get biggest ressources -------------------------- message("~ Get biggest ressources\n") ## images message("\tImages") top_image <- crawl_new %>% filter(str_detect(content, "image")) %>% select(address, size_bytes) %>% arrange(desc(size_bytes)) %>% as.data.frame() ## JavaScript message("\tJavaScript") top_js <- crawl_new %>% filter(str_detect(content, "javascript")) %>% select(address, size_bytes) %>% arrange(desc(size_bytes)) %>% as.data.frame() ## CSS message("\tCSS\n") top_css <- crawl_new %>% filter(str_detect(content, "css")) %>% select(address, size_bytes) %>% arrange(desc(size_bytes)) %>% as.data.frame() |
Hier nutzen wir die Library XLSX, um eine Arbeitsmappe zu erstellen und diese mit unseren sheets in der „sheet_order“ variable und den im Loop erstellten Data-Frames zu befüllen. Mit saveWorkbook() wird dem Export der Pfad übergeben und der Crawlabgleich, mit neuem und altem Datum im Namen, im entsprechenden Ordner der Website abgespeichert.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
## Create Excel ------------------------------------ message("~ Write Excel for: [ ", WEBSITE, " ] ", strrep("*", 40), "\n") ## set order of sheets here sheet_order <- c("new_urls", "no_longer_linked_urls", "change_orphan_pages", "change_status_code", "change_index", "change_canonical", # "change_indexability", # "change_indexability_status", "change_title", "change_description", "change_h1", "change_word_count", "change_content_type", "change_crawl_depth", "top_image", "top_js", "top_css") wb <- createWorkbook() for (sheet in sheet_order) { if (nrow(eval(parse(text = sheet))) > 0) add_sheets(wb, sheet) } saveWorkbook(wb, file.path(PATH_TO_XLSX_EXPORTS, WEBSITE, paste0(CRAWL_NEW_DATE, "__", CRAWL_OLD_DATE, "__", WEBSITE, ".xlsx"))) |
Zu guter Letzt unsere Slack-Notification. Hier wird eine Message aus dem momentanen Iterationsobjekt und einer vorgefertigten Nachricht zusammengebaut und mithilfe der vorher erstellten Variablen und des Packages SlackR in den Channel gepostet. Alle Slack-Inhalte sind im download-Skript auskommentiert, da die Nutzung von „SlackR“ rein optional ist.
Damit ist der For-Loop abgeschlossen und das Skript erstellt die Crawlabgleiche aller vorhandenen Websites.
Viel Spaß beim Abgleichen! Wenn ihr Fragen oder Anregungen habt, gerne kommentieren oder mir direkt eine Mail schreiben 🙂
Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.
SEO Berlin – [sichtbar & erfolgreich]
Wielandstraße 9, 12159 Berlin
030 / 296 73 998
berlin@gettraction.de
SEO Darmstadt- [sichtbar & erfolgreich]
Heinrich-Hertz-Straße 6, 64295 Darmstadt
06151 / 860 69 85
darmstadt@gettraction.de
5 Gründe für get:traction:
Mike
Hi,
I’m running in an error called:
NA does not exist in the current working directory.
I assume, that the path contains a mistake? Is that right?
Patrick Lürwer
Hi Mike,
that’s right. You have a backslash in your second path (
C:\Users/
). If you change it, it should work.Best regards,
Patrick
Mike
Thanks for your answer. Sorry that was a short mistake from my site. The error also exists with the right backslash.
Patrick Lürwer
Hi Mike,
that may be a stupid question, but are there two crawls in the folder? I.e., in your first comment you specified the
path_to_crawls with
"C:/Users/customerweb/Documents/Websiteaenderungen/Crawls/KUNDE_2017-12-01_internal_all"
. I did not notice the first time, but it seems to me that you are providing a file name instead of a folder.Suppose the two crawls KUNDE_2017-12-01_internal_all.csv and KUNDE_2017-12-08_internal_all.csv you would like to compare are located in your folder
"C:/Users/customerweb/Documents/Websiteaenderungen/Crawls/
you have to assign thepath_to_crawls
-variable as follows:path_to_crawls <- "C:/Users/customerweb/Documents/Websiteaenderungen/Crawls/
.If that is not the case let me know.
Dave
Sehr cooles Skript. Bei einem Vergleich von 2 relativ großen Crawls (200.000 URLs) crashed mir allerdings R irgendwie immer mit einem "garbage collection overheat". Hast du dafür zufällig irgendeinen Tipp?
Patrick Lürwer
Servus Dave,
cool, dass dir das Skript gefällt. Blöd, dass R stirbt. Der Fehler ist mir noch nicht untergekommen. Ich würde jetzt aber ad hoc darauf tippen, dass dein RAM vollläuft und die garbage collection nicht hinterherkommt, neuen Speicherplatz frei zu machen. Das ist jetzt aber wirklich nur eine Vermutung.
Wie viel RAM steht R denn zur Verfügung (in der Console
memory.limit()
ausführen)? Tritt der Fehler denn auch auf, wenn du kleinere Crawls miteinander vergleichst?Sollte es wirklich daran liegen, dass dein RAM vollläuft hift eigentlich nur mehr RAM. Oder das Skript müsste so umgeschrieben werden, dass die Crawls in einer Datenbank liegen und nicht im RAM zum Abgleich vorgehalten werden.
Tut mir Leid, dass ich dir da nicht wirklich weiter helfen kann.
LG
Dave
Bei Crawls unter 10.000 URLs läuft alles super. Das Einlesen größerer Crawls ist übrigens auch kein Problem, es hängt am Ende dabei jedoch immer beim xlsx Schreiben (in der Global Environment sind die eingelesenen Variablen auch betrachtbar).
Memory war bisher anscheinend laut memory.limit() auf 16000 mb begrenzt, hab das jetzt mal mit dem Befehl auf 50000 erhöht.
Es müsste ja eigentlich entweder am generellen Ram, Java (ist eigentlich in der 64 bit Version installiert) oder dem Excel written/xlsx Befehl was gemacht werden können. Werde diese Woche dafür ein bisschen rumspielen mit der ganzen Sache.
Nochmal danke für das Skript und deine Antwort 🙂
Stefan
Vielen Dank für das tolle Skript – wenn ich es nur zum Laufen bringen könnte 🙁
Direkt am Anfang erhalte ich Meldungen zu irgendwelchen Konflikten. Ich kann damit leider gar nichts anfangen ….
> library(tidyverse)
── Attaching packages ─────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.2.1 ✔ purrr 0.3.3
✔ tibble 2.1.3 ✔ dplyr 0.8.3
✔ tidyr 1.0.0 ✔ stringr 1.4.0
✔ readr 1.3.1 ✔ forcats 0.4.0
── Conflicts ────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
> library(lubridate)
Attaching package: ‘lubridate’
The following object is masked from ‘package:base’:
date
> library(stringr)
> library(xlsx)
Unable to find any JVMs matching version "(null)".
No Java runtime present, try –request to install.
Error: package or namespace load failed for ‘xlsx’:
.onLoad failed in loadNamespace() for 'rJava', details:
call: dyn.load(file, DLLpath = DLLpath, …)
error: unable to load shared object '/Library/Frameworks/R.framework/Versions/3.6/Resources/library/rJava/libs/rJava.so':
dlopen(/Library/Frameworks/R.framework/Versions/3.6/Resources/library/rJava/libs/rJava.so, 6): Library not loaded: /Library/Java/JavaVirtualMachines/jdk-11.0.1.jdk/Contents/Home/lib/server/libjvm.dylib
Referenced from: /Library/Frameworks/R.framework/Versions/3.6/Resources/library/rJava/libs/rJava.so
Reason: image not found
In addition: Warning message:
In system("/usr/libexec/java_home", intern = TRUE) :
running command '/usr/libexec/java_home' had status 1
> library(httr)
Mittendrin dann weiteres Meldungen in rot:
See spec(…) for full column specifications.
Warning messages:
1: Missing column names filled in: 'X3' [3], 'X8' [8], 'X11' [11], 'X14' [14], 'X16' [16], 'X18' [18], 'X20' [20], 'X22' [22], 'X24' [24], 'X25' [25], 'X26' [26], 'X27' [27], 'X28' [28], 'X29' [29], 'X30' [30], 'X31' [31], 'X35' [35], 'X43' [43], 'X44' [44], 'X45' [45], 'X46' [46], 'X47' [47]
Error in stri_detect_regex(string, pattern, negate = negate, opts_regex = opts(pattern)) :
argument
str
should be a character vector (or an object coercible to)Naja und dann am Ende noch
Error in eval(lhs, parent, parent) : object 'internal_all_html' not found
Habe R und R Studio frisch installiert. Kannst du mir Tipps geben, was da eventuell schief läuft?
Danke!
David
Hallo Stefan,
vielen Dank für deinen Kommentar!
Meine erste Einschätzung wäre, dass Java nicht installiert ist.
Um das herauszufinden:
1. Windows Taste > cmd
2.
Java -version
ausführen.Entweder wird dir nun (wenn korrekt installiert) die aktuell installierte Java Version angezeigt oder du erhältst eine Fehlermeldung. – In diesem Fall solltest du Java installieren: https://www.java.com/de/download/
LG
Stefan
Hey David, danke dir für die Einschätzung. Java ist aber auf meinem Mac installiert 🙁
Stefan
upps, mein Kommentar war nicht ganz präzise wie sich nach weiterer Recherche rausstellt. Ich musste das Java Development Kit noch auf dem Mac installieren. Juhu, damit sind zumindest die beiden Fehlerzeilen "Unable to find any JVMs matching version "(null)".
No Java runtime present, try –request to install." nicht mehr da … Leider immernoch weit entfernt davon, das Skript zum Laufen zu bringen
David
Um die nächsten Errors anzugehen, wurden alle Libraries die im Script genutzt werden,
library(tidyverse)
library(lubridate)
library(stringr)
library(xlsx)
library(httr)
, auch vorher beispielsweise für xlsx mittels:
install.packages("xlsx")
installiert?Des weiteren haben sich im Screaming Frog die Exporte geändert. Die erste Zeile muss beim Einlesen nun nicht mehr übersprungen werden also sollte das Argument
, skip = 1
beim Einlesen der Crawls in der Funktionread_csv
herausgenommen werden.Stefan
Das ist auch ein wertvoller Tipp! Hmm, also anscheinend sind die nicht installiert. Na dann mache ich mich mal da ran … Danke abermals
Stefan
Hmm, so langsam werden die Fehler tatsächlich weniger.
> library(tidyverse)
Fehler: package or namespace load failed for ‘tidyverse’ in loadNamespace(i, c(lib.loc, .libPaths()), versionCheck = vI[[i]]):
es gibt kein Paket namens ‘scales’
Woran kann das denn jetzt schon wieder liegen?
Patrick Lürwer
Moin, das liegt daran, dass die Library scales nicht installiert ist. Kleiner Tipp, Fehlermeldungen kann man meist recht gut googlen. 😉 LG
Sandra
Hallo und vielen Dank für die tolle Anleitung 🙂
Bei mir läuft das Skript leider auch nicht und ich weiß absolut nicht, woran das liegen könnte.
skip = 1 habe ich bereits wie oben beschrieben aus dem Code entfernt.
Nach dem Punkt "Einlesen der Crawls" kommt bei mir die folgende Fehlermeldung:
Parsed with column specification:
cols(
Internal - All
= col_character())
Warnung: 159 parsing failures.
row col expected actual file
1 — 1 columns 43 columns 'C:/Users/Kunde/Documents/Kunde1/Crawls//kunde_2019-11-19_internal_all.csv'
2 — 1 columns 43 columns 'C:/Users/Kunde/Documents/Kunde1/Crawls//kunde_2019-11-19_internal_all.csv'
3 — 1 columns 43 columns 'C:/Users/Kunde/Documents/Kunde1/Crawls//kunde_2019-11-19_internal_all.csv'
… … ……… ………. …………………………………………………………………………….
See problems(…) for more details.
Parsed with column specification:
cols(
Internal - All
= col_character())
Warnung: 161 parsing failures.
row col expected actual file
1 — 1 columns 43 columns 'C:/Users/Kunde/Documents/Kunde1/Crawls//kunde_2019-11-27_internal_all.csv'
2 — 1 columns 43 columns 'C:/Users/Kunde/Documents/Kunde1/Crawls//kunde_2019-11-27_internal_all.csv'
3 — 1 columns 43 columns 'C:/Users/Kunde/Documents/Kunde1/Crawls//kunde_2019-11-27_internal_all.csv'
… … ……… ………. …………………………………………………………………………….
See problems(…) for more details.
Dann folgt nach dem Punkt "Ein bisschen aufräumen" die folgende Fehlermeldung:
Fehler in stri_detect_regex(string, pattern, negate = negate, opts_regex = opts(pattern)) :
argument
str
should be a character vector (or an object coercible to)Zum Schluss bekomme ich am Ende nur die folgende Info:
Fehler in eval(lhs, parent, parent) :
Objekt 'internal_all_html' nicht gefunden
Ich kann mir vorstellen, dass es am Einlesen der Dokumente liegen könnte, aber ich verstehe nicht, welches Problem es hierbei gibt.
Könnt ihr mir evtl. Tipps geben, woran das liegen könnte?
Vielen Dank und liebe Grüße
Sandra
Patrick Lürwer
Hi Sandra,
> cols(
Internal – All = col_character()
)
das sieht für mich so aus, dass du einen alten Crawl-Export laden willst, der in der ersten Zeile noch die Angabe über die Art des Crawls enhält. Entweder den Frosch updaten und noch mal exportieren oder das skip = 1 zum Überspringen der ersten Zeil wieder einfügen.
LG
Sandra
Hallo Patrick,
vielen Dank für Deine Antwort.
Ich habe beides versucht, wobei ich das Gefühl habe, dass ich durch das Screaming-Frog-Update der Lösung näher komme.
Nun (mit der neuen Version des Screaming Frogs, ohne Einfügen von skip = 1) wird mir nach "Einlesen der Crawls" die folgende Fehlermeldung angezeigt:
Parsed with column specification:
cols(
.default = col_double(),
Address = col_character(),
Content = col_character(),
Status = col_character(),
Indexability = col_character(),
Indexability Status
= col_character(),Title 1
= col_character(),Meta Description 1
= col_character(),Meta Keyword 1
= col_character(),H1-1
= col_character(),H2-1
= col_character(),H2-2
= col_character(),Meta Robots 1
= col_character(),X-Robots-Tag 1
= col_logical(),Meta Refresh 1
= col_logical(),Canonical Link Element 1
= col_character(),rel="next" 1
= col_logical(),rel="prev" 1
= col_logical(),HTTP rel="next" 1
= col_logical(),HTTP rel="prev" 1
= col_logical(),Link Score
= col_logical()# … with 5 more columns
)
See spec(…) for full column specifications.
Parsed with column specification:
cols(
.default = col_double(),
Address = col_character(),
Content = col_character(),
Status = col_character(),
Indexability = col_character(),
Indexability Status
= col_character(),Title 1
= col_character(),Meta Description 1
= col_character(),Meta Keyword 1
= col_character(),H1-1
= col_character(),H2-1
= col_character(),H2-2
= col_character(),Meta Robots 1
= col_character(),X-Robots-Tag 1
= col_logical(),Meta Refresh 1
= col_logical(),Canonical Link Element 1
= col_character(),rel="next" 1
= col_logical(),rel="prev" 1
= col_logical(),HTTP rel="next" 1
= col_logical(),HTTP rel="prev" 1
= col_logical(),Link Score
= col_logical()# … with 5 more columns
)
See spec(…) for full column specifications.
Das Skript endet dann mit der Fehlermeldung:
Fehler: Objekt 'size_new' nicht gefunden
Kann das an der Bezeichnung der "size"-Spalte liegen?
Mir ist schon aufgefallen, dass die alte Spalte "size" im neuen Screaming Frog jetzt "Size (bytes)" heißt. Im Skript ist aber nur von "size" die Rede.
Ich habe schon versucht, den Code entsprechend anzupassen, komme aber mit den Klammern und Leerzeichen in der neuen Bezeichnung nicht zurecht. Wenn ich im Code "size" in "size_(bytes)" ändere, kommen lauter weitere Fehlermeldungen.
Könnt ihr mir nochmal weiterhelfen?
Vielen Dank und liebe Grüße
Sandra
Jon
Wirklich sehr cool! Unendlich viel Liebe für diese Arbeitserleichterung. Genau so etwas hab ich immer gesucht, sind die Kunden doch selten so gut einem alle Änderungen zuverlässig mitzuteilen.
Für neue Nutzer wäre es praktisch, auf GitHub eine leicht abgeänderte Version (mit Zeile 1, mit umbenannter "Size (bytes)"-Spalte) hochzuladen, um auch für die aktuellen ScreamingFrog-Exporte zu passen.
Patrick Lürwer
Servus Jon,
danke, danke. 🙂 Ja, die Anpassung muss ich mal angehen. Ich schieb es nur immer vor mir her, weil ich das Skript mittlerweile weiter aufgebohrt habe und eigentlich eine neuere Version bereitstellen könnte. Nur muss ich dann wieder was schreiben und so liegt es gerade halt leider liegen….
Sercan
Hallo David,
danke für die Anleitung. Habe sie soweit durchgearbeitet.
In Schritt 9 bei der Aufgabenverwaltung scheint euer CMS die doppelten „–“ durch einfache „-“ ersetzt zu haben. Hab da die Scheduling Funktion von SF verwendet und dann die Werte entsprechend eurer Vorgabe geändert. „–project-name“ scheint SF nicht zu verstehen.
Nachdem ich alles eingestellt habe und das Crawling etc. funktionieren, bekomme ich bei RStudio folgenden Fehler:
Nur bei R habe ich ein Problem:
„Attache Paket: ‚lubridate‘
The following object is masked from ‚package:base‘:
date
Fehler in matrix(NA_character_, nrow = nrow(indt), ncol = Ncol) :
ungültiger ’ncol‘ Wert (zu groß oder NA)
Ruft auf: %>% … -> -> lapply -> FUN -> matrix
Zusätzlich: Warnmeldungen:
1: In max(unlist(lapply(SetUp, function(y) y[[„Mat“]][, 2]), use.names = FALSE)) :
kein nicht-fehlendes Argument für max; gebe -Inf zurück
2: In matrix(NA_character_, nrow = nrow(indt), ncol = Ncol) :
NAs introduced by coercion to integer range
Ausführung angehalten“
–
Java neuinstalliert etc., aber leider kommt der Fehler noch immer.
Hast du vielleicht einen Tipp für mich?
Danke vielmals.
LG
Sercan
Sercan
Hallo David,
danke für die Anleitung. Habe sie soweit durchgearbeitet.
In Schritt 9 bei der Aufgabenverwaltung scheint euer CMS die doppelten „–“ durch einfache „-“ ersetzt zu haben. Hab da die Scheduling Funktion von SF verwendet und dann die Werte entsprechend eurer Vorgabe geändert. „–project-name“ scheint SF nicht zu verstehen.
Nachdem ich alles eingestellt habe und das Crawling etc. funktionieren, bekomme ich bei RStudio folgenden Fehler:
Nur bei R habe ich ein Problem:
„Attache Paket: ‚lubridate‘
The following object is masked from ‚package:base‘:
date
Fehler in matrix(NA_character_, nrow = nrow(indt), ncol = Ncol) : -> -> lapply -> FUN -> matrix
ungültiger ’ncol‘ Wert (zu groß oder NA)
Ruft auf: %>% …
Zusätzlich: Warnmeldungen:
1: In max(unlist(lapply(SetUp, function(y) y[[„Mat“]][, 2]), use.names = FALSE)) :
kein nicht-fehlendes Argument für max; gebe -Inf zurück
2: In matrix(NA_character_, nrow = nrow(indt), ncol = Ncol) :
NAs introduced by coercion to integer range
Ausführung angehalten“
–
Java neuinstalliert etc., aber leider kommt der Fehler noch immer.
Hast du vielleicht einen Tipp für mich?
Danke vielmals.
LG
Sercan
David Meyer
Hi Sercan
Dieser Fehler ist typisch, wenn die CSV-Dateien nicht richtig eingelesen/gefunden werden konnten.
Sprich: Das Skript an der Stelle #Split File Paths keine Pfade zum Splitten hat, da die Variable „files“ leer ist.
Am besten einmal sicherstellen, dass die Pfade zu deinen CSV Dateien unter # VARS richtig angegeben sind. Sonst gerne nochmal melden 🙂
LG
David