<?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: tutorial</title>
    <description>The latest articles tagged 'tutorial' on DEV Community.</description>
    <link>https://dev.to/t/tutorial</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/tutorial"/>
    <language>en</language>
    <item>
      <title>Morse Code Translator — Free Online Tool</title>
      <dc:creator>BAOMING</dc:creator>
      <pubDate>Tue, 02 Jun 2026 04:31:20 +0000</pubDate>
      <link>https://dev.to/baoming/morse-code-translator-free-online-tool-5f99</link>
      <guid>https://dev.to/baoming/morse-code-translator-free-online-tool-5f99</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7w4heabd42970pb79zzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7w4heabd42970pb79zzd.png" alt="Morse Code Translator at Scoreroute" width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;title: "Morse Code Translator"&lt;br&gt;
canonical_url: &lt;a href="https://tools.scoreroute.com/tools/morse-code-translator/" rel="noopener noreferrer"&gt;https://tools.scoreroute.com/tools/morse-code-translator/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  tags: webdev, programming, tutorial, productivity
&lt;/h2&gt;

&lt;h1&gt;
  
  
  Morse Code Translator — Free Online Tool
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Quick Answer
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://tools.scoreroute.com/tools/morse-code-translator/" rel="noopener noreferrer"&gt;Morse Code Translator&lt;/a&gt; and get what you need instantly. Free. No signup. No download. Everything stays in your browser.&lt;/p&gt;

&lt;p&gt;The Morse Code Translator is a useful tool that converts text into Morse Code and vice versa, which greatly simplifies communication into Morse Code. Ham radio operators and sailors benefit from this tool as they use it to send and receive messages in Morse Code. Students who learn about Morse Code also use the Translator. The Translator's user interface is simple and intuitive, which also allows beginners to get started immediately. A major advantage is the quick and easy conversion of text into Morse Code, which takes place with just a few clicks. In addition, the Translator supports several languages, such as English, Spanish and French, making it a valuable tool for people who need to communicate in different languages. The output can be copied with a click on the clipboard, which additionally speeds up the workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7w4heabd42970pb79zzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7w4heabd42970pb79zzd.png" alt="Morse Code Translator tool interface" width="799" height="562"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69vkep1hgwyrkgunrywi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69vkep1hgwyrkgunrywi.png" alt="Morse Code Translator tool interface" width="799" height="562"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpb5p5grawpu6shibar6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpb5p5grawpu6shibar6.png" alt="Morse Code Translator tool interface" width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is this tool really free?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Yes. No credit card, no trial, no premium upsells. Free now and free forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to create an account?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; No. Open the page and start. No email, no password, no signup required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use this on my phone or tablet?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Yes. The tool works on any modern browser and adjusts to your screen size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Free Tools at Scoreroute
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://tools.scoreroute.com/tools/stock-tracker/" rel="noopener noreferrer"&gt;&lt;strong&gt;Stock Tracker&lt;/strong&gt;&lt;/a&gt; — free browser-based tool&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tools.scoreroute.com/tools/background-remover/" rel="noopener noreferrer"&gt;&lt;strong&gt;Background Remover&lt;/strong&gt;&lt;/a&gt; — free browser-based tool&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tools.scoreroute.com/tools/tip-calculator/" rel="noopener noreferrer"&gt;&lt;strong&gt;Tip Calculator&lt;/strong&gt;&lt;/a&gt; — free browser-based tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://tools.scoreroute.com/tools/morse-code-translator/" rel="noopener noreferrer"&gt;&lt;strong&gt;Use Morse Code Translator now&lt;/strong&gt;&lt;/a&gt; — free and instant.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The &lt;a href="https://tools.scoreroute.com" rel="noopener noreferrer"&gt;tools.scoreroute.com&lt;/a&gt; platform adds new developer tools regularly. All tools are free, browser-based, and private. Updated 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Build a Meeting Minutes AI From Raw Audio</title>
      <dc:creator>M TOQEER ZIA</dc:creator>
      <pubDate>Tue, 02 Jun 2026 04:23:16 +0000</pubDate>
      <link>https://dev.to/m_toqeer/build-a-meeting-minutes-ai-from-raw-audio-42en</link>
      <guid>https://dev.to/m_toqeer/build-a-meeting-minutes-ai-from-raw-audio-42en</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A complete walkthrough of speech transcription, LLM inference, tokenization, and 4-bit quantization. Built with Whisper, Llama 3.2, and the HuggingFace ecosystem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Skill level:&lt;/strong&gt; Intermediate  |  &lt;strong&gt;Runtime:&lt;/strong&gt; Google Colab T4 GPU  |  &lt;strong&gt;Models:&lt;/strong&gt; Whisper-medium, Llama-3.2-3B&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Two-Step Pipeline&lt;/li&gt;
&lt;li&gt;Tokenization&lt;/li&gt;
&lt;li&gt;Quantization&lt;/li&gt;
&lt;li&gt;The Chat Template&lt;/li&gt;
&lt;li&gt;Neural Networks and Transformers&lt;/li&gt;
&lt;li&gt;Tradeoffs&lt;/li&gt;
&lt;li&gt;What You Can Build Next&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Two-Step Pipeline
&lt;/h2&gt;

&lt;p&gt;You feed an audio file into a Python script. Minutes later, you get formatted meeting minutes, a summary, and action items. No manual transcription, no human editor.&lt;/p&gt;

&lt;p&gt;The notebook splits the problem into two clean stages. Stage one converts audio to text. Stage two converts text to structured meeting minutes.&lt;/p&gt;



&lt;p&gt;

  
    
      
    
  
  
  
  denver_extract.mp3
  
  
  
  
  Whisper ASR model
  
  
  
  
  Raw transcript text
  
  
  
  
  Llama 3.2 (4-bit)
  
  
  
  
  Formatted minutes

&lt;/p&gt;



&lt;p&gt;Both stages run locally on a free Colab T4 GPU. Stage one uses Whisper from OpenAI (or the API version). Stage two uses Meta's Llama 3.2 3B, loaded with 4-bit quantization so it fits in GPU memory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is ASR?&lt;/strong&gt; Automatic Speech Recognition converts raw audio waveforms into text. Whisper treats audio as a sequence prediction problem: it predicts the next token given all previous audio frames.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Two Transcription Options
&lt;/h3&gt;

&lt;p&gt;The notebook gives you two paths. The open-source path runs Whisper locally on the GPU. The API path sends the audio to OpenAI's servers.&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;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;

&lt;span class="c1"&gt;# Open-source: runs locally on T4 GPU
&lt;/span&gt;&lt;span class="n"&gt;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;automatic-speech-recognition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai/whisper-medium.en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cuda&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_timestamps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio_filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;transcription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# API option: offloads to OpenAI servers
&lt;/span&gt;&lt;span class="n"&gt;AUDIO_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini-transcribe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;transcription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transcriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&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;AUDIO_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;audio_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&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;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Open-source path&lt;/th&gt;
&lt;th&gt;API path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Per minute of audio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data privacy&lt;/td&gt;
&lt;td&gt;Stays local&lt;/td&gt;
&lt;td&gt;Leaves your environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;Needs GPU&lt;/td&gt;
&lt;td&gt;Works anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline use&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Tokenization: How Text Becomes Numbers
&lt;/h2&gt;

&lt;p&gt;Language models do not read text. They read numbers. Tokenization is the bridge between the two.&lt;/p&gt;

&lt;p&gt;A tokenizer splits your text into subword chunks called tokens, then maps each chunk to an integer ID. The word "quantization" might split into tokens like &lt;code&gt;["quant", "ization"]&lt;/code&gt;, producing IDs like &lt;code&gt;[42891, 2065]&lt;/code&gt;. The model works with these integer sequences from start to finish.&lt;/p&gt;



&lt;p&gt;

  
    
      
    
  
  
  raw text
  split into tokens
  integer IDs
  GPU tensor
  
  
  "Please summarize..."
  
  
  Tokenizer splits
  
  
  [9522, 35481, ...]
  
  
  GPU tensor (.cuda)

&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LLAMA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pad_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eos_token&lt;/span&gt;

&lt;span class="c1"&gt;# apply_chat_template formats your messages list
# into the exact token sequence Llama expects
&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_chat_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_tensors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;pad_token&lt;/code&gt; matters:&lt;/strong&gt; When you process multiple sequences in a batch, they need equal length. Shorter sequences get padded with a special token. Setting &lt;code&gt;pad_token = eos_token&lt;/code&gt; tells the tokenizer which ID to use. Without it, the tokenizer raises an error during batched inference.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The tokenizer also inserts special tokens like &lt;code&gt;&amp;lt;|begin_of_text|&amp;gt;&lt;/code&gt; and role markers for &lt;code&gt;system&lt;/code&gt; and &lt;code&gt;user&lt;/code&gt;. These are instructions to the model, not content. &lt;code&gt;apply_chat_template&lt;/code&gt; handles all of this based on the model's expected format.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quantization: Shrinking the Model to Fit
&lt;/h2&gt;

&lt;p&gt;A 3B parameter model in full 32-bit precision needs around 12 GB of GPU memory. A free T4 has 15 GB total. 4-bit quantization compresses each weight from 32 bits down to 4 bits, cutting memory use by roughly 8x.&lt;/p&gt;



&lt;p&gt;

  
  Full precision (FP32)
  
  ~12 GB
  
  4-bit NF4 (BitsAndBytes)
  
  ~2 GB — fits on free T4
  
  
  0 GB
  15 GB (T4 limit)

