<?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>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>
    <item>
      <title>How I Use Competitor Traffic Analysis to Discover New SEO Opportunities</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Mon, 06 Jul 2026 06:47:08 +0000</pubDate>
      <link>https://dev.to/lucy-green/how-i-use-competitor-traffic-analysis-to-discover-new-seo-opportunities-42hl</link>
      <guid>https://dev.to/lucy-green/how-i-use-competitor-traffic-analysis-to-discover-new-seo-opportunities-42hl</guid>
      <description>&lt;p&gt;If you're serious about SEO, you need to know what your competitors are doing. The SERPSpur Traffic &amp;amp; Competitor Explorer makes it easy to analyze website traffic, organic keywords, and competitor performance across different search markets. I wrote a small PHP script to automate competitor analysis:&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%2Flcq4477kaae5pqjcbltc.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%2Flcq4477kaae5pqjcbltc.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This helps me identify which keywords I'm missing and where to focus my efforts. Head over to &lt;a href="https://serpspur.com" rel="noopener noreferrer"&gt;https://serpspur.com&lt;/a&gt; to start exploring.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I Verify Website Traffic Before Buying a Domain or Building Backlinks</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Fri, 03 Jul 2026 05:59:31 +0000</pubDate>
      <link>https://dev.to/lucy-green/how-i-verify-website-traffic-before-buying-a-domain-or-building-backlinks-35do</link>
      <guid>https://dev.to/lucy-green/how-i-verify-website-traffic-before-buying-a-domain-or-building-backlinks-35do</guid>
      <description>&lt;p&gt;Before buying a domain on Flippa or investing in link building, I always check traffic authenticity. Fake bot traffic can ruin your ROI. I built a simple Python script to analyze user-agent patterns, but it's not enough. That's why I use the SERPSpur Bot Traffic Detector—it identifies fake bot and low-quality traffic sources, giving you confidence in your investment.&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
import re&lt;/p&gt;

&lt;p&gt;def detect_bot_traffic(log_entry):&lt;br&gt;
    bot_patterns = [r'bot', r'crawler', r'spider', r'scraper']&lt;br&gt;
    user_agent = log_entry.get('user_agent', '')&lt;br&gt;
    for pattern in bot_patterns:&lt;br&gt;
        if re.search(pattern, user_agent, re.IGNORECASE):&lt;br&gt;
            return True&lt;br&gt;
    return False&lt;/p&gt;

&lt;h1&gt;
  
  
  Example log entry
&lt;/h1&gt;

