<?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: Lucy Green</title>
    <description>The latest articles on DEV Community by Lucy Green (@lucy-green).</description>
    <link>https://dev.to/lucy-green</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%2F3948792%2Ff87afe80-7cce-4547-aae8-742cf98586e3.png</url>
      <title>DEV Community: Lucy Green</title>
      <link>https://dev.to/lucy-green</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lucy-green"/>
    <language>en</language>
    <item>
      <title>Google PageRank Is Dead: Why Legacy Scores No Longer Tell the Full SEO Story</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Mon, 05 Oct 2026 05:41:46 +0000</pubDate>
      <link>https://dev.to/lucy-green/google-pagerank-is-dead-why-legacy-scores-no-longer-tell-the-full-seo-story-bm8</link>
      <guid>https://dev.to/lucy-green/google-pagerank-is-dead-why-legacy-scores-no-longer-tell-the-full-seo-story-bm8</guid>
      <description>&lt;p&gt;Legacy Google PageRank data still lingers in old SEO spreadsheets, and plenty of teams keep referencing it during authority audits. The problem is that the toolbar has been dead for years, so anyone quoting a PR4 or PR6 is working from stale signals that no longer reflect how a domain performs today.&lt;/p&gt;

&lt;p&gt;Here is a practical way to handle it. Use a Google PageRank checker to pull whatever historical PageRank value is still archived for a domain, then immediately place it next to a live authority metric. The SERPSpur Google PageRank Checker does exactly this: it surfaces legacy PR data and pairs it with SERPSpur Trust Rate in a single view, so you can see how a domain's old reputation lines up with its current standing.&lt;/p&gt;

&lt;p&gt;The workflow is simple. First, run the domain through the checker to capture its archived PageRank. Second, read the Trust Rate alongside it to gauge present-day link equity and credibility. Third, use the gap between the two as your signal. A site with high legacy PR but a weak Trust Rate has likely lost authority or changed ownership. A site with modest legacy PR but a strong Trust Rate is climbing.&lt;/p&gt;

&lt;p&gt;Treat legacy PageRank as historical context, not a ranking factor. Pair it with a modern trust metric, and your authority analysis gets sharper without the guesswork.&lt;a href="https://serpspur.com/tool/google-pagerank-checker/" rel="noopener noreferrer"&gt;https://serpspur.com/tool/google-pagerank-checker/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Stop Using a Different Converter for Every File Format: Build a Smarter Conversion Workflow</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Mon, 05 Oct 2026 05:40:19 +0000</pubDate>
      <link>https://dev.to/lucy-green/stop-using-a-different-converter-for-every-file-format-build-a-smarter-conversion-workflow-126</link>
      <guid>https://dev.to/lucy-green/stop-using-a-different-converter-for-every-file-format-build-a-smarter-conversion-workflow-126</guid>
      <description>&lt;p&gt;Most file converters you find online are built for a single format pair. Need PDF to DOCX? One site. WebP to PNG? Another. CSV to XLSX? A third, probably with a file size cap and a watermark. The friction adds up fast when you're converting across a dozen formats in a week.&lt;/p&gt;

&lt;p&gt;The practical setup I'd recommend: keep a local toolkit for the conversions you run constantly, and use a universal converter for the long tail. Local first, because it's faster and your data never leaves the machine.&lt;/p&gt;

&lt;p&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Batch image conversion with ImageMagick
&lt;/h1&gt;

&lt;p&gt;mogrify -format webp -quality 85 *.png&lt;/p&gt;

&lt;h1&gt;
  
  
  Documents via LibreOffice headless
&lt;/h1&gt;

&lt;p&gt;libreoffice --headless --convert-to docx *.pdf&lt;/p&gt;

&lt;h1&gt;
  
  
  CSV to XLSX with pandas
&lt;/h1&gt;

&lt;p&gt;python -c "import pandas as pd; pd.read_csv('in.csv').to_excel('out.xlsx', index=False)"&lt;/p&gt;

&lt;p&gt;That handles 90% of routine work. The remaining 10% is the awkward stuff—an HTML invoice that needs to become CSV, a format you've never heard of, a one-off from a client. That's where a universal converter that covers 200+ formats including PDF, DOCX, WebP, and CSV earns its place, because installing a new library for a single conversion is a waste of an afternoon.&lt;/p&gt;

