<?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>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>
    <item>
      <title>Automating Page Speed Audits: Python Script for Core Web Vitals Monitoring</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Thu, 04 Jun 2026 07:01:45 +0000</pubDate>
      <link>https://dev.to/lucy-green/automating-page-speed-audits-python-script-for-core-web-vitals-monitoring-3hpa</link>
      <guid>https://dev.to/lucy-green/automating-page-speed-audits-python-script-for-core-web-vitals-monitoring-3hpa</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2F8phy015n1n5t5zv84zti.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.amazonaws.com%2Fuploads%2Farticles%2F8phy015n1n5t5zv84zti.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;Just automated my weekly page speed audit with a Python script that pulls Core Web Vitals data and flags regressions before they hit production. No more surprise Lighthouse drops on Monday mornings.&lt;/p&gt;

&lt;p&gt;Here's the core loop:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
import requests&lt;br&gt;
import json&lt;br&gt;
from datetime import datetime&lt;/p&gt;

&lt;p&gt;for url in urls:&lt;br&gt;
    response = requests.get&lt;br&gt;
    data = response.json()&lt;br&gt;
    if data['lcp'] &amp;gt; 2500 or data['cls'] &amp;gt; 0.1:&lt;br&gt;
        print(f"ALERT: {url} has poor vitals - LCP: {data['lcp']}ms, CLS: {data['cls']}")&lt;/p&gt;

&lt;p&gt;It checks LCP, FID, and CLS against Google's thresholds. If any metric exceeds the limit, it logs the URL with a timestamp. I've got it running as a cron job every 6 hours.&lt;/p&gt;

&lt;p&gt;The biggest win? Catching a third-party widget that bloated my TBT from 50ms to 350ms after an update. Rolled back within the hour.&lt;/p&gt;

&lt;p&gt;This approach works with any API that exposes raw Web Vitals data. I'm using &lt;strong&gt;&lt;a href="http://serpspur.com/tool/core-web-vitals-speed-forensics/" rel="noopener noreferrer"&gt;SERPSpur&lt;/a&gt;&lt;/strong&gt;'s Speed Forensics endpoint because it also gives me field data from Chrome UX Report, not just lab metrics.&lt;/p&gt;

&lt;p&gt;Anyone else automating performance monitoring? What thresholds do you set for alerts?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Testing Geo-Targeted Google Search Results Without a VPN Using Puppeteer</title>
      <dc:creator>Lucy Green</dc:creator>
      <pubDate>Mon, 01 Jun 2026 07:10:29 +0000</pubDate>
      <link>https://dev.to/lucy-green/testing-geo-targeted-google-search-results-without-a-vpn-using-puppeteer-26kl</link>
      <guid>https://dev.to/lucy-green/testing-geo-targeted-google-search-results-without-a-vpn-using-puppeteer-26kl</guid>
      <description>&lt;p&gt;Just discovered a clever way to bypass Google's location redirect when testing local search results. Instead of relying on VPNs, I modify the &lt;code&gt;navigator.geolocation&lt;/code&gt; API in a headless browser. Here's a Puppeteer snippet:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
const puppeteer = require('puppeteer');&lt;/p&gt;

&lt;p&gt;async function run() {&lt;br&gt;
    const browser = await puppeteer.launch();&lt;br&gt;
    const page = await browser.newPage();&lt;br&gt;
    await page.goto;&lt;br&gt;
    await page.evaluate(() =&amp;gt; {&lt;br&gt;
        navigator.geolocation.getCurrentPosition = (success) =&amp;gt; {&lt;br&gt;
            success({ coords: { latitude: 40.7128, longitude: -74.0060 } });&lt;br&gt;
        };&lt;br&gt;
    });&lt;br&gt;
    // Now search for 'coffee shops' and see local results for NYC&lt;br&gt;
}&lt;br&gt;
run();&lt;/p&gt;

&lt;p&gt;For production testing, I've been using SERPSpur's API which handles location spoofing natively. What's your most creative method for testing geo-targeted search results?&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>spanish</category>
    </item>
  </channel>
</rss>