&lt;p&gt;log = {'user_agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; +&lt;a href="http://www.google.com/bot.html)'" rel="noopener noreferrer"&gt;http://www.google.com/bot.html)'&lt;/a&gt;}&lt;br&gt;
print('Bot detected' if detect_bot_traffic(log) else 'Human traffic')&lt;/p&gt;

&lt;p&gt;This snippet is a starting point, but the tool does a triple-signal audit for accuracy. Check it out: &lt;a href="https://serpspur.com/tool/bot-traffic-detector/" rel="noopener noreferrer"&gt;https://serpspur.com/tool/bot-traffic-detector/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Automating SEO Audits with Python (and Taking Them Further)</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Sat, 27 Jun 2026 04:37:56 +0000</pubDate>
      <link>https://dev.to/lucy-green/automating-seo-audits-with-python-and-taking-them-further-20fm</link>
      <guid>https://dev.to/lucy-green/automating-seo-audits-with-python-and-taking-them-further-20fm</guid>
      <description>&lt;p&gt;If you've ever managed more than a handful of websites, you've probably spent far too much time checking the same things over and over again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the title tag missing?&lt;/li&gt;
&lt;li&gt;Does every page have a meta description?&lt;/li&gt;
&lt;li&gt;Are images missing &lt;code&gt;alt&lt;/code&gt; attributes?&lt;/li&gt;
&lt;li&gt;Is there exactly one &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Are canonical tags configured correctly?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Doing this manually gets old fast.&lt;/p&gt;

&lt;p&gt;I wanted a lightweight way to catch the obvious issues before running a full SEO audit, so I put together a simple Python script that scans a page and reports common on-page problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the Dependencies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests beautifulsoup4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Basic SEO Audit Script
&lt;/h2&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;audit_page&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;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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing title tag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&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;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Title exceeds recommended length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&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&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&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;name&lt;/span&gt;&lt;span class="sh"&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;description&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;description&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing meta description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;h1_tags&lt;/span&gt; &lt;span class="o"&gt;=&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h1&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1_tags&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="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing H1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&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;h1_tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Multiple H1 tags detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;img&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;missing_alt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;img&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src&lt;/span&gt;&lt;span class="sh"&gt;"&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;img&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;img&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alt&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;missing_alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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="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;missing_alt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; images missing alt text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;canonical&lt;/span&gt; &lt;span class="o"&gt;=&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&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;canonical&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="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing canonical tag&lt;/span&gt;&lt;span class="sh"&gt;"&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;issues&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://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;audit_page&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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;issue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For quick technical checks, this script works surprisingly well.&lt;/p&gt;

&lt;p&gt;But after using it for client projects, I realized something important:&lt;/p&gt;

&lt;p&gt;An on-page audit is only one piece of the puzzle.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO Doesn't Stop at HTML
&lt;/h2&gt;

&lt;p&gt;Even when every page has perfect metadata, rankings can still suffer because of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weak backlink profiles&lt;/li&gt;
&lt;li&gt;Poor keyword targeting&lt;/li&gt;
&lt;li&gt;Slow Core Web Vitals&lt;/li&gt;
&lt;li&gt;Indexing issues&lt;/li&gt;
&lt;li&gt;Broken internal links&lt;/li&gt;
&lt;li&gt;Missing structured data&lt;/li&gt;
&lt;li&gt;Competitor content gaps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Checking all of those manually quickly becomes overwhelming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending the Workflow
&lt;/h2&gt;

&lt;p&gt;Instead of writing separate scripts for every SEO task, I now use this script as the first validation step and then follow it with a more comprehensive audit using &lt;strong&gt;SERPSpur&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It gives me additional insights such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On-page SEO analysis&lt;/li&gt;
&lt;li&gt;Backlink reports&lt;/li&gt;
&lt;li&gt;Keyword research&lt;/li&gt;
&lt;li&gt;Competitor comparisons&lt;/li&gt;
&lt;li&gt;Domain authority metrics&lt;/li&gt;
&lt;li&gt;Rank tracking&lt;/li&gt;
&lt;li&gt;Technical SEO diagnostics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means I can identify both HTML-level issues and broader SEO problems from a single workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ideas for Improvements
&lt;/h2&gt;

&lt;p&gt;If you're extending this script, here are a few useful additions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate Open Graph tags&lt;/li&gt;
&lt;li&gt;Check Twitter Card metadata&lt;/li&gt;
&lt;li&gt;Detect broken internal links&lt;/li&gt;
&lt;li&gt;Verify structured data (JSON-LD)&lt;/li&gt;
&lt;li&gt;Audit robots meta directives&lt;/li&gt;
&lt;li&gt;Measure page response time&lt;/li&gt;
&lt;li&gt;Detect duplicate meta descriptions&lt;/li&gt;
&lt;li&gt;Export results as CSV&lt;/li&gt;
&lt;li&gt;Crawl an entire sitemap&lt;/li&gt;
&lt;li&gt;Generate HTML reports&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Automation won't replace a proper SEO strategy, but it can eliminate a lot of repetitive work.&lt;/p&gt;

&lt;p&gt;A simple Python script is perfect for catching common on-page issues before they become bigger problems. Pairing it with a comprehensive SEO platform lets you move beyond HTML validation and understand how your site performs in search.&lt;/p&gt;

&lt;p&gt;Whether you're maintaining your own projects or auditing client websites, combining lightweight automation with a full SEO toolkit can save hours every week.&lt;/p&gt;

&lt;p&gt;If you're looking for an all-in-one SEO platform to complement your own scripts, take a look at &lt;strong&gt;SERPSpur&lt;/strong&gt;: &lt;a href="https://serpspur.com" rel="noopener noreferrer"&gt;https://serpspur.com&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Backlink Gap Analysis: The Smartest Way to Find High-Quality Link Opportunities</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Wed, 24 Jun 2026 05:47:35 +0000</pubDate>
      <link>https://dev.to/lucy-green/backlink-gap-analysis-the-smartest-way-to-find-high-quality-link-opportunities-4el9</link>
      <guid>https://dev.to/lucy-green/backlink-gap-analysis-the-smartest-way-to-find-high-quality-link-opportunities-4el9</guid>
      <description>&lt;p&gt;Backlink gaps are one of the easiest ways to find new link-building opportunities. The concept is simple: find sites that link to your competitors but not to you. Those are potential targets for outreach.&lt;/p&gt;

&lt;p&gt;I've used this method for years. It's more efficient than cold pitching random sites because you know these sites already link to similar content.&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%2F5e39l6q8ki8mwxs80auh.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%2F5e39l6q8ki8mwxs80auh.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Here's a basic Python script to find backlink gaps using a hypothetical API:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
import requests&lt;/p&gt;

&lt;p&gt;def find_backlink_gaps(your_domain, competitor_domain):&lt;br&gt;
    # Pseudocode—replace with actual API calls&lt;br&gt;
    your_backlinks = get_backlinks(your_domain)&lt;br&gt;
    competitor_backlinks = get_backlinks(competitor_domain)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gaps = set(competitor_backlinks) - set(your_backlinks)
return gaps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;gaps = find_backlink_gaps("yoursite.com", "competitor.com")&lt;br&gt;
print(f"Found {len(gaps)} backlink opportunities")&lt;/p&gt;

&lt;p&gt;But scraping backlink data from scratch is complex. You need a reliable index. That's where a tool like SERPSpur's Backlink Gap Analysis Tool comes in. It compares your domain against competitors and lists all the sites linking to them but not to you.&lt;/p&gt;

&lt;p&gt;Once you have that list, outreach becomes targeted. You know these sites are relevant and already link to similar content. Just craft a personalized pitch showing why your resource adds value.&lt;/p&gt;

&lt;p&gt;This approach consistently yields high-quality backlinks because the linkers are pre-qualified.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>PageRank May Be Dead, But Its Ghost Still Haunts SEO</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Tue, 23 Jun 2026 13:52:12 +0000</pubDate>
      <link>https://dev.to/lucy-green/pagerank-may-be-dead-but-its-ghost-still-haunts-seo-2238</link>
      <guid>https://dev.to/lucy-green/pagerank-may-be-dead-but-its-ghost-still-haunts-seo-2238</guid>
      <description>&lt;p&gt;Ever tried explaining PageRank to a new SEO? They think it's a dinosaur. Dead. Buried. But here's the thing—legacy data still tells a story. I was auditing an old client site recently, and the historical PageRank score was a ghost in the machine. It wasn't active, but the trust signals from back then were still echoing in their current backlink profile.&lt;/p&gt;

&lt;p&gt;Here's a quick Python snippet to scrape the Wayback Machine for old PageRank data (if you have an API key for the deprecated Google Toolbar API, or you're using a fallback like Open PageRank):&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_legacy_pagerank&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;# This is a simplified example using Open PageRank (free tier)
&lt;/span&gt;    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&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;https://openpagerank.com/api/v1.0/getPageRank?domains[]=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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;API-OPR&lt;/span&gt;&lt;span class="sh"&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;your_api_key_here&lt;/span&gt;&lt;span class="sh"&gt;"&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;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&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;json&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;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page_rank_decimal&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="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="n"&gt;legacy_rank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_legacy_pagerank&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="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;Legacy PageRank for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;legacy_rank&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;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fyd2abgajnsh6aqcv44xu.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%2Fyd2abgajnsh6aqcv44xu.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
But here's the catch—raw PageRank is just a number. It doesn't tell you if the trust is real or if it's from a link farm in 2008. That's where a modern trust metric comes in. I started cross-referencing these legacy scores with a real-time trust rate. For example, on SERPSpur, you can check the Trust Rate against historical PageRank. It's like having a time machine and a truth serum.&lt;/p&gt;

