{"id":14067,"date":"2024-10-20T18:00:00","date_gmt":"2024-10-20T16:00:00","guid":{"rendered":"https:\/\/u-labs.de\/portal\/?p=14067"},"modified":"2024-12-03T20:22:47","modified_gmt":"2024-12-03T18:22:47","slug":"links-automatisch-in-changedetection-io-trotz-sonderzeichen","status":"publish","type":"post","link":"https:\/\/u-labs.de\/portal\/links-automatisch-in-changedetection-io-trotz-sonderzeichen\/","title":{"rendered":"Links automatisch in changedetection.io importieren: So klappt es trotz Sonderzeichen"},"content":{"rendered":"<p>Changedetection.io ist ein hervorragendes OSS-Projekt, welches ich seit l\u00e4ngerem einsetze, um verschiedene Webseiten auf \u00c4nderungen zu \u00fcberwachen. Insbesondere jene, die keinen eigenen RSS-Feed besitzen. Nun hatte ich einen etwas komplexeren Anwendungsfall: Ich m\u00f6chte alle Produkte der Kategorie eines Shops auf Ver\u00e4nderungen sowie neue Eintr\u00e4ge \u00fcberwachen. Die Software soll also zun\u00e4chst die Kategorie-Seite abfragen, alle Produktlinks extrahieren und diese mit einem entsprechenden Tag hinzuf\u00fcgen, falls diese noch nicht existieren. <\/p>\n<p>Dazu existiert eine recht aktuelle Anleitung von Anfang 2024 auf der Hersteller-Seite.<sup data-fn=\"fa683ef6-19ca-4c2c-a1d4-a1e6f1d10b8a\" class=\"fn\"><a href=\"#fa683ef6-19ca-4c2c-a1d4-a1e6f1d10b8a\" id=\"fa683ef6-19ca-4c2c-a1d4-a1e6f1d10b8a-link\">1<\/a><\/sup> \u00dcber einen XPath-Selektor holt sich der Job lediglich die Links, welche als Benachrichtigung an die eigene API gesendet werden. Im Body muss man lediglich die Variable <code class=\"\" data-line=\"\">{{current_snapshot}}<\/code> f\u00fcr die Rohdaten eintragen. Im Beitrag werden sogar aus relativen links absolute erzeugt.<\/p>\n<p>Womit keiner gerechnet hat, sind Sonderzeichen in URLs. Die setzt der Shop flei\u00dfig lokalisiert ein und hat einen Anker f\u00fcr die Gr\u00f6\u00dfe eingebaut:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">https:\/\/shop.com\/de\/kategorie\/12345-produkt-xyz#\/2-Gr\u00f6\u00dfe-m<\/code><\/pre>\n<p>\u00dcber die Web-Oberfl\u00e4che konnte ich mehrere zuf\u00e4llig ausgew\u00e4hlte problemlos per Hand hinzuf\u00fcgen. Nachdem ich in der Anwendung und stdout keine Hinweise gefunden habe (au\u00dfer einer irref\u00fchrenden Meldung, die Daten nicht ins JSON-Format umwandeln zu k\u00f6nnen), versuchte ich die API zum Importieren h\u00e4ndisch aufzurufen. Praktischerweise steht in der Doku ein <code class=\"\" data-line=\"\">curl<\/code> Beispielaufruf.<sup data-fn=\"cd606ef0-4d20-4b76-a965-41adb558ce57\" class=\"fn\"><a href=\"#cd606ef0-4d20-4b76-a965-41adb558ce57\" id=\"cd606ef0-4d20-4b76-a965-41adb558ce57-link\">2<\/a><\/sup> Der schlug fehl, weil ihm die URL nicht passt:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\"># curl http:\/\/changedetection.internal\/api\/v1\/import?tag=demo --data-binary @\/tmp\/urls.txt -H&quot;x-api-key:12345&quot;\nInvalid or unsupported URL<\/code><\/pre>\n<p>Bei quelloffener Software kann man im Programmcode nachschauen, was konkret passiert.<sup data-fn=\"d649211c-88f3-41a9-a569-9ac3208c6f4d\" class=\"fn\"><a href=\"#d649211c-88f3-41a9-a569-9ac3208c6f4d\" id=\"d649211c-88f3-41a9-a569-9ac3208c6f4d-link\">3<\/a><\/sup> Es wird eine Bibliothek zur Validierung aufgerufen. Schnell wurde klar, dass es an den Sonderzeichen liegt. Mit folgender Adresse funktioniert der Aufruf und liefert die Id des angelegten Eintrags zur\u00fcck:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">https:\/\/shop.com\/de\/kategorie\/12345-produkt-xyz#\/2-Gr<\/code><\/pre>\n<p>Theoretisch sind Sonderzeichen in URLs g\u00fcltig, wenn sie entsprechend maskiert sind. Das hat der Shop vers\u00e4umt. Die Browser sehen das offensichtlich gro\u00dfz\u00fcgiger, dort war das manuelle Aufrufen von Produkten aus der Kategorie m\u00f6glich. Nachdem der Changedetection.io Beitrag eine Foreach-Schleife in XPath verwendete, schaute ich nach, ob es Funktionen zur Maskierung von URLs gibt. Und tats\u00e4chlich existiert <code class=\"\" data-line=\"\">fn:escape-html-uri<\/code>.<sup data-fn=\"20ff6cc5-62c4-4ea9-a045-9ef997491804\" class=\"fn\"><a href=\"#20ff6cc5-62c4-4ea9-a045-9ef997491804\" id=\"20ff6cc5-62c4-4ea9-a045-9ef997491804-link\">4<\/a><\/sup> Dies wird zwar von Onlinewerkzeugen zum Testen von Xpath (z.B. xpather.com) nicht unterst\u00fctzt, doch von Changedetection.io ausgef\u00fchrt. Ich passte meinen einzelnen XPath-Selektor an, dass dieser stattdessen per Schleife die Funktion f\u00fcr jeden Link aufruft:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">xpath:for-each(\/\/a[@class=&quot;product-thumbnail-link&quot;]\/@href, function($a) { escape-html-uri($a) })<\/code><\/pre>\n<p>Damit werden s\u00e4mtliche potenziell problematische Sonderzeichen mit &#8222;%&#8220; gefolgt von zwei hexadezimalen Zahlen dargestellt. Dies ist auch als <em>percent encoding<\/em> bekannt.<sup data-fn=\"42f911ce-3688-4a67-84d8-1090e87dafe9\" class=\"fn\"><a href=\"#42f911ce-3688-4a67-84d8-1090e87dafe9\" id=\"42f911ce-3688-4a67-84d8-1090e87dafe9-link\">5<\/a><\/sup><\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"\" data-line=\"\">https%3A%2F%2Fshop.com%2Fde%2Fkategorie%2F12345-produkt-xyz%23%2F2-Gr%C3%B6%C3%9Fe-m<\/code><\/pre>\n<p>Nun funktioniert der Import. Und ich sehe mich darin best\u00e4tigt, in meinen Anwendungen <em>niemals<\/em> URLs mit Sonderzeichen zu generieren. Es mag zwar mittlerweile an vielen Stellen keinen \u00c4rger mehr bereiten. Aus Sicht eines durchschnittlichen Nutzers, der lediglich im Browser auf die Links klickt, w\u00e4re das sogar hier unproblematisch. Nicht \u00fcberall ist man jedoch so tolerant. Ich sehe zudem nicht den Mehrwert, warum in der Adresse zwingend alles mit s\u00e4mtlichen Sonderzeichen dargestellt werden muss. Eine ungef\u00e4hre Vorstellung, was man erwarten kann, liefert [a-z0-9] bereits v\u00f6llig ausreichend. Das funktioniert daf\u00fcr \u00fcberall.<\/p>\n<h2 class=\"wp-block-heading\">Quellen<\/h2>\n<ol class=\"wp-block-footnotes\">\n<li id=\"fa683ef6-19ca-4c2c-a1d4-a1e6f1d10b8a\">https:\/\/changedetection.io\/tutorial\/automatically-adding-new-pages-watch-existing-results <a href=\"#fa683ef6-19ca-4c2c-a1d4-a1e6f1d10b8a-link\" aria-label=\"Zur Fu\u00dfnotenreferenz 1 navigieren\">\u21a9\ufe0e<\/a><\/li>\n<li id=\"cd606ef0-4d20-4b76-a965-41adb558ce57\">https:\/\/changedetection.io\/docs\/api_v1\/index.html#api-Watch-Import <a href=\"#cd606ef0-4d20-4b76-a965-41adb558ce57-link\" aria-label=\"Zur Fu\u00dfnotenreferenz 2 navigieren\">\u21a9\ufe0e<\/a><\/li>\n<li id=\"d649211c-88f3-41a9-a569-9ac3208c6f4d\">https:\/\/www.gitlockr.com\/Self-Hosted\/changedetection.io\/src\/branch\/master\/changedetectionio\/api\/api_v1.py#L230 <a href=\"#d649211c-88f3-41a9-a569-9ac3208c6f4d-link\" aria-label=\"Zur Fu\u00dfnotenreferenz 3 navigieren\">\u21a9\ufe0e<\/a><\/li>\n<li id=\"20ff6cc5-62c4-4ea9-a045-9ef997491804\">http:\/\/www.datypic.com\/xq\/fn_escape-html-uri.html <a href=\"#20ff6cc5-62c4-4ea9-a045-9ef997491804-link\" aria-label=\"Zur Fu\u00dfnotenreferenz 4 navigieren\">\u21a9\ufe0e<\/a><\/li>\n<li id=\"42f911ce-3688-4a67-84d8-1090e87dafe9\">https:\/\/www.w3schools.com\/tags\/ref_urlencode.ASP <a href=\"#42f911ce-3688-4a67-84d8-1090e87dafe9-link\" aria-label=\"Zur Fu\u00dfnotenreferenz 5 navigieren\">\u21a9\ufe0e<\/a><\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>Changedetection.io ist ein hervorragendes OSS-Projekt, welches ich seit l\u00e4ngerem einsetze, um verschiedene Webseiten auf \u00c4nderungen zu \u00fcberwachen. Insbesondere jene, die keinen eigenen RSS-Feed besitzen. Nun hatte ich einen etwas komplexeren Anwendungsfall: Ich m\u00f6chte alle Produkte der Kategorie eines Shops auf Ver\u00e4nderungen sowie neue Eintr\u00e4ge \u00fcberwachen. Die Software soll also zun\u00e4chst die Kategorie-Seite abfragen, alle Produktlinks &#8230;<\/p>\n","protected":false},"author":5,"featured_media":14099,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"[{\"content\":\"https:\/\/changedetection.io\/tutorial\/automatically-adding-new-pages-watch-existing-results\",\"id\":\"fa683ef6-19ca-4c2c-a1d4-a1e6f1d10b8a\"},{\"content\":\"https:\/\/changedetection.io\/docs\/api_v1\/index.html#api-Watch-Import\",\"id\":\"cd606ef0-4d20-4b76-a965-41adb558ce57\"},{\"content\":\"https:\/\/www.gitlockr.com\/Self-Hosted\/changedetection.io\/src\/branch\/master\/changedetectionio\/api\/api_v1.py#L230\",\"id\":\"d649211c-88f3-41a9-a569-9ac3208c6f4d\"},{\"content\":\"http:\/\/www.datypic.com\/xq\/fn_escape-html-uri.html\",\"id\":\"20ff6cc5-62c4-4ea9-a045-9ef997491804\"},{\"content\":\"https:\/\/www.w3schools.com\/tags\/ref_urlencode.ASP\",\"id\":\"42f911ce-3688-4a67-84d8-1090e87dafe9\"}]"},"categories":[78,1],"tags":[1108],"class_list":["post-14067","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software","category-technik-news","tag-changedetection-io"],"_links":{"self":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/14067","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/comments?post=14067"}],"version-history":[{"count":5,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/14067\/revisions"}],"predecessor-version":[{"id":14072,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/14067\/revisions\/14072"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media\/14099"}],"wp:attachment":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media?parent=14067"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/categories?post=14067"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/tags?post=14067"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}