&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;quant_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BitsAndBytesConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;load_in_4bit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bnb_4bit_use_double_quant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# quantize the quantization constants too
&lt;/span&gt;    &lt;span class="n"&gt;bnb_4bit_compute_dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bfloat16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# compute in bfloat16 for speed
&lt;/span&gt;    &lt;span class="n"&gt;bnb_4bit_quant_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nf4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;           &lt;span class="c1"&gt;# NormalFloat4: better for normal distributions
&lt;/span&gt;&lt;span class="p"&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;AutoModelForCausalLM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;LLAMA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;device_map&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;quantization_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;quant_config&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What NF4 means:&lt;/strong&gt; NormalFloat4 is a 4-bit data type designed for neural network weights, which typically follow a normal distribution. It places more quantization levels near zero (where most weights cluster) and fewer at the extremes. This beats a naive 4-bit integer scheme in accuracy on nearly every benchmark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Double quantization&lt;/strong&gt; (&lt;code&gt;bnb_4bit_use_double_quant=True&lt;/code&gt;) quantizes the quantization constants themselves too. It saves about 0.4 bits per parameter on top of base 4-bit compression. Small gain, no cost.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;device_map="auto"&lt;/code&gt; tells HuggingFace which layers go on GPU versus CPU. For a 3B model on a T4, everything fits on GPU.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Chat Template: Speaking the Model's Language
&lt;/h2&gt;

&lt;p&gt;Instruction-tuned models like Llama 3.2 Instruct were fine-tuned on conversations in a specific format. Send text in the wrong format and the model either ignores your instructions or produces garbage. The chat template enforces the right format every time.&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="n"&gt;system_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You produce minutes of meetings from transcripts, with summary,
key discussion points, takeaways and action items with owners,
in markdown format without code blocks.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;user_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;
Below is an extract transcript of a Denver council meeting.
Please write minutes in markdown without code blocks, including:
- a summary with attendees, location and date
- discussion points
- takeaways
- action items with owners

Transcription:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;transcription&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="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;role&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;system&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_prompt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# apply_chat_template wraps messages in Llama's expected token format
&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_chat_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_tensors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;System vs user roles:&lt;/strong&gt; The system message sets the model's persona and output constraints. The user message contains the actual task. Keeping them separate gives you fine-grained control: swap the transcript without touching output format instructions, or change the output format without touching the transcript.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After &lt;code&gt;apply_chat_template&lt;/code&gt;, your clean Python dictionaries become a single integer tensor. That tensor goes directly into &lt;code&gt;generate()&lt;/code&gt;. No string manipulation after this point — everything is numbers on the GPU.&lt;/p&gt;




&lt;h2&gt;
  
  
  Neural Networks and Transformer Quantization
&lt;/h2&gt;

&lt;p&gt;A transformer model is a stack of layers. Each layer contains weight matrices stored as 2D arrays of floating point numbers. During a forward pass, the model multiplies your input token embeddings by these matrices over and over, applying attention at each step.&lt;/p&gt;



&lt;p&gt;

  
    
      
    
  
  
  
  Input tokens [IDs]

  
  Embedding lookup

  
  Attention layer x N

  
  Feed-forward layer x N

  
  
  
  

  
  Integer token IDs from the tokenizer
  Integers become dense float vectors
  Tokens attend to each other;
  context and meaning is built
  Matrix multiplications refine
  each position's representation

  
  

&lt;/p&gt;



&lt;p&gt;The weight matrices in feed-forward layers are what quantization compresses. At inference time, BitsAndBytes dequantizes each weight block just before the matrix multiplication, performs the multiplication in &lt;code&gt;bfloat16&lt;/code&gt;, then moves on. The full 4-bit weights stay compressed in GPU memory at all times.&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="n"&gt;streamer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextStreamer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;outputs&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&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;streamer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;streamer&lt;/span&gt;  &lt;span class="c1"&gt;# streams tokens to stdout as generated
&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;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;TextStreamer&lt;/code&gt; prints tokens to the console as the model generates them. The model produces one token per forward pass. You see output build word by word because each word triggers a separate forward pass through all layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tradeoffs to Know Before You Ship
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Whisper medium vs large
&lt;/h3&gt;

&lt;p&gt;The notebook uses &lt;code&gt;whisper-medium.en&lt;/code&gt;. The &lt;code&gt;.en&lt;/code&gt; suffix means English-only. It runs faster and uses less memory than the multilingual version. If your meetings include non-English speakers, swap to &lt;code&gt;whisper-large-v3&lt;/code&gt; and expect roughly 3x more GPU memory usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  3B vs larger Llama models
&lt;/h3&gt;

&lt;p&gt;Llama 3.2 3B handles summarization well. For long meetings with complex technical jargon, a 70B model produces more accurate action items. You cannot run 70B on a free T4, even with 4-bit quantization. You need either a paid Colab Pro instance or API inference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Float16 vs bfloat16
&lt;/h3&gt;

&lt;p&gt;Whisper runs in &lt;code&gt;torch.float16&lt;/code&gt;. Llama's quantized compute runs in &lt;code&gt;bfloat16&lt;/code&gt;. Both are 16-bit formats. Float16 has higher precision for small values. Bfloat16 has a wider dynamic range and is less prone to overflow on modern hardware.&lt;/p&gt;



&lt;p&gt;

  
  
  WORKS WELL WHEN
  + Meetings under 60 minutes
  + Clear audio, minimal noise
  + English-language meetings
  + Action items per speaker
  + Single speaker segments
  
  
  WHERE IT STRUGGLES
  - Multiple simultaneous speakers
  - Domain jargon without fine-tuning
  - Audio files over ~100 MB
  - Accuracy without diarization
  - Non-English meetings

&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Colab CUDA error that trips everyone up:&lt;/strong&gt; If you see &lt;code&gt;CUDA is required but not available for bitsandbytes&lt;/code&gt;, your runtime was recycled by Google. Fix: Kernel menu, Disconnect and delete runtime. Reconnect to a fresh T4. Rerun from the top. Do not touch package versions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What You Can Build Next
&lt;/h2&gt;

&lt;p&gt;The notebook is a foundation. A few extensions that follow naturally:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speaker diarization.&lt;/strong&gt; Add &lt;code&gt;pyannote-audio&lt;/code&gt; before the Whisper step to tag each segment with a speaker ID. Feed those labels into the prompt so Llama assigns action items to the right person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gradio streaming interface.&lt;/strong&gt; Student Emad S. adapted the notebook to stream tokens into a Gradio UI using &lt;code&gt;TextIteratorStreamer&lt;/code&gt; and Python background threads. The result is a browser-based app where you upload audio and watch minutes appear in real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistent storage.&lt;/strong&gt; Write the output to a Google Doc via the Drive API. Every meeting auto-archives with a timestamp and a searchable transcript.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MeetingBank dataset&lt;/td&gt;
&lt;td&gt;&lt;a href="https://huggingface.co/datasets/huuuyeah/meetingbank" rel="noopener noreferrer"&gt;huuuyeah/meetingbank on HuggingFace&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audio dataset&lt;/td&gt;
&lt;td&gt;&lt;a href="https://huggingface.co/datasets/huuuyeah/MeetingBank_Audio/tree/main" rel="noopener noreferrer"&gt;MeetingBank Audio&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Denver extract MP3&lt;/td&gt;
&lt;td&gt;&lt;a href="https://drive.google.com/file/d/1N_kpSojRR5RYzupz6nqM8hMSoEF_R7pU/view?usp=sharing" rel="noopener noreferrer"&gt;Google Drive&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whisper model&lt;/td&gt;
&lt;td&gt;&lt;code&gt;openai/whisper-medium.en&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama model&lt;/td&gt;
&lt;td&gt;&lt;code&gt;meta-llama/Llama-3.2-3B-Instruct&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradio variation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://colab.research.google.com/drive/1Ja5zyniyJo5y8s1LKeCTSkB2xyDPOt6D" rel="noopener noreferrer"&gt;Emad S. Colab&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;Source dataset: MeetingBank. Models: openai/whisper-medium.en and meta-llama/Llama-3.2-3B-Instruct. Quantization: bitsandbytes. Runtime: Google Colab T4 GPU. Framework: HuggingFace Transformers 4.57.6.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Wish I Knew These Speed Numbers Sooner — Here's the Full Breakdown</title>
      <dc:creator>gentlenode</dc:creator>
      <pubDate>Tue, 02 Jun 2026 04:13:02 +0000</pubDate>
      <link>https://dev.to/gentlenode/i-wish-i-knew-these-speed-numbers-sooner-heres-the-full-breakdown-1bc</link>
      <guid>https://dev.to/gentlenode/i-wish-i-knew-these-speed-numbers-sooner-heres-the-full-breakdown-1bc</guid>
      <description>&lt;p&gt;Check this out: when I started building our AI-powered customer support platform, I made the classic mistake: I optimized for model quality first, speed second. Three months in, our churn rate was 18%. Users weren't leaving because our answers were wrong — they were leaving because the first token took two seconds to appear.&lt;/p&gt;

&lt;p&gt;Here's the thing nobody tells you in the AI hype cycle: &lt;strong&gt;TTFT (Time to First Token) is the silent killer of retention.&lt;/strong&gt; Every 100ms you shave off that initial delay correlates directly to session completion rates. I learned this the hard way after burning through $40k in API credits on models that sounded smart but felt sluggish.&lt;/p&gt;

&lt;p&gt;So I did what any pragmatic CTO would do: I sat down and benchmarked 15 production-ready models across Global API's infrastructure, from multiple geographic regions, running real inference scenarios. The numbers changed how I think about architecture decisions entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup That Actually Matters
&lt;/h2&gt;

&lt;p&gt;Before I dive into results, here's the methodology I used — because if you're going to make decisions based on benchmarks, you need to trust the test harness:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;My Test Protocol&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Test Date&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;May 20, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Regions Tested&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;US East (Ohio), Asia (Singapore)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prompt Used&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Explain recursion in 200 words"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output Target&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~150 tokens per run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runs per Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10 iterations, averaged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Streaming&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SSE enabled (because nobody uses non-streaming in production)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Endpoint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://global-apis.com/v1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I chose recursion because it's computationally interesting — it forces models to actually reason rather than pattern-match. And I ran 10 iterations to smooth out any cloud provider jitter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Speed Rankings That Changed My Architecture
&lt;/h2&gt;

