<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: lineagersiui-boop</title>
    <description>The latest articles on DEV Community by lineagersiui-boop (@lineagersiuiboop).</description>
    <link>https://dev.to/lineagersiuiboop</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4216250%2F731d7b52-1cf9-4ea4-891f-d62120fe2465.png</url>
      <title>DEV Community: lineagersiui-boop</title>
      <link>https://dev.to/lineagersiuiboop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lineagersiuiboop"/>
    <language>en</language>
    <item>
      <title>How to find broken URLs in your sitemap (free methods first)</title>
      <dc:creator>lineagersiui-boop</dc:creator>
      <pubDate>Fri, 30 Oct 2026 05:21:49 +0000</pubDate>
      <link>https://dev.to/lineagersiuiboop/how-to-find-broken-urls-in-your-sitemap-free-methods-first-1o9p</link>
      <guid>https://dev.to/lineagersiuiboop/how-to-find-broken-urls-in-your-sitemap-free-methods-first-1o9p</guid>
      <description>&lt;p&gt;A sitemap lists the URLs you want search engines to index. Entries that return 404, server errors, redirect chains or &lt;code&gt;noindex&lt;/code&gt; waste crawl budget and send mixed signals. Check your sitemap at three moments: before a migration, after a CMS update, and on a regular schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Free: check a few URLs by hand
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code} %{redirect_url}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://example.com/some-page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for 5 URLs. It gets tedious at 500.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Free: a short script (PowerShell)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$xml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://example.com/sitemap.xml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UseBasicParsing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Content&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$xml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Head&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-MaximumRedirection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UseBasicParsing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StatusCode&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$u&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StatusCode&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$u&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Start-Sleep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Milliseconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;500&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script prints one line per URL: &lt;code&gt;200&lt;/code&gt; means OK, &lt;code&gt;3xx&lt;/code&gt; means a redirect, and &lt;code&gt;404&lt;/code&gt; or &lt;code&gt;5xx&lt;/code&gt; means broken. It was tested in Windows PowerShell 5.1 against a public sitemap, a missing page (404) and an HTTP-to-HTTPS redirect (307).&lt;/p&gt;

&lt;p&gt;Limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't handle sitemap indexes or &lt;code&gt;.xml.gz&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;It doesn't follow redirect chains.&lt;/li&gt;
&lt;li&gt;It doesn't check robots.txt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep requests paced and respect the site's robots.txt.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Free desktop crawlers
&lt;/h2&gt;

&lt;p&gt;Several desktop SEO crawlers have free tiers with URL limits, and other free link checkers exist. Check their current limits yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Hosted option (I built this)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apify.com/lineagersiui/sitemap-url-health-checker" rel="noopener noreferrer"&gt;Broken Link &amp;amp; Sitemap Checker&lt;/a&gt; is an Apify Actor (&lt;code&gt;lineagersiui/sitemap-url-health-checker&lt;/code&gt;) that does the same job on Apify's servers, and covers the gaps in the script above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it checks:&lt;/strong&gt; the HTTP status of every URL in your sitemap. It flags 404s, server errors, redirects, redirect chains, https-to-http downgrades and &lt;code&gt;noindex&lt;/code&gt; headers. It accepts sitemap indexes and &lt;code&gt;.xml.gz&lt;/code&gt; files, and it honours robots.txt (URLs robots.txt disallows are skipped and not billed).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope:&lt;/strong&gt; only URLs listed in the sitemap. It does not follow links inside pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission:&lt;/strong&gt; real runs need you to tick &lt;code&gt;confirmPermission&lt;/code&gt; (you own the site or may check it).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing:&lt;/strong&gt; pay-per-event, $0.0005 per URL checked (1,000 URLs = $0.50), plus Apify platform usage. Timeouts and network errors are not billed. Leave the input empty for a free demo of 20 URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track record:&lt;/strong&gt; it is a new Actor with very few users, so there are no customer results to show.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to do with the results
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Fix or remove sitemap entries that return 404 or 410.&lt;/li&gt;
&lt;li&gt;Point sitemap entries at the final URL, not at a redirect.&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;noindex&lt;/code&gt; URLs from the sitemap.&lt;/li&gt;
&lt;li&gt;Re-run the check on a schedule after releases.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I built the Actor mentioned here and earn its pay-per-event fees. I'm not affiliated with or endorsed by Apify beyond publishing on the Apify Store. This post was drafted with AI assistance under my direction. It first appeared as a guide on GitHub (canonical link above).&lt;/p&gt;