&lt;p&gt;The math is simple: if a domain had a high PageRank in 2013 but a low Trust Rate now, it's probably been penalized or sold. If both are high, you've got a gem. I use this to filter out expired domains for side projects. No more wasting time on "authority" that's just a ghost.&lt;/p&gt;

&lt;p&gt;Try it yourself. Grab your legacy PageRank data, then run it through a live trust checker. The difference is often shocking. And remember—data without context is just noise.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Security Doesn't Have to Be Ugly: Why I Chose Vintage-Style Rim Locks for My Front Door</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Fri, 12 Jun 2026 09:37:03 +0000</pubDate>
      <link>https://dev.to/lucy-green/security-doesnt-have-to-be-ugly-why-i-chose-vintage-style-rim-locks-for-my-front-door-3g3c</link>
      <guid>https://dev.to/lucy-green/security-doesnt-have-to-be-ugly-why-i-chose-vintage-style-rim-locks-for-my-front-door-3g3c</guid>
      <description>&lt;p&gt;When renovating an older property, one of the biggest challenges is finding hardware that provides modern security without compromising the character of the home. Many contemporary locks prioritize function over appearance, often looking out of place on traditional doors. Fortunately, I recently discovered a solution that delivers both style and security: vintage-style rim locks.&lt;/p&gt;

&lt;p&gt;After searching through countless options, I came across the rim lock collection from Infinity Decor. The combination of classic design, solid construction, and practical installation made them an easy choice for our front door renovation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Rim Locks Are Ideal for Older Properties&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many period homes feature original wooden doors that were never designed to accommodate modern mortice locks. Installing a mortice lock often requires cutting deep into the door, which can weaken the structure and potentially damage historic woodwork.&lt;/p&gt;

&lt;p&gt;Rim locks offer a practical alternative. Because they are surface-mounted, they require far less modification to the door itself. This makes them particularly suitable for:&lt;/p&gt;

&lt;p&gt;Victorian properties&lt;br&gt;
Georgian homes&lt;br&gt;
Edwardian houses&lt;br&gt;
Cottage renovations&lt;br&gt;
Heritage restoration projects&lt;/p&gt;