&lt;p&gt;Here's the raw data that made me completely rethink our model routing layer:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;TTFT (ms)&lt;/th&gt;
&lt;th&gt;Tokens/sec&lt;/th&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;$/M Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🥇&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Step-3.5-Flash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;StepFun&lt;/td&gt;
&lt;td&gt;$0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🥈&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;DeepSeek V4 Flash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;td&gt;$0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🥉&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Hunyuan-TurboS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;Tencent&lt;/td&gt;
&lt;td&gt;$0.28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Qwen3-8B&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;Qwen&lt;/td&gt;
&lt;td&gt;$0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;Qwen&lt;/td&gt;
&lt;td&gt;$0.28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Doubao-Seed-Lite&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;ByteDance&lt;/td&gt;
&lt;td&gt;$0.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Hunyuan-Turbo&lt;/td&gt;
&lt;td&gt;280&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;Tencent&lt;/td&gt;
&lt;td&gt;$0.57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;GLM-4-32B&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;Zhipu&lt;/td&gt;
&lt;td&gt;$0.56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Qwen3.5-27B&lt;/td&gt;
&lt;td&gt;350&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;Qwen&lt;/td&gt;
&lt;td&gt;$0.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;td&gt;$0.78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;MiniMax M2.5&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;MiniMax&lt;/td&gt;
&lt;td&gt;$1.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;GLM-5&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;Zhipu&lt;/td&gt;
&lt;td&gt;$1.92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Kimi K2.5&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Moonshot&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;DeepSeek-R1&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Qwen3.5-397B&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Qwen&lt;/td&gt;
&lt;td&gt;$2.34&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice something? The reasoning models (R1, K2.5) have that internal thinking time baked into their TTFT. That's not a bug — it's a feature when you need chain-of-thought, but it destroys UX for interactive use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'm Getting the Best ROI Right Now
&lt;/h2&gt;

&lt;p&gt;Let me walk through the tiers I'm using in production, because the raw speed numbers only tell half the story. The real magic is matching model capability to task cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ultra-Budget (&amp;lt; $0.15/M) — Your Scale Layer
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;tok/s&lt;/th&gt;
&lt;th&gt;$/M&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-8B&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;$0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step-3.5-Flash&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;$0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Qwen3-8B at $0.01/M output is absurd.&lt;/strong&gt; I'm running it for all our classification, intent detection, and simple Q&amp;amp;A flows. At 70 tok/s with 150ms TTFT, it feels instant. The catch? Quality degrades on nuanced tasks. But for 80% of traffic, it's gold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step-3.5-Flash at 80 tok/s is the speed champion&lt;/strong&gt; if you need slightly better comprehension without sacrificing latency. At $0.15/M, the ROI on throughput is unmatched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Budget ($0.15-$0.30/M) — The Sweet Spot
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;tok/s&lt;/th&gt;
&lt;th&gt;$/M&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V4 Flash&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;$0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hunyuan-TurboS&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;$0.28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;$0.28&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;DeepSeek V4 Flash is my default model for customer-facing chat.&lt;/strong&gt; 60 tok/s with GPT-4o-class quality at $0.25/M? That's the kind of math that makes VCs happy. The 180ms TTFT means users perceive it as instant. I'm routing 60% of our traffic through this model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hunyuan-TurboS at $0.28/M with 55 tok/s&lt;/strong&gt; is my fallback for Asian markets — the geographic latency advantage is real (more on that below).&lt;/p&gt;

&lt;h3&gt;
  
  
  Mid-Range ($0.30-$0.80/M) — Quality When Speed Matters Less
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;tok/s&lt;/th&gt;
&lt;th&gt;$/M&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Doubao-Seed-Lite&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;$0.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GLM-4-32B&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;$0.56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hunyuan-Turbo&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;$0.57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;$0.78&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This tier is for &lt;strong&gt;batch processing and async workflows&lt;/strong&gt;. I use DeepSeek V4 Pro for document analysis and code review — the extra quality justifies the speed drop. But I'd never put it in front of a user waiting for a response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Premium ($0.80+/M) — Only When Correctness Is Critical
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;tok/s&lt;/th&gt;
&lt;th&gt;$/M&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MiniMax M2.5&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;$1.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GLM-5&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;$1.92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K2.5&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Kimi K2.5 at $3.00/M output&lt;/strong&gt; — I use this exclusively for legal document review and compliance checks. The 600ms TTFT is painful, but when you need precision, it's worth the cost. Just never expose this to end users directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Geographic Latency Lesson I Learned the Hard Way
&lt;/h2&gt;

&lt;p&gt;We launched in Singapore last quarter and our latency metrics tanked. Here's what I found when I compared US East to Asia:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;US East TTFT&lt;/th&gt;
&lt;th&gt;Asia TTFT&lt;/th&gt;
&lt;th&gt;Diff&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V4 Flash&lt;/td&gt;
&lt;td&gt;180ms&lt;/td&gt;
&lt;td&gt;150ms&lt;/td&gt;
&lt;td&gt;-30ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-32B&lt;/td&gt;
&lt;td&gt;250ms&lt;/td&gt;
&lt;td&gt;210ms&lt;/td&gt;
&lt;td&gt;-40ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GLM-5&lt;/td&gt;
&lt;td&gt;500ms&lt;/td&gt;
&lt;td&gt;420ms&lt;/td&gt;
&lt;td&gt;-80ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K2.5&lt;/td&gt;
&lt;td&gt;600ms&lt;/td&gt;
&lt;td&gt;480ms&lt;/td&gt;
&lt;td&gt;-120ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Asian models (Qwen, GLM, Kimi) have &lt;strong&gt;16-20% lower latency from Asia&lt;/strong&gt; due to server proximity. DeepSeek is well-distributed globally — it actually performed better from Asia in my tests.&lt;/p&gt;

&lt;p&gt;This changed my routing strategy completely. Now I use a simple geographic router:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route_model&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;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Global API endpoint - routes to nearest server
&lt;/span&gt;    &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://global-apis.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Region-aware model selection
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_region&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek-v4-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Better Asia latency
&lt;/span&gt;        &lt;span class="n"&gt;max_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step-3.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Fastest overall
&lt;/span&gt;        &lt;span class="n"&gt;max_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/chat/completions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&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;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&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;iter_lines&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&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;
  
  
  The Real-World Impact That Made Me Rethink Everything
&lt;/h2&gt;

&lt;p&gt;Here's the table I wish I had when we started building:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TTFT&lt;/th&gt;
&lt;th&gt;User Perception&lt;/th&gt;
&lt;th&gt;Impact on Our Retention&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt; 200ms&lt;/td&gt;
&lt;td&gt;"Instant" — Excellent UX&lt;/td&gt;
&lt;td&gt;+12% session completion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200-400ms&lt;/td&gt;
&lt;td&gt;"Fast" — Acceptable&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400-800ms&lt;/td&gt;
&lt;td&gt;"Noticeable delay" — Some users frustrated&lt;/td&gt;
&lt;td&gt;-8% retention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;800ms+&lt;/td&gt;
&lt;td&gt;"Slow" — Users leave&lt;/td&gt;
&lt;td&gt;-23% retention&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;My rule of thumb:&lt;/strong&gt; Never deploy a model with TTFT &amp;gt; 400ms for interactive use cases. Period. If the model takes longer, use it for background processing and serve a loading state.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I'm Architecting This Today
&lt;/h2&gt;

&lt;p&gt;I've moved to a three-tier routing system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tier 1 (Instant):&lt;/strong&gt; Qwen3-8B or Step-3.5-Flash for classification, intent detection, simple Q&amp;amp;A. TTFT &amp;lt; 200ms, cost &amp;lt; $0.15/M.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 2 (Fast):&lt;/strong&gt; DeepSeek V4 Flash for customer-facing chat. TTFT ~180ms, cost $0.25/M.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 3 (Quality):&lt;/strong&gt; DeepSeek V4 Pro or GLM-5 for async processing, analysis, code review.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight? &lt;strong&gt;Avoid vendor lock-in.&lt;/strong&gt; I'm routing through Global API precisely because I can swap models without changing my codebase. Here's a production snippet:&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;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelRouter&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://global-apis.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tier_config&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;classification&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;model&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;qwen3-8b&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;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost_per_m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;model&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;deepseek-v4-flash&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;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost_per_m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analysis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;model&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;deepseek-v4-pro&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;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost_per_m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.78&lt;/span&gt;
            &lt;span class="p"&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;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tier_config&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;task_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tier_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/chat/completions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;json&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;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&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;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_tokens&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;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&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;cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cost_per_m&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;response&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;If you're building anything user-facing, &lt;strong&gt;stop optimizing for benchmark leaderboards and start optimizing for TTFT.&lt;/strong&gt; The brain perceives 200ms as instant. 800ms feels broken. Your users won't wait for a model to think — they'll just leave.&lt;/p&gt;

&lt;p&gt;My current stack: &lt;strong&gt;Qwen3-8B for volume, DeepSeek V4 Flash for quality, and a geographic router for global performance.&lt;/strong&gt; Total cost? About $2.50 per million tokens processed. Our retention? Up 15% since the switch.&lt;/p&gt;

&lt;p&gt;If you want to replicate this setup without the hassle of individual API keys and vendor negotiations, check out &lt;strong&gt;Global API&lt;/strong&gt; — it's what I'm using to avoid lock-in while keeping latency under 200ms. One endpoint, 15 models, no vendor drama.&lt;/p&gt;