</description>
      <category>automation</category>
      <category>seo</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Your scraper says SUCCEEDED but the data is empty: three ways to catch it</title>
      <dc:creator>lineagersiui-boop</dc:creator>
      <pubDate>Fri, 30 Oct 2026 01:43:01 +0000</pubDate>
      <link>https://dev.to/lineagersiuiboop/your-scraper-says-succeeded-but-the-data-is-empty-three-ways-to-catch-it-3e4l</link>
      <guid>https://dev.to/lineagersiuiboop/your-scraper-says-succeeded-but-the-data-is-empty-three-ways-to-catch-it-3e4l</guid>
      <description>&lt;p&gt;Your scraper run says &lt;code&gt;SUCCEEDED&lt;/code&gt;. The dataset has 3 items instead of 3,000, or every &lt;code&gt;price&lt;/code&gt; field is empty, or the results are a cookie-consent page. Nothing alerted you, because the run status only says the process finished.&lt;/p&gt;

&lt;p&gt;Here are three ways to catch this, cheapest first.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Use Apify's free monitoring first
&lt;/h2&gt;

&lt;p&gt;Each Actor or task has a Monitoring tab. You can set alerts on result-count thresholds and run status. Alerts on dataset field statistics need a dataset schema. Apify also provides a free Monitoring Actor suite for validating content and checking duplicates, and its docs state the monitoring system is free for all users.&lt;/p&gt;

&lt;p&gt;Where fixed thresholds struggle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the item count varies a lot from run to run&lt;/li&gt;
&lt;li&gt;you run Store Actors that have no dataset schema&lt;/li&gt;
&lt;li&gt;you monitor many Actors and can't tune each threshold&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Compare each run to its own history
&lt;/h2&gt;

&lt;p&gt;If a fixed threshold doesn't fit, compare the latest run with the median of recent successful runs, and check that the fields you rely on aren't empty in too many sampled items.&lt;/p&gt;

&lt;p&gt;The guide on GitHub has a small Node.js script that does this with three API calls (list runs, get dataset metadata, fetch the first 200 items). It exits with 1 for unhealthy data and 2 when the checker itself is broken, so a scheduler can tell the two apart. Use a read-only token stored as a secret.&lt;/p&gt;

&lt;p&gt;Caveats, as stated in the guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the median needs at least three finished successful runs&lt;/li&gt;
&lt;li&gt;check tasks separately&lt;/li&gt;
&lt;li&gt;only the first 200 items are sampled&lt;/li&gt;
&lt;li&gt;these are heuristics, not proof&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not pasting the script here. I haven't re-run it for this post, so read it and its tests in the repo before relying on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A ready-made Actor
&lt;/h2&gt;

&lt;p&gt;There are third-party Actors with similar checks, such as Output Completeness Monitor and Dataset Drift Monitor. I built one too, Scraper Output Guard. It adds schema drift, duplicate detection and item-limit checks, at $0.005 per Actor checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Turn on the free monitoring first. If counts vary or you run many Actors, compare each run to its own history and check the fields you depend on. Alert on content, not just status.&lt;/p&gt;

&lt;p&gt;Full guide with the script: &lt;a href="https://github.com/lineagersiui-boop/apify-output-checks/tree/main/guides/silent-scraper-failures" rel="noopener noreferrer"&gt;https://github.com/lineagersiui-boop/apify-output-checks/tree/main/guides/silent-scraper-failures&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I built the Apify Actor mentioned here and earn its pay-per-event fees. I'm not affiliated with Apify beyond publishing on the Apify Store. This post was drafted with AI assistance under my direction. It first appeared as a guide on GitHub (canonical link above).&lt;/p&gt;

</description>
      <category>data</category>
      <category>monitoring</category>
      <category>webscraping</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