&lt;p&gt;Two things to check on any converter before you rely on it: does it preserve metadata (EXIF, document properties) or strip it, and does it handle batch input or force you to click through files one at a time? Batch support matters more than raw speed once you're past a handful of files. And if the files are sensitive, local conversion sidesteps the trust question entirely.&lt;a href="https://serpspur.com/tool/all-type-free-file-converter/" rel="noopener noreferrer"&gt;https://serpspur.com/tool/all-type-free-file-converter/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SerpSpur vs Semrush: Which SEO Tool Fits Your Everyday SEO Workflow?</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Sun, 04 Oct 2026 17:27:24 +0000</pubDate>
      <link>https://dev.to/lucy-green/serpspur-vs-semrush-which-seo-tool-fits-your-everyday-seo-workflow-f42</link>
      <guid>https://dev.to/lucy-green/serpspur-vs-semrush-which-seo-tool-fits-your-everyday-seo-workflow-f42</guid>
      <description>&lt;p&gt;Comparing SEO tools is tricky because the honest answer is usually "it depends on the job." Here's a fair look at SerpSpur vs Semrush across the features that actually matter day to day.&lt;/p&gt;

&lt;p&gt;Keyword research — Semrush has a massive keyword database with deep historical data. SerpSpur covers volume, difficulty, and SERP intent well, and it's fast for quick lookups. If you're doing heavy market sizing, Semrush wins. For everyday targeting, SerpSpur holds up.&lt;/p&gt;

&lt;p&gt;Rank tracking — Both track positions. SerpSpur's interface is lighter and updates are quick; Semrush offers more granular historical charts and segmentation.&lt;/p&gt;

&lt;p&gt;Site audits — SerpSpur's audit surfaces technical issues, broken links, indexing problems, and metadata gaps cleanly. Semrush's crawler is more configurable for enterprise needs.&lt;/p&gt;

&lt;p&gt;Backlinks — Semrush's index is genuinely hard to replicate. SerpSpur's Bulk Backlink Exporter is great for organizing and processing link data, but if link forensics is your core work, Semrush is the deeper well.&lt;/p&gt;

&lt;p&gt;Usability &amp;amp; value — This is where SerpSpur pulls ahead for most teams. Modern UI, faster workflows, no complexity tax on the 90% of tasks that don't need enterprise depth.&lt;/p&gt;

&lt;p&gt;A practical setup: SerpSpur for daily monitoring, audits, and reporting; Semrush for quarterly deep dives. Test it yourself — export a keyword list from Semrush into SerpSpur and see if the data holds for your niche.&lt;/p&gt;

&lt;p&gt;Try it: &lt;a href="https://serpspur.com" rel="noopener noreferrer"&gt;https://serpspur.com&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Your Rankings Aren’t Broken—Your Technical SEO Debt Is</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Thu, 10 Sep 2026 11:27:07 +0000</pubDate>
      <link>https://dev.to/lucy-green/your-rankings-arent-broken-your-technical-seo-debt-is-46ji</link>
      <guid>https://dev.to/lucy-green/your-rankings-arent-broken-your-technical-seo-debt-is-46ji</guid>
      <description>&lt;p&gt;You fixed the redirect chain, updated the sitemap, and resubmitted. Two weeks later, half your product pages are still sitting in "Discovered - currently not indexed" and a chunk of revenue-driving URLs return 404s from an old migration nobody documented.&lt;/p&gt;

&lt;p&gt;This is the real cost of technical SEO debt: it hides in plain sight. Google quietly drops pages, crawl budget gets wasted on parameter junk, and duplicate title tags split relevance across near-identical URLs. You do not notice until traffic dips and the culprit is buried three layers deep in a crawl report.&lt;/p&gt;

&lt;p&gt;SerpSpur Site Audit approaches this as a diagnostic pass, not a vanity score. Point it at your domain and it surfaces the issues that actually move rankings: broken internal and external links, indexing blockers like stray noindex tags and canonical conflicts, missing or duplicated metadata, thin pages that dilute topical authority, and orphaned URLs no internal link reaches.&lt;/p&gt;