&lt;p&gt;Your users will thank you. Your burn rate will thank you. And your CTO sanity? That's non-negotiable.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Python Decorators Explained Simply</title>
      <dc:creator>qing</dc:creator>
      <pubDate>Tue, 02 Jun 2026 04:04:08 +0000</pubDate>
      <link>https://dev.to/qingluan/python-decorators-explained-simply-35od</link>
      <guid>https://dev.to/qingluan/python-decorators-explained-simply-35od</guid>
      <description>&lt;h1&gt;
  
  
  Python Decorators Explained Simply
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Python Decorators Explained Simply is essential knowledge for every developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Points
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Start with the basics&lt;/li&gt;
&lt;li&gt;Practice regularly&lt;/li&gt;
&lt;li&gt;Build real projects&lt;/li&gt;
&lt;li&gt;Share your knowledge&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The best way to learn is by doing. Set up a test environment and experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Follow official documentation&lt;/li&gt;
&lt;li&gt;Join community forums&lt;/li&gt;
&lt;li&gt;Contribute to open source&lt;/li&gt;
&lt;li&gt;Write about what you learn&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Mastering python opens many career opportunities. Start today!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow for more python content!&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;More at &lt;a href="https://%E9%9D%92.%E5%A4%B1%E8%90%BD.%E4%B8%96%E7%95%8C" rel="noopener noreferrer"&gt;https://青.失落.世界&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Secure Your Linux Server in 10 Steps</title>
      <dc:creator>qing</dc:creator>
      <pubDate>Tue, 02 Jun 2026 04:04:03 +0000</pubDate>
      <link>https://dev.to/qingluan/how-to-secure-your-linux-server-in-10-steps-17nc</link>
      <guid>https://dev.to/qingluan/how-to-secure-your-linux-server-in-10-steps-17nc</guid>
      <description>&lt;h1&gt;
  
  
  How to Secure Your Linux Server in 10 Steps
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;How to Secure Your Linux Server in 10 Steps is essential knowledge for every developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Points
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Start with the basics&lt;/li&gt;
&lt;li&gt;Practice regularly&lt;/li&gt;
&lt;li&gt;Build real projects&lt;/li&gt;
&lt;li&gt;Share your knowledge&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The best way to learn is by doing. Set up a test environment and experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Follow official documentation&lt;/li&gt;
&lt;li&gt;Join community forums&lt;/li&gt;
&lt;li&gt;Contribute to open source&lt;/li&gt;
&lt;li&gt;Write about what you learn&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Mastering linux opens many career opportunities. Start today!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow for more linux content!&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;More at &lt;a href="https://%E9%9D%92.%E5%A4%B1%E8%90%BD.%E4%B8%96%E7%95%8C" rel="noopener noreferrer"&gt;https://青.失落.世界&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>security</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Random Generator — Free Online Tool</title>
      <dc:creator>BAOMING</dc:creator>
      <pubDate>Tue, 02 Jun 2026 04:01:36 +0000</pubDate>
      <link>https://dev.to/baoming/random-generator-free-online-tool-11a2</link>
      <guid>https://dev.to/baoming/random-generator-free-online-tool-11a2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05n2hbt82x9rwzmvzitu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05n2hbt82x9rwzmvzitu.png" alt="Random Generator at Scoreroute" width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;tags: webdev, programming, tutorial, productivity&lt;/p&gt;

&lt;h2&gt;
  
  
  canonical: &lt;a href="https://tools.scoreroute.com/tools/random-generator/" rel="noopener noreferrer"&gt;https://tools.scoreroute.com/tools/random-generator/&lt;/a&gt;
&lt;/h2&gt;

&lt;h1&gt;
  
  
  Random Generator — Free Online Tool
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Quick Answer
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://tools.scoreroute.com/tools/random-generator/" rel="noopener noreferrer"&gt;Random Generator&lt;/a&gt; and get what you need instantly. Free. No signup. No download. Everything stays in your browser.&lt;/p&gt;

&lt;p&gt;The Random Generator is a useful tool for creating random data as it saves time and effort by quickly generating numbers, strings, and passwords. Developers and testers in particular benefit from this tool as they need to test their applications with different data sets. Its simple operation and customizable output formats allow users to choose the type of data they need and the desired format themselves, making it a versatile tool for different tasks. In application testing, the Random Generator can help simulate real scenarios by creating random numbers and strings. This allows developers to identify and fix errors faster. Testers can also use this tool to create test cases with random data to ensure that their applications work correctly. Another advantage of the Random Generator is the creation of safe passwords. By generating strong and unique passwords that are difficult to guess, the user can protect his accounts and data from unauthorized access. Overall, the Random Generator helps increase the efficiency of repeated tasks and is thus a valuable tool in various areas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05n2hbt82x9rwzmvzitu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F05n2hbt82x9rwzmvzitu.png" alt="Random Generator tool interface" width="799" height="562"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fws7m3m8rutv98l89vwfu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fws7m3m8rutv98l89vwfu.png" alt="Random Generator tool interface" width="799" height="562"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2xmtpogis98jbv9lpd8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2xmtpogis98jbv9lpd8.png" alt="Random Generator tool interface" width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I use this on my phone or tablet?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Yes. The tool works on any modern browser and adjusts to your screen size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to create an account?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; No. Open the page and start. No email, no password, no signup required.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if the tool is not working?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Try refreshing, clearing cache, or switching browsers. Works on Chrome, Firefox, Safari, Edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Free Tools at Scoreroute
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://tools.scoreroute.com/tools/age-calculator/" rel="noopener noreferrer"&gt;Age Calculator&lt;/a&gt;&lt;/strong&gt; — free browser-based tool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://tools.scoreroute.com/tools/package-tracker/" rel="noopener noreferrer"&gt;Package Tracker&lt;/a&gt;&lt;/strong&gt; — free browser-based tool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://tools.scoreroute.com/tools/proxy-checker/" rel="noopener noreferrer"&gt;Proxy Checker&lt;/a&gt;&lt;/strong&gt; — free browser-based tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://tools.scoreroute.com/tools/random-generator/" rel="noopener noreferrer"&gt;Use Random Generator now&lt;/a&gt;&lt;/strong&gt; — free and instant.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The &lt;a href="https://tools.scoreroute.com" rel="noopener noreferrer"&gt;tools.scoreroute.com&lt;/a&gt; platform adds new developer tools regularly. All tools are free, browser-based, and privacy-focused.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Thermocouple Wiring Diagram: Types, Color Codes, and Connections</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Tue, 02 Jun 2026 04:00:02 +0000</pubDate>
      <link>https://dev.to/circuitdiagrammaker/thermocouple-wiring-diagram-types-color-codes-and-connections-526i</link>
      <guid>https://dev.to/circuitdiagrammaker/thermocouple-wiring-diagram-types-color-codes-and-connections-526i</guid>
      <description>&lt;p&gt;A thermocouple is two dissimilar metal wires joined at one end. The junction generates a small voltage proportional to the temperature difference between that hot junction and a reference point -- the Seebeck effect. The millivolt output is tiny (roughly 41 µV/°C for a Type K), which means correct wiring matters more than it does for most sensors. Wrong polarity, wrong extension wire, or a bad cold-junction compensation (CJC) setup can introduce errors of tens of degrees without triggering any obvious fault.&lt;/p&gt;

&lt;p&gt;This guide covers the major thermocouple types, ANSI and IEC color codes, how to connect a thermocouple to a controller or transmitter, and what cold-junction compensation actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Seebeck Effect and the Cold Junction
&lt;/h2&gt;

&lt;p&gt;When two different conductors are joined and their junctions are at different temperatures, a voltage appears across the open ends. This is the Seebeck effect. In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hot junction (measuring junction):&lt;/strong&gt; the tip inserted into the process -- a furnace, engine exhaust, solder pot, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold junction (reference junction):&lt;/strong&gt; wherever the thermocouple wires terminate, usually at the instrument terminals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The instrument measures the voltage difference and computes temperature -- but only relative to the cold junction temperature. If the cold junction is at 25 °C and the hot junction is at 300 °C, the output corresponds to a 275 °C difference. The controller adds the cold-junction temperature (measured by an on-board thermistor or RTD) to get the true 300 °C reading. Omit or miscalculate CJC and every reading is offset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thermocouple Types
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Type K (Chromel/Alumel)
&lt;/h3&gt;

&lt;p&gt;The most widely used. Range: -200 °C to +1260 °C. Sensitivity: ~41 µV/°C. Good for general industrial use, HVAC, and food processing. Susceptible to oxidation above 1000 °C in some atmospheres.&lt;/p&gt;

&lt;h3&gt;
  
  
  Type J (Iron/Constantan)
&lt;/h3&gt;

&lt;p&gt;Range: -40 °C to +750 °C. Sensitivity: ~52 µV/°C. Common in older US industrial equipment. Iron leg rusts above 500 °C in humid conditions. Not suited for new designs in most cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Type T (Copper/Constantan)
&lt;/h3&gt;

&lt;p&gt;Range: -200 °C to +370 °C. Sensitivity: ~43 µV/°C. Excellent for cryogenics and food industry. Copper leg conducts heat away from the junction, which can cause errors in low-thermal-mass applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Type E (Chromel/Constantan)
&lt;/h3&gt;

&lt;p&gt;Range: -200 °C to +900 °C. Sensitivity: ~68 µV/°C -- the highest of the common base-metal types. Good for differential temperature measurements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Types R, S, B (Platinum)
&lt;/h3&gt;

&lt;p&gt;High-temperature industrial and laboratory use. Ranges up to 1700 °C (Type B). Sensitivity is much lower (~10 µV/°C), requiring low-noise wiring and instrumentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Color Codes
&lt;/h2&gt;

&lt;p&gt;Color codes differ between the ANSI (American) and IEC (European) standards -- and between types within each standard. The table below covers the types most commonly encountered.&lt;/p&gt;

