<?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: Rafael Romero</title>
    <description>The latest articles on DEV Community by Rafael Romero (@rafajrg21).</description>
    <link>https://dev.to/rafajrg21</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F60601%2F8d4e424e-2f6f-499b-a330-2f4d3bee7e0d.jpg</url>
      <title>DEV Community: Rafael Romero</title>
      <link>https://dev.to/rafajrg21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rafajrg21"/>
    <language>en</language>
    <item>
      <title>From Just a Scanner to a Smart Agent: How I Improved my SEO Prospecting Tool 🐍</title>
      <dc:creator>Rafael Romero</dc:creator>
      <pubDate>Fri, 28 Nov 2025 16:16:15 +0000</pubDate>
      <link>https://dev.to/rafajrg21/from-just-a-scanner-to-a-smart-agent-how-i-improved-my-seo-prospecting-tool-3eb3</link>
      <guid>https://dev.to/rafajrg21/from-just-a-scanner-to-a-smart-agent-how-i-improved-my-seo-prospecting-tool-3eb3</guid>
      <description>&lt;p&gt;I recently built a prospecting agent with Python to find local businesses on Google’s lower-ranked pages and pitch them SEO services.&lt;/p&gt;

&lt;p&gt;The initial version was... &lt;strong&gt;promising but flawed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It tried to pitch &lt;strong&gt;Indeed.com&lt;/strong&gt; because they didn't have a local phone number. It told &lt;strong&gt;Ford Dealerships&lt;/strong&gt; their site was "down" because their firewall blocked my bot. It sent robotic emails starting with &lt;code&gt;"Fail: H1 Missing"&lt;/code&gt; ... not exactly a charming opener.&lt;/p&gt;

&lt;p&gt;I realized that to make this tool useful, I needed to move from a &lt;em&gt;simple scraper&lt;/em&gt; to a true &lt;em&gt;agent&lt;/em&gt;. Here is the breakdown of how I refactored the code to filter noise, crawl for contacts, and use GenAI to write personalized campaigns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Filtering the Noise (The "No-Go" List)
&lt;/h2&gt;

&lt;p&gt;The first problem with scraping generic keywords is that half the results aren't businesses, they are directories, job boards, and government sites. My script was wasting resources auditing &lt;code&gt;ZipRecruiter&lt;/code&gt; and &lt;code&gt;Texas.gov&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
I made the &lt;code&gt;clean_and_deduplicate&lt;/code&gt; function even more robust with a strict blocklist. I expanded the existing blocklist significantly. We categorized domains into "Job Boards," "Government," "Social Media," and "National Brands" (like Penske) that wouldn't hire a local agency anyway.&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="c1"&gt;# We filter these out before we even attempt an audit
&lt;/span&gt;&lt;span class="n"&gt;DIRECTORY_DOMAINS&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;indeed&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;glassdoor&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;ziprecruiter&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Job Boards
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.gov&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;texas.gov&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;fmcsa&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# Gov sites
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yelp&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;yellowpages&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;bbb.org&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# Directories
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;penske&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;uhaul&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;ford.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;          &lt;span class="c1"&gt;# National Brands
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_directory&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;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="ow"&gt;in&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;lower&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;domain&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;DIRECTORY_DOMAINS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Result: The list went from ~200 "leads" to ~70 actual local businesses.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Smarter On-Page Auditing
&lt;/h2&gt;

&lt;p&gt;My original script checked for H1 tags using exact string matching. If the keyword was &lt;code&gt;diesel mechanic&lt;/code&gt; and the H1 was &lt;code&gt;Best Diesel Mechanic in Texas&lt;/code&gt;, the script marked it as a &lt;strong&gt;FAIL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix: Fuzzy Logic&lt;/strong&gt;&lt;br&gt;
I switched to token-based set matching. If the H1 contains a significant percentage of the target keywords (over 50%), it passes.&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="c1"&gt;# Breaking strings into sets of words for flexible matching
&lt;/span&gt;&lt;span class="n"&gt;required_words&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="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;found_words&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="n"&gt;h1_text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;# Calculate intersection
&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;required_words&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found_words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;match_percentage&lt;/span&gt; &lt;span class="o"&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;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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;required_words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# If &amp;gt;50% overlap, it's a Pass.
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;match_percentage&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;audit_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;H1_Audit_Result&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Distinguishing "Broken" from "Blocked"
&lt;/h2&gt;

