<?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: ashk asef</title>
    <description>The latest articles on DEV Community by ashk asef (@ashk_asef_b70c07a3f73a15e).</description>
    <link>https://dev.to/ashk_asef_b70c07a3f73a15e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4048041%2Ffbb26ec8-793a-473c-b9c1-d128dec2606d.png</url>
      <title>DEV Community: ashk asef</title>
      <link>https://dev.to/ashk_asef_b70c07a3f73a15e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ashk_asef_b70c07a3f73a15e"/>
    <language>en</language>
    <item>
      <title>Fetching Instagram Data Concurrently in Python with HikerAPI</title>
      <dc:creator>ashk asef</dc:creator>
      <pubDate>Sun, 26 Jul 2026 14:39:01 +0000</pubDate>
      <link>https://dev.to/ashk_asef_b70c07a3f73a15e/fetching-instagram-data-concurrently-in-python-with-hikerapi-4m5a</link>
      <guid>https://dev.to/ashk_asef_b70c07a3f73a15e/fetching-instagram-data-concurrently-in-python-with-hikerapi-4m5a</guid>
      <description>&lt;p&gt;When I started collecting  at , a simple sequential loop quickly became the bottleneck. Even if each request only takes a fraction of a second, hundreds or thousands of API calls add up.&lt;/p&gt;

&lt;p&gt;The solution wasn't making each request faster—it was making many requests at the same time.&lt;/p&gt;

&lt;p&gt;In this article I'll show the simple progression I followed: start with a single request, then parallelize it with threads, discuss an async alternative, and finish with some practical rate-limit handling.&lt;/p&gt;

&lt;p&gt;For this example I'm using HikerAPI, but the concurrency patterns apply to most REST APIs.&lt;/p&gt;

&lt;p&gt;The baseline&lt;/p&gt;

&lt;p&gt;Everything starts with a single request:&lt;/p&gt;

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

&lt;p&gt;headers = {"x-access-key": "YOUR_KEY"}&lt;/p&gt;

