<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Antonio Blago on Medium]]></title>
        <description><![CDATA[Stories by Antonio Blago on Medium]]></description>
        <link>https://medium.com/@antonioblago?source=rss-b37386bbf180------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*HU9Ewmo1_-ob76jT2S3zzQ.jpeg</url>
            <title>Stories by Antonio Blago on Medium</title>
            <link>https://medium.com/@antonioblago?source=rss-b37386bbf180------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 16:48:42 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@antonioblago/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[The Agentic Blueprint: How I Built a 50k-Client Machine (and the Exact Architecture Behind It)]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://pub.towardsai.net/the-agentic-blueprint-how-i-built-a-50k-client-machine-and-the-exact-architecture-behind-it-2261f2280943?source=rss-b37386bbf180------2"><img src="https://cdn-images-1.medium.com/max/889/1*ktaPlnIhf5wtCrM51NaZzQ.png" width="889"></a></p><p class="medium-feed-snippet">The Agentic Blueprint is the system architecture behind an operation that can scale to tens of thousands of clients without scaling&#x2026;</p><p class="medium-feed-link"><a href="https://pub.towardsai.net/the-agentic-blueprint-how-i-built-a-50k-client-machine-and-the-exact-architecture-behind-it-2261f2280943?source=rss-b37386bbf180------2">Continue reading on Towards AI »</a></p></div>]]></description>
            <link>https://pub.towardsai.net/the-agentic-blueprint-how-i-built-a-50k-client-machine-and-the-exact-architecture-behind-it-2261f2280943?source=rss-b37386bbf180------2</link>
            <guid isPermaLink="false">https://medium.com/p/2261f2280943</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[agentic-ai]]></category>
            <category><![CDATA[clients]]></category>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[seo]]></category>
            <dc:creator><![CDATA[Antonio Blago]]></dc:creator>
            <pubDate>Wed, 17 Jun 2026 09:41:06 GMT</pubDate>
            <atom:updated>2026-06-21T05:44:13.229Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Stop Running Claude Code in the Terminal, Do This.]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://pub.towardsai.net/stop-running-claude-code-in-the-terminal-do-this-fc18120ac043?source=rss-b37386bbf180------2"><img src="https://cdn-images-1.medium.com/max/1080/1*Cfu2ckvwEpnE8mv_WQNODQ.png" width="1080"></a></p><p class="medium-feed-snippet">Same tool, different surface.</p><p class="medium-feed-link"><a href="https://pub.towardsai.net/stop-running-claude-code-in-the-terminal-do-this-fc18120ac043?source=rss-b37386bbf180------2">Continue reading on Towards AI »</a></p></div>]]></description>
            <link>https://pub.towardsai.net/stop-running-claude-code-in-the-terminal-do-this-fc18120ac043?source=rss-b37386bbf180------2</link>
            <guid isPermaLink="false">https://medium.com/p/fc18120ac043</guid>
            <category><![CDATA[anthropic-claude]]></category>
            <category><![CDATA[vscode]]></category>
            <category><![CDATA[seo]]></category>
            <category><![CDATA[claude-code]]></category>
            <dc:creator><![CDATA[Antonio Blago]]></dc:creator>
            <pubDate>Tue, 16 Jun 2026 09:20:28 GMT</pubDate>
            <atom:updated>2026-06-21T05:45:07.716Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[R-evolution: How We Built an AI Tutor That Actually Understands R and Shiny]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/r-evolution/r-evolution-how-we-built-an-ai-tutor-that-actually-understands-r-and-shiny-aa6a508c1bf2?source=rss-b37386bbf180------2"><img src="https://cdn-images-1.medium.com/max/1605/1*U9bKzik5oz16HWntsTq9jQ.png" width="1605"></a></p><p class="medium-feed-snippet">The Problem Every R Developer Knows Too Well</p><p class="medium-feed-link"><a href="https://medium.com/r-evolution/r-evolution-how-we-built-an-ai-tutor-that-actually-understands-r-and-shiny-aa6a508c1bf2?source=rss-b37386bbf180------2">Continue reading on R-evolution »</a></p></div>]]></description>
            <link>https://medium.com/r-evolution/r-evolution-how-we-built-an-ai-tutor-that-actually-understands-r-and-shiny-aa6a508c1bf2?source=rss-b37386bbf180------2</link>
            <guid isPermaLink="false">https://medium.com/p/aa6a508c1bf2</guid>
            <category><![CDATA[r]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[ai-tutor]]></category>
            <category><![CDATA[r-shiny]]></category>
            <dc:creator><![CDATA[Antonio Blago]]></dc:creator>
            <pubDate>Mon, 08 Sep 2025 07:12:22 GMT</pubDate>
            <atom:updated>2025-09-08T07:12:22.761Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[R-evolution: How We Built an AI Tutor That Actually Understands R and Shiny]]></title>
            <link>https://medium.com/r-evolution/r-evolution-how-we-built-an-ai-tutor-that-actually-understands-r-and-shiny-223e8d84d830?source=rss-b37386bbf180------2</link>
            <guid isPermaLink="false">https://medium.com/p/223e8d84d830</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[r]]></category>
            <category><![CDATA[developerrs]]></category>
            <category><![CDATA[r-shiny]]></category>
            <dc:creator><![CDATA[Antonio Blago]]></dc:creator>
            <pubDate>Sat, 06 Sep 2025 13:33:32 GMT</pubDate>
            <atom:updated>2025-09-06T13:33:32.802Z</atom:updated>
            <content:encoded><![CDATA[<h3>The Problem Every R Developer Knows Too Well</h3><p>Picture this: It’s 2 AM.</p><p>You’re debugging a reactive expression in your Shiny app that worked perfectly yesterday. ChatGPT gives you wrong code. The official documentation is technically correct but doesn’t address your specific edge case. Sound familiar?</p><p>After teaching R and Shiny for over a decade, I’ve seen this scenario play out thousands of times. The learning curve isn’t just steep — it’s a cliff face covered in reactive expressions, non-standard evaluation, and cryptic error messages.</p><p>That’s why we built something different: an R Shiny AI tutor that doesn’t just regurgitate documentation. It understands R the way experienced developers do.</p><h3>Beyond Generic AI: The Power of Domain-Specific RAG</h3><p>Most AI assistants treat R like any other programming language. They’ll happily suggest numpy solutions to your dplyr problems or confidently hallucinate functions that don&#39;t exist. We took a radically different approach.</p><p>Our RAG (Retrieval-Augmented Generation) model is trained exclusively on:</p><ul><li><strong>thousands of production R scripts</strong> from real-world applications</li><li><strong>Every CRAN package documentation</strong> (yes, all 20,000+ packages)</li><li><strong>Curated educational content</strong> from Hadley Wickham, Jenny Bryan, and other R luminaries</li><li><strong>Battle-tested Shiny applications</strong> from Fortune 500 deployments</li></ul><p>The result? An AI that thinks in pipes %&gt;%, understands why aes_string() was deprecated, and knows the difference between observe() and observeEvent() without mixing them up.</p><h3>The Secret Sauce: Zero Hallucination Architecture</h3><p>Here’s our dirty little secret: Our AI can say “I don’t know.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U9bKzik5oz16HWntsTq9jQ.png" /><figcaption>Screenshot from AI Tutor</figcaption></figure><p>Instead of creatively inventing functions, our system:</p><ol><li><strong>Searches its verified knowledge base first</strong></li><li><strong>Validates code against actual R syntax</strong></li><li><strong>Tests reactive logic for consistency</strong></li><li><strong>Admits uncertainty when beyond its training</strong></li></ol><p>Every piece of code is traceable to a source — whether it’s official Posit documentation, a validated Stack Overflow solution, or our proprietary script collection.</p><h3>Real Learning, Not Just Answers</h3><p>Traditional documentation tells you <em>what</em>. Stack Overflow tells you <em>how</em>. Our AI tutor tells you <em>why</em>.</p><p>Example interaction:</p><pre>User: &quot;My reactive isn&#39;t updating when the file uploads&quot;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*00TRntAggSvvtCq1oblxdQ.png" /></figure><p>The AI doesn’t just fix your code — it teaches you to think in reactive patterns.</p><h3>What Makes This Different</h3><h3>1. Context-Aware Teaching</h3><p>The AI remembers your skill level, adapts explanations, and progressively challenges you with harder concepts.</p><h3>2. Production-Ready Patterns</h3><p>Learn the patterns actually used in production, not toy examples. Every tutorial includes error handling, performance considerations, and deployment notes.</p><h3>3. Interactive Debugging</h3><p>Upload your broken code. The AI diagnoses issues, explains the root cause, and guides you through the fix — teaching principles, not patches.</p><h3>4. Package Ecosystem Mastery</h3><p>It knows which packages play well together, which functions are deprecated, and what alternatives exist. No more conflicting dependencies or outdated tutorials.</p><h3>The Technical Architecture</h3><p>For the curious, here’s what’s under the hood:</p><p><strong>Knowledge Base:</strong></p><ul><li>2.3 TB of deduplicated R code</li><li>50,000+ validated script patterns</li><li>Complete CRAN + Bioconductor documentation</li><li>Custom embeddings for R-specific concepts</li></ul><p><strong>Validation Layer:</strong></p><ul><li>Syntax checking via R’s native parser</li><li>Reactive flow analysis for Shiny</li><li>Package compatibility verification</li><li>Performance profiling for suggested solutions</li></ul><p><strong>Learning System:</strong></p><ul><li>Adaptive difficulty based on user interactions</li><li>Mistake pattern recognition</li><li>Personalized learning path generation</li><li>Progress tracking across concepts</li></ul><h3>Early Access: 10 Beta Spots Available</h3><p>We’re opening our beta to just 10 developers. Why so few? Because we want deep, meaningful feedback from serious R users who will push our system to its limits.</p><p><strong>Beta testers receive:</strong></p><ul><li>Lifetime premium access (worth $119/month)</li><li>Direct input on feature development</li><li>Custom training on your codebase</li></ul><p><strong>We’re looking for:</strong></p><ul><li>Active R/Shiny developers (any level)</li><li>5–10 hours per week for testing</li><li>Detailed feedback on learning experience</li><li>Passion for improving R education</li></ul><h3>The Future of R Learning</h3><p>Imagine an AI that:</p><ul><li>Reviews your pull requests for R best practices</li><li>Automatically optimizes your Shiny app’s performance</li><li>Generates test cases for your functions</li><li>Teaches you new packages as they’re released</li></ul><p>This isn’t science fiction. It’s what we’re building.</p><h3>Join the R-evolution</h3><p>The R community has always been about democratizing data science. We’re taking that mission further — democratizing expertise itself.</p><p>If you’re tired of:</p><ul><li>Generic AI that doesn’t understand R’s quirks</li><li>Outdated tutorials that break with new package versions</li><li>The gap between documentation and real-world usage</li><li>Learning alone without expert guidance</li></ul><p>Then you’re exactly who we built this for.</p><p><strong>Applications close in 72 hours. 10 spots.</strong></p><p>Apply via my email to info@antonioblago.com</p><p>Ready to accelerate your R journey?</p><p><em>P.S. — Yes, it knows the difference between </em><em>&lt;- and </em><em>=. And yes, it has opinions about which you should use (but it&#39;s configurable to your style guide).</em></p><p><em>P.P.S. — For the skeptics: We’ll be publishing our benchmark results against GPT-4 and Claude on R-specific tasks next month. Spoiler: Domain-specific training matters more than model size.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=223e8d84d830" width="1" height="1" alt=""><hr><p><a href="https://medium.com/r-evolution/r-evolution-how-we-built-an-ai-tutor-that-actually-understands-r-and-shiny-223e8d84d830">R-evolution: How We Built an AI Tutor That Actually Understands R and Shiny</a> was originally published in <a href="https://medium.com/r-evolution">R-evolution</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tired of Keyword Planner Limitations?]]></title>
            <link>https://python.plainenglish.io/tired-of-keyword-planner-limitations-ee2b007dfa56?source=rss-b37386bbf180------2</link>
            <guid isPermaLink="false">https://medium.com/p/ee2b007dfa56</guid>
            <category><![CDATA[seo]]></category>
            <category><![CDATA[keyword-research]]></category>
            <category><![CDATA[keywords-planner]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data4seo]]></category>
            <dc:creator><![CDATA[Antonio Blago]]></dc:creator>
            <pubDate>Sun, 13 Jul 2025 11:39:37 GMT</pubDate>
            <atom:updated>2025-08-01T19:05:21.209Z</atom:updated>
            <content:encoded><![CDATA[<h3>Here’s How I Pulled 130,000 Search Volume Keywords with Python — for Less than 10€.</h3><h4>As an SEO analyst working with massive keyword datasets, I kept running into the same wall: the Google Keyword Planner simply couldn’t keep up. Ahrefs or Semrush is way to expensive. Whether it was bulk limits, poor export options, or the lack of automation — it just wasn’t made for serious scale.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*oiXWBmyHcqUKfYTR" /><figcaption>Photo by <a href="https://unsplash.com/@nisoncoprseo?utm_source=medium&amp;utm_medium=referral">NisonCo PR and SEO</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>So I built my own solution.</p><p>Using Python and the <a href="https://antonioblago.de/dataforseo">DataForSEO API</a>, I created a workflow that can handle 130,000+ keywords, filter them, track API costs, and export clean, structured Excel files — all in a matter of hours.</p><p>In this guide, I’ll walk you through how to recreate that workflow from scratch — including:</p><p>What tools you’ll need</p><ul><li>How batching and tracking work</li><li>How to structure your project</li><li>A side-by-side comparison with Google’s tool</li></ul><p>Let’s get started.</p><h4>Why Seek an Alternative to the Keyword Planner?</h4><p>While the Google Keyword Planner is a solid tool, it has notable constraints:</p><ol><li><strong>Limited Capacity</strong>: Struggles with processing extensive keyword lists.</li><li><strong>Automation Challenges</strong>: Lacks flexibility for automated workflows.</li><li><strong>API Access Restrictions</strong>: Requires an active Google Ads account for API usage.</li></ol><p>For handling substantial keyword datasets from sources like BigQuery, SEO tools, or proprietary data, a scalable solution like <a href="https://antonioblago.de/dataforseo">DataForSEO&#39;s API</a> + Python proves invaluable.</p><h3>Getting Started</h3><ol><li>Install Necessary Python Packages</li></ol><pre>pip install pandas requests openpyxl</pre><p>2. Set Up DataForSEO Access</p><p>Sign up at <a href="https://antonioblago.de/dataforseo">DataForSEO</a> to obtain your API credentials (login and password).</p><p>These credentials are used for Basic Authentication.</p><h3>Workflow Overview</h3><ol><li>Load keywords from an Excel file.</li><li>Send them in batches (e.g., 1,000 keywords per batch) to the DataForSEO API.</li><li>Retrieve data like CPC, search volume, and monthly trends.</li><li>Track progress, manage costs, and export results.</li></ol><p>Implement recovery functions to resume after interruptions.</p><h3>Key Features of the Python Script</h3><ul><li>Batch Processing: Handles large keyword sets and adheres to API rate limits.</li><li>Pickle-Based Progress Tracking: Saves your state so you can pause/resume processing.</li><li>API Cost Monitoring: Tracks costs per batch and generates a report in Excel.</li><li>Keyword Filtering: Cleans up terms with symbols (™ © ®) and limits keyword length.</li></ul><h4>Suggested Project Structure</h4><pre>project/<br>│<br>├── keywords.xlsx            # ✅ Your raw keyword list (must contain a &quot;keyword&quot; column)<br>│<br>├── get_sv_data4seo.py       # 🧠 Main script to fetch search volume data via DataForSEO API<br>│                            # Includes keyword cleaning, batching, retries, and saving<br>│<br>├── backup_results.pkl       # 💾 Auto-saved backup with progress &amp; results (resume support)<br>│                            # Contains: results, monthly data, cost tracking, progress index<br>│<br>├── final_results.xlsx       # 📊 Final export with 3 sheets:<br>│                            #   - &quot;Keyword Data&quot; → search volume, CPC, competition<br>│                            #   - &quot;Monthly Volume&quot; → time series search volumes<br>│                            #   - &quot;API Costs&quot; → costs per batch + total<br>│<br>├── invalid_keywords.txt     # 🚫 Log of invalid keywords (too long, too many words, bad chars)<br>│<br>└── progress.txt             # ⏳ (Optional) Tracks last processed batch for crash recovery</pre><h3>Script Overview</h3><p>This script automates the <strong>collection of keyword data</strong> (search volume, CPC, competition, monthly trends) from the <a href="https://antonioblago.de/dataforseo">DataForSEO API</a>. It reads keywords from an Excel file, cleans them, sends them in batches, handles errors and retries, and saves everything to Excel and Pickle for future use or resume.</p><h3>1. Setup &amp; Configuration</h3><h4>Import libraries</h4><pre>import os, time, json, base64, pandas as pd, requests, pickle<br>import numpy as np, re, unicodedata<br>from dotenv import load_dotenv<br>load_dotenv()</pre><h4>API Config &amp; Constants</h4><pre>BATCH_SIZE = 1000<br>LOCATION_CODE = 2276<br>LANGUAGE_CODE = &quot;de&quot;<br>MAX_RETRIES = 3<br>RETRY_DELAY = 3</pre><pre>API_URL = &quot;https://api.dataforseo.com/v3/keywords_data/google_ads/search_volume/live&quot;<br>AUTH_HEADER = os.getenv(&quot;d4seo&quot;)</pre><h4>Excel path</h4><pre>def return_path():<br>    return r&quot;C:\Path\to\your\KeywordList.xlsx&quot;</pre><h3>2. Keyword Cleaning &amp; Validation</h3><h4>Basic cleaning: Remove symbols like ™, ©, etc.</h4><pre>def clean_keyword_simple(kw: str) -&gt; str:<br>    FORBIDDEN_CHARS = set(&quot;™©®✓•→←≠∞¿¡§¶…&quot;)<br>    return &#39;&#39;.join(c for c in kw if c not in FORBIDDEN_CHARS).strip()</pre><h4>Full validation</h4><pre>def is_valid_keyword(kw: str) -&gt; bool:<br>    if not kw or len(kw.split()) &gt; 10 or len(kw) &gt; 80:<br>        return False<br>    # Only allow readable characters<br>    valid_chars = re.findall(r&quot;[a-zA-Z0-9äöüßéèêáàâíìîóòôúùûčšžăîâăëç\- ]&quot;, kw.lower())<br>    ratio = len(valid_chars) / max(len(kw), 1)<br>    return ratio &gt;= 0.7 and len(re.findall(r&quot;[a-zA-Z]&quot;, kw)) &gt;= 3</pre><h4>Filtering + logging invalid keywords</h4><pre>def filter_keywords(keywords, log_file=&quot;invalid_keywords.txt&quot;):<br>    valid = []<br>    invalid = []<br>    for kw in keywords:<br>        kw_clean = clean_keyword_simple(kw).lower()<br>        if is_valid_keyword(kw_clean):<br>            valid.append(kw_clean)<br>        else:<br>            invalid.append(kw)<br>    if invalid:<br>        with open(log_file, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:<br>            f.write(&quot;\n&quot;.join(invalid))<br>    return valid</pre><h3>3. Load Keywords and Create Batches</h3><pre>df = pd.read_excel(return_path())<br>keywords = df[&quot;keyword&quot;].dropna().astype(str).tolist()<br>keywords = filter_keywords(keywords)<br>keyword_batches = [keywords[i:i + BATCH_SIZE] for i in range(0, len(keywords), BATCH_SIZE)]</pre><h3>4. Send Requests to DataForSEO</h3><h4>Payload creation and request sending</h4><pre>def send_batch_request(keywords_batch):<br>    payload = [{<br>        &quot;keywords&quot;: keywords_batch,<br>        &quot;location_code&quot;: LOCATION_CODE,<br>        &quot;language_code&quot;: LANGUAGE_CODE,<br>        &quot;sort_by&quot;: &quot;relevance&quot;,<br>        &quot;date_from&quot;: &quot;2021-06-01&quot;,<br>        &quot;search_partners&quot;: True,<br>        &quot;include_adult_keywords&quot;: False<br>    }]<br>    headers = {<br>        &#39;Authorization&#39;: f&#39;Basic {AUTH_HEADER}&#39;,<br>        &#39;Content-Type&#39;: &#39;application/json&#39;<br>    }<br>    response = requests.post(API_URL, headers=headers, data=json.dumps(payload))<br>    return response.json() if response.status_code == 200 else {}</pre><h4>5. Parse and Save Results</h4><pre>def parse_response(response_json):<br>    keyword_rows, monthly_rows = [], []<br>    for task in response_json.get(&quot;tasks&quot;, []):<br>        for item in task.get(&quot;result&quot;, []):<br>            keyword_rows.append({<br>                &quot;keyword&quot;: item.get(&quot;keyword&quot;),<br>                &quot;search_volume&quot;: item.get(&quot;search_volume&quot;),<br>                &quot;cpc&quot;: item.get(&quot;cpc&quot;),<br>                &quot;competition&quot;: item.get(&quot;competition&quot;)<br>            })<br>            for ms in item.get(&quot;monthly_searches&quot;, []):<br>                monthly_rows.append({<br>                    &quot;keyword&quot;: item[&quot;keyword&quot;],<br>                    &quot;year&quot;: ms[&quot;year&quot;],<br>                    &quot;month&quot;: ms[&quot;month&quot;],<br>                    &quot;search_volume&quot;: ms[&quot;search_volume&quot;]<br>                })<br>    return pd.DataFrame(keyword_rows), pd.DataFrame(monthly_rows)</pre><h3>6. Loop With Retry + Resume</h3><pre>for idx in range(start_index, len(keyword_batches)):<br>    for attempt in range(1, MAX_RETRIES + 1):<br>        try:<br>            response = send_batch_request(keyword_batches[idx])<br>            df_keywords, df_monthly = parse_response(response)<br>            # Append to existing data and save<br>            break<br>        except Exception as e:<br>            time.sleep(RETRY_DELAY)</pre><h3>7. Save Progress &amp; Results</h3><pre>def save_all_to_pickle(results_df, monthly_df, api_costs_df, progress_index, total_api_cost):<br>    data = {<br>        &quot;results&quot;: results_df,<br>        &quot;monthly&quot;: monthly_df,<br>        &quot;api_costs&quot;: api_costs_df,<br>        &quot;status&quot;: {<br>            &quot;progress_index&quot;: progress_index,<br>            &quot;total_cost&quot;: total_api_cost<br>        }<br>    }<br>    pd.to_pickle(data, PICKLE_BACKUP)</pre><pre>with pd.ExcelWriter(&quot;final_results.xlsx&quot;) as writer:<br>    results_df.to_excel(writer, sheet_name=&quot;Keyword Data&quot;, index=False)<br>    monthly_df.to_excel(writer, sheet_name=&quot;Monthly Volume&quot;, index=False)<br>    api_costs_df.to_excel(writer, sheet_name=&quot;API Costs&quot;, index=False)</pre><h3>Bonus: Update Short Time Series Only</h3><pre>def update_short_time_series_keywords(cache_path=&quot;traffic_cache.pkl&quot;):<br>    with open(cache_path, &quot;rb&quot;) as f:<br>        df_all = pickle.load(f)<br>    short_keywords = [kw for kw in df_all.columns if df_all[kw].dropna().shape[0] &lt;= 12]<br>    response_json = send_batch_request(short_keywords)<br>    _, df_monthly = parse_response(response_json)<br>    # Merge updated values back</pre><h3>✅ Summary</h3><p>Component Purpose filter_keywords() Cleans and validates input keywords send_batch_request() Sends keyword batches to DataForSEO API parse_response() Extracts structured data from API response save_all_to_pickle() Saves current progress for resume after crash update_short_time_series_keywords() Optionally fetches full historical volume for incomplete entries final_results.xlsx Your final output with keyword metrics, monthly volumes, and API costs</p><p>To the full github repo:</p><p><a href="https://github.com/AntonioBlago/python_seo_tasks">GitHub - AntonioBlago/python_seo_tasks: A repo with python scripts, useful for SEO tasks. Star the repo and follow me.</a></p><h3>Conclusion</h3><p>By combining DataForSEO’s API with Python, you create a powerful, flexible, and scalable keyword research system—especially for large-scale analysis. This setup is ideal for SEO professionals and analysts looking for automation and transparency without breaking the bank.</p><p>For detailed instructions, code examples, and best practices, check out the original article in German:<br><a href="https://antonioblago.de/seo/guenstigere-alternative-zum-keyword-planner-130k-keywords-mit-python-abrufen/">Günstigere Alternative zum Keyword Planner</a></p><h3>Thank you for being a part of the community</h3><p><em>Before you go:</em></p><ul><li>Be sure to <strong>clap</strong> and <strong>follow</strong> the writer ️👏<strong>️️</strong></li><li>Follow us: <a href="https://x.com/inPlainEngHQ"><strong>X</strong></a> | <a href="https://www.linkedin.com/company/inplainenglish/"><strong>LinkedIn</strong></a> | <a href="https://www.youtube.com/@InPlainEnglish"><strong>YouTube</strong></a> | <a href="https://newsletter.plainenglish.io/"><strong>Newsletter</strong></a> | <a href="https://open.spotify.com/show/7qxylRWKhvZwMz2WuEoua0"><strong>Podcast</strong></a> | <a href="https://twitch.tv/inplainenglish"><strong>Twitch</strong></a></li><li><a href="https://differ.blog/"><strong>Start your own free AI-powered blog on Differ</strong></a> 🚀</li><li><a href="https://discord.gg/in-plain-english-709094664682340443"><strong>Join our content creators community on Discord</strong></a> 🧑🏻‍💻</li><li>For more content, visit <a href="https://plainenglish.io/"><strong>plainenglish.io</strong></a> + <a href="https://stackademic.com/"><strong>stackademic.com</strong></a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ee2b007dfa56" width="1" height="1" alt=""><hr><p><a href="https://python.plainenglish.io/tired-of-keyword-planner-limitations-ee2b007dfa56">Tired of Keyword Planner Limitations?</a> was originally published in <a href="https://python.plainenglish.io/">Python in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automating On-Page SEO & Keyword Analysis — The Smarter Way to Research Your Competition]]></title>
            <link>https://antonioblago.medium.com/automating-on-page-seo-keyword-analysis-the-smarter-way-to-research-your-competition-9205590d2184?source=rss-b37386bbf180------2</link>
            <guid isPermaLink="false">https://medium.com/p/9205590d2184</guid>
            <category><![CDATA[keyword-research]]></category>
            <category><![CDATA[seo-analysis]]></category>
            <category><![CDATA[on-page-seo]]></category>
            <category><![CDATA[seo-automation]]></category>
            <category><![CDATA[seo]]></category>
            <dc:creator><![CDATA[Antonio Blago]]></dc:creator>
            <pubDate>Sun, 06 Jul 2025 10:59:01 GMT</pubDate>
            <atom:updated>2025-07-13T11:01:51.347Z</atom:updated>
            <content:encoded><![CDATA[<h3>Manual SEO analysis is dead weight.</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5JpVlSGfRF8kd4sf" /><figcaption>Photo by <a href="https://unsplash.com/@aedrian?utm_source=medium&amp;utm_medium=referral">Aedrian Salazar</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>If you’ve ever opened 10 browser tabs to inspect titles, meta descriptions, and headings — then pasted keyword usage into spreadsheets just to compare who’s ranking and why — you know what I mean.</p><p>That’s exactly why I built an Apify (<a href="https://apify.com/apify/web-scraper?fpr=dngg1">Check out their webscaper</a>) Actor that automates this entire process. But before we jump into how it works, let’s get clear on what a proper keyword and competition analysis actually requires.</p><h3>Before You Automate: What You Need for a Solid Keyword and Competitor Analysis</h3><p>SEO success begins with clarity.<br>If you&#39;re not sure which keywords to target or why your competitors are ranking, no tool will save you.</p><h3>1. Identify the Right Keywords</h3><p>It’s not just about what gets searched. It’s about why it gets searched and how users behave afterward.</p><p>Key factors:</p><ul><li>Search Intent: Is the user looking to buy, learn, or compare?</li><li>Keyword Types: Head terms, long-tails, semantic variations</li><li>SERP Layout: Does Google show shopping ads, AI Overviews, videos?</li></ul><p>Example:<br>“silver rings” vs. “925 sterling silver women’s ring” — one is broad and vague, the other commercial and specific. Your strategy depends on recognizing that.</p><h3>2. Understand the Competition</h3><p>Ranking is relative.<br>To outrank someone, you need to know what they&#39;re doing — and do it better.</p><p>Here’s what matters:</p><ul><li>Title &amp; Meta Tags: Are they optimized for click-through?</li><li>Headings (H1–H3): Do they align with intent?</li><li>Keyword Usage: Is it natural or stuffed?</li><li>Content Length: Are they ranking with 300 or 3,000 words?</li><li>Entity &amp; Topic Coverage: Do they cover the full context?</li></ul><p>This is the tedious part — and the most important.<br>Until now, this meant hours of manual work.</p><h3>Enter the SEO On-Page Keyword Analyzer (Apify Actor)</h3><p>This tool automates exactly what I described — all in a single run.</p><p>Here’s how it works:</p><h4>Step 1: Input One or Multiple Keywords</h4><p>You can feed in a single keyword or a whole list. The actor runs a live Google search (configurable by country and language) and fetches the top-ranking URLs.</p><h4>Step 2: Scrape Top Competitors</h4><p>For each result, the tool extracts:</p><ul><li>Title &amp; Meta Description</li><li>H1, H2, H3 headings</li><li>Word Count</li><li>Keyword usage</li><li>Internal &amp; outbound link count</li><li>TF-IDF and semantic term analysis</li><li>Entity detection (using NLP)</li></ul><h4>Step 3: Analyze &amp; Compare</h4><p>It then:</p><ol><li>Compares your URL (if provided) to top results</li><li>Highlights missing keywords or weak headings</li><li>Scores your page on content relevance and depth</li><li>Flags overuse or keyword stuffing</li><li>Detects search intent mismatches</li></ol><p>You get structured data, CSV reports, and JSON output — perfect for reporting or feeding into a custom SEO dashboard.</p><h3>Real Use Cases</h3><p>Here’s where the tool shines:</p><ul><li>Auditing a product or category page</li><li>Optimizing blog articles before publishing</li><li>Launching a new landing page and checking alignment</li><li>Reverse-engineering top competitors&#39; strategies</li></ul><p>⚡ Start Now — It’s Live on Apify</p><p>👉 <a href="https://apify.com/antonio_espresso/keyword-competitor-recommendation?fpr=dngg1">Run the SEO On-Page Keyword Analyzer</a></p><p>You can use the actor in n8n, make or zapier automations, schedule runs, connect it to Google Sheets or Notion — or just export and go.<br>It’s like SurferSEO meets Screaming Frog — but lean, fast, and tailored for automation workflows.</p><h3>What’s Next</h3><p>The roadmap includes:</p><ul><li>AI-generated content suggestions</li><li>Competitor change tracking over time</li><li>Integration with GSC &amp; Analytics for performance feedback</li></ul><p>If you’re serious about scaling your SEO with systems — and want more than generic keyword tools — this actor is built for you.</p><p>Connect with me on <a href="https://www.linkedin.com/in/antonioblago">LinkedIn</a>, let me know your thoughts.</p><p>Continue reading similar aritcles:</p><ul><li><a href="https://blog.antonioblago.com/3-simple-ways-to-check-the-http-status-of-any-url-without-losing-your-mind-395b82947b1a">3 Simple Ways to Check the HTTP Status of Any URL (Without Losing Your Mind)</a></li><li><a href="https://blog.antonioblago.com/a-python-solution-to-convert-markdown-formatting-in-make-or-n8n-automations-e45f4addd888">A python solution to convert markdown formatting in make or n8n automations</a></li><li><a href="https://medium.com/r-evolution/maximizing-seo-with-internal-linking-a-guide-to-visualization-and-analysis-with-this-tool-r-8903369f66ee">Maximizing SEO with Internal Linking: A Guide to Visualization and Analysis with this tool &amp; R</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9205590d2184" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[3 Simple Ways to Check the HTTP Status of Any URL (Without Losing Your Mind)]]></title>
            <link>https://antonioblago.medium.com/3-simple-ways-to-check-the-http-status-of-any-url-without-losing-your-mind-395b82947b1a?source=rss-b37386bbf180------2</link>
            <guid isPermaLink="false">https://medium.com/p/395b82947b1a</guid>
            <category><![CDATA[http-status-checker]]></category>
            <category><![CDATA[404-error-page]]></category>
            <category><![CDATA[http-status-code]]></category>
            <category><![CDATA[seo]]></category>
            <dc:creator><![CDATA[Antonio Blago]]></dc:creator>
            <pubDate>Sun, 06 Jul 2025 10:19:35 GMT</pubDate>
            <atom:updated>2025-07-13T11:11:19.266Z</atom:updated>
            <content:encoded><![CDATA[<h3>Whether you’re an SEO, developer, or just tired of broken links ruining your flow, here’s how to check the HTTP status of a URL—fast.</h3><figure><img alt="TikTok 404 page" src="https://cdn-images-1.medium.com/max/1024/0*KR1cE0sbk9u4exiA" /><figcaption>Photo by <a href="https://unsplash.com/@visuals?utm_source=medium&amp;utm_medium=referral">visuals</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>We’ve all been there.</p><p>You click a link, and… nothing.</p><p>A 404.</p><p>Or worse, a sneaky redirect to somewhere unexpected.</p><p>Whether you&#39;re doing technical SEO, maintaining a content-heavy site, or auditing client domains, knowing how to quickly and reliably check the HTTP status of a URL is essential.</p><p>Let me show you three practical methods—ranging from dead simple to programmatic. No fluff, just solid ways to get your status codes right.</p><h3>1. Method: Use Your Browser’s Dev Tools</h3><p>The easiest way to check a URL&#39;s status code is right inside your browser.</p><p>Here’s how:</p><p>Open the site in Chrome or Firefox.</p><p>Right-click → “Inspect”.</p><p>Navigate to the Network tab.</p><p>Refresh the page.</p><p>Click the top request (usually the page URL).</p><p>Look for “Status Code” in the Headers tab.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X7-5Pcg1A-FR9Cv6dmSAFQ.png" /><figcaption>Http status check, screenshot by author Antonio Blago</figcaption></figure><p>Best for: Quick manual checks<br>Not great for: Bulk analysis or redirects that resolve before you can catch them</p><h3>2. Method: Use Command Line with curl</h3><p>For those comfortable with a terminal, curl is a powerful tool.</p><p>Try this:</p><pre>curl -I https://example.com </pre><p>This returns just the headers. You’ll see something like:</p><pre>HTTP/1.1 301 Moved Permanently Location: https://www.example.com/ </pre><p>Add -L to follow redirects:</p><pre>curl -IL https://example.com </pre><p>Best for: Redirect chains, automation, scripting<br>Not great for: Non-technical users or bulk checks</p><h3>3. Method 3: Use a Cloud-Based URL Status Checker</h3><p>Sometimes, you need to go big—like checking 1,000+ URLs. That’s where browser-based methods and curl break down.</p><p>It is relevant for online shops or websites where it is critical when your page is not online for a day.</p><p>Imagine, you have a product, which makes 1 k $ a day. You loose 1 k a day, when this site is not reachable.</p><p>That’s why I created as an <a href="https://antonioblago.de/">E-commerce SEO</a> this actor.</p><p>Instead, let a scraper or cloud tool do the work for you.</p><h4>⚡ My tool: <a href="https://apify.com/antonio_espresso/website-status-code-crawler?fpr=dngg1">Apify HTTP Status Checker</a></h4><p>I built an <a href="https://apify.com/antonio_espresso/website-status-code-crawler?fpr=dngg1">Apify actor</a> that lets you upload a list of URLs and get back a structured report with:</p><ul><li>HTTP status codes</li><li>Final resolved URLs</li><li>Redirect chains</li><li>Broken or timeout URLs</li><li>Export as Excel or JSON</li></ul><p>It runs in the cloud, so no setup needed. Just plug &amp; go.</p><p>You can also use it in n8n or make automations. Here is the blog article in German.</p><p><a href="https://antonioblago.de/automation/status-checker-workflow-mit-n8n-und-apify-eine-schritt-fuer-schritt-anleitung/">https://antonioblago.de/automation/status-checker-workflow-mit-n8n-und-apify-eine-schritt-fuer-schritt-anleitung/</a></p><h3>For n8n</h3><pre>{<br>  &quot;name&quot;: &quot;HTTP Status Checker&quot;,<br>  &quot;nodes&quot;: [<br>    {<br>      &quot;parameters&quot;: {<br>        &quot;url&quot;: &quot;https://api.apify.com/v2/acts/antonio_espresso~website-status-code-crawler/run-sync-get-dataset-items&quot;,<br>        &quot;sendQuery&quot;: true,<br>        &quot;queryParameters&quot;: {<br>          &quot;parameters&quot;: [<br>            {<br>              &quot;name&quot;: &quot;token&quot;,<br>              &quot;value&quot;: &quot;&quot;<br>            }<br>          ]<br>        },<br>        &quot;sendHeaders&quot;: true,<br>        &quot;headerParameters&quot;: {<br>          &quot;parameters&quot;: [<br>            {<br>              &quot;name&quot;: &quot;Content-Type&quot;,<br>              &quot;value&quot;: &quot;application/json&quot;<br>            }<br>          ]<br>        },<br>        &quot;sendBody&quot;: true,<br>        &quot;specifyBody&quot;: &quot;json&quot;,<br>        &quot;jsonBody&quot;: &quot;={\n  \&quot;start_urls\&quot;: [\n    {\n      \&quot;url\&quot;: \&quot;{{ $json.url }}\&quot; \n    }\n  ],\n  \&quot;max_depth\&quot;: 1\n}\n&quot;,<br>        &quot;options&quot;: {}<br>      },<br>      &quot;type&quot;: &quot;n8n-nodes-base.httpRequest&quot;,<br>      &quot;typeVersion&quot;: 4.2,<br>      &quot;position&quot;: [<br>        420,<br>        0<br>      ],<br>      &quot;id&quot;: &quot;9eb4e5b4-12df-40e3-99ed-c254101e31c3&quot;,<br>      &quot;name&quot;: &quot;HTTP Request&quot;,<br>      &quot;notesInFlow&quot;: true<br>    },<br>    {<br>      &quot;parameters&quot;: {<br>        &quot;assignments&quot;: {<br>          &quot;assignments&quot;: [<br>            {<br>              &quot;id&quot;: &quot;83dc4403-6b9e-4e59-94b4-cd8f76a0cc25&quot;,<br>              &quot;name&quot;: &quot;url&quot;,<br>              &quot;value&quot;: &quot;&quot;,<br>              &quot;type&quot;: &quot;string&quot;<br>            }<br>          ]<br>        },<br>        &quot;options&quot;: {}<br>      },<br>      &quot;type&quot;: &quot;n8n-nodes-base.set&quot;,<br>      &quot;typeVersion&quot;: 3.4,<br>      &quot;position&quot;: [<br>        220,<br>        0<br>      ],<br>      &quot;id&quot;: &quot;65fcae99-1fd4-4bce-8b9b-516024b516dd&quot;,<br>      &quot;name&quot;: &quot;Edit Fields&quot;<br>    },<br>    {<br>      &quot;parameters&quot;: {<br>        &quot;rule&quot;: {<br>          &quot;interval&quot;: [<br>            {<br>              &quot;triggerAtHour&quot;: 1<br>            }<br>          ]<br>        }<br>      },<br>      &quot;type&quot;: &quot;n8n-nodes-base.scheduleTrigger&quot;,<br>      &quot;typeVersion&quot;: 1.2,<br>      &quot;position&quot;: [<br>        0,<br>        0<br>      ],<br>      &quot;id&quot;: &quot;bbc97cd7-36ff-460c-bc5d-0efc90ad4aae&quot;,<br>      &quot;name&quot;: &quot;Schedule Trigger&quot;<br>    },<br>    {<br>      &quot;parameters&quot;: {<br>        &quot;chatId&quot;: &quot;1225059959&quot;,<br>        &quot;text&quot;: &quot;={{ $json.message }}&quot;,<br>        &quot;additionalFields&quot;: {}<br>      },<br>      &quot;type&quot;: &quot;n8n-nodes-base.telegram&quot;,<br>      &quot;typeVersion&quot;: 1.2,<br>      &quot;position&quot;: [<br>        840,<br>        0<br>      ],<br>      &quot;id&quot;: &quot;a9c3f06b-112d-4bc0-922d-a4be23feeeca&quot;,<br>      &quot;name&quot;: &quot;Telegram&quot;,<br>      &quot;credentials&quot;: {<br>        &quot;telegramApi&quot;: {<br>          &quot;id&quot;: &quot;&quot;,<br>          &quot;name&quot;: &quot;Telegram account&quot;<br>        }<br>      }<br>    },<br>    {<br>      &quot;parameters&quot;: {<br>        &quot;jsCode&quot;: &quot;// Extract data from input\nconst inputData = $input.first().json;\n\n// Start the message\nlet message = `🚀 *Website Status Code Report* 📊\\n\\n`;\n\n// Add details section\nmessage += `🔍 *Checked URLs:* \\n`;\ninputData.details.forEach(item =&gt; {\n    // Assign ✅ for 200, ❌ for 404, and ⚠️ for other status codes\n    let statusIcon = item.status === 200 ? \&quot;✅\&quot; : item.status === 404 ? \&quot;❌\&quot; : \&quot;⚠️\&quot;;\n    message += `${statusIcon} [${item.url}](${item.url}) → *${item.status}*\\n`;\n});\n\n// Add summary section\nmessage += `\\n📊 *Status Code Summary:* \\n`;\ninputData.overview.forEach(item =&gt; {\n    let statusIcon = item[\&quot;Status Code\&quot;] === 200 ? \&quot;✅\&quot; : item[\&quot;Status Code\&quot;] === 404 ? \&quot;❌\&quot; : \&quot;⚠️\&quot;;\n    message += `${statusIcon} *${item[\&quot;Status Code\&quot;]}:* ${item.Count} occurrences\\n`;\n});\n\n// Final message output\nreturn [{ json: { message } }];\n&quot;<br>      },<br>      &quot;type&quot;: &quot;n8n-nodes-base.code&quot;,<br>      &quot;typeVersion&quot;: 2,<br>      &quot;position&quot;: [<br>        620,<br>        0<br>      ],<br>      &quot;id&quot;: &quot;fb9d2b60-c652-4bb2-93ad-41b75af60577&quot;,<br>      &quot;name&quot;: &quot;Code&quot;<br>    }<br>  ],<br>  &quot;pinData&quot;: {},<br>  &quot;connections&quot;: {<br>    &quot;Edit Fields&quot;: {<br>      &quot;main&quot;: [<br>        [<br>          {<br>            &quot;node&quot;: &quot;HTTP Request&quot;,<br>            &quot;type&quot;: &quot;main&quot;,<br>            &quot;index&quot;: 0<br>          }<br>        ]<br>      ]<br>    },<br>    &quot;Schedule Trigger&quot;: {<br>      &quot;main&quot;: [<br>        [<br>          {<br>            &quot;node&quot;: &quot;Edit Fields&quot;,<br>            &quot;type&quot;: &quot;main&quot;,<br>            &quot;index&quot;: 0<br>          }<br>        ]<br>      ]<br>    },<br>    &quot;HTTP Request&quot;: {<br>      &quot;main&quot;: [<br>        [<br>          {<br>            &quot;node&quot;: &quot;Code&quot;,<br>            &quot;type&quot;: &quot;main&quot;,<br>            &quot;index&quot;: 0<br>          }<br>        ]<br>      ]<br>    },<br>    &quot;Telegram&quot;: {<br>      &quot;main&quot;: [<br>        []<br>      ]<br>    },<br>    &quot;Code&quot;: {<br>      &quot;main&quot;: [<br>        [<br>          {<br>            &quot;node&quot;: &quot;Telegram&quot;,<br>            &quot;type&quot;: &quot;main&quot;,<br>            &quot;index&quot;: 0<br>          }<br>        ]<br>      ]<br>    }<br>  },<br>  &quot;active&quot;: true,<br>  &quot;settings&quot;: {<br>    &quot;executionOrder&quot;: &quot;v1&quot;<br>  },<br>  &quot;versionId&quot;: &quot;8abc4e32-bf8e-4f80-89bf-b5c2c8d12060&quot;,<br>  &quot;meta&quot;: {<br>    &quot;templateCredsSetupCompleted&quot;: true,<br>    &quot;instanceId&quot;: &quot;8765f91253fa7ac79c96beaedfb54b4af7cb62e52733ce474c26d612f6e3136b&quot;<br>  },<br>  &quot;id&quot;: &quot;wR9giX3QROdRaPfI&quot;,<br>  &quot;tags&quot;: []<br>}</pre><p>Perfect for:</p><ul><li>SEO audits</li><li>Redirect mapping</li><li>Broken link detection</li></ul><p>Keeping your site squeaky clean</p><p>👉 Try it out here:</p><p><a href="https://apify.com/antonio_espresso/website-status-code-crawler?fpr=dngg1">https://apify.com/antonio_espresso/website-status-code-crawler</a></p><h3>Final Thoughts</h3><p>The web is messy. Links break. Redirects go rogue. But with the right tools, checking HTTP status codes becomes painless.</p><p>Whether you’re debugging a landing page or mapping legacy redirects, one of these methods will fit your flow.</p><p>And if you need a reliable way to check thousands of URLs without losing your weekend, give my Apify actor a spin.</p><p>✍️ If you found this useful, feel free to share or drop feedback. And if you’re curious about SEO automation, follow me here on Medium or connect on <a href="https://www.linkedin.com/in/antonioblago">LinkedIn</a>.</p><p>Let me know if you’d like me to publish it directly to Medium or help optimize your actor’s landing page for better conversions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=395b82947b1a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A python solution to convert markdown formatting in make or n8n automations]]></title>
            <link>https://antonioblago.medium.com/a-python-solution-to-convert-markdown-formatting-in-make-or-n8n-automations-e45f4addd888?source=rss-b37386bbf180------2</link>
            <guid isPermaLink="false">https://medium.com/p/e45f4addd888</guid>
            <category><![CDATA[social-media]]></category>
            <category><![CDATA[markdown]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[apify]]></category>
            <category><![CDATA[automation]]></category>
            <dc:creator><![CDATA[Antonio Blago]]></dc:creator>
            <pubDate>Sat, 14 Jun 2025 15:45:49 GMT</pubDate>
            <atom:updated>2025-06-14T15:45:49.764Z</atom:updated>
            <content:encoded><![CDATA[<h4><strong><em>TL;DR</em></strong><em> Most AI tools give you neat Markdown, but social networks reward scannable, emoji‑spiced micro‑content. Stop copy‑pasting raw Markdown and work with Python re‑format it for you in one call.</em></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m8qBwREyqzrCIdc07zpdzA.jpeg" /></figure><h3>1 The Hidden Formatting Gap Between Dev &amp; Social Worlds</h3><p><strong>Markdown was born in 2004</strong> to make developer docs readable in raw text <em>and</em> on the web. Great for READMEs, terrible for TikTok carousels. When you paste ## or 1. into LinkedIn, the platform strips the semantics—leaving a grey wall.</p><p>But social feeds are <strong>two‑second first impressions</strong>. Eye‑tracking studies from Nielsen show users scan for:</p><ul><li>Visual anchors (emoji, caps, icons)</li><li>Short 40–70‑character lines</li><li>Negative space that lets the eye rest</li></ul><p>So even if your insight is gold, poor formatting <em>kills dwell time</em> and the algo buries you.</p><p>You ask ChatGPT for a LinkedIn thread or carousel. It replies with pristine Markdown:</p><pre>## 3 Lessons I Learned Scaling to $1M ARR<br>1. **Focus** on one distribution channel until it breaks 😤<br>2. *Talk to users daily* — qualitative beats quantitative in the early days 📞<br>3. Ignore vanity metrics; chase **retention** not followers 🏃‍♂️</pre><p>You paste it into LinkedIn.</p><p><em>No bullet icons, no hierarchy, walls of text.</em> Engagement tanks.</p><p>Why? <strong>Platforms don’t render Markdown</strong>, and humans fly past anything that looks dense. The best posts are:</p><ul><li><strong>Icon‑driven</strong> (🔹 &gt; ◽️ &gt; •)</li><li><strong>Upper‑case highlights</strong> for quick scanning</li><li><strong>Single‑line spacing</strong> — no double blanks</li></ul><p>Yet rewriting every post is a chore, so most creators skip it and settle for mediocre reach.</p><h3>2 The 90‑Second Manual Fix (That You’ll Never Do)</h3><ol><li>Strip ## / 1. / - prefixes</li><li>Replace with emoji bullets</li><li>Upper‑case bold phrases</li><li>Italicise emphasis with underscores</li><li>Convert [links](url) → links (url)</li><li>Pray you didn’t miss a line</li></ol><p>Even with TextExpander snippets this is tedious. <strong>Creators post daily — compound that over a year and you’re wasting weeks.</strong></p><h3>3 One‑Click Conversion With markdown2social</h3><p>Behind the scenes this Apify Actor parses Markdown, then applies <strong>four transformation layers</strong>:</p><ol><li><strong>Structure</strong> → Detect headings, lists, blockquotes.</li><li><strong>Visual hooks</strong> → Swap ## → 🔹, numbers → 1️⃣, bullets → •.</li><li><strong>Emphasis</strong> → Turn **bold** into ALL‑CAPS, *italic* into <em>italic</em>.</li><li><strong>Cleanup</strong> → Collapse double blanks, convert [link](url) → link (url).</li></ol><p>All in under 80 ms on a free plan.</p><p>markdown2social is a tiny Apify Actor that turns any Markdown into social‑optimised text:</p><ul><li><strong>Emoji prefixes</strong> for H1–H3 &amp; lists</li><li><strong>Readable bullets</strong> •, 1️⃣, 🔹</li><li><strong>Inline bold ➜ CAPS</strong>, <em>italics ➜ italics</em></li><li>Removes double line breaks</li><li>Keeps links: Medium (medium.com)</li></ul><h3>Example</h3><p><strong>Input Markdown</strong></p><pre>## Growth secrets<br>1. **Ship daily**<br>2. *Talk to users*<br>&gt; Great products win.</pre><p><strong>Output Text</strong></p><pre>🔹 GROWTH SECRETS<br>1️⃣ SHIP DAILY<br>2️⃣ _Talk to users_<br>“Great products win.”</pre><h3>4 How to Run It — in 3 Lines of Python (plus a no‑code option) — in 3 Lines of Python</h3><h4>Quick-Start “AI Markdown → Social” Automation</h4><p>Below is a <strong>ready-made blueprint</strong> that turns any AI-generated Markdown (ChatGPT, Gemini, etc.) into perfectly formatted social-media text — fully automated, no manual copy-pasting.</p><h3>Components</h3><ol><li><strong>Source</strong></li></ol><ul><li>ChatGPT / Gemini API</li><li>or a Google Sheets row, Webhook, Airtable, etc.</li></ul><p><strong>2. Formatter</strong></p><ul><li><strong>Apify Actor:</strong> markdown2social</li></ul><p><strong>3. Distributor</strong></p><p>Buffer, Hootsuite, Publer, Twitter/X API, etc.</p><p>We’ll wire them together in <strong>Make (Integromat)</strong>, but the same flow works in Zapier or n8n.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/651/1*sP6QlcTcfkOkNaQXz8JlZg.png" /></figure><h3>2 Step-by-Step in Make</h3><p># Module What it does</p><p>1 <strong>Webhook / OpenAI</strong> Receives raw Markdown from your AI</p><p>2 <strong>Apify — Run Actor</strong></p><pre>POST https://api.apify.com/v2/acts/USERNAME~markdown2social/run-sync?token={{APIFY_TOKEN}}</pre><p><strong>Body</strong>:</p><pre>{ &quot;markdown&quot;: &quot;{{1.text}}&quot;, &quot;heading_style&quot;:&quot;title&quot;, &quot;emoji_headings&quot;: true, &quot;emoji_lists&quot;: true }</pre><p>3 <strong>HTTP — Get OUTPUT</strong></p><pre>GET https://api.apify.com/v2/key-value-stores/{{2.defaultKeyValueStoreId}}/records/OUTPUT?token={{APIFY_TOKEN}}</pre><p>4 <strong>Buffer / Twitter / LinkedIn</strong></p><p>Publishes the returned plain-text post</p><p>➡ Total setup time: <strong>~10 minutes</strong>.</p><h3>3 If You Prefer Code</h3><pre>from apify_client import ApifyClient<br>from openai import OpenAI<br><br>ai = OpenAI(api_key=&quot;OPENAI_KEY&quot;)<br>client = ApifyClient(&quot;APIFY_TOKEN&quot;)<br>prompt = &quot;Write a LinkedIn carousel about deep work in Markdown.&quot;<br>md = ai.chat.completions.create(<br>    model=&quot;gpt-4o-mini&quot;,<br>    messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: prompt}]<br>).choices[0].message.content<br>run = client.actor(&quot;USERNAME/markdown2social&quot;).call(<br>    run_input={<br>        &quot;markdown&quot;: md,<br>        &quot;heading_style&quot;: &quot;title&quot;,<br>        &quot;emoji_headings&quot;: True,<br>        &quot;emoji_lists&quot;: True,<br>    }<br>)<br>social = client.key_value_store(run[&quot;defaultKeyValueStoreId&quot;]).get_record(&quot;OUTPUT&quot;)[&quot;value&quot;]<br># ↳ now push `social` to your scheduling tool (Buffer, API, etc.)<br>print(social)</pre><h3>4 Why This Works</h3><ul><li><strong>Structured → Scannable</strong>: Unicode icons &amp; caps make headlines pop.</li><li><strong>Zero Copy-Paste</strong>: AI → Actor → Scheduler in one flow.</li><li><strong>Configurable</strong>: Toggle emojis, heading styles, list formatting.</li></ul><h3>Ready to try?</h3><ol><li>Deploy the Actor (free tier is enough).</li><li>Drop the Make scenario (or Python snippet) in your workflow.</li><li>Watch every AI draft auto-transform into a high-engagement post.</li></ol><p><strong>Stop formatting by hand — let the pipeline do it.</strong></p><pre>from apify_client import ApifyClient<br>import os<br><br># 1)  Get your API token from an env variable or .env file<br>APIFY_TOKEN = os.getenv(&quot;APIFY_TOKEN&quot;, &quot;YOUR_API_TOKEN&quot;)<br>client = ApifyClient(APIFY_TOKEN)<br><br># 2)  Example: AI-generated SEO post in Markdown<br>seo_post = &quot;&quot;&quot;<br># 5 Reasons Your Website Still Isn’t Ranking<br><br>## 1. **Thin Content**<br><br>Google rewards depth, not word count. One killer article beats ten shallow posts.<br><br>## 2. **Slow Page Speed**<br><br>A 3-second delay costs you 40 % of visitors. Compress images, lazy-load scripts.<br><br>## 3. **Weak Internal Links**<br><br>Pages without context are orphans. Link them like a roadmap.<br><br>## 4. **Missing E-E-A-T Signals**<br><br>Show real authors, sources, and expertise or risk dropping off page 1.<br><br>## 5. **No Keyword Mapping**<br><br>Stop guessing. Match search intent to every URL—then own that topic.<br><br>---<br><br>**Ready to outrank competitors?**<br><br>👉  [Book your free SEO audit](https://antonioblago.de/)  <br><br>Or explore the full service line-up on [antonioblago.de](https://antonioblago.de) 🚀<br>&quot;&quot;&quot;<br><br>run_input = {<br>    &quot;markdown&quot;: seo_post,<br>    &quot;heading_style&quot;: &quot;title&quot;,   # Title-case headings<br>    &quot;emoji_headings&quot;: True,     # 🔹 H2 icons, etc.<br>    &quot;emoji_lists&quot;: True         # 1️⃣ 2️⃣ bullets<br>}<br><br># 3)  Replace with your own Actor slug: username/markdown2social<br>ACTOR_SLUG = &quot;username/markdown2social&quot;<br><br>run = client.actor(ACTOR_SLUG).call(run_input=run_input)<br><br># 4)  Fetch result text from the default Key-Value Store<br>store_id = run[&quot;defaultKeyValueStoreId&quot;]<br>output_text = client.key_value_store(store_id).get_record(&quot;OUTPUT&quot;)[&quot;value&quot;]<br><br># 5)  Save locally<br>with open(&quot;seo_social_output.txt&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:<br>    f.write(output_text)<br><br>print(&quot;✅ Saved output to &#39;seo_social_output.txt&#39;&quot;)</pre><p>Prefer no‑code? Use <strong>Make/Integromat → HTTP → Run Actor</strong> and pipe the defaultKeyValueStoreId → OUTPUT record into your social scheduler.</p><p>👉 <a href="https://apify.com/antonio_espresso/markdowntosocialmediapost">Try the Actor for free</a> | 🔗 Visit me on <a href="https://antonioblago.de/">antonioblago.de</a> or <a href="https://www.linkedin.com/in/antonioblago/">follow me on LinkedIn</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e45f4addd888" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Full Tutorial: Token Calculation for Batching and Error Handling in OpenAI API]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://antonioblago.medium.com/full-tutorial-token-calculation-for-batching-and-error-handling-in-openai-api-47821560fc7a?source=rss-b37386bbf180------2"><img src="https://cdn-images-1.medium.com/max/2600/0*BQk4CcGJMtEuvOdY" width="3991"></a></p><p class="medium-feed-snippet">In this tutorial, we&#x2019;ll implement token calculation for batching requests to the OpenAI API while handling rate limits, token usage, and&#x2026;</p><p class="medium-feed-link"><a href="https://antonioblago.medium.com/full-tutorial-token-calculation-for-batching-and-error-handling-in-openai-api-47821560fc7a?source=rss-b37386bbf180------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://antonioblago.medium.com/full-tutorial-token-calculation-for-batching-and-error-handling-in-openai-api-47821560fc7a?source=rss-b37386bbf180------2</link>
            <guid isPermaLink="false">https://medium.com/p/47821560fc7a</guid>
            <category><![CDATA[api]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[openai]]></category>
            <category><![CDATA[token]]></category>
            <category><![CDATA[batching]]></category>
            <dc:creator><![CDATA[Antonio Blago]]></dc:creator>
            <pubDate>Wed, 25 Sep 2024 23:14:33 GMT</pubDate>
            <atom:updated>2024-09-25T23:26:07.436Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[How to Implement reCAPTCHA v3 with Flask to Prevent Bot Submissions]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://python.plainenglish.io/how-to-implement-recaptcha-v3-with-flask-to-prevent-bot-submissions-48afa9c75d76?source=rss-b37386bbf180------2"><img src="https://cdn-images-1.medium.com/max/2600/0*jetFYMt_vLYaiAzc" width="3840"></a></p><p class="medium-feed-snippet">In this tutorial, we&#x2019;re going to walk through the process of integrating Google&#x2019;s reCAPTCHA v3 into a web form using Flask, a lightweight&#x2026;</p><p class="medium-feed-link"><a href="https://python.plainenglish.io/how-to-implement-recaptcha-v3-with-flask-to-prevent-bot-submissions-48afa9c75d76?source=rss-b37386bbf180------2">Continue reading on Python in Plain English »</a></p></div>]]></description>
            <link>https://python.plainenglish.io/how-to-implement-recaptcha-v3-with-flask-to-prevent-bot-submissions-48afa9c75d76?source=rss-b37386bbf180------2</link>
            <guid isPermaLink="false">https://medium.com/p/48afa9c75d76</guid>
            <category><![CDATA[recaptcha-v3]]></category>
            <category><![CDATA[bots]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[flask]]></category>
            <category><![CDATA[recaptcha]]></category>
            <dc:creator><![CDATA[Antonio Blago]]></dc:creator>
            <pubDate>Fri, 29 Dec 2023 11:55:48 GMT</pubDate>
            <atom:updated>2023-12-29T14:28:40.072Z</atom:updated>
        </item>
    </channel>
</rss>