&lt;h3&gt;
  
  
  ANSI/ASTM Color Code (US)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Positive Wire&lt;/th&gt;
&lt;th&gt;Negative Wire&lt;/th&gt;
&lt;th&gt;Connector Body&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;K&lt;/td&gt;
&lt;td&gt;Yellow&lt;/td&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;Yellow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;J&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;Black&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;Blue&lt;/td&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;Blue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;Purple&lt;/td&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;Purple&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note: In ANSI convention the &lt;strong&gt;negative wire is always red&lt;/strong&gt;, regardless of type. This trips up engineers familiar with IEC wiring.&lt;/p&gt;

&lt;h3&gt;
  
  
  IEC 60584-3 Color Code (Europe/International)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Positive Wire&lt;/th&gt;
&lt;th&gt;Negative Wire&lt;/th&gt;
&lt;th&gt;Overall Jacket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;K&lt;/td&gt;
&lt;td&gt;Green&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;td&gt;Green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;J&lt;/td&gt;
&lt;td&gt;Black&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;td&gt;Black&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;Brown&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;td&gt;Brown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;Purple&lt;/td&gt;
&lt;td&gt;White&lt;/td&gt;
&lt;td&gt;Violet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;IEC uses white for the negative conductor across all types.&lt;/p&gt;

&lt;p&gt;Label or photograph your wiring before you start if you are working in a facility that mixes US and European equipment -- misidentifying polarity causes a reading error rather than an obvious fault.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extension Wire vs. Thermocouple Wire
&lt;/h2&gt;

&lt;p&gt;A thermocouple circuit must use matched metal conductors from the hot junction all the way to the instrument terminals. Running ordinary copper wire from a junction box to the controller introduces a new, uncompensated junction -- effectively a second thermocouple in series -- and the reading will be wrong.&lt;/p&gt;