&lt;p&gt;resp = requests.get(&lt;br&gt;
    "&lt;a href="https://api.hikerapi.com/v2/hashtag/medias/top" rel="noopener noreferrer"&gt;https://api.hikerapi.com/v2/hashtag/medias/top&lt;/a&gt;",&lt;br&gt;
    params={"name": "travel"},&lt;br&gt;
    headers=headers,&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;print(resp.json())&lt;/p&gt;

&lt;p&gt;This is perfectly fine if you're making one request.&lt;/p&gt;

&lt;p&gt;It becomes slow when you need dozens or hundreds.&lt;/p&gt;

&lt;p&gt;Parallelizing with ThreadPoolExecutor&lt;/p&gt;

&lt;p&gt;Since HTTP requests spend most of their time waiting on the network, Python threads work well here.&lt;/p&gt;

&lt;p&gt;import requests&lt;br&gt;
from concurrent.futures import ThreadPoolExecutor, as_completed&lt;/p&gt;

&lt;p&gt;headers = {"x-access-key": "YOUR_KEY"}&lt;/p&gt;

&lt;p&gt;hashtags = [&lt;br&gt;
    "travel",&lt;br&gt;
    "nature",&lt;br&gt;
    "food",&lt;br&gt;
    "fitness",&lt;br&gt;
    "photography",&lt;br&gt;
    "technology",&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;def fetch_hashtag(name):&lt;br&gt;
    response = requests.get(&lt;br&gt;
        "&lt;a href="https://api.hikerapi.com/v2/hashtag/medias/top" rel="noopener noreferrer"&gt;https://api.hikerapi.com/v2/hashtag/medias/top&lt;/a&gt;",&lt;br&gt;
        params={"name": name},&lt;br&gt;
        headers=headers,&lt;br&gt;
        timeout=30,&lt;br&gt;
    )&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;response.raise_for_status()

return {
    "hashtag": name,
    "data": response.json(),
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;p&gt;with ThreadPoolExecutor(max_workers=5) as executor:&lt;br&gt;
    futures = [executor.submit(fetch_hashtag, tag) for tag in hashtags]&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for future in as_completed(futures):
    results.append(future.result())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;print(f"Fetched {len(results)} hashtag responses.")&lt;/p&gt;

&lt;p&gt;The nice thing about this approach is that the rest of my code barely changes. I still use the familiar requests library while making multiple API calls concurrently.&lt;/p&gt;

&lt;p&gt;Async is another option&lt;/p&gt;

&lt;p&gt;If I'm already using an async application (FastAPI, asyncio workers, etc.), I'd probably use httpx.AsyncClient or aiohttp instead of threads.&lt;/p&gt;

&lt;p&gt;The overall idea stays the same:&lt;/p&gt;

&lt;p&gt;create one HTTP client&lt;br&gt;
schedule many requests&lt;br&gt;
wait for all of them together&lt;br&gt;
process the results&lt;/p&gt;

&lt;p&gt;Async usually integrates more naturally into async applications, while threads are often the quickest upgrade for existing synchronous code.&lt;/p&gt;

&lt;p&gt;Handle rate limits gracefully&lt;/p&gt;

&lt;p&gt;Concurrency doesn't mean "send unlimited requests."&lt;/p&gt;

&lt;p&gt;Every API has limits, and increasing the number of workers too aggressively usually hurts reliability instead of improving throughput.&lt;/p&gt;

&lt;p&gt;A few practices have worked well for me:&lt;/p&gt;

&lt;p&gt;Keep the worker count conservative (for example, 5–10 to start).&lt;br&gt;
Retry temporary failures with exponential backoff.&lt;br&gt;
Respect HTTP 429 responses.&lt;br&gt;
Add request timeouts.&lt;br&gt;
Log failed requests so they can be retried later.&lt;/p&gt;

&lt;p&gt;Here's a simple retry example:&lt;/p&gt;

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

&lt;p&gt;def fetch_with_retry(url, params, headers, retries=3):&lt;br&gt;
    for attempt in range(retries):&lt;br&gt;
        response = requests.get(&lt;br&gt;
            url,&lt;br&gt;
            params=params,&lt;br&gt;
            headers=headers,&lt;br&gt;
            timeout=30,&lt;br&gt;
        )&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    if response.status_code == 429:
        wait = 2 ** attempt
        time.sleep(wait)
        continue

    response.raise_for_status()
    return response.json()

raise RuntimeError("Maximum retries exceeded.")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The important part isn't just retrying—it's slowing down after receiving a rate-limit response instead of immediately sending more requests.&lt;/p&gt;

&lt;p&gt;Choosing the right concurrency level&lt;/p&gt;

&lt;p&gt;It's tempting to keep increasing the worker count until requests stop getting faster.&lt;/p&gt;

&lt;p&gt;In practice, there's usually a sweet spot.&lt;/p&gt;

&lt;p&gt;Too few workers leave bandwidth unused.&lt;/p&gt;

&lt;p&gt;Too many workers increase contention, retries, and rate-limit responses.&lt;/p&gt;

&lt;p&gt;I usually start small, monitor success rates and latency, then increase concurrency gradually until additional workers stop improving overall throughput.&lt;/p&gt;

&lt;p&gt;Final thoughts&lt;/p&gt;

&lt;p&gt;Once I started collecting  at , sequential requests simply didn't scale well enough.&lt;/p&gt;

&lt;p&gt;Using a small thread pool dramatically reduced total execution time without making the code much more complicated. If my project were already async, I'd use an async HTTP client instead, but the underlying principle would be exactly the same: keep multiple network requests in flight while respecting the API's rate limits.&lt;/p&gt;

&lt;p&gt;Concurrency isn't about overwhelming an API—it's about making efficient use of the time your application would otherwise spend waiting for network responses.&lt;/p&gt;

&lt;p&gt;I'm part of HikerAPI's user-rewards program — they credit my account for posts like this. Sharing my actual experience.&lt;/p&gt;

</description>
      <category>python</category>
      <category>api</category>
    </item>
    <item>
      <title>Why I Stopped Self-Hosting My Instagram Scraper</title>
      <dc:creator>ashk asef</dc:creator>
      <pubDate>Sun, 26 Jul 2026 14:35:07 +0000</pubDate>
      <link>https://dev.to/ashk_asef_b70c07a3f73a15e/why-i-stopped-self-hosting-my-instagram-scraper-2c37</link>
      <guid>https://dev.to/ashk_asef_b70c07a3f73a15e/why-i-stopped-self-hosting-my-instagram-scraper-2c37</guid>
      <description>&lt;h1&gt;
  
  
  Why I Stopped Self-Hosting My Instagram Scraper
&lt;/h1&gt;

&lt;p&gt;For a long time, I assumed self-hosting was the "right" way to build anything that interacted with Instagram. If I controlled the infrastructure, I could customize everything, avoid vendor lock-in, and potentially save money.&lt;/p&gt;

&lt;p&gt;That assumption lasted until my project became more than a weekend experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the scraper became the project
&lt;/h2&gt;

&lt;p&gt;I originally built &lt;strong&gt;&lt;/strong&gt; using &lt;strong&gt;instagrapi&lt;/strong&gt; because it gave me direct access to Instagram without relying on a hosted service. It worked well enough in development, and getting the first features running was surprisingly straightforward.&lt;/p&gt;

&lt;p&gt;The problems started once I wanted the system to run consistently.&lt;/p&gt;

&lt;p&gt;Instead of focusing on building product features, I found myself spending more and more time dealing with infrastructure problems. Login sessions expired. Accounts required verification. Some requests suddenly stopped working after platform changes. Every time I thought everything was stable, something else needed attention.&lt;/p&gt;

&lt;p&gt;The scraper slowly became a project of its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What kept breaking
&lt;/h2&gt;

&lt;p&gt;None of these issues were impossible to solve individually, but together they created a steady maintenance burden.&lt;/p&gt;

&lt;p&gt;Some of the recurring problems included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login sessions expiring unexpectedly.&lt;/li&gt;
&lt;li&gt;Account challenges and verification requests.&lt;/li&gt;
&lt;li&gt;Temporary rate limits.&lt;/li&gt;
&lt;li&gt;Occasional account restrictions or bans.&lt;/li&gt;
&lt;li&gt;Library updates following Instagram changes.&lt;/li&gt;
&lt;li&gt;Monitoring multiple accounts and keeping them healthy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My biggest frustration wasn't that things broke—it was that they broke unpredictably.&lt;/p&gt;

&lt;p&gt;I'd fix one issue, only to discover another one a week later. The actual business logic for &lt;strong&gt;&lt;/strong&gt; became the easy part. Keeping the data pipeline alive was the difficult part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking for a different approach
&lt;/h2&gt;

&lt;p&gt;Eventually I started asking myself whether I actually wanted to maintain Instagram scraping infrastructure.&lt;/p&gt;

&lt;p&gt;The answer was no.&lt;/p&gt;

&lt;p&gt;I wanted to build &lt;strong&gt;&lt;/strong&gt;, not become an expert in session cookies, account recovery, and scraper maintenance.&lt;/p&gt;

&lt;p&gt;That's when I decided to try &lt;strong&gt;&lt;a href="https://hikerapi.com/?utm_source=user_post&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rewards_v1&amp;amp;utm_content=default&amp;amp;utm_term=12704" rel="noopener noreferrer"&gt;HikerAPI&lt;/a&gt;&lt;/strong&gt;, a hosted REST API for Instagram.&lt;/p&gt;

&lt;p&gt;The migration wasn't really about adding features.&lt;/p&gt;

&lt;p&gt;It was about removing operational work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed after migrating
&lt;/h2&gt;

&lt;p&gt;The biggest improvement wasn't performance or speed.&lt;/p&gt;

&lt;p&gt;It was simplicity.&lt;/p&gt;

&lt;p&gt;Instead of maintaining accounts and scraper infrastructure myself, my application could simply make HTTP requests and consume structured JSON responses.&lt;/p&gt;

&lt;p&gt;A minimal example looks like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-access-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;user&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.hikerapi.com/v2/user/by/username?username=google&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;resp&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.hikerapi.com/v2/user/followers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That reduced a surprising amount of complexity in my backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoffs
&lt;/h2&gt;

&lt;p&gt;Moving to a hosted API isn't a perfect solution.&lt;/p&gt;

&lt;p&gt;The most obvious downside is cost. Running your own scraper can be cheaper if you're operating at very low volume and don't mind maintaining it yourself. A hosted service also means depending on another company, which reduces some of the control you have over your stack.&lt;/p&gt;

&lt;p&gt;If you enjoy optimizing infrastructure or need highly customized behavior, self-hosting may still be the better option.&lt;/p&gt;

&lt;p&gt;On the other hand, if your goal is shipping product features, paying for managed infrastructure can be a reasonable tradeoff.&lt;/p&gt;

&lt;p&gt;For me, it came down to opportunity cost.&lt;/p&gt;

&lt;p&gt;Every hour spent debugging logins or recovering accounts was an hour I wasn't improving &lt;strong&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would I make the same decision again?
&lt;/h2&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;If I were starting from scratch today, I would spend less time building scraping infrastructure and more time building the product itself.&lt;/p&gt;

&lt;p&gt;That doesn't mean self-hosting is the wrong approach. It's an excellent way to learn how these systems work, and for some projects it's still the right choice.&lt;/p&gt;

&lt;p&gt;But once maintenance became the largest consumer of my development time, I realized I had optimized for the wrong thing.&lt;/p&gt;

&lt;p&gt;Today I'd rather invest my time in features users actually notice than in fixing &lt;strong&gt;&lt;/strong&gt; behind the scenes.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;I'm part of HikerAPI's user-rewards program — they credit my account for posts like this. Sharing my actual experience.&lt;/p&gt;
&lt;/blockquote&gt;

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