&lt;p&gt;What makes it practical is the ordering. Instead of dumping 40,000 warnings, it groups findings by impact so you fix the handful of problems affecting crawl efficiency and indexation first. Each issue comes with the affected URL, the reason it matters, and the fix. You can rerun the audit after deploying to confirm the count drops.&lt;/p&gt;

&lt;p&gt;A useful routine: audit monthly, and always immediately after a migration, CMS upgrade, or bulk content publish. Most ranking recoveries are not new content plays. They are old technical debt finally cleared.&lt;/p&gt;

&lt;p&gt;Run the audit on your site, fix the top ten findings, then rerun. That loop beats speculation every time.&lt;a href="https://serpspur.com/" rel="noopener noreferrer"&gt;https://serpspur.com/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Stop Overpaying for SEO Tools: A Faster, Smarter Alternative to Expensive SEO Suites</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Tue, 08 Sep 2026 16:18:29 +0000</pubDate>
      <link>https://dev.to/lucy-green/stop-overpaying-for-seo-tools-a-faster-smarter-alternative-to-expensive-seo-suites-3k8a</link>
      <guid>https://dev.to/lucy-green/stop-overpaying-for-seo-tools-a-faster-smarter-alternative-to-expensive-seo-suites-3k8a</guid>
      <description>&lt;p&gt;Tired of paying $200+ a month just to peek at your competitor’s keywords? You are not alone. The real pain point isn’t the data—it’s the bloat. Legacy SEO suites force you to navigate clunky dashboards and pay for modules you never touch, just to answer one simple question: “Why is my traffic flat?”&lt;/p&gt;

&lt;p&gt;Here is the fix. Stop renting a legacy monolith and start using a modular, agile toolkit. SerpSpur gives you the exact metrics that matter—organic traffic volume, keyword difficulty, backlink gaps, and on-page health—without the enterprise bloat. Instead of waiting for a weekly PDF report, you get real-time site audits that flag technical issues like broken links or slow CLS scores instantly.&lt;/p&gt;

&lt;p&gt;The actionable workflow? Run a domain audit first. SerpSpur crawls every page to expose thin content and duplicate meta tags. Then, pivot to the competitor gap analysis. Enter your top rival’s URL, filter by “keywords they rank for but you don’t,” and export that list to your content calendar. Done. No learning curve, no training videos.&lt;/p&gt;

&lt;p&gt;Modern SEO does not require a legacy price tag. It requires speed and specificity. Pull up the live dashboard, spot the drop in branded traffic, and drill down to the exact referring page that lost its backlink.&lt;/p&gt;

&lt;p&gt;Stop overpaying for complexity. Start analyzing with clarity. Try SerpSpur free and see your traffic blind spots today.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Google PageRank Is Dead: Stop Using Outdated Scores to Evaluate Domains</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Tue, 08 Sep 2026 07:52:30 +0000</pubDate>
      <link>https://dev.to/lucy-green/google-pagerank-is-dead-stop-using-outdated-scores-to-evaluate-domains-284p</link>
      <guid>https://dev.to/lucy-green/google-pagerank-is-dead-stop-using-outdated-scores-to-evaluate-domains-284p</guid>
      <description>&lt;p&gt;Google killed the public PageRank API back in 2016, yet countless SEOs still chase that outdated 0-10 metric. You check an old directory, see a 4, and assume the domain is solid. But that number is frozen in time. It doesn't reflect current link velocity, content quality, or algorithmic penalties.&lt;/p&gt;

&lt;p&gt;The real problem is trust. You cannot make a confident decision about a domain or competitor using a relic. You need a live signal that correlates with how Google actually evaluates authority today.&lt;/p&gt;

&lt;p&gt;That is why we built the Google PageRank Checker with a modern twist. Instead of forcing you to interpret a dead number, it pulls the historical PageRank data for context and pairs it instantly with the SERPSpur Trust Rate. This is our proprietary, real-time score that analyzes backlink profiles, domain age, and current ranking signals.&lt;/p&gt;

&lt;p&gt;The workflow is simple. Paste any URL, hit check, and you get a side-by-side view. See if the legacy authority matches the current reality. A high historical PageRank with a low Trust Rate exposes a decayed or penalized site. A low historical score with a high Trust Rate reveals a rising player you would otherwise ignore.&lt;/p&gt;