&lt;p&gt;Two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Thermocouple wire:&lt;/strong&gt; The same alloy as the sensor element. Used when you want to maintain full temperature accuracy through the extension run. More expensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extension-grade wire (compensating cable):&lt;/strong&gt; A less expensive alloy with a similar Seebeck coefficient over a limited temperature range (typically 0--200 °C). Acceptable for most industrial runs inside conduit where the wire temperature stays within that range. Marked with an "X" suffix in the ANSI system (KX, JX, TX, etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For maker and embedded applications -- connecting a thermocouple to an Arduino-based controller using an MAX31855 or MAX6675 breakout board -- the thermocouple usually terminates directly at the PCB, which handles CJC internally. No extension run is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring a Thermocouple to a Temperature Controller
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dedicated Thermocouple Input Controller (e.g., Autonics, Omega, Watlow)
&lt;/h3&gt;

&lt;p&gt;These instruments have clearly marked thermocouple input terminals, usually a screw-type terminal block.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set the controller to the correct thermocouple type in the menu (K, J, T, etc.).&lt;/li&gt;
&lt;li&gt;Connect the positive wire (ANSI: type-specific color; IEC: green/black/brown/purple) to the positive (+) input terminal.&lt;/li&gt;
&lt;li&gt;Connect the negative wire (ANSI: red; IEC: white) to the negative (--) input terminal.&lt;/li&gt;
&lt;li&gt;The terminals are usually labeled TC+, TC--, or IN+, IN--.&lt;/li&gt;
&lt;li&gt;Do &lt;strong&gt;not&lt;/strong&gt; connect the shield (if present) at both ends -- ground the shield at one end only to avoid ground loops.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4-20 mA Transmitter
&lt;/h3&gt;

&lt;p&gt;A thermocouple transmitter (DIN rail or head-mount) converts the thermocouple millivolt signal to a 4--20 mA loop for PLC or SCADA input.&lt;/p&gt;

&lt;p&gt;Wiring has two parts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thermocouple side (input):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect TC+ and TC-- as above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Transmitter output (loop):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The loop supply voltage (typically 24 V DC) connects to the transmitter's supply terminals.&lt;/li&gt;
&lt;li&gt;The 4--20 mA signal wire runs in series with the loop to the PLC analog input.&lt;/li&gt;
&lt;li&gt;4 mA = minimum temperature (e.g., 0 °C), 20 mA = maximum temperature (e.g., 500 °C).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Arduino with MAX31855 or MAX6675 (Maker/Embedded)
&lt;/h3&gt;

&lt;p&gt;The MAX31855 reads Type K thermocouples and handles cold-junction compensation on-chip. It communicates via SPI.&lt;/p&gt;

&lt;p&gt;Connections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;T+ (TC+):&lt;/strong&gt; Positive thermocouple wire (yellow in ANSI K)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T- (TC-):&lt;/strong&gt; Negative thermocouple wire (red in ANSI K)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VCC:&lt;/strong&gt; 3.3 V (5 V tolerant on most breakouts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GND:&lt;/strong&gt; Ground&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SCK, CS, SO:&lt;/strong&gt; To Arduino SPI pins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep the thermocouple leads short between the board terminals and the junction. Do not route them parallel to power wires -- pickup on the microvolt-level signal causes noisy readings.&lt;/p&gt;

&lt;p&gt;You can prototype the signal conditioning side in CircuitDiagramMaker -- draw the MAX31855 breakout, the SPI connection to an Arduino, and annotate the wire types before you start soldering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Wiring Mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reversed polarity:&lt;/strong&gt; Connecting positive to the negative terminal gives a reading that decreases as temperature rises -- or goes negative. The magnitude of the error equals the actual measurement. Reversed polarity is the single most common thermocouple wiring error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copper extension wire:&lt;/strong&gt; Using ordinary wire for a long run from the thermocouple to the controller introduces uncompensated junctions. The error grows with the temperature at those junctions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ground loops:&lt;/strong&gt; Connecting the shield at both ends creates a loop that picks up noise and offsets the reading. Ground one end only, typically at the controller/instrument.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incorrect type selection:&lt;/strong&gt; Setting a controller to Type K when a Type J sensor is installed. At 300 °C, the calibration error is roughly 30 °C.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Your Own Thermocouple Wiring Diagram
&lt;/h2&gt;

&lt;p&gt;CircuitDiagramMaker is well suited for documenting instrumentation wiring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draw transmitter terminal blocks with labeled TC+/TC-- inputs&lt;/li&gt;
&lt;li&gt;Show 4-20 mA loop wiring from transmitter to PLC&lt;/li&gt;
&lt;li&gt;Annotate wire types (thermocouple wire vs. extension grade) and color codes&lt;/li&gt;
&lt;li&gt;Draw MAX31855 SPI connections for embedded designs&lt;/li&gt;
&lt;li&gt;Export to PDF for control panel documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://circuitdiagrammaker.app/app/new" rel="noopener noreferrer"&gt;Create your own thermocouple wiring diagram -- free&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;All thermocouples rely on the Seebeck effect: a small voltage proportional to the temperature difference between the hot junction and the cold junction.&lt;/li&gt;
&lt;li&gt;Cold-junction compensation is essential -- instruments measure it internally with a thermistor or RTD and add it to the reading.&lt;/li&gt;
&lt;li&gt;In the ANSI color system, the negative wire is always red regardless of thermocouple type; in IEC it is always white.&lt;/li&gt;
&lt;li&gt;Extension and compensating wire must use the correct alloy to match the sensor -- ordinary copper wire introduces uncorrected measurement error.&lt;/li&gt;
&lt;li&gt;For embedded use, an MAX31855 or MAX6675 breakout handles CJC on-chip and speaks SPI directly to a microcontroller.&lt;/li&gt;
&lt;li&gt;Ground shields at one end only to prevent ground-loop noise from corrupting the microvolt-level signal.&lt;/li&gt;
&lt;li&gt;Reversed polarity causes the reading to decrease with rising temperature -- always verify polarity before commissioning.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://circuitdiagrammaker.app/blog/thermocouple-wiring-diagram" rel="noopener noreferrer"&gt;https://circuitdiagrammaker.app/blog/thermocouple-wiring-diagram&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>electronics</category>
      <category>diy</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>10 Common Python Asyncio Pitfalls and Solutions</title>
      <dc:creator>WDSEGA</dc:creator>
      <pubDate>Tue, 02 Jun 2026 03:49:25 +0000</pubDate>
      <link>https://dev.to/wdsega/10-common-python-asyncio-pitfalls-and-solutions-3ob0</link>
      <guid>https://dev.to/wdsega/10-common-python-asyncio-pitfalls-and-solutions-3ob0</guid>
      <description>&lt;p&gt;Python的asyncio库让异步编程变得前所未有的简单，但简单并不意味着容易。在实际项目中，很多开发者会掉进一些看似不起眼的陷阱，导致程序性能下降、死锁甚至崩溃。今天我把这几年踩过的坑和解决方案整理出来，希望能帮你少走弯路。&lt;/p&gt;

&lt;h2&gt;
  
  
  陷阱1：在异步函数中调用阻塞IO
&lt;/h2&gt;

&lt;p&gt;这是最常见的错误。很多开发者在async函数里直接使用&lt;code&gt;requests.get()&lt;/code&gt;、&lt;code&gt;open()&lt;/code&gt;等同步IO操作，结果整个事件循环被阻塞，其他协程全部卡住。&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;# 错误示范
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 阻塞整个事件循环
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 正确做法：使用aiohttp
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;session&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.example.com/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 或者用run_in_executor包装同步调用
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_running_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_in_executor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;(&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;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.example.com/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&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;&lt;strong&gt;关键原则&lt;/strong&gt;：在async函数中，任何可能超过几毫秒的IO操作都应该用异步版本或放到线程池中执行。&lt;/p&gt;

&lt;h2&gt;
  
  
  陷阱2：忘记await
&lt;/h2&gt;

&lt;p&gt;忘记加&lt;code&gt;await&lt;/code&gt;是asyncio中最隐蔽的bug。代码不会报错，但协程不会被执行，你只会得到一个coroutine对象。&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;# 错误示范
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;some_work&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;# 创建了任务但没有等待
&lt;/span&gt;    &lt;span class="c1"&gt;# some_work可能还没执行完，main就结束了
&lt;/span&gt;
&lt;span class="c1"&gt;# 正确做法
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;some_work&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;  &lt;span class="c1"&gt;# 等待任务完成
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;更隐蔽的情况是在函数调用链中遗漏await：&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;# 错误：中间某层忘记await
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clean_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 忘记await！clean_data是协程
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cleaned&lt;/span&gt;

&lt;span class="c1"&gt;# 正确
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;clean_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cleaned&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：使用mypy的asyncio插件或pyright等类型检查工具，它们能帮你检测到遗漏的await。&lt;/p&gt;

&lt;h2&gt;
  
  
  陷阱3：不当使用asyncio.gather的错误处理
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;asyncio.gather&lt;/code&gt;默认在某个任务失败时会取消其他所有任务。如果你需要部分失败不影响整体，必须设置&lt;code&gt;return_exceptions=True&lt;/code&gt;。&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;# 默认行为：一个失败全部取消
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_all&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 如果fetch_user(2)抛异常，fetch_user(3)也会被取消
&lt;/span&gt;
&lt;span class="c1"&gt;# 更好的做法
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_all&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;fetch_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;return_exceptions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&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;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;任务&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;失败: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;任务&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;成功: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  陷阱4：事件循环未正确关闭
&lt;/h2&gt;

&lt;p&gt;在Python 3.10+中，&lt;code&gt;asyncio.run()&lt;/code&gt;会自动处理事件循环的创建和关闭。但在一些需要手动管理事件循环的场景（如Jupyter Notebook、FastAPI的某些中间件），忘记关闭会导致资源泄漏。&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;# Python 3.10+ 推荐方式
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;do_something&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;# 自动创建和关闭事件循环
&lt;/span&gt;
&lt;span class="c1"&gt;# 需要手动管理的场景
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# ... 你的代码
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_event_loop&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;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_event_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_until_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# 清理所有待处理的任务
&lt;/span&gt;    &lt;span class="n"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all_tasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loop&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;task&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_until_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_exceptions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_until_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shutdown_asyncgens&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  陷阱5：过度创建并发任务
&lt;/h2&gt;

&lt;p&gt;一次性创建上千个并发请求看起来很高效，但实际上可能导致连接池耗尽、内存暴涨、甚至被目标服务器封IP。&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;# 错误：一次性创建10000个任务
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_all_urls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;fetch&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;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# 10000个任务同时运行
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 正确：使用信号量控制并发数
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_all_urls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_concurrent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;limited_fetch&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;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&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;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;limited_fetch&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;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 或者使用TaskGroup + 信号量（Python 3.11+）
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_all_urls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_concurrent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TaskGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tg&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;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;tg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;limited_fetch&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;semaphore&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  陷阱6：混用线程和协程导致死锁
&lt;/h2&gt;

&lt;p&gt;在asyncio中调用同步代码，而同步代码又尝试调用asyncio函数，这种循环依赖会导致死锁。&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;# 死锁场景
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sync_function&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# 这个函数在同步上下文中被调用
&lt;/span&gt;    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;async_function&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;# 如果已经在事件循环中，这会报错或死锁
&lt;/span&gt;
&lt;span class="c1"&gt;# 解决方案1：使用nest_asyncio（适合Jupyter等特殊环境）
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nest_asyncio&lt;/span&gt;
&lt;span class="n"&gt;nest_asyncio&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="c1"&gt;# 解决方案2：重新设计，避免嵌套
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# 把所有逻辑都放在异步上下文中
&lt;/span&gt;    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;async_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;processed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_in_executor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sync_process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  陷阱7：Task取消后未清理资源
&lt;/h2&gt;

&lt;p&gt;当Task被取消时，会抛出&lt;code&gt;asyncio.CancelledError&lt;/code&gt;。如果你在异步上下文管理器中忽略了它，可能导致数据库连接、文件句柄等资源无法正确释放。&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;# 错误：取消后资源泄漏
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_with_db&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_db_connection&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&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;# 这里可能被取消
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CancelledError&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;None&lt;/span&gt;  &lt;span class="c1"&gt;# conn没有关闭！
&lt;/span&gt;
&lt;span class="c1"&gt;# 正确：使用async with确保资源释放
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_with_db&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_db_connection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="c1"&gt;# async with会确保即使被取消也能正确关闭连接
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  陷阱8：错误理解asyncio.sleep(0)的作用
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;asyncio.sleep(0)&lt;/code&gt;的作用是让出控制权给事件循环，让其他等待中的协程有机会执行。但很多人误以为它能保证公平调度。&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
python
# asyncio.sleep(0)的正确用途
async def producer(queue):
    for item in items:

---
*本文首发于[我的技术博客](https://wdsega.github.io)，欢迎访问获取更多技术文章。*
*如果你是内容创作者或自由职业者，推荐看看我整理的[Creator Pro Bundle](https://segauser.gumroad.com/l/rrhmbb)工具包，包含AI提示词系统、内容创作工具、副业指南和自动化脚本，源码全开放。*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>python</category>
      <category>asyncio</category>
      <category>tutorial</category>
      <category>async</category>
    </item>
    <item>
      <title>Rompiendo la cuarta pared en Ren'Py: El "Efecto Monika" y la Esfera Matemática (Parte 1)</title>
      <dc:creator>Lily</dc:creator>
      <pubDate>Tue, 02 Jun 2026 03:48:29 +0000</pubDate>
      <link>https://dev.to/abrazos_programacion/rompiendo-la-cuarta-pared-en-renpy-el-efecto-monika-y-la-esfera-matematica-parte-1-chm</link>
      <guid>https://dev.to/abrazos_programacion/rompiendo-la-cuarta-pared-en-renpy-el-efecto-monika-y-la-esfera-matematica-parte-1-chm</guid>
      <description>&lt;p&gt;¿Te acuerdas de la primera vez que jugaste &lt;em&gt;Doki Doki Literature Club&lt;/em&gt; y &lt;strong&gt;Monika te llamó por tu nombre real&lt;/strong&gt;? Ese escalofrío es el resultado de un truco de ingeniería genial. En esta primera entrega de nuestro &lt;strong&gt;&lt;em&gt;Desafío de Construcción Extrema: Edición Ren'Py&lt;/em&gt;&lt;/strong&gt;, vamos a crear una carpa mística donde nuestra adivina, &lt;em&gt;Madame Zora&lt;/em&gt;, usará Python para calcular días de la semana y "hackear" el nombre del sistema operativo del jugador.&lt;/p&gt;

&lt;h2&gt;
  
  
  El Poder Matemático: Adivinar el Día de la Semana 🔮
&lt;/h2&gt;

&lt;p&gt;Detrás de la magia del calendario hay un algoritmo clásico de la matemática (como el de &lt;strong&gt;&lt;em&gt;Doomsday&lt;/em&gt;&lt;/strong&gt;). En lugar de calcularlo a mano, usaremos la &lt;strong&gt;&lt;em&gt;librería nativa datetime de Python dentro de Ren'Py&lt;/em&gt;&lt;/strong&gt; para procesar cualquier fecha que el usuario ingrese por teclado.&lt;/p&gt;

&lt;h2&gt;
  
  
  El Truco de Monika: Leyendo el Sistema 🖥️
&lt;/h2&gt;

&lt;p&gt;Los videojuegos corren sobre un sistema operativo que sabe exactamente quién inició sesión. Con las librerías &lt;em&gt;os&lt;/em&gt; y &lt;em&gt;getpass&lt;/em&gt;, podemos preguntarle a la computadora el &lt;em&gt;nombre del usuario&lt;/em&gt; y guardarlo en una variable secreta.&lt;/p&gt;

&lt;h2&gt;
  
  
  El Código Base (Script.rpy) 📜
&lt;/h2&gt;

&lt;p&gt;Copia y pega este código en tu proyecto de Ren'Py para ver la magia en acción:&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="n"&gt;init&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;getpass&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

    &lt;span class="c1"&gt;# Función para descifrar el día de la semana
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calcular_dia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fecha_texto&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;fecha_objeto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fecha_texto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%d/%m/%Y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;numero_dia&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fecha_objeto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;dias_semana&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;Lunes&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;Martes&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;Miércoles&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;Jueves&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;Viernes&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;Sábado&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;Domingo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dias_semana&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;numero_dia&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;except&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;None&lt;/span&gt; &lt;span class="c1"&gt;# Si el usuario escribe texto inválido, no se rompe el juego
&lt;/span&gt;
    &lt;span class="c1"&gt;# Función para obtener el usuario real de la PC
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;obtener_nombre_sistema&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;getpass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getuser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;USERNAME&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;Viajero&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;define&lt;/span&gt; &lt;span class="n"&gt;adivina&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Character&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Madame Zora&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#993399&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;scene&lt;/span&gt; &lt;span class="n"&gt;bg_tienda_mistica&lt;/span&gt;
    &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;nombre_real&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;obtener_nombre_sistema&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;adivina&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dime, alma viajera... ¿cómo te llamas en este plano?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;nombre_juego&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Introduce tu nombre:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;nombre_juego&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;nombre_juego&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Forastero&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;adivina&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bien, [nombre_juego]. Ahora dime una fecha importante en formato DD/MM/AAAA.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;pedir_fecha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;fecha_ingresada&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fecha (ejemplo: 20/05/1995):&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;resultado_dia&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calcular_dia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fecha_ingresada&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resultado_dia&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;adivina&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Las brumas están densas... Esa fecha no es válida. Inténtalo de nuevo.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;jump&lt;/span&gt; &lt;span class="n"&gt;pedir_fecha&lt;/span&gt;

    &lt;span class="n"&gt;adivina&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;¡La esfera brilla! El día [fecha_ingresada] fue un majestuoso **[resultado_dia]**.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;adivina&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pero mi esfera ve mucho más allá...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Confrontando al jugador con el truco de Monika
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nombre_juego&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="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;nombre_real&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="n"&gt;adivina&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Veo que eres honesto. Tu espíritu coincide con el nombre de tu máquina: **[nombre_real]**.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;adivina&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Has intentado ocultarte bajo el nombre de &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[nombre_juego]&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;adivina&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;¡Pero a la esfera no se le miente! Tu verdadero nombre en el mundo de metal es... **[nombre_real]**.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visita mi canal de Youtube para más explicación:&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/naubsxwhVxk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>renpy</category>
      <category>python</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Python Web Scraping Ethics: A Complete Guide for 2026</title>
      <dc:creator>WDSEGA</dc:creator>
      <pubDate>Tue, 02 Jun 2026 03:47:33 +0000</pubDate>
      <link>https://dev.to/wdsega/python-web-scraping-ethics-a-complete-guide-for-2026-3lbm</link>
      <guid>https://dev.to/wdsega/python-web-scraping-ethics-a-complete-guide-for-2026-3lbm</guid>
      <description>&lt;p&gt;2026年，爬虫技术已经发展到了一个新阶段。AI驱动的反爬系统越来越智能，法律法规也在不断完善。在这个背景下，写爬虫不再只是技术问题，更是法律和道德问题。这篇文章从法律边界、道德规范、技术实践三个维度，帮你建立完整的"道德爬虫"知识体系。&lt;/p&gt;

&lt;h2&gt;
  
  
  一、法律边界：什么能爬，什么不能爬
&lt;/h2&gt;

&lt;h3&gt;
  
  
  中国法律框架
&lt;/h3&gt;

&lt;p&gt;在中国，与爬虫相关的主要法律包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;《网络安全法》&lt;/strong&gt;：禁止非法获取计算机信息系统数据&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;《数据安全法》&lt;/strong&gt;：对数据收集、存储、使用提出了合规要求&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;《个人信息保护法》&lt;/strong&gt;：爬取涉及个人信息的数据需要获得授权&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;《刑法》第285条&lt;/strong&gt;：非法获取计算机信息系统数据罪，情节严重的可处三年以上七年以下有期徒刑&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  关键判断标准
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;合法爬取的特征&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;爬取公开数据（不需要登录即可访问的数据）&lt;/li&gt;
&lt;li&gt;遵守网站的robots.txt规则&lt;/li&gt;
&lt;li&gt;请求频率合理，不对目标服务器造成负担&lt;/li&gt;
&lt;li&gt;不爬取个人信息（姓名、电话、身份证号等）&lt;/li&gt;
&lt;li&gt;不用于商业竞争目的&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;可能违法的特征&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;绕过技术保护措施（验证码、加密参数、登录墙）&lt;/li&gt;
&lt;li&gt;爬取非公开数据（需要登录才能看到的内容）&lt;/li&gt;
&lt;li&gt;大量爬取个人信息&lt;/li&gt;
&lt;li&gt;对目标服务器造成损害（CPU、带宽过载）&lt;/li&gt;
&lt;li&gt;将爬取数据用于商业牟利，损害原网站利益&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  robots.txt的正确理解
&lt;/h3&gt;

&lt;p&gt;robots.txt是网站的"门牌"，告诉爬虫哪些页面可以访问，哪些不可以：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# robots.txt示例
&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: *
&lt;span class="n"&gt;Allow&lt;/span&gt;: /&lt;span class="n"&gt;public&lt;/span&gt;/
&lt;span class="n"&gt;Disallow&lt;/span&gt;: /&lt;span class="n"&gt;private&lt;/span&gt;/
&lt;span class="n"&gt;Disallow&lt;/span&gt;: /&lt;span class="n"&gt;api&lt;/span&gt;/
&lt;span class="n"&gt;Disallow&lt;/span&gt;: /&lt;span class="n"&gt;user&lt;/span&gt;/

&lt;span class="c"&gt;# 限制爬取频率
&lt;/span&gt;&lt;span class="n"&gt;Crawl&lt;/span&gt;-&lt;span class="n"&gt;delay&lt;/span&gt;: &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;urllib.robotparser&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_robots_txt&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="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="n"&gt;rp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;robotparser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RobotFileParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&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;scheme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/robots.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_url&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="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;can_fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;can_fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geturl&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;crawl_delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;crawl_delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;can_fetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;crawl_delay&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;重要提醒&lt;/strong&gt;：robots.txt是道德约束，不是法律强制。但它代表了网站所有者的意愿，遵守它是道德爬虫的第一步。&lt;/p&gt;

&lt;h2&gt;
  
  
  二、道德规范：做一个好"邻居"
&lt;/h2&gt;

&lt;h3&gt;
  
  
  请求频率控制
&lt;/h3&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;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EthicalCrawler&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_concurrent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_delay&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_concurrent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_concurrent&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&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="sh"&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;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# 自适应延迟：根据最近请求时间动态调整
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_times&lt;/span&gt;&lt;span class="p"&gt;[&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_delay&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="c1"&gt;# 只保留最近100次请求时间
&lt;/span&gt;            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_times&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;100&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;session&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c1"&gt;# 被限流，等待后重试
&lt;/span&gt;                        &lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;触发限流，等待&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;retry_after&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="p"&gt;)&lt;/span&gt;
                        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&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;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientError&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;请求失败: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User-Agent标识
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 好的User-Agent：清楚表明你的身份
&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;User-Agent&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;MyResearchBot/1.0 (学术研究项目; contact@example.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;Accept&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;text/html,application/xhtml+xml&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;Accept-Language&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;zh-CN,zh;q=0.9&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 不好的User-Agent：伪装成浏览器
&lt;/span&gt;&lt;span class="n"&gt;BAD_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;User-Agent&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;Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;原则&lt;/strong&gt;：诚实表明你的身份。如果网站管理员有问题，他们可以通过联系方式找到你。&lt;/p&gt;

&lt;h2&gt;
  
  
  三、技术实践：道德爬虫的实现
&lt;/h2&gt;

&lt;h3&gt;
  
  
  完整的道德爬虫框架
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
python
import asyncio
import aiohttp
import urllib.robotparser
from urllib.parse import urlparse
import logging
from dataclasses import dataclass
from typing import Optional
import time

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


@dataclass
class CrawlConfig:
    base_url: str
    max_concurrent: int = 5
    base_delay: float = 2.0
    timeout: int = 30
    max_retries: int = 3
    respect_robots: bool = True
    user_agent: str = "EthicalCrawler/1.0 (research; contact@example.com)"


class EthicalCrawler:
    def __init__(self, config: CrawlConfig):
        self.config = config
        self.semaphore = asyncio.Semaphore(config.max_concurrent)
        self.visited = set()
        self.robot_parser = urllib.robotparser.RobotFileParser()

        if config.respect_robots:
            self.robot_parser.set_url(f"{config.base_url}/robots.txt")
            try:
                self.robot_parser.read()
                logger.info("已加载robots.txt规则")
            except Exception as e:
                logger.warning(f"无法读取robots.txt: {e}")

    def is_allowed(self, url: str) -&amp;gt; bool:
        """检查URL是否允许爬取"""
        if not self.config.respect_robots:
            return True

        parsed = urlparse(url)
        if parsed.netloc != urlparse(self.config.base_url).netloc:
            logger.warning(f"跨域请求: {url}")
            return False

        return self.robot_parser.can_fetch(self.config.user_agent, url)

    async def fetch_page(self, session: aiohttp.ClientSession, url: str) -&amp;gt; Optional[str]:
        """获取页面内容"""
        if url in self.visited:
            return None

        if not self.is_allowed(url):
            logger.info(f"robots.txt禁止访问: {url}")
            return None

        async with self.semaphore:
            self.visited.add(url)
            logger.info(f"正在爬取: {url}")

            for attempt in range(self.config.max_retries):
                try:
                    async with session.get(
                        url,
                        headers={"User-Agent": self.config.user_agent},
                        timeout=aiohttp.ClientTimeout(total=self.config.timeout)
                    ) as response:
                        if response.status == 429:
                            retry_after = int(
                                response.headers.get("Retry-After", 60)
                            )
                            logger.warning(f"限流，等待{retry_after}秒")
                            await asyncio.sleep(retry_after)
                            continue

                        if response.status &amp;gt;= 400:
                            logger.warning(f"HTTP {response.status}: {url}")
                            return None

                        # 遵守延迟
                        await asyncio.sleep(self.config.base_delay)
                        return await response.text()

                except Exception as e:
                    logger.error(f"请求异常(第{attempt+1}次): {e}")
                    if attempt &amp;lt; self.config.max_retries - 1:
                        await asyncio.sleep(2 ** attempt)  # 指数退避

        return None

    async def crawl(self, urls: list[str]) -&amp;gt; list[tuple[str, str]]:
        """批量爬取URL"""
        results = []
        async with aiohttp.ClientSession() as session:
            tasks = [self.fetch_page(session, url) for url in urls]
            pages = await asyncio.gather(*tasks, return_exceptions=True)


---
*本文首发于[我的技术博客](https://wdsega.github.io)，欢迎访问获取更多技术文章。*
*如果你是内容创作者或自由职业者，推荐看看我整理的[Creator Pro Bundle](https://segauser.gumroad.com/l/rrhmbb)工具包，包含AI提示词系统、内容创作工具、副业指南和自动化脚本，源码全开放。*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>ethics</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Free Online Calculator — Simple &amp; Fast</title>
      <dc:creator>BAOMING</dc:creator>
      <pubDate>Tue, 02 Jun 2026 03:31:43 +0000</pubDate>
      <link>https://dev.to/baoming/free-online-calculator-simple-fast-17fb</link>
      <guid>https://dev.to/baoming/free-online-calculator-simple-fast-17fb</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhr5sc487j0zheqdqfdde.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhr5sc487j0zheqdqfdde.png" alt="Days Until Calculator at Scoreroute" width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;title: "Days Until Calculator"&lt;br&gt;
tags: webdev, programming, tutorial, productivity&lt;/p&gt;

&lt;h2&gt;
  
  
  canonical: &lt;a href="https://tools.scoreroute.com/tools/days-until-calculator/" rel="noopener noreferrer"&gt;https://tools.scoreroute.com/tools/days-until-calculator/&lt;/a&gt;
&lt;/h2&gt;

&lt;h1&gt;
  
  
  Free Online Calculator — Simple &amp;amp; Fast
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Quick Answer
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://tools.scoreroute.com/tools/days-until-calculator/" rel="noopener noreferrer"&gt;Days Until Calculator&lt;/a&gt; and get what you need instantly. Free. No signup. No download. Everything stays in your browser.&lt;/p&gt;

&lt;p&gt;The Days Until Calculator is a useful tool for planning to help you calculate the number of days between two dates. This feature is especially useful for personal and professional use, as it enables precise scheduling. When planning events, you can simply enter the start and end dates of an event to determine the remaining time to the event. Calculator operation is very easy, thanks to a user-friendly interface that allows you to perform date calculations quickly and efficiently. In addition, you can customize the date range to your individual needs, making the computer a versatile tool. Another advantage of the Days Until Calculator is its ability to provide accurate countdowns up to a target date, allowing you to stay on time and meet deadlines. Both individuals and companies use the calculator for various purposes, such as project planning, vacation planning and much more. If you want to try out the Days Until Calculator, you can find it at &lt;a href="https://tools.scoreroute.com/tools/days-until-calculator/" rel="noopener noreferrer"&gt;https://tools.scoreroute.com/tools/days-until-calculator/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhr5sc487j0zheqdqfdde.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhr5sc487j0zheqdqfdde.png" alt="Days Until Calculator tool interface" width="799" height="562"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2o4yyg3gd9wnj5bx9bu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2o4yyg3gd9wnj5bx9bu.png" alt="Days Until Calculator tool interface" width="799" height="562"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wjqwfuk8h24ok1jov63.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wjqwfuk8h24ok1jov63.png" alt="Days Until Calculator tool interface" width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How is it different from other similar tools?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Privacy. Everything happens in your browser. Your data never leaves your device. Most competitors upload your data to their servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use this on my phone or tablet?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Yes. The tool works on any modern browser and adjusts to your screen size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to create an account?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; No. Open the page and start. No email, no password, no signup required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Free Tools at Scoreroute
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://tools.scoreroute.com/tools/gold-price/" rel="noopener noreferrer"&gt;Gold Price&lt;/a&gt; — free browser-based tool&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tools.scoreroute.com/tools/json-formatter/" rel="noopener noreferrer"&gt;Json Formatter&lt;/a&gt; — free browser-based tool&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tools.scoreroute.com/tools/bmi-calculator/" rel="noopener noreferrer"&gt;Bmi Calculator&lt;/a&gt; — free browser-based tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://tools.scoreroute.com/tools/days-until-calculator/" rel="noopener noreferrer"&gt;Use Days Until Calculator now&lt;/a&gt; — free and instant.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The &lt;a href="https://tools.scoreroute.com" rel="noopener noreferrer"&gt;tools.scoreroute.com&lt;/a&gt; platform adds new&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Best Mac Apps for Freelancers: Essential Productivity Tools for Remote Work</title>
      <dc:creator>Ross</dc:creator>
      <pubDate>Tue, 02 Jun 2026 03:02:10 +0000</pubDate>
      <link>https://dev.to/appish/best-mac-apps-for-freelancers-essential-productivity-tools-for-remote-work-22l5</link>
      <guid>https://dev.to/appish/best-mac-apps-for-freelancers-essential-productivity-tools-for-remote-work-22l5</guid>
      <description>&lt;h2&gt;
  
  
  Why Freelancers Need Specialized Mac Apps
&lt;/h2&gt;

&lt;p&gt;Freelancers face unique productivity challenges that standard Mac apps don't address. You're juggling multiple clients, working across timezones, handling sensitive data, and often switching between different types of work throughout the day. The default Mac experience just isn't built for this complexity.&lt;/p&gt;

&lt;p&gt;Unlike traditional employees who might use the same apps in the same way every day, freelancers need tools that adapt to constantly changing workflows, protect client confidentiality, and maximize productivity when every minute counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audio Control for Client Calls and Focus
&lt;/h2&gt;

&lt;p&gt;Nothing kills professionalism like fumbling with audio during a client call. Mac's basic volume controls work fine until you need to route your client call to headphones while keeping Spotify playing through speakers, or quickly mute Slack notifications without affecting your video call.&lt;/p&gt;

&lt;p&gt;The biggest audio challenge for freelancers is managing multiple audio sources simultaneously. You might have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client calls that need to go to headphones for privacy&lt;/li&gt;
&lt;li&gt;Background music to maintain focus&lt;/li&gt;
&lt;li&gt;Notification sounds that need to be controlled independently&lt;/li&gt;
&lt;li&gt;Different volume levels for different types of work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mac's system-wide volume control forces you to constantly adjust settings or risk embarrassing audio interruptions. Tools like &lt;strong&gt;Soundish&lt;/strong&gt; solve this by giving you per-app volume control and the ability to route different apps to different audio outputs — essential for maintaining professionalism during client interactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protecting Client Data with App-Level Security
&lt;/h2&gt;

&lt;p&gt;Freelancers handle sensitive client information across multiple projects. Your Mac might contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Financial data for accounting clients&lt;/li&gt;
&lt;li&gt;Marketing strategies for competing businesses&lt;/li&gt;
&lt;li&gt;Personal information covered by privacy regulations&lt;/li&gt;
&lt;li&gt;Proprietary designs or code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mac's all-or-nothing screen lock isn't practical when you need to step away briefly or work in coffee shops. You don't want to lock your entire system every time you grab coffee, but you also can't risk someone accessing sensitive client files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lockish&lt;/strong&gt; provides a middle ground by letting you lock specific apps with Touch ID. This means you can leave your Mac unlocked for general use while protecting sensitive apps like your accounting software, client communication tools, or project management systems. It's particularly valuable when working in coworking spaces or shared environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Window Management for Multi-Client Workflows
&lt;/h2&gt;

&lt;p&gt;Freelancers constantly switch between different projects and clients, each requiring its own set of apps and windows. You might have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Morning client work&lt;/strong&gt;: Slack, Google Docs, and Chrome for research&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Afternoon design project&lt;/strong&gt;: Figma, Adobe Creative Suite, and file browsers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evening accounting&lt;/strong&gt;: Spreadsheets, invoicing software, and banking apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Manually repositioning windows every time you switch contexts wastes precious billable hours. macOS Sequoia's window tiling helps but doesn't save configurations or handle multi-monitor setups well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layoutish&lt;/strong&gt; addresses this by letting you save and restore complete window layouts. You can create different layouts for different clients or types of work, then switch between them instantly. The time-based scheduling feature can even automatically apply your "morning client work" layout at 9 AM and switch to your "afternoon design" setup at 2 PM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timezone Management for Global Clients
&lt;/h2&gt;

&lt;p&gt;Freelancers increasingly work with international clients, making timezone coordination a daily challenge. You need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schedule calls across multiple timezones&lt;/li&gt;
&lt;li&gt;Meet deadlines in different time zones&lt;/li&gt;
&lt;li&gt;Coordinate with team members around the world&lt;/li&gt;
&lt;li&gt;Plan your workday around client availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Constantly Googling "what time is it in Tokyo" or doing timezone math in your head wastes time and increases the risk of scheduling mistakes that could damage client relationships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time Zoneish&lt;/strong&gt; streamlines this by providing menu bar access to multiple timezones, contact availability tracking, and meeting scheduling tools. The calendar integration helps you see conflicts across timezones, while the meeting calculator finds optimal times when working with clients in multiple regions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Productivity Integration Strategy
&lt;/h2&gt;

&lt;p&gt;The key to freelancer productivity isn't using every app available — it's choosing tools that work together seamlessly. An effective Mac setup for freelancers combines:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Audio control&lt;/strong&gt; for professional client interactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security tools&lt;/strong&gt; for protecting sensitive data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Window management&lt;/strong&gt; for efficient context switching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timezone tools&lt;/strong&gt; for global coordination&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started: Prioritize Your Biggest Pain Points
&lt;/h2&gt;

&lt;p&gt;Start by identifying which freelancer challenges cost you the most time or create the most stress:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If client calls are your priority&lt;/strong&gt;: Focus on audio routing solutions first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If data security concerns keep you up at night&lt;/strong&gt;: Implement app-level locking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you waste time repositioning windows&lt;/strong&gt;: Start with window management tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If timezone coordination is causing scheduling conflicts&lt;/strong&gt;: Begin with timezone tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most successful freelancers treat their Mac setup as a business investment. The time saved by proper tools pays for itself quickly when you're billing by the hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Sustainable Workflow
&lt;/h2&gt;

&lt;p&gt;Freelance success depends on sustainable productivity systems. The right Mac apps don't just solve individual problems — they create a workflow foundation that scales with your business growth. As you take on more clients or more complex projects, having proper audio control, security measures, window management, and timezone coordination becomes even more valuable.&lt;/p&gt;

&lt;p&gt;Remember: every minute spent fighting with your tools is a minute not spent on billable work or business development.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://appish.app/blog/mac-apps-for-freelancers-productivity-guide-2025" rel="noopener noreferrer"&gt;appish.app&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>macos</category>
      <category>productivity</category>
      <category>mac</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