&lt;p&gt;Originally, if a site returned a &lt;code&gt;403 Forbidden&lt;/code&gt;, my script flagged it as "Actionable: Server Error." Pitching a client saying "Your site is down" when it's actually just secure is a great way to look incompetent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix: Handling Firewalls&lt;/strong&gt;&lt;br&gt;
I updated the requests logic to explicitly catch &lt;code&gt;403&lt;/code&gt; and &lt;code&gt;406&lt;/code&gt; errors and mark them as &lt;code&gt;SKIP&lt;/code&gt;. Now, the agent only flags &lt;em&gt;genuine&lt;/em&gt; connection errors (like &lt;code&gt;500&lt;/code&gt; or &lt;code&gt;SSLError&lt;/code&gt;) as actionable leads.&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="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;RequestException&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="c1"&gt;# If the server explicitly blocked us (Firewall/WAF), it's not a lead.
&lt;/span&gt;    &lt;span class="k"&gt;if&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;status_code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;406&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;audit_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;Error_Status&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Blocked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; 
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;audit_data&lt;/span&gt;  &lt;span class="c1"&gt;# Stop processing, we will filter this out later
&lt;/span&gt;
    &lt;span class="c1"&gt;# Real connection errors (DNS failure, Timeout) are actual leads 
&lt;/span&gt;    &lt;span class="c1"&gt;# We want to pitch "Site Health" services to these.
&lt;/span&gt;    &lt;span class="n"&gt;audit_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;Error_Status&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__class__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: The "Gap Analysis"
&lt;/h2&gt;

&lt;p&gt;This was the strategic game-changer. A site with a missing H1 tag isn't necessarily a good lead. But a business with &lt;strong&gt;50 five-star reviews&lt;/strong&gt; and a missing H1 tag? &lt;strong&gt;That is a gold mine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I integrated a secondary API call to fetch the &lt;strong&gt;Google Business Profile (GBP)&lt;/strong&gt; ratings for every prospect to identify "Hidden Gems": businesses with great real-world reputations but poor digital presence.&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="c1"&gt;# We categorize the lead before generating the pitch
&lt;/span&gt;&lt;span class="n"&gt;is_gbp_strong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gbp_rating&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;gbp_reviews&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;is_gbp_missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gbp_rating&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# Strategy A: Strong GBP + Weak Site = "Your site hurts your reputation"
# Strategy B: No GBP + Weak Site = "You are invisible"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: The "Missing Link" - The Crawler
&lt;/h2&gt;

&lt;p&gt;At this point, I had great prospects, but I was missing the most important piece of data: &lt;strong&gt;The Email Address.&lt;/strong&gt; Many local businesses don't put their email in the Header; they hide it on the "Contact Us" page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix: The Spider Logic&lt;/strong&gt;&lt;br&gt;
I upgraded the agent to act like a human user:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Scan Home:&lt;/strong&gt; Look for &lt;code&gt;mailto:&lt;/code&gt; links or regex matches.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Heuristic Scoring:&lt;/strong&gt; If no email is found, scan all links and score them. &lt;code&gt;/contact-us&lt;/code&gt; gets 100 points, &lt;code&gt;/about&lt;/code&gt; gets 30 points.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Hop:&lt;/strong&gt; The agent navigates to the highest-scoring URL and scrapes that page.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_best_contact_url&lt;/span&gt;&lt;span class="p"&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;base_url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Heuristic Scoring Logic
&lt;/span&gt;    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;contact&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;url_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;contact&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;link_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;link_is_in_footer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="c1"&gt;# Returns the URL with the highest score to crawl next
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;best_candidate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This logic alone saved ~40% of leads that would have otherwise been discarded as "No Contact Info."&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: From Template to GenAI (Gemini Integration)
&lt;/h2&gt;

&lt;p&gt;Finally, I tackled the outreach itself. My previous email template was rigid and impersonal. I wanted a 3-email sequence that felt human.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix: Google Gemini 2.5 Flash&lt;/strong&gt;&lt;br&gt;
I integrated the Gemini API (which is proven to be fast and cost-efficient). Instead of using a fixed string, I feed the Audit Data + GBP Data into a prompt.&lt;/p&gt;