&lt;p&gt;Stop guessing with outdated data. Get the historical context, but make your decision on the live metric that matters now. Check any domain at SERPSpur.&lt;a href="https://serpspur.com/tool/google-pagerank-checker/" rel="noopener noreferrer"&gt;https://serpspur.com/tool/google-pagerank-checker/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Stop Fighting Invoice Formats: Convert PDFs, Excel Files, and HTML to CSV Without the Headaches</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Thu, 06 Aug 2026 06:34:45 +0000</pubDate>
      <link>https://dev.to/lucy-green/stop-fighting-invoice-formats-convert-pdfs-excel-files-and-html-to-csv-without-the-headaches-5018</link>
      <guid>https://dev.to/lucy-green/stop-fighting-invoice-formats-convert-pdfs-excel-files-and-html-to-csv-without-the-headaches-5018</guid>
      <description>&lt;p&gt;Anyone who's worked with financial data knows that invoice processing is rarely as simple as it sounds.&lt;/p&gt;

&lt;p&gt;One client sends PDFs.&lt;/p&gt;

&lt;p&gt;Another sends old Excel spreadsheets.&lt;/p&gt;

&lt;p&gt;Someone else exports HTML reports from a legacy accounting system.&lt;/p&gt;

&lt;p&gt;Yet your accounting software, reporting dashboard, or Python script only wants one thing: a clean CSV file.&lt;/p&gt;

&lt;p&gt;That's where the real work begins.&lt;/p&gt;

&lt;p&gt;Why Invoice Parsing Is Harder Than It Looks&lt;/p&gt;

&lt;p&gt;Developers usually start by writing a parser.&lt;/p&gt;

&lt;p&gt;For PDFs, libraries like pdfplumber, camelot, or tabula-py are popular choices.&lt;/p&gt;

&lt;p&gt;They work well—until they don't.&lt;/p&gt;

&lt;p&gt;PDFs aren't designed for structured data extraction. Tables often contain:&lt;/p&gt;

&lt;p&gt;Misaligned columns&lt;br&gt;
Empty cells&lt;br&gt;
Broken rows&lt;br&gt;
Unexpected whitespace&lt;br&gt;
Missing headers&lt;/p&gt;

&lt;p&gt;A simple extraction frequently turns into a cleanup project.&lt;/p&gt;

&lt;p&gt;Here's a basic example using pdfplumber.&lt;/p&gt;

&lt;p&gt;import pdfplumber&lt;br&gt;
import pandas as pd&lt;/p&gt;

&lt;p&gt;with pdfplumber.open("invoice.pdf") as pdf:&lt;br&gt;
    page = pdf.pages[0]&lt;br&gt;
    table = page.extract_table()&lt;/p&gt;

&lt;p&gt;clean_rows = []&lt;/p&gt;

&lt;p&gt;for row in table:&lt;br&gt;
    clean_rows.append([&lt;br&gt;
        cell.strip() if cell else ""&lt;br&gt;
        for cell in row&lt;br&gt;
    ])&lt;/p&gt;

&lt;p&gt;df = pd.DataFrame(clean_rows)&lt;/p&gt;

&lt;p&gt;df.to_csv("output.csv", index=False)&lt;/p&gt;

&lt;p&gt;This works for one invoice.&lt;/p&gt;

&lt;p&gt;Now imagine processing 100 invoices from multiple clients.&lt;/p&gt;

&lt;p&gt;Suddenly you're maintaining different parsers for:&lt;/p&gt;

&lt;p&gt;PDF&lt;br&gt;
XLS&lt;br&gt;
XLSX&lt;br&gt;
HTML&lt;br&gt;
Different invoice layouts&lt;br&gt;
Various encodings&lt;/p&gt;

&lt;p&gt;Instead of analyzing financial data, you're debugging file formats.&lt;/p&gt;

&lt;p&gt;A Simpler Workflow&lt;/p&gt;

&lt;p&gt;For one-off imports and bulk migrations, I prefer converting everything into a consistent CSV format first.&lt;/p&gt;

&lt;p&gt;Once every file looks the same, the rest of the workflow becomes much easier.&lt;/p&gt;