&lt;p&gt;For homeowners looking to preserve original features while improving security, rim locks provide an excellent balance between practicality and authenticity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Appeal of Vintage Brass Finishes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the first things that caught my attention was the beautiful brass finish. Hardware may seem like a small detail, but it can significantly influence the overall appearance of a property.&lt;/p&gt;

&lt;p&gt;The warm brass tones complement traditional wood doors perfectly, creating a timeless look that feels authentic rather than decorative. Instead of standing out as a modern addition, the lock feels like it belongs to the property.&lt;/p&gt;

&lt;p&gt;The result is a front entrance that looks elegant, welcoming, and historically appropriate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security You Can Rely On&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While aesthetics were important, security remained the top priority.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://infinitydecor.co.uk/collections/rim-locks" rel="noopener noreferrer"&gt;rim lock&lt;/a&gt;&lt;/strong&gt; I selected includes a robust deadlock mechanism that provides confidence and peace of mind. It offers the reassuring feel of quality engineering while maintaining the traditional appearance that many homeowners seek.&lt;/p&gt;

&lt;p&gt;Unlike some decorative hardware that focuses solely on looks, these locks are designed to perform as well as they look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Straightforward Installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another pleasant surprise was how simple the installation process turned out to be.&lt;/p&gt;

&lt;p&gt;The included hardware and clear fitting points meant the lock could be installed without extensive carpentry work. Since rim locks are mounted onto the surface of the door, the process is often less invasive than fitting a mortice lock.&lt;/p&gt;

&lt;p&gt;For DIY enthusiasts, this can make the project far more approachable while reducing the risk of damaging an older door.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating a Cohesive Look&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One tip I would strongly recommend is pairing the lock with matching door furniture.&lt;/p&gt;

&lt;p&gt;Coordinating the rim lock with brass handles, escutcheons, and other hardware creates a unified appearance that elevates the entire entrance. Small details like these can transform a simple door into a standout architectural feature.&lt;/p&gt;

&lt;p&gt;Consistency in finishes and styling helps maintain the character of a period property while giving it a polished, professional appearance.&lt;/p&gt;

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

&lt;p&gt;Home security doesn't have to come at the expense of style. For anyone renovating an older property, vintage-style rim locks offer an attractive solution that combines classic aesthetics with dependable functionality.&lt;/p&gt;

&lt;p&gt;The brass finish adds charm and authenticity, the deadlock mechanism provides reliable protection, and the straightforward installation makes them suitable for both professionals and confident DIY homeowners.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Anyone Else Seeing Big Ranking Differences Between Mobile and Desktop?</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Fri, 12 Jun 2026 07:47:44 +0000</pubDate>
      <link>https://dev.to/lucy-green/anyone-else-seeing-big-ranking-differences-between-mobile-and-desktop-1e85</link>
      <guid>https://dev.to/lucy-green/anyone-else-seeing-big-ranking-differences-between-mobile-and-desktop-1e85</guid>
      <description>&lt;p&gt;Tracking keyword rankings across different devices and locations is crucial for understanding your SEO performance. I built this small script using the SerpSpur Live Search Engine Ranking Checker API to compare rankings by device type:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
import requests&lt;/p&gt;

&lt;p&gt;API_KEY = "your_api_key_here"&lt;/p&gt;

&lt;p&gt;def compare_rankings_by_device(keyword, location):&lt;br&gt;
    results = {}&lt;br&gt;
    for device in ["desktop", "mobile", "tablet"]:&lt;br&gt;
        response = requests.get(&lt;br&gt;
            "&lt;a href="https://api.serpspur.com/v1/live-ranking-checker" rel="noopener noreferrer"&gt;https://api.serpspur.com/v1/live-ranking-checker&lt;/a&gt;",&lt;br&gt;
            headers={"Authorization": f"Bearer {API_KEY}"},&lt;br&gt;
            params={"q": keyword, "location": location, "device": device, "num": 10}&lt;br&gt;
        )&lt;br&gt;
        data = response.json()&lt;br&gt;
        results[device] = [r["position"] for r in data.get("organic_results", [])]&lt;br&gt;
    return results&lt;/p&gt;

&lt;h1&gt;
  
  
  Example usage
&lt;/h1&gt;

&lt;p&gt;keyword = "SEO tools"&lt;br&gt;
location = "United States"&lt;br&gt;
rankings = compare_rankings_by_device(keyword, location)&lt;br&gt;
for device, positions in rankings.items():&lt;br&gt;
    print(f"{device.upper()}: {positions[:5]}...")&lt;/p&gt;

&lt;p&gt;This revealed some surprising differences—mobile rankings often vary significantly from desktop. Have you noticed similar patterns in your own tracking?&lt;/p&gt;

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