&lt;p&gt;The AI generates a 3-stage campaign:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Email 1:&lt;/strong&gt; The Hook (Referencing their specific Reputation vs. Site Gap).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Email 2:&lt;/strong&gt; The Value (Educational content about the error found).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Email 3:&lt;/strong&gt; The Breakup (Professional closing).
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Feeding the Gap Strategy into the LLM
&lt;/span&gt;&lt;span class="n"&gt;prompt&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;
    PROSPECT: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Rating: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gbp_rating&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; stars.
    ISSUES: H1: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h1_status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, NAP: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nap_status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

    STRATEGY:
    1. If rating &amp;gt; 4.0, praise reputation but warn about site errors.
    2. Explain WHY &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h1_status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; kills rankings.
    3. Gentle breakup.

    OUTPUT FORMAT: JSON {{ &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject_1&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="s"&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="s"&gt;body_1&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="s"&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="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&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;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;The agent now runs autonomously. It scans SERPs, filters junk, crawls for emails across multiple pages, and uses LLMs to write custom campaigns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Metrics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Raw Scrape:&lt;/strong&gt; 200 URLs&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;After Cleaning Directories:&lt;/strong&gt; 70 Businesses&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Actionable Leads (With Emails):&lt;/strong&gt; ~30 High-Quality Prospects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway:&lt;/strong&gt; When developing an Agent or any tool in general, &lt;strong&gt;Iteration is King&lt;/strong&gt;. You have to be able to know what you currently have and what's missing to reach that optimal output. In my case, the difference between "just a script" and an "agent" is the ability to handle imperfection, hopping pages when data is missing, understanding context, and generating dynamic output. This project has become something I look forward to working on and the most exciting part is that there's still room to grow. &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🔗 Check out the Code:&lt;/strong&gt;&lt;br&gt;
You can find the full source code and contribute to the project on GitHub:&lt;br&gt;
&lt;a href="https://github.com/Rafa-romero-dev/seo-agent" rel="noopener noreferrer"&gt;https://github.com/Rafa-romero-dev/seo-agent&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;A special thank you to the Dev.to team for featuring my previous article in the &lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-e4i"&gt;Top 7 Featured Dev Posts of the Week&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What do you think I should focus on next? What could use some refinement? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>python</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Beyond Page One: Building a Highly Robust SEO Lead Generation Agent with Python and SerpApi🤖</title>
      <dc:creator>Rafael Romero</dc:creator>
      <pubDate>Fri, 14 Nov 2025 20:38:47 +0000</pubDate>
      <link>https://dev.to/rafajrg21/beyond-page-one-building-a-highly-robust-seo-lead-generation-agent-with-python-and-serpapi-331h</link>
      <guid>https://dev.to/rafajrg21/beyond-page-one-building-a-highly-robust-seo-lead-generation-agent-with-python-and-serpapi-331h</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As a SEO consultant or agency owner, finding actionable leads is a constant hustle. The obvious Page 1 opportunities are highly competitive so the real opportunity is on Google Page 2 and beyond, where basic SEO mistakes by local businesses are common.&lt;/p&gt;

&lt;p&gt;This post describes how I created a production-ready Python agent (depending on your definition) that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scrapes targeted SERP pages (Page 2+)&lt;/li&gt;
&lt;li&gt;Performs instant on-page audits (H1, local NAP, etc.)&lt;/li&gt;
&lt;li&gt;Generates a personalized sales pitch for every failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project focuses on three pillars of production code: Efficiency, Precision, and Robustness.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1 — Core Logic: Efficiency &amp;amp; Data Gathering
&lt;/h2&gt;

&lt;p&gt;Scaling Google searches is the primary challenge. I used SerpApi to avoid scraping blocks and programmatically target long-tail local queries defined in &lt;code&gt;serp_config.py&lt;/code&gt; (a combination of &lt;code&gt;CITIES&lt;/code&gt; and &lt;code&gt;KEYWORDS&lt;/code&gt;). I tried making a free version with open source tools but it was a huge failure, so SerpApi really did the heavy lifting here.&lt;/p&gt;

&lt;p&gt;Key takeaway: target Page 2+ instead of Page 1. Use CLI arguments to control which pages to scan and iterate the &lt;code&gt;start&lt;/code&gt; parameter.&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="c1"&gt;# The SerpApi parameters in serpapi_extractor
&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="c1"&gt;# ... other parameters ...
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# page offset (0=Page 1, 10=Page 2, etc.)
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Iterate from start_page to end_page (increase by 10 per page)
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;current_rank&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end_page&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ... API call ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Phase 2 — From Raw Data to Actionable Targets: Precision
&lt;/h2&gt;

&lt;p&gt;After the data extraction we are presented with two main problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Duplicate URLs (same company ranking for multiple queries)&lt;/li&gt;
&lt;li&gt;Irrelevant directories (e.g., Yelp, YellowPages)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So how did we handle these cases:&lt;/p&gt;

&lt;p&gt;Deduplication&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convert raw results to a DataFrame&lt;/li&gt;
&lt;li&gt;Normalize URLs (strip &lt;code&gt;http://&lt;/code&gt;, &lt;code&gt;https://&lt;/code&gt;, &lt;code&gt;www.&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Keep the entry with the best (lowest) rank&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Filtering directories&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exclude known directory domains to avoid wasted audits and false positives.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Directory filter example (from __main__ section)
&lt;/span&gt;&lt;span class="n"&gt;directory_domains&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;yelp.com&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;google.com/maps&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;yellowpages.com&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;facebook.com&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;linkedin.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_directory&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;return&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="ow"&gt;in&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;lower&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;domain&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;directory_domains&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cleaned_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Is_Directory&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="n"&gt;cleaned_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;URL&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_directory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cleaned_df_filtered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cleaned_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cleaned_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Is_Directory&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="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&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;Is_Directory&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;h2&gt;
  
  
  Phase 3 — The Critical Audit: Robustness &amp;amp; Personalization
&lt;/h2&gt;

&lt;p&gt;Direct audits are fragile: sites block bots or return transient errors. Initial runs produced many leads caused by &lt;code&gt;ConnectionError&lt;/code&gt; or &lt;code&gt;HTTP 403&lt;/code&gt; (technical failures, not SEO misses). Two enhancements fixed this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;CLI automation&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace &lt;code&gt;input()&lt;/code&gt; with &lt;code&gt;argparse&lt;/code&gt; so the script can run non-interactively (e.g., &lt;code&gt;python agent.py 2 5&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Retry mechanism&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry transient request errors up to &lt;code&gt;max_retries&lt;/code&gt; and record real audit failures only after retries fail.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Retry snippet from run_on_page_audit
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&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;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;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;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="c1"&gt;# raises HTTPError for 4xx/5xx
&lt;/span&gt;        &lt;span class="k"&gt;break&lt;/span&gt;  &lt;span class="c1"&gt;# success
&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&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="c1"&gt;# Retry logic...
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt; &lt;span class="o"&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;audit_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;H1_Status&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: Request Failed (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__class__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;audit_data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Sales Pitch Generation
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;generate_sales_pitch&lt;/code&gt; function turns audit failures into tailored outreach messages. It targets common failures like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing H1&lt;/li&gt;
&lt;li&gt;Missing NAP (Name, Address, Phone)&lt;/li&gt;
&lt;li&gt;Server errors / blocked pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each CSV row (audited URL) becomes a qualified sales opportunity with a targeted pitch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion and Next Steps
&lt;/h2&gt;

&lt;p&gt;This agent provides a powerful foundation for automated SEO lead generation. It is highly efficient in its use of paid API credits, precise in its targeting, and robust enough to handle the realities of web scraping. The output is a highly qualified list of prospects ready for immediate outreach.&lt;/p&gt;

&lt;p&gt;As a Frontend Developer primarily focused on JavaScript, I’m thrilled with how this project turned out! Python is a huge, yet rewarding, undiscovered land of possibilities for me. Collaborative tools like Gemini were essential, providing a step-by-step path to bring this script to an acceptable, production-ready state.&lt;/p&gt;

&lt;p&gt;I believe open sourcing this kind of tool benefits the whole developer community. You can find the full, production-ready code, including the agent.py and instructions on how to set up serp_config.py, on GitHub: &lt;a href="https://github.com/Rafa-romero-dev/seo-agent" rel="noopener noreferrer"&gt;Seo Agent on Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love to hear your experiences and ideas!&lt;/p&gt;

&lt;p&gt;Have you used automation for lead generation? What other on-page checks do you find most effective for a quick audit? Or is there a tool that you use that I can take inspiration from?&lt;/p&gt;

&lt;p&gt;Let me know what improvements should I make! Drop a comment below or open an Issue on the repo to share your thoughts and suggestions.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>gemini</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The Frontend Challenge 4 - Glam Up My Markup: Recreation</title>
      <dc:creator>Rafael Romero</dc:creator>
      <pubDate>Mon, 05 Aug 2024 03:21:59 +0000</pubDate>
      <link>https://dev.to/rafajrg21/the-frontend-challenge-4-glam-up-my-markup-recreation-318g</link>
      <guid>https://dev.to/rafajrg21/the-frontend-challenge-4-glam-up-my-markup-recreation-318g</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2024-07-24"&gt;Frontend Challenge v24.07.24&lt;/a&gt;, Glam Up My Markup: Recreation&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;After a little break I came back for this challenge, I really liked the premise and I tried to create a design that was smooth and dynamic, this is a sports page after all. Is not as responsive and accessible as I wanted it to be, but this is the best I can do with my current time constrains and all the difficult situations on my country. &lt;/p&gt;

&lt;p&gt;Of course I always try to adhere to the challenge rules but this time I went all out with the animations, those are the star of the show, specially the scroll animation. &lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;For this demo I recommend to always see it in the full page view, specially in a new tab (please) and of course it will be functional in desktop (the best experience) and in mobile (the most common way).&lt;/p&gt;

&lt;p&gt;You can see the page on a new tab clicking &lt;a href="https://codepen.io/Rafa-romero-dev/full/poXNmzp" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/Rafa-romero-dev/embed/poXNmzp?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;I can keep improving the code with some dry principles, specially with the aforementioned scroll animation, I repeated a lot of logic in the code but I did try my best to keep it as clean and clear as possible. The design process for this submission was my favorite part, it was not easy to choose a good color scheme, but I'm glad I landed on that red, grey and cream combo and I hope you find it pretty to look at as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqn5slostpa53qks2566.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqn5slostpa53qks2566.gif" alt="I-hope-you-enjoyed-it" width="498" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finding some "real" cricket images was easy with my new knowledge of the Commons Creative License, and I found some good icons in a tool I discovered in a recent DEV post (I'll try to find the post to credit it in the comments). But I also made some assets like the cricket ball pattern in the schedule section and of course, the cover image on this post.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5owlixmbr8gkkwh0uxd.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5owlixmbr8gkkwh0uxd.gif" alt="madagascar-he-has-style" width="498" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's all for this one. If you're participating in the challenge, I hope you have as much satisfaction from your work as I do from mine. If not, perhaps the upcoming challenge will be your time to shine.&lt;/p&gt;

&lt;p&gt;And as always, if you have some feedback you can write a comment or send me a DM. English is my second language, so please be nice to me ❣️ Thank you for reading up to this point, and until the next time, bye!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>One Byte Explainer - The Nibble</title>
      <dc:creator>Rafael Romero</dc:creator>
      <pubDate>Fri, 14 Jun 2024 15:55:31 +0000</pubDate>
      <link>https://dev.to/rafajrg21/one-byte-explainer-the-nibble-lp6</link>
      <guid>https://dev.to/rafajrg21/one-byte-explainer-the-nibble-lp6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/cs"&gt;DEV Computer Science Challenge v24.06.12: One Byte Explainer&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Explainer
&lt;/h2&gt;

&lt;p&gt;Half a byte. Is a small unit of information used for more efficient use of memory &amp;amp; processing power when resources are limited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Context
&lt;/h2&gt;

&lt;p&gt;You could call this one a One Nibble Explainer&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuiql5r21qw3y6j822nm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuiql5r21qw3y6j822nm.gif" alt="Ryan Gosling laughing" width="460" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>cschallenge</category>
      <category>computerscience</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Frontend Challenge v2: Earth Day Edition🌎</title>
      <dc:creator>Rafael Romero</dc:creator>
      <pubDate>Mon, 29 Apr 2024 03:33:19 +0000</pubDate>
      <link>https://dev.to/rafajrg21/the-frontend-challenge-v2-earth-day-edition-1fal</link>
      <guid>https://dev.to/rafajrg21/the-frontend-challenge-v2-earth-day-edition-1fal</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/devteam/join-us-for-the-next-frontend-challenge-earth-day-edition-52e4"&gt;Frontend Challenge v24.04.17&lt;/a&gt;, Glam Up My Markup: Earth Day Celebration Landing Page&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;For this challenge, I endeavored to build a distinctive design, consistently prioritizing responsiveness and exploring an unexpected yet vibrant color scheme. As with the first challenge, I strived to adhere to the guidelines as closely as possible. The comprehensiveness of my design this time around might be somewhat daunting, but I am confident that it is engaging (and hopefully resonates with you).&lt;/p&gt;

&lt;p&gt;This marks my second venture into the Frontend Challenges. Despite grappling with time management for my side projects (and kinda losing), I find immense enjoyment in these challenges that inspire creativity and encourage thinking outside the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The demo is (again) as responsive as I could do it. I think the earth's image could be better handled (any ideas please comment down below). I tried to take into consideration the main ways to see the demo in Codepen: In the 3 different default "Change view" options and the Live View in a different page. Always considering the mobile view, which I think is the most common. But the Live View (or full page view) in Desktop is my favorite.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/Rafa-romero-dev/embed/jORdQJm?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;As with all projects, I required a concept to guide my efforts. This time, the project was relatively straightforward, being a landing page. Naturally, the Earth theme opened up a plethora of opportunities for intriguing animations to enhance the design. However, I didn’t want to simply add animations and consider the job done. My rationale is that when you encounter an aesthetically pleasing landing page, you appreciate it, but then what? You consume the content and move on, it becomes forgettable. Therefore, I aimed to create something that would elicit a “wow, what a fun idea” reaction from people. They would explore the page, perhaps experience an “aha!” moment, and be pleasantly surprised by a little something extra.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3mwq2mti06e18fyvhp9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3mwq2mti06e18fyvhp9.gif" alt="Bob sponge writing" width="480" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With an idea in mind, I set out to create a mini-game. However, crafting something as complex as Space Invaders was out of the question (given my professional procrastination tendencies). Instead, I envisioned something swift and vibrant: an Easter egg hunt. At the bottom of the page, I incorporated a message that reads, &lt;em&gt;"Can you make a difference on this page? Find the 5 'green' actions. Hint: Some icons are clickable!"&lt;/em&gt; Now, those who have merely skimmed the landing page from top to bottom have a reason to revisit and discover these clickable elements. Alternatively, if you're sufficiently curious (or have a mouse), you'll notice the cursor transforming into a pointer, indicating that something is about to happen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j4wbuq84yi894mr3cre.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j4wbuq84yi894mr3cre.gif" alt="Monkey clicking mouse" width="498" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, what happens next? I was brimming with ideas, many of which were too complex to execute in a single day. However, I was determined to implement a color transition, something that would signify a transformation on the "planet" or page. We start with a vibrant planet, adorned with clear blue skies and lush green hills. Yet, pollution taints this vibrancy, the air turns toxic and loses its clarity, and the once vibrant green becomes dull and lifeless. Implementing these changes on the page while maintaining sufficient contrast was a challenge in itself. However, I believe I've made a commendable effort in articulating my concept here, so at least some of you reading this post can comprehend why I opted for these colors and why they undergo a transition.&lt;/p&gt;

&lt;p&gt;But wait...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faw885y8i48pobfrf8t0e.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faw885y8i48pobfrf8t0e.gif" alt="Fresh prince wait a minute" width="498" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why do I persistently refer to it as a game? As the text indicates, you're tasked with identifying all 5 "green" actions. Similar to switching off a lightbulb, you can alter the state of most icons on the page. While most are conspicuously visible, one is cleverly concealed. Once you've successfully adjusted all of them to the correct state, a surprise awaits you at the top of the page.&lt;/p&gt;

&lt;p&gt;That's about it! There isn't much to elaborate on this time, as I didn't incorporate a multitude of features. However, there's ample room for enhancement. Landing pages are the most prevalent type of page online, and my submission could benefit from additional accessibility features and animations. I may revisit this project in the future to further refine it, adding more features. While a theme switch may not be particularly relevant for this project, the option for different languages could be a valuable addition. Anyway... &lt;/p&gt;

&lt;p&gt;If you're participating in the challenge, I hope you have as much satisfaction from your work as I do from mine. If not, perhaps the upcoming challenge will be the turning point for you. Thank you for reading up to this point, and until the next time, bye!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg98nufq0t3y6qzbpes3b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg98nufq0t3y6qzbpes3b.gif" alt="Penguin saying goodbye" width="498" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have some feedback you can write a comment or send a DM. English is my second language so please be nice to me ❣️&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Frontend Challenge: Glammed Up Camp Activities</title>
      <dc:creator>Rafael Romero</dc:creator>
      <pubDate>Sun, 31 Mar 2024 22:11:13 +0000</pubDate>
      <link>https://dev.to/rafajrg21/the-frontend-challenge-glammed-up-camp-activities-3j6j</link>
      <guid>https://dev.to/rafajrg21/the-frontend-challenge-glammed-up-camp-activities-3j6j</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for DEV Challenge v24.03.20, Glam Up My Markup: Camp Activities&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;For this challenge, I tried to create a project that is dynamic, responsive, and accessible, all within my current abilities and adhering to the challenge rules. Although there may be some areas where improvements could be made, I believe my work is comprehensive and engaging. The process of crafting this submission was truly enjoyable and fun for me and I hope you find something interesting in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The demo itself is as responsive as I could do it. I tried to take into consideration the main ways to see the demo in Codepen: In the 3 different default "Change view" options and the Live View in a different page. I even considered the mobile view.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/Rafa-romero-dev/embed/GRLMdMd?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;It all started with an idea, I wanted to add dynamic images on the form, to show the different camp activities, that way filling up the form wouldn't be so boring and you could better think of the option you want the most before submitting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4aht0fivkhg47v698uyp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4aht0fivkhg47v698uyp.gif" alt="Keyboard mashing" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To achieve that, I started to search for free pics and resources, until I stumbled upon &lt;a href="https://storyset.com/work"&gt;Storyset&lt;/a&gt; which -to my surprise- had images for all the activities so I could have the same style of images in the project.&lt;/p&gt;

&lt;p&gt;Then I started thinking about the overall style of the project. While thinking about the project I was watching the Mashle season 2 opening and if you know what I'm talking about you would understand why I chose the yellow color palette. Of course, because this is a Dev.to challenge, I wanted to do a minimalist "note" style form. Hopefully, my design represents this style decision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9zbs809gpostukr75hq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9zbs809gpostukr75hq.gif" alt="Mashle bling bang bang born" width="498" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I started adding the images in the project I realized that adding a lot of images would be difficult as the template can't be really manipulated (now I know this isn't true) so as a solution, I did a multi-step form, that way I can organize the info that's shown on screen. But that came with its own challenges.&lt;/p&gt;

&lt;p&gt;I tried to make the code as clean and explicit as possible (with a lot of comments) but essentially, I needed to add new elements like the images, some buttons to control the steps, steps indicators and a random div to add some style. I also needed to add and remove (conditionally) a lot of attributes and classes, to make the form reactive and responsive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fami5uqanzrqaexta1dnt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fami5uqanzrqaexta1dnt.gif" alt="This is fine dog meme" width="498" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After all the styling was on place and worked in the tab functionality I then started to make some accessibility changes, for example, I added focus classes, and manipulated the tab index to make keyboard navigation as clear as possible. Sadly, I'm very new to accessibility and I am not sure I did the best job in it. Nevertheless, I tried making it accessible and I think trying and learning from mistakes is better that not doing anything for fear that it comes up wrong.&lt;/p&gt;

&lt;p&gt;Responsiveness was a difficult step, because I am using a lot of absolute positioning which I think is not the best option but is the easier. Like I said in the demo portion, I tried to take into consideration the default page views and different combinations of width and height. I hope you can see a version that looks good and don't judge me for the failed resolutions 🥲&lt;/p&gt;

&lt;p&gt;Finally, the project was finished!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9smb9anttlxpisqnpt7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9smb9anttlxpisqnpt7.gif" alt="The end patrick start" width="498" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Until it wasn't...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5302fngwntgwhzei9qr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5302fngwntgwhzei9qr.gif" alt="Shocked face meme" width="423" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok let me explain, this is my first challenge and big post I make for dev.to in the 6 YEARS I have in this platform. Seeing that I still had some hours to submit the project, I wanted to have a Plus Ultra moment and add some more features, form validation and dark mode.&lt;/p&gt;

&lt;p&gt;It was actually fairly simple to add them considering that dark mode is one of the most common features in virtually all modern web projects and the validation was in the page that I was referencing when doing the multi step form. After adding this, and testing some more, checking all the styles and functionalities where in place, I now consider this project a huge success.&lt;/p&gt;

&lt;p&gt;I really don't care too much about winning the challenge, I know there are thousands of people with insane skills and lots of experience, but I wanted to feel satisfied with my contribution, that I did all I could do, to show off my skills and most of all, remind myself that I can do amazing stuff when I put my heart into it.&lt;/p&gt;

&lt;p&gt;Finally, if you are participating in the challenge I hope you feel the same and if not, maybe the next challenge will be your moment. Thanks for reading until here, and I have no idea how to end this so... Goodbye.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx4hfba72mtmkqfvpt76x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx4hfba72mtmkqfvpt76x.gif" alt="Adios gif" width="304" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have some feedback you can write a comment or send a DM. English is my second language and this is my first "technical" post so please be nice to me ❣️&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>My Hacktoberfest 2023 Pledge</title>
      <dc:creator>Rafael Romero</dc:creator>
      <pubDate>Wed, 11 Oct 2023 14:56:22 +0000</pubDate>
      <link>https://dev.to/rafajrg21/my-hacktoberfest-2023-pledge-164a</link>
      <guid>https://dev.to/rafajrg21/my-hacktoberfest-2023-pledge-164a</guid>
      <description>&lt;p&gt;Hi I'm Rafael Romero, a Computer Engineer and a Frontend Developer from Venezuela. I always introduce myself the same way because I'm not very known in any tech community, I am a 5 year club member of dev.to and I really enjoy reading a lot of articles and seeing the wins of the week. There are a lot of amazing people in Dev and I've always wanted to be part of the community in a more significant way, write blogs and share experiences. That's why I joined Dev in the first place and I hope to write some posts before this year's end.&lt;/p&gt;

&lt;p&gt;I really love Hacktoberfest. My first one was in 2019 and it was --in part-- the reason why I got a job in 2020 and since then I've always tried to complete the challenge and I always try to up the complexity of my contributions. In 2020 and 2021 I was full of work in October but I successfully returned to the challenge last year, I made my first project with Next.js and overall I felt really good with my contributions even if they are small.&lt;/p&gt;

&lt;p&gt;This year, I've had some trouble starting my open source journey but I will start today, even if I start with something simple like some minor UI changes. My goal is to keep contributing after the Hacktoberfest so I want to find projects I think are cool or innovative and if able, help with frontend related issues so I can apply my knowledge in different projects, learn from different systems and code bases and help others. &lt;/p&gt;

&lt;p&gt;Finally, congrats to everyone that already completed the challenge as of today, cheers to those that are half way there and for all of you that haven't made up your mind or are having some difficulties on the way, keep going, you can do it. Happy coding!&lt;/p&gt;

</description>
      <category>hacktoberfest23</category>
    </item>
    <item>
      <title>Which terminal do you use?</title>
      <dc:creator>Rafael Romero</dc:creator>
      <pubDate>Sat, 08 Jun 2019 01:53:08 +0000</pubDate>
      <link>https://dev.to/rafajrg21/which-terminal-do-you-use-1h85</link>
      <guid>https://dev.to/rafajrg21/which-terminal-do-you-use-1h85</guid>
      <description>&lt;p&gt;Ok so I'm used to use the windows command prompt to do almost anything in my laptop, and git Bash for git related stuff, after seeing hyper and zsh on some posts and videos, it sparked my curiosity, maybe I need to level up my terminal game.&lt;/p&gt;

&lt;p&gt;So, I ask you people of Dev.to, which one is your favorite terminal and why, how do you configure yours to be your master shell? 👀&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
  </channel>
</rss>