&lt;p&gt;Recently I tested the Invoice to CSV Converter from SERPSpur, which supports PDF, XLS, XLSX, and HTML files without requiring custom parsing logic.&lt;/p&gt;

&lt;p&gt;Instead of spending time cleaning extracted tables, I received a structured CSV that was immediately usable.&lt;/p&gt;

&lt;p&gt;Working with the Converted Data&lt;/p&gt;

&lt;p&gt;Once the invoices are converted, you can focus on analysis instead of extraction.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;import pandas as pd&lt;/p&gt;

&lt;p&gt;df = pd.read_csv("converted_invoices.csv")&lt;/p&gt;

&lt;p&gt;df["Total"] = (&lt;br&gt;
    df["Total"]&lt;br&gt;
      .str.replace("$", "", regex=False)&lt;br&gt;
      .astype(float)&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;vendor_totals = (&lt;br&gt;
    df.groupby("Vendor")["Total"]&lt;br&gt;
      .sum()&lt;br&gt;
      .sort_values(ascending=False)&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;print(vendor_totals)&lt;/p&gt;

&lt;p&gt;Now you can build reports, automate bookkeeping, or import everything into your accounting system with minimal effort.&lt;/p&gt;

&lt;p&gt;Why This Approach Saves Time&lt;/p&gt;

&lt;p&gt;Converting invoices into a standardized CSV upfront has several advantages:&lt;/p&gt;

&lt;p&gt;One consistent format regardless of the original file type&lt;br&gt;
Less custom parsing code&lt;br&gt;
Fewer data-cleaning issues&lt;br&gt;
Easier automation with Pandas&lt;br&gt;
Faster imports into databases or accounting software&lt;/p&gt;

&lt;p&gt;For occasional migrations, this is often much more efficient than building a parser for every document format you'll encounter.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;Writing parsers can be fun.&lt;/p&gt;

&lt;p&gt;Cleaning messy invoice exports usually isn't.&lt;/p&gt;

&lt;p&gt;If your goal is simply to move data from invoices into a spreadsheet, database, or analytics pipeline, it's often better to normalize the files first and spend your time working with the data—not fixing extraction errors.&lt;/p&gt;

&lt;p&gt;For developers, that means fewer edge cases, cleaner code, and a workflow that's much easier to maintain.maintain.&lt;a href="https://serpspur.com/tool/invoice-pdf-to-csv-converter/" rel="noopener noreferrer"&gt;https://serpspur.com/tool/invoice-pdf-to-csv-converter/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Stop Missing Easy Backlinks: Find Competitor Link Opportunities with Backlink Gap Analysis 🔗📈</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Tue, 04 Aug 2026 06:37:12 +0000</pubDate>
      <link>https://dev.to/lucy-green/stop-missing-easy-backlinks-find-competitor-link-opportunities-with-backlink-gap-analysis-3ld5</link>
      <guid>https://dev.to/lucy-green/stop-missing-easy-backlinks-find-competitor-link-opportunities-with-backlink-gap-analysis-3ld5</guid>
      <description>&lt;p&gt;Ever feel like your competitors are getting all the good backlinks while you’re stuck scraping the bottom of the barrel? I’ve been there. You publish great content, but your domain authority crawls while theirs skyrockets. The secret isn’t always creating better content—it’s finding the links you’re missing.&lt;/p&gt;

&lt;p&gt;That’s where a &lt;strong&gt;backlink gap analysis&lt;/strong&gt; comes in. It’s the SEO equivalent of finding a cheat code. The idea is simple: find websites that link to your competitors but not to you. Those are low-hanging fruit—sites already interested in your niche.&lt;/p&gt;

&lt;p&gt;Here’s a quick, technical way to do this without burning hours in spreadsheets.&lt;/p&gt;

&lt;p&gt;First, you need a list of your main competitors. Let’s say you run a SaaS for project management. Your competitors might be Asana, Trello, and Monday.com.&lt;/p&gt;

&lt;p&gt;Next, you need a tool that exports link data. While many paid tools do this, I’ve been using a free backlink gap checker from SerpSpur (&lt;a href="https://serpspur.com/tool/backlink-gap/" rel="noopener noreferrer"&gt;https://serpspur.com/tool/backlink-gap/&lt;/a&gt;). It pulls the intersection of domains linking to them but not you.&lt;/p&gt;

&lt;p&gt;Here’s a sample of what the output logic looks like in Python if you were to build this yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="c1"&gt;# Pseudocode for fetching and comparing backlinks
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_backlinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# API call to your favorite index
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="c1"&gt;#039;source&amp;amp;#039;] for link in api_response)
&lt;/span&gt;
&lt;span class="n"&gt;competitors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="c1"&gt;#039;asana.com&amp;amp;#039;, &amp;amp;#039;trello.com&amp;amp;#039;, &amp;amp;#039;monday.com&amp;amp;#039;]
&lt;/span&gt;&lt;span class="n"&gt;my_domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="c1"&gt;#039;yourproject.com&amp;amp;#039;
&lt;/span&gt;
&lt;span class="n"&gt;my_links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_backlinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;gap_links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;comp&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;competitors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;comp_links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_backlinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gap_links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comp_links&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;my_links&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;Found&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gap_links&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="n"&gt;potential&lt;/span&gt; &lt;span class="n"&gt;prospects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have that list, filter it. Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sites with real traffic (check via SimilarWeb)&lt;/li&gt;
&lt;li&gt;Pages that are resource lists or "best tools" roundups&lt;/li&gt;
&lt;li&gt;Blogs that recently wrote about your niche&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, do the outreach. The key is to personalize. Don’t say, "I noticed you linked to Trello." Instead, say, "I saw your roundup on project management tools. I’ve built a comparison table that includes Trello and Asana, but I think adding [Your Tool] would give your readers a fresh perspective since we offer [unique feature]."&lt;/p&gt;

&lt;p&gt;That’s it. Rinse and repeat monthly. The gap shrinks, and your authority climbs.&lt;/p&gt;

&lt;p&gt;If you want to skip the coding, just use the tool I linked above—it does the heavy lifting and exports a CSV you can filter in Google Sheets. Happy hunting.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Uncover Hidden Internal Linking Issues with a Free SEO Audit Workflow 🔍</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Tue, 28 Jul 2026 06:18:26 +0000</pubDate>
      <link>https://dev.to/lucy-green/uncover-hidden-internal-linking-issues-with-a-free-seo-audit-workflow-49np</link>
      <guid>https://dev.to/lucy-green/uncover-hidden-internal-linking-issues-with-a-free-seo-audit-workflow-49np</guid>
      <description>&lt;p&gt;Tired of burning cash on expensive SEO suites just to run a simple competitor analysis? Me too. That’s why I built a lean workflow using the Chrome DevTools Network tab and a lightweight, free Python script to map out any page’s internal link structure—no paid subscriptions required.&lt;/p&gt;

&lt;p&gt;Most people don’t realize the DOM alone can be misleading. JavaScript frameworks often inject placeholder links that differ from the actual &lt;code&gt;href&lt;/code&gt; attributes rendered after hydration. The real source of truth? The network waterfall. When you scrape the &lt;code&gt;fetch/XHR&lt;/code&gt; requests from a live page, you get the raw data that drives the site’s navigation.&lt;/p&gt;

&lt;p&gt;Here’s a quick snippet to grab internal links from a page’s response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urljoin&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_internal_links&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="c1"&gt;#039;html.parser&amp;amp;#039;)
&lt;/span&gt;        &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt;
        &lt;span class="n"&gt;internal_links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a_tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="c1"&gt;#039;a&amp;amp;#039;, href=True):
&lt;/span&gt;            &lt;span class="n"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a_tag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="c1"&gt;#039;href&amp;amp;#039;]
&lt;/span&gt;            &lt;span class="n"&gt;full_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urljoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="c1"&gt;#039;http&amp;amp;#039;, &amp;amp;#039;https&amp;amp;#039;):
&lt;/span&gt;                &lt;span class="n"&gt;internal_links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;internal_links&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_internal_links&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;Found&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="n"&gt;internal&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;quot&lt;/span&gt;&lt;span class="p"&gt;;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script filters out external links, ads, and social media icons. But it still misses dynamic routes loaded via API calls. To catch those, you’d hook into the Network tab using a headless browser like Playwright—but that’s a tutorial for another day.&lt;/p&gt;

&lt;p&gt;Why does this matter? SEO audits often get stuck on homepage metrics. But internal link depth signals content priority to search engines. If your “Services” page is three clicks deep and only linked from a footer, it’s practically invisible.&lt;/p&gt;

&lt;p&gt;I’ve been using this approach to spot orphaned pages and broken breadcrumbs on client sites. If you want to scale this into a full crawl—including backlink gaps and keyword mapping—you’ll eventually need a tool that automates the heavy lifting. That’s where SerpSpur comes in as a solid Semrush alternative. It gives you the same site audit, backlink analyzer, and keyword tracking without the enterprise price tag.&lt;/p&gt;

&lt;p&gt;But for today, just run the script on your own site. You’ll be surprised what you find. Drop your results in the comments—I’d love to see what you uncover.&lt;/p&gt;

</description>
      <category>python</category>
      <category>seo</category>
      <category>tools</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Uncover High-Value Link Opportunities with Backlink Gap Analysis 🔗📈</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Thu, 23 Jul 2026 10:18:10 +0000</pubDate>
      <link>https://dev.to/lucy-green/uncover-high-value-link-opportunities-with-backlink-gap-analysis-4k0g</link>
      <guid>https://dev.to/lucy-green/uncover-high-value-link-opportunities-with-backlink-gap-analysis-4k0g</guid>
      <description>&lt;p&gt;Building backlinks is hard, but finding opportunities shouldn't be. I recently started using a backlink gap analysis tool, and it's been a game-changer.&lt;/p&gt;

&lt;p&gt;The idea is simple: you compare your domain with competitors and find websites that link to them but not to you. These are low-hanging fruit for outreach.&lt;/p&gt;

&lt;p&gt;Here's how I approach it. First, I enter my domain and up to five competitors into the tool. It scans their backlink profiles and highlights links unique to them.&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
// Example: Fetching gap data&lt;br&gt;
const apiUrl = "&lt;a href="https://serpspur.com/tool/backlink-gap/" rel="noopener noreferrer"&gt;https://serpspur.com/tool/backlink-gap/&lt;/a&gt;";&lt;br&gt;
const params = { domain: "mysite.com", competitors: ["comp1.com", "comp2.com"] };&lt;/p&gt;

&lt;p&gt;fetch(apiUrl, {&lt;br&gt;
    method: "POST",&lt;br&gt;
    body: JSON.stringify(params)&lt;br&gt;
})&lt;br&gt;
.then(response =&amp;gt; response.json())&lt;br&gt;
.then(data =&amp;gt; {&lt;br&gt;
    console.log("Missing links:", data.missing_links);&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;The output lists domains linking to competitors but not me. From there, I prioritize by domain authority and relevance. For each, I craft a personalized email explaining why my content is a better fit.&lt;/p&gt;

&lt;p&gt;This method has already earned me several high-quality backlinks. If you want to try it, the tool is free to use. Visit &lt;a href="https://serpspur.com" rel="noopener noreferrer"&gt;SERPSpur&lt;/a&gt; for more details.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2wmipaialr8q78yhbd91.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2wmipaialr8q78yhbd91.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title># How I Automated Search Engine Penalty Checks to Catch SEO Issues Before They Hurt Traffic</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Tue, 21 Jul 2026 04:59:26 +0000</pubDate>
      <link>https://dev.to/lucy-green/-how-i-automated-search-engine-penalty-checks-to-catch-seo-issues-before-they-hurt-traffic-2f55</link>
      <guid>https://dev.to/lucy-green/-how-i-automated-search-engine-penalty-checks-to-catch-seo-issues-before-they-hurt-traffic-2f55</guid>
      <description>&lt;p&gt;One of the most stressful moments for anyone running a website is waking up to a sudden drop in organic traffic.&lt;/p&gt;

&lt;p&gt;I've been there.&lt;/p&gt;

&lt;p&gt;My first instinct was to open Google Search Console, expecting to find a manual action or security warning. Everything looked normal. No errors. No notifications.&lt;/p&gt;

&lt;p&gt;After several hours of digging through logs and deployment history, I found the culprit: a &lt;code&gt;robots.txt&lt;/code&gt; change made during a site migration had unintentionally blocked search engines from crawling important pages.&lt;/p&gt;

&lt;p&gt;It was a small mistake with a big impact.&lt;/p&gt;

&lt;p&gt;That experience convinced me to stop relying on manual checks. Instead, I wanted something that would tell me immediately if a site's indexing status changed or if search engines started flagging problems.&lt;/p&gt;

&lt;p&gt;So I put together a small Python script that checks a domain's status using SERPSpur's Search Engine Penalty Radar API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DOMAIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_penalty_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.serpspur.com/v1/penalty-radar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;include_blacklists&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_penalty_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;indexed_pages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⚠️ Possible deindexing detected.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blacklisted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⚠️ Listed on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;blacklists&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; blacklist(s).&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script is intentionally simple, but it's enough to monitor a few signals that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Indexed page count&lt;/li&gt;
&lt;li&gt;Blacklist status&lt;/li&gt;
&lt;li&gt;Search engine penalty indicators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of running it manually, I scheduled it with cron so it executes every morning.&lt;br&gt;
&lt;a href="https://serpspur.com/tool/search-engine-penalty-radar/" rel="noopener noreferrer"&gt;https://serpspur.com/tool/search-engine-penalty-radar/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh1lykrvyfij8gby16o6y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh1lykrvyfij8gby16o6y.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 8 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /usr/bin/python3 /home/user/check_penalty.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If something changes, I get notified immediately.&lt;/p&gt;

&lt;p&gt;The biggest advantage isn't automation—it's shortening the feedback loop.&lt;/p&gt;

&lt;p&gt;Without monitoring, you might not notice an indexing issue until rankings have already dropped. With an automated check, you can often spot the problem within hours.&lt;/p&gt;

&lt;p&gt;I also like combining this with server log analysis.&lt;/p&gt;

&lt;p&gt;If Googlebot activity suddenly disappears while indexed pages start dropping, it's usually a strong signal that something needs attention. Sometimes it's a deployment issue. Sometimes it's an accidental &lt;code&gt;noindex&lt;/code&gt; tag. Sometimes it's a server configuration problem.&lt;/p&gt;

&lt;p&gt;Either way, having multiple data points makes debugging much easier than relying on traffic reports alone.&lt;/p&gt;

&lt;p&gt;This isn't meant to replace Google Search Console or other SEO tools. It's simply another layer of monitoring that has saved me more than once.&lt;/p&gt;

&lt;p&gt;If you manage several websites, automating these kinds of health checks is one of those small improvements that pays for itself the first time it catches a problem before your rankings do.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Simplify Keyword Research with Search Volume, CPC &amp; Difficulty</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Tue, 07 Jul 2026 09:34:28 +0000</pubDate>
      <link>https://dev.to/lucy-green/simplify-keyword-research-with-search-volume-cpc-difficulty-99n</link>
      <guid>https://dev.to/lucy-green/simplify-keyword-research-with-search-volume-cpc-difficulty-99n</guid>
      <description>&lt;p&gt;Keyword research is the backbone of any SEO strategy. But with so many metrics—search volume, CPC, keyword difficulty, ads competition—it can get overwhelming. The Keyword Research Tool simplifies this by providing all these metrics in one place, filtered by country. For example, you can quickly compare 'best coffee maker' across the US and UK to see where the opportunity lies. Here's a quick Python script to fetch keyword data programmatically:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft9w2avhpdjma40mgvpo7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft9w2avhpdjma40mgvpo7.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
python&lt;br&gt;
import requests&lt;/p&gt;

&lt;p&gt;url = '&lt;a href="https://serpspur.com/tool/keyword-research-tool/" rel="noopener noreferrer"&gt;https://serpspur.com/tool/keyword-research-tool/&lt;/a&gt;'&lt;br&gt;
params = {'keyword': 'best coffee maker', 'country': 'US'}&lt;br&gt;
response = requests.get(url, params=params)&lt;br&gt;
data = response.json()&lt;br&gt;
print(f'Search Volume: {data["search_volume"]}, Difficulty: {data["difficulty"]}')&lt;/p&gt;

&lt;p&gt;Check it out at &lt;a href="https://serpspur.com/tool/keyword-research-tool/" rel="noopener noreferrer"&gt;https://serpspur.com/tool/keyword-research-tool/&lt;/a&gt; to refine your keyword strategy.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
