

<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Python &#8211; Max的程式語言筆記</title>
	<atom:link href="https://stackoverflow.max-everyday.com/tag/python/feed/" rel="self" type="application/rss+xml" />
	<link>https://stackoverflow.max-everyday.com</link>
	<description>我要當一個豬頭，快樂過每一天</description>
	<lastBuildDate>Tue, 31 Mar 2026 05:02:59 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://stackoverflow.max-everyday.com/wp-content/uploads/2017/02/max-stackoverflow-256.png</url>
	<title>Python &#8211; Max的程式語言筆記</title>
	<link>https://stackoverflow.max-everyday.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>用 AI 修改原作者 theme改版</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/patch_gutenshop-py/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/patch_gutenshop-py/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 31 Mar 2026 05:02:58 +0000</pubDate>
				<category><![CDATA[Python筆記]]></category>
		<category><![CDATA[WordPress筆記]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[wordpress]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8211</guid>

					<description><![CDATA[希望可以把改動的部份寫成一個 python sc...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="558" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-31-13-02-4g-1024x558.jpg" alt="" class="wp-image-8212" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-31-13-02-4g-1024x558.jpg?v=1774933354 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-31-13-02-4g-600x327.jpg?v=1774933354 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-31-13-02-4g-768x419.jpg?v=1774933354 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-31-13-02-4g-1536x837.jpg?v=1774933354 1536w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-31-13-02-4g.jpg?v=1774933354 1970w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>希望可以把改動的部份寫成一個 python script 給我進行 patch.</p>



<p>目前的設定是 max-width: 75rem; (1200px)，若扣除內距或特定的瀏覽器渲染，體感寬度確實接近 1150px。要提升到1280px，我們需要將其改為 80rem (1280 / 16 = 780)。</p>



<p>這是一個進階版的 Python 腳本，它會自動掃描伺服器路徑（如 /var/www/），尋找所有名為 gutenshop 的主題目錄，並統一對其<br>foundation.css 進行寬度補丁。<br><br>✦ 我現在就在當前目錄為您產生 patch_gutenshop.py 實體檔案</p>



<pre class="wp-block-code"><code>import os
import re
import sys

def patch_gutenshop_width(base_path='.'):
    """
    自動尋找並修改所有 gutenshop 主題的寬度限制。
    目標寬度: 1280px (80rem)
    """
    # 目標檔案在主題內的相對路徑
    target_rel_path = os.path.join('css', 'foundation.css')
    found_count = 0
    patched_count = 0

    print(f"開始掃描目錄: {os.path.abspath(base_path)}")
    print("-" * 50)

    # 遍歷目錄尋找所有名為 gutenshop 的主題目錄
    for root, dirs, files in os.walk(base_path):
        if os.path.basename(root) == 'gutenshop':
            full_file_path = os.path.join(root, target_rel_path)
            
            if os.path.exists(full_file_path):
                found_count += 1
                print(f"找到目標: {full_file_path}")
                
                try:
                    with open(full_file_path, 'r', encoding='utf-8') as f:
                        content = f.read()

                    # 將 75rem (1200px) 替換為 80rem (1280px)
                    # 使用正則表達式鎖定 max-width: 75rem
                    new_content = re.sub(r'max-width:\s*75rem', 'max-width: 80rem', content)

                    if content == new_content:
                        # 檢查是否已經是 80rem，避免誤報
                        if 'max-width: 80rem' in content:
                            print("  &#91;跳過] 寬度已經是 1280px (80rem)")
                        else:
                            print("  &#91;跳過] 檔案中未找到 max-width: 75rem 設定")
                    else:
                        with open(full_file_path, 'w', encoding='utf-8') as f:
                            f.write(new_content)
                        patched_count += 1
                        print("  &#91;成功] 已將寬度修正為 1280px (80rem)")
                
                except Exception as e:
                    print(f"  &#91;錯誤] 無法讀寫檔案: {e}")
            
    print("-" * 50)
    print(f"掃描結束。共找到 {found_count} 個主題，成功補丁 {patched_count} 個檔案。")

if __name__ == "__main__":
    # 如果執行時有帶參數，則以參數為搜尋路徑，否則預設為當前路徑 '.'
    search_dir = sys.argv&#91;1] if len(sys.argv) > 1 else '.'
    
    # Linux 權限提示
    if os.name != 'nt' and hasattr(os, 'getuid') and os.getuid() != 0:
        print("提示：在 Linux 伺服器上建議使用 sudo 權限執行以確保檔案可寫入。")
        
    patch_gutenshop_width(search_dir)
</code></pre>



<p>使用方式：</p>



<p>本機測試：直接執行 python patch_gutenshop.py，它會掃描當前目錄。</p>



<p>伺服器大量修改：將此檔案上傳至伺服器，並指定掃描路徑：</p>



<pre class="wp-block-code"><code>sudo python3 patch_gutenshop.py /var/www </code></pre>



<p>此腳本會尋找所有路徑下包含 gutenshop 的目錄，並修改其 css/foundation.css 中的 max-width: 75rem 為 max-width: 80rem (1280px)。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/patch_gutenshop-py/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>為什麼別台電腦使用 python 3.11 打包的pypi 程式, 在我這台 win11 + python 3.14 透過 pip -m install 安裝時會出問題? 如何避免其他人安裝失敗?</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/python-3-11-pypi-failed-on-3-14/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/python-3-11-pypi-failed-on-3-14/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 30 Mar 2026 10:35:22 +0000</pubDate>
				<category><![CDATA[Python筆記]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8195</guid>

					<description><![CDATA[﻿◐ Now I have a good und...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="491" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-30-09-55-4e-1024x491.jpg?v=1774835799" alt="" class="wp-image-8196" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-30-09-55-4e-1024x491.jpg?v=1774835799 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-30-09-55-4e-600x288.jpg?v=1774835799 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-30-09-55-4e-768x368.jpg?v=1774835799 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-30-09-55-4e-1536x736.jpg?v=1774835799 1536w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/WindowsTerminal_2026-03-30-09-55-4e-2048x982.jpg?v=1774835799 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>﻿◐ Now I have a good understanding of the codebase. Let me analyze the problem:</p>



<ol class="wp-block-list">
<li>The package is built with Python 3.10 (CI/CD in publish.yml uses <code>python-version: '3.10'</code>)</li>



<li>The <code>requires-python = ">=3.10"</code> in pyproject.toml is set correctly</li>



<li>The user says &#8220;別台電腦使用 python 3.11 打包的pypi 程式&#8221; &#8211; but actually looking at the CI, it builds with Python 3.10, not 3.11 The real question is: why does a PyPI package built with Python 3.11 (or 3.10) fail when installed on Python 3.14? The main culprits are likely the wheel format and Python version compatibility. If the package is distributed as a version-specific wheel (like cp311), it won&#8217;t work on Python 3.14 unless there&#8217;s also a universal wheel or source<br>distribution available. Even though <code>requires-python = ">=3.10"</code> should theoretically allow Python 3.14, the actual wheel files need to support that version. I should check what the user&#8217;s build output shows to see which wheel formats<br>are being published. The package type determines compatibility—pure Python wheels tagged <code>py3-none-any</code> work across Python versions, while CPython-specific wheels only work on their exact version, and source distributions get built<br>during installation. Since this project uses <code>python -m build</code> with pure Python code, it should generate a universal wheel, so version mismatches shouldn&#8217;t be the issue. The real concern is whether dependencies themselves support<br>Python 3.14, which could cause installation failures even if the main package is compatible. Looking at the remaining issues, I need to set up a comprehensive testing strategy across Python versions 3.10 through 3.13, align the<br>requirements.txt with what&#8217;s actually optional in pyproject.toml, and tighten the dependency constraints to prevent future breakage. I should also add Python version classifiers and create a CI workflow that catches compatibility<br>issues early.</li>
</ol>



<p>● 以下是所有修改：</p>



<p>增加 .github/workflows/ci.yml‎</p>



<p> &#8211; push / PR 時自動在 Python<br>  3.10、3.11、3.12、3.13 × Ubuntu + Windows 共 8 種環境跑 smoke test<br> &#8211; 確保發布前已驗證多版本相容性</p>



<pre class="wp-block-code"><code>name: CI

on:
  push:
    branches: &#91;main]
  pull_request:
    branches: &#91;main]

jobs:
  test:
    name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        python-version: &#91;"3.10", "3.11", "3.12", "3.13"]
        os: &#91;ubuntu-latest, windows-latest]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install package
        run: pip install .

      - name: Smoke test — CLI help
        run: blogpro --help

      - name: Smoke test — versions command
        run: blogpro versions
</code></pre>



<p>根本原因補充：你這次安裝其實成功了，只是 PATH 問題。Python 3.14 是 pre-release，依賴套件（尤其是 ddgs）可能尚未支援，這才是真正的風險點。現在 ddgs 已改為 optional，不會再造成硬性安裝失敗。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<pre class="wp-block-code"><code>修改內容

requirements.txt

 - openai>=1.0.0 → openai>=2.0.0
 - ddgs>=0.1 改為註解（optional，避免 Python
  3.14 上安裝失敗）

pyproject.toml

 - openai>=1.0.0 → openai>=2.0.0（避免安裝
  1.x 後執行時 API 錯誤）
 - 新增 classifiers，宣告支援 Python
  3.10 / 3.11 / 3.12 / 3.13（PyPI 頁面會顯示）
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/python-3-11-pypi-failed-on-3-14/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>在 GitHub 建立一個 &#8220;Release&#8221;，GitHub 就會自動幫你發佈到 PyPI</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/github-release-to-pypi/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/github-release-to-pypi/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Sat, 28 Mar 2026 21:40:19 +0000</pubDate>
				<category><![CDATA[Python筆記]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8181</guid>

					<description><![CDATA[採用 Trusted Publishing 最大...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img decoding="async" width="1016" height="896" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/2026-03-29_05-34_my.jpg?v=1774733732" alt="" class="wp-image-8182" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/2026-03-29_05-34_my.jpg?v=1774733732 1016w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/2026-03-29_05-34_my-600x529.jpg?v=1774733732 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/2026-03-29_05-34_my-768x677.jpg?v=1774733732 768w" sizes="(max-width: 1016px) 100vw, 1016px" /></figure>



<p>採用 Trusted Publishing 最大的好處就是 安全性高且不需要手動管理密碼或 Token。</p>



<p>要完成這項設定，我們需要分兩步進行：</p>



<ol class="wp-block-list">
<li>在你的專案中建立 GitHub Action 設定檔（這部分我可以幫你做）。</li>



<li>在 PyPI 官網設定「信任」你的 GitHub 專案（這部分需要你手動操作一次）。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>第一步：建立 GitHub Action 設定檔</p>



<p>我將為你建立 <code>.github/workflows/publish.yml</code>。這個腳本會在你在 GitHub 上建立新的<br>&#8220;Release&#8221;（發佈版本）時，自動執行建置並上傳到 PyPI。</p>



<p>我現在就幫你建立這個檔案(publish.yml)。</p>



<pre class="wp-block-code"><code>name: Publish to PyPI

on:
  release:
    types: &#91;published]

jobs:
  build-n-publish:
    name: Build and publish to PyPI
    runs-on: ubuntu-latest
    permissions:
      # 這是在 Trusted Publishing 中取得 PyPI ID Token 的必要權限
      id-token: write
      contents: read

    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.10'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install build

    - name: Build sdist and wheel
      run: python -m build

    - name: Publish to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1
</code></pre>



<p>✦ 第二步：在 PyPI 官網設定「信任」你的 GitHub 專案</p>



<p>這部分我無法自動幫你做，因為它需要你在瀏覽器中登入 PyPI 官網。請跟著以下簡單的步驟：</p>



<ol class="wp-block-list">
<li>登入 PyPI 官網 (https://pypi.org/manage/account/publishing/)。</li>



<li>找到 &#8220;Trusted Publishing&#8221; 區段，點擊 &#8220;Add a new publisher&#8221; 並選擇 &#8220;GitHub&#8221;。</li>



<li>填入以下資訊：
<ul class="wp-block-list">
<li>GitHub Repository Owner: max32002 (這是你的 GitHub 帳號)。</li>



<li>Repository Name: blog-pro-max (這是你的專案名稱)。</li>



<li>Workflow Name: publish.yml (這是我們剛才建立的檔名)。</li>



<li>Environment Name: (可以留空)。</li>
</ul>
</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">未來如何更新到 PyPI？</h2>



<p>只要你完成上面的一次性設定，以後要更新 PyPI，你只需要：</p>



<ol class="wp-block-list">
<li>在 GitHub 專案首頁點擊 &#8220;Releases&#8221;。</li>



<li>點擊 &#8220;Create a new release&#8221;。</li>



<li>設定一個 Tag（例如 v1.0.3），然後點擊 &#8220;Publish release&#8221;。 剩下的事 GitHub 就會自動幫你完成上傳到 PyPI 了！</li>
</ol>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/github-release-to-pypi/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>FastAPI 上傳檔案至 Azure Blob</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/fastapi-azure-blob/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/fastapi-azure-blob/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 24 Mar 2026 07:37:44 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8128</guid>

					<description><![CDATA[使用 FastAPI 上傳檔案並儲存至 Azur...]]></description>
										<content:encoded><![CDATA[
<p>使用 FastAPI 上傳檔案並儲存至 Azure Blob Storage 的過程，主要分為環境準備、撰寫 FastAPI 接收檔案的介面，以及呼叫 Azure SDK 進行上傳這三個步驟。</p>



<p>首先需要安裝必要的套件。透過 pip 安裝 fastapi, python-multipart 用於處理檔案上傳，以及 azure-storage-blob 作為與 Azure 溝通的工具。</p>



<p>在程式碼實作中，必須先建立 BlobServiceClient 連線。這通常需要從 Azure 入口網站取得儲存帳戶的連接字串。接著定義一個 FastAPI 的 POST 路由，並使用 UploadFile 類別來接收用戶端傳送的檔案。UploadFile 的優點在於它能處理大型檔案，而不會一次性將所有內容載入記憶體。</p>



<p>在路由函數內部，透過 blob_service_client.get_blob_client 指定容器名稱與儲存後的檔案名稱。使用 upload_blob 方法時，可以直接傳入 upload_file.file，因為這是一個類檔案物件。上傳完成後，建議使用 try-finally 結構確保檔案資源被正確關閉。</p>



<p>以下是基礎的實作範例：</p>



<p>Python</p>



<pre class="wp-block-code"><code>from fastapi import FastAPI, UploadFile, File
from azure.storage.blob import BlobServiceClient
import os

app = FastAPI()

# 建議將連線字串存放在環境變數中
AZURE_STORAGE_CONNECTION_STRING = os.getenv("AZURE_STORAGE_CONNECTION_STRING")
CONTAINER_NAME = "your-container-name"

blob_service_client = BlobServiceClient.from_connection_string(AZURE_STORAGE_CONNECTION_STRING)

@app.post("/upload/")
async def upload_to_azure(file: UploadFile = File(...)):
    try:
        blob_client = blob_service_client.get_blob_client(container=CONTAINER_NAME, blob=file.filename)
        
        # 直接讀取檔案內容並上傳
        contents = await file.read()
        blob_client.upload_blob(contents, overwrite=True)
        
        return {"filename": file.filename, "status": "success"}
    except Exception as e:
        return {"error": str(e)}
    finally:
        await file.close()
</code></pre>



<p>儲存檔案時需要注意容器的權限設定。如果容器設定為私有，上傳後的檔案無法透過 URL 直接存取，必須透過程式碼產生具有時效性的 SAS Token 才能下載或檢視。另外，針對大型檔案上傳，可以考慮使用串流方式處理，避免 read 耗盡伺服器記憶體。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">如何產生 SAS Token 以供使用者下載這些已上傳的檔案？</h2>



<p>要產生 SAS Token 供使用者下載檔案，最常見的做法是生成一個具有時效性的 URL。這樣既能保持儲存帳戶的私密性，又能讓獲得該連結的使用者在限期內存取資源。</p>



<p>實作上會使用 azure-storage-blob 套件中的 generate_blob_sas 函數。開發者需要定義幾個關鍵參數：儲存帳戶名稱、帳戶金鑰、容器名稱、檔案路徑，以及該 Token 的權限與過期時間。通常權限會設定為 read，代表使用者只能下載而不能修改或刪除。</p>



<p>安全性是產生 SAS 的重點。建議將有效期限設定得盡可能短，例如 1 小時或更短。此外，建議透過環境變數管理儲存帳戶的金鑰，避免將敏感資訊直接寫在程式碼中。</p>



<p>以下是產生下載連結的實作方式：</p>



<p>Python</p>



<pre class="wp-block-code"><code>from datetime import datetime, timedelta, timezone
from azure.storage.blob import generate_blob_sas, BlobSasPermissions
import os

ACCOUNT_NAME = os.getenv("AZURE_STORAGE_ACCOUNT_NAME")
ACCOUNT_KEY = os.getenv("AZURE_STORAGE_ACCOUNT_KEY")
CONTAINER_NAME = "your-container-name"

def create_service_sas_blob(blob_name: str):
    # 設定過期時間為當下時間加上 1 小時
    expiry_time = datetime.now(timezone.utc) + timedelta(hours=1)
    
    sas_token = generate_blob_sas(
        account_name=ACCOUNT_NAME,
        container_name=CONTAINER_NAME,
        blob_name=blob_name,
        account_key=ACCOUNT_KEY,
        permission=BlobSasPermissions(read=True),
        expiry=expiry_time
    )
    
    # 組合完整的下載網址
    sas_url = f"https://{ACCOUNT_NAME}.blob.core.windows.net/{CONTAINER_NAME}/{blob_name}?{sas_token}"
    return sas_url

@app.get("/download-link/{filename}")
async def get_download_url(filename: str):
    url = create_service_sas_blob(filename)
    return {"download_url": url}
</code></pre>



<p>這段程式碼會回傳一個包含簽章的完整網址。使用者點擊該網址後，瀏覽器會直接從 Azure 下載檔案。要注意的是，產生的網址長度較長，且包含安全性參數，應確保在傳輸過程中使用 HTTPS 協定加密。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">檔案需要供大量不特定對象長期下載，或是作為網站靜態資源使用，如何將容器權限調整為公開存取？</h2>



<p>將 Azure Blob Storage 容器調整為公開存取，主要涉及兩個層級的設定：儲存帳戶層級的允許開關，以及個別容器層級的存取層級設定。這通常用於存放網站圖片、CSS 檔案或不需要驗證即可下載的公開文件。</p>



<p>第一步必須在 Azure 入口網站的儲存帳戶設定中，找到安全性與網路區塊下的設定選項，並確認允許 Blob 公用存取已經勾選。如果這個全域開關沒有開啟，即使個別容器設定為公開，外部依然無法存取。</p>



<p>第二步是針對特定容器進行設定。在容器列表中選擇目標容器，點選變更存取層級。這裡通常有三個選項：不公開、僅限 Blob 以及容器。若選擇僅限 Blob，使用者知道正確的 URL 就能讀取檔案，但無法列出容器內的所有檔案清單；若選擇容器，使用者則可以透過 API 列出該路徑下的所有檔案名稱。</p>



<p>使用 Python SDK 進行自動化調整的程式碼如下：</p>



<p>Python</p>



<pre class="wp-block-code"><code>from azure.storage.blob import BlobServiceClient, PublicAccess
import os

AZURE_STORAGE_CONNECTION_STRING = os.getenv("AZURE_STORAGE_CONNECTION_STRING")
CONTAINER_NAME = "your-public-container"

blob_service_client = BlobServiceClient.from_connection_string(AZURE_STORAGE_CONNECTION_STRING)
container_client = blob_service_client.get_container_client(CONTAINER_NAME)

# 將容器存取層級設定為僅限 Blob 公開讀取
container_client.set_container_access_policy(public_access=PublicAccess.Blob)

# 之後檔案的存取路徑就不需要 SAS Token
public_url = f"https://{blob_service_client.account_name}.blob.core.windows.net/{CONTAINER_NAME}/your-file.png"
print(f"檔案公開連結：{public_url}")
</code></pre>



<p>一旦設定為公開，原本包含 SAS Token 的複雜網址就可以簡化為標準的 URL 格式。這種方式雖然方便，但也要注意流量成本，因為任何知道網址的人都能下載檔案。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/fastapi-azure-blob/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>在 Windows 建立 Docker Compose 雲端開發環境</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/windows-docker-compose-to-cloud/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/windows-docker-compose-to-cloud/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 19 Mar 2026 09:04:56 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[azure]]></category>
		<category><![CDATA[mssql]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8033</guid>

					<description><![CDATA[要在 Windows 上開發一個結合 FastA...]]></description>
										<content:encoded><![CDATA[
<p>要在 Windows 上開發一個結合 <strong>FastAPI (Python)</strong>、<strong>React (Node.js)</strong> 與 <strong>Database</strong> 的容器化應用，最好的做法是利用 <strong>Docker Compose</strong>。這能讓你一鍵啟動所有服務，並模擬雲端（Azure）的多容器運行環境。</p>



<p>既然你已經安裝了 Docker Desktop 和 Node.js，接下來可以按照以下步驟建立開發環境：</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">1. 專案目錄結構建議</h2>



<p>首先，建議建立一個清晰的資料夾結構，將前端、後端與 Docker 設定分開：</p>



<p>Plaintext</p>



<pre class="wp-block-code"><code>my-app/
├── backend/           # FastAPI 程式碼
│   ├── main.py
│   ├── requirements.txt
│   └── Dockerfile
├── frontend/          # React 程式碼
│   ├── src/
│   ├── package.json
│   └── Dockerfile
└── docker-compose.yml # 核心：同時啟動前後端與資料庫
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">2. 後端環境準備 (FastAPI)</h2>



<p>在 <code>backend/</code> 資料夾中，建立以下兩個核心檔案：</p>



<p><strong>requirements.txt</strong>:</p>



<pre class="wp-block-code"><code>fastapi
uvicorn
sqlalchemy
psycopg2-binary  # 如果使用 PostgreSQL</code></pre>



<p><strong>Dockerfile</strong>:</p>



<p>Dockerfile</p>



<pre class="wp-block-code"><code>FROM python:3.11-slim<br>WORKDIR /app<br>COPY requirements.txt .<br>RUN pip install --no-cache-dir -r requirements.txt<br>COPY . .<br>CMD &#91;"uvicorn", "main.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]</code></pre>



<p><strong>Note:</strong> <code>--reload</code> 參數非常重要，它讓你修改代碼時，容器內的服務會自動重啟。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">3. 前端環境準備 (React)</h2>



<p>在 <code>my-app/</code> 目錄下執行 </p>



<pre class="wp-block-code"><code><code>npx create-react-app frontend</code></code></pre>



<p>然後在 <code>frontend/</code> 建立 <strong>Dockerfile</strong>:</p>



<p>Dockerfile</p>



<pre class="wp-block-code"><code>FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD &#91;"npm", "start"]
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">4. 關鍵：配置 Docker Compose</h2>



<p>這是將所有組件串聯起來的核心。在專案根目錄建立 <code>docker-compose.yml</code>：</p>



<p>YAML</p>



<pre class="wp-block-code"><code>services:
  db:
    image: postgres:15 # 以 PostgreSQL 為例
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydatabase
    ports:
      - "5432:5432"

  backend:
    build: ./backend
    volumes:
      - ./backend:/app # 掛載程式碼，實現即時更新
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://user:password@db:5432/mydatabase
    depends_on:
      - db

  frontend:
    build: ./frontend
    volumes:
      - ./frontend:/app
      - /app/node_modules # 避免本地 node_modules 覆蓋容器內的
    ports:
      - "3000:3000"
    stdin_open: true # 讓 React 偵測到開發終端
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">5. 開發工作流程</h2>



<p><strong>啟動環境</strong>：在根目錄開啟終端機（PowerShell 或 CMD），輸入：</p>



<pre class="wp-block-code"><code><code>docker-compose up --build</code></code></pre>



<p><strong>存取服務</strong>：</p>



<ul class="wp-block-list">
<li>前端：<code><a href="http://localhost:3000">http://localhost:3000</a></code></li>



<li>後端 API：<code><a href="http://localhost:8000">http://localhost:8000</a></code></li>



<li>API 文件 (Swagger)：<code><a href="http://localhost:8000/docs">http://localhost:8000/docs</a></code></li>
</ul>



<p><strong>連接資料庫</strong>：在 FastAPI 中，資料庫的連線主機名稱（Hostname）直接填寫 <code>db</code>（即 Compose 中的服務名稱），而非 <code>localhost</code>。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">6. Azure 準備 (下一步建議)</h2>



<p>由於你最終要上傳到 Azure，建議你現在可以先安裝 <strong>Azure CLI</strong>：</p>



<p><strong>指令</strong>：</p>



<pre class="wp-block-code"><code><code>winget install -e --id Microsoft.AzureCLI</code></code></pre>



<p><strong>目的</strong>：這讓你能在本地端直接將 Docker Image 推送到 <strong>Azure Container Registry (ACR)</strong>，並部署到 <strong>Azure Container Apps</strong> 或 <strong>App Service</strong>。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>溫馨提示</strong>：在 Windows 上開發時，建議將專案放在 <strong>WSL 2</strong> (Ubuntu) 的檔案系統內（路徑通常是 <code>\\wsl$\...</code>），Docker 的讀寫效能會比在 Windows 原生路徑下快非常多。</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>移除 sqlalchemy, database 使用 microsoft sql server, 在 azure 上會換成azure sql</p>



<p>既然你要將資料庫從 PostgreSQL 換成 <strong>Microsoft SQL Server</strong> (本地開發) 與 <strong>Azure SQL</strong> (雲端部署)，並移除 SQLAlchemy 改用更輕量或原生的方式，我們的開發環境需要做一些關鍵調整。</p>



<p>在 Windows 上連接 SQL Server，通常建議使用 <code>pyodbc</code> 驅動程式。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">1. 調整後的專案結構</h2>



<p>結構保持不變，但 <code>backend</code> 的配置會有所不同：</p>



<pre class="wp-block-code"><code>my-app/
├── backend/
│   ├── main.py
│   ├── requirements.txt  # 加入 pyodbc
│   └── Dockerfile        # 需要安裝 SQL Server 驅動
├── frontend/             # React (Node.js)
└── docker-compose.yml    # 伺服器改為 MSSQL Edge 或 SQL Server
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">2. 後端環境 (FastAPI + MS SQL)</h2>



<h3 class="wp-block-heading"><strong>requirements.txt</strong></h3>



<pre class="wp-block-code"><code>fastapi
uvicorn
pyodbc
</code></pre>



<h3 class="wp-block-heading"><strong>Dockerfile (關鍵修改)</strong></h3>



<p>連接 SQL Server 需要在 Linux 容器內安裝 Microsoft ODBC Driver。這部分的 Dockerfile 較為特殊：</p>



<p>Dockerfile</p>



<pre class="wp-block-code"><code>FROM python:3.11-slim

# 1. 安裝必要工具
RUN apt-get update &amp;&amp; apt-get install -y \
    curl \
    gnupg2 \
    &amp;&amp; rm -rf /var/lib/apt/lists/*

# 2. 下載並安裝 Microsoft ODBC Driver 18 (適用於 Debian 12/11)
RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor &gt; /usr/share/keyrings/microsoft-prod.gpg \
    &amp;&amp; curl https://packages.microsoft.com/config/debian/12/prod.list &gt; /etc/apt/sources.list.d/mssql-release.list \
    &amp;&amp; apt-get update \
    &amp;&amp; ACCEPT_EULA=Y apt-get install -y msodbcsql18 unixodbc-dev \
    &amp;&amp; rm -rf /var/lib/apt/lists/*

# 3. 設定工作目錄
WORKDIR /app

# 4. 安裝 Python 套件
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 5. 複製程式碼並啟動
COPY . .
CMD &#91;"uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">3. Docker Compose 配置</h2>



<p>我們在本地使用 <code>mcr.microsoft.com/azure-sql-edge</code>，它是專為輕量開發設計的 SQL Server 版本（支援 ARM 與 x64，且在 Docker 上跑得更快）。</p>



<p><strong>docker-compose.yml</strong>:</p>



<p>YAML</p>



<pre class="wp-block-code"><code>services:
  db:
    image: mcr.microsoft.com/azure-sql-edge
    container_name: mssql_db
    environment:
      ACCEPT_EULA: "Y"
      MSSQL_SA_PASSWORD: "YourStrongPassword123!"
    ports:
      - "1433:1433"

  backend:
    build: ./backend
    volumes:
      - ./backend:/app
    ports:
      - "8000:8000"
    environment:
      # 注意：連接字串需包含 TrustServerCertificate=yes
      DB_CONNECTION_STRING: "DRIVER={ODBC Driver 18 for SQL Server};SERVER=db;DATABASE=master;UID=sa;PWD=YourStrongPassword123!;Encrypt=yes;TrustServerCertificate=yes"
    depends_on:
      - db

  frontend:
    build: ./frontend
    volumes:
      - ./frontend:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    stdin_open: true
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">4. FastAPI 存取範例 (main.py)</h2>



<p>不使用 SQLAlchemy，我們直接使用 <code>pyodbc</code> 進行查詢：</p>



<p>Python</p>



<pre class="wp-block-code"><code>from fastapi import FastAPI
import pyodbc
import os

app = FastAPI()

# 從環境變數讀取連線資訊
conn_str = os.getenv("DB_CONNECTION_STRING")

@app.get("/")
def read_root():
    try:
        conn = pyodbc.connect(conn_str)
        cursor = conn.cursor()
        cursor.execute("SELECT @@VERSION")
        row = cursor.fetchone()
        return {"database_version": row&#91;0]}
    except Exception as e:
        return {"error": str(e)}
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">5. 開發建議與注意事項</h2>



<ul class="wp-block-list">
<li><strong>Azure SQL 的差異</strong>：當你部署到 Azure 時，只需修改 <code>DB_CONNECTION_STRING</code> 環境變數。Azure SQL 強制要求加密 (<code>Encrypt=yes</code>)，這也是為什麼我們在 Dockerfile 中安裝了最新版的 <strong>ODBC Driver 18</strong>。</li>



<li><strong>防火牆</strong>：如果你在本地使用 SQL Server Management Studio (SSMS) 連接 Docker 裡的資料庫，伺服器名稱請填 <code>127.0.0.1,1433</code>。</li>



<li><strong>前端 Proxy</strong>：在 React 的 <code>package.json</code> 中加入 <code>"proxy": "http://backend:8000"</code>，可以簡化前端呼叫 API 的程式碼，避免 CORS 問題。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>要在 Windows 本地測試並推送到 Azure，流程主要分為 <strong>「本地聯動測試」</strong> 與 <strong>「雲端推送部署」</strong> 兩個階段。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">1. 本地 Docker Desktop 測試</h2>



<p>在本地測試時，最重要的是確保 <strong>跨容器通訊</strong> 正常（例如 React 如何抓到 FastAPI 的資料）。</p>



<h3 class="wp-block-heading"><strong>執行測試指令</strong></h3>



<p>開啟 PowerShell 或終端機，進入專案根目錄（包含 <code>docker-compose.yml</code> 的地方）：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 1. 啟動所有服務 (包含 MSSQL, Backend, Frontend)
docker-compose up --build

# 2. 如果要以後台模式執行
docker-compose up -d
</code></pre>



<h3 class="wp-block-heading"><strong>如何驗證測試成功？</strong></h3>



<ul class="wp-block-list">
<li><strong>API 測試</strong>：瀏覽 <code>http://localhost:8000/docs</code>，點擊 <code>Execute</code> 測試 API 是否能成功從 MSSQL 讀取資料。</li>



<li><strong>前端測試</strong>：瀏覽 <code>http://localhost:3000</code>。</li>



<li><strong>查看日誌</strong>：如果連線失敗，執行 <code>docker-compose logs backend</code> 查看 Python 的 <code>pyodbc</code> 報錯訊息。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">2. 使用 Azure CLI 推送到 Azure Container Registry (ACR)</h2>



<p>當本地測試沒問題後，你需要將 Image 推送到雲端倉庫。Azure 提供 <strong>ACR (Azure Container Registry)</strong> 來存放這些映像檔。</p>



<h3 class="wp-block-heading"><strong>步驟 A：登入 Azure 並建立資源</strong></h3>



<p>首先，確保你已安裝 <a target="_blank" rel="noreferrer noopener" href="https://learn.microsoft.com/zh-tw/cli/azure/install-azure-cli-windows">Azure CLI</a>。</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 1. 登入 Azure
az login

# 2. 建立資源群組 (如果還沒有)
az group create --name MyResourceGroup --location eastasia

# 3. 建立 ACR (名稱必須是唯一且小寫)
az acr create --resource-group MyResourceGroup --name myuniqueacr --sku Basic

# 4. 登入 ACR 
az acr login --name myuniqueacr
</code></pre>



<h3 class="wp-block-heading"><strong>步驟 B：標記並推送 Image</strong></h3>



<p>你需要將本地的 Image 給予一個符合 ACR 規範的「標籤 (Tag)」。</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 1. 取得 ACR 的登入伺服器名稱 (通常是 myuniqueacr.azurecr.io)
$ACR_SERVER = "myuniqueacr.azurecr.io"

# 2. 標記本地 Image (假設你在 compose 產生的 image 叫 my-app_backend)
docker tag my-app_backend $ACR_SERVER/fastapi-backend:v1
docker tag my-app_frontend $ACR_SERVER/react-frontend:v1

# 3. 推送到雲端
docker push $ACR_SERVER/fastapi-backend:v1
docker push $ACR_SERVER/react-frontend:v1
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">3. Azure 端的資料庫銜接</h2>



<p>當你推送到 Azure 後，你的 FastAPI 需要改連 <strong>Azure SQL Database</strong>。</p>



<ol start="1" class="wp-block-list">
<li><strong>建立 Azure SQL</strong>：在 Azure Portal 建立一個 SQL Database。</li>



<li><strong>防火牆設定</strong>：在 Azure SQL 的防火牆設定中，勾選 <strong>「允許 Azure 服務和資源存取此伺服器」</strong>。</li>



<li><strong>環境變數</strong>：在部署 Container（如使用 Azure Container Apps）時，將 <code>DB_CONNECTION_STRING</code> 改為 Azure SQL 的連線字串。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">4. 懶人部署法：Azure Container Apps (推薦)</h2>



<p>如果你不想管複雜的 Kubernetes，可以使用 <strong>Azure Container Apps</strong>，它支援多容器應用。</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 建立 Container App 環境並直接從 ACR 部署 (簡化指令範例)
az containerapp up --name my-fastapi-app `
  --resource-group MyResourceGroup `
  --image $ACR_SERVER/fastapi-backend:v1 `
  --environment 'MyEnv'
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>常見踩雷提醒：</strong></p>



<ul class="wp-block-list">
<li><strong>晶片架構</strong>：如果你使用的是 Mac M1/M2 開發但推送到 Azure (x86_64)，打包時要加上 <code>--platform linux/amd64</code>。<strong>在 Windows 上開發則通常沒這個問題。</strong></li>



<li><strong>MSSQL 密碼</strong>：Azure SQL 的密碼要求較嚴格，本地測試時建議就先用複雜密碼。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>錯誤訊息 <code>failed to read dockerfile: open Dockerfile: no such file or directory</code> 非常直觀：<strong>Docker 在你指定的路徑下找不到 <code>Dockerfile</code></strong>。</p>



<p>這通常是因為 <code>docker-compose.yml</code> 裡面的 <code>build</code> 路徑設定，與你實際資料夾結構不一致造成的。</p>



<h3 class="wp-block-heading">1. 檢查你的目錄結構</h3>



<p>請確認你的資料夾長得像這樣（注意大小寫，Linux 容器對大小寫很敏感）：</p>



<p>Plaintext</p>



<pre class="wp-block-code"><code>C:\max\work\policy-recommends\my-fast-app\
├── docker-compose.yml
├── backend/
│   └── Dockerfile      &lt;-- 確認這個檔案存在，且副檔名沒有被隱藏 (不是 Dockerfile.txt)
└── frontend/
    └── Dockerfile      &lt;-- 確認這個檔案存在</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">重新執行的正確姿勢</h3>



<p>修正路徑或檔案後，建議先清理一下快取再執行：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 強制重新檢查 build context 並建立
docker-compose build --no-cache

# 啟動
docker-compose up
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">如何推送到 Azure ACR (完整指令流)</h3>



<p>等你本地 <code>docker-compose up</code> 成功執行後，按照以下順序推送到 Azure：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 1. 登入 Azure (會跳出瀏覽器)
az login

# 2. 登入你的 ACR (假設名字叫 myacr123)
az acr login --name myacr123

# 3. 取得本地剛剛 build 好的 Image ID 或名稱
# 執行 docker images 查看剛剛產生的 image 名稱
docker images

# 4. 幫 Image 打上標籤 (Tag)
# 格式：docker tag &#91;本地名稱] &#91;ACR名稱].azurecr.io/&#91;映像檔名稱]:&#91;版本]
docker tag my-fast-app-backend myacr123.azurecr.io/fastapi-api:v1

# 5. 推送
docker push myacr123.azurecr.io/fastapi-api:v1</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>針對 <strong>React (Node.js)</strong> 前端，在開發環境與生產環境（Azure）的需求不同。為了讓你現在能在 Windows Local 成功執行 <code>docker-compose up</code>，我們採用<strong>開發模式</strong>的 Dockerfile。</p>



<p>請在 <code>frontend/</code> 資料夾下建立一個名為 <code>Dockerfile</code> 的檔案（不含副檔名），內容如下：</p>



<p>Dockerfile</p>



<pre class="wp-block-code"><code># 1. 使用 Node.js 官方映像檔 (建議版本 18 或 20)
FROM node:20

# 2. 設定容器內的工作目錄
WORKDIR /app

# 3. 先複製 package.json 與 package-lock.json (利用 Docker 快取機制)
COPY package*.json ./

# 4. 安裝相依套件
RUN npm install

# 5. 複製其餘的所有程式碼到容器內
COPY . .

# 6. React 預設開發埠號為 3000
EXPOSE 3000

# 7. 啟動 React 開發伺服器
# 使用 --host 0.0.0.0 是為了讓容器外的瀏覽器能存取
CMD &#91;"npm", "start"]
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 兩個關鍵注意事項 (Windows 平台)</h3>



<h4 class="wp-block-heading">1. <code>.dockerignore</code> 檔案</h4>



<p>在 <code>frontend/</code> 資料夾內建立一個 <code>.dockerignore</code> 檔案。這能防止將本地巨大的 <code>node_modules</code> 複製進容器，導致 Build 速度極慢或產生錯誤。</p>



<p><strong>內容如下：</strong></p>



<pre class="wp-block-code"><code>node_modules
build
.git
*.md
</code></pre>



<h4 class="wp-block-heading">2. 解決 Docker 抓不到檔案變更 (Hot Reload)</h4>



<p>在 Windows 上使用 Docker Desktop 時，有時 React 的熱更新（存檔後網頁自動重新整理）會失效。如果發生這種情況，請在 <code>frontend/</code> 資料夾建立一個 <code>.env</code> 檔案，加入以下內容：</p>



<pre class="wp-block-code"><code>CHOKIDAR_USEPOLLING=true
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">接下來的操作步驟：</h3>



<p><strong>檢查 <code>package.json</code></strong>：確保你的 <code>frontend/</code> 資料夾內確實有 <code>package.json</code><br>（通常是執行 <code>npx create-react-app .</code> 產生的）。</p>



<p><strong>執行 Build</strong>：</p>



<pre class="wp-block-code"><code><code>docker-compose build frontend</code></code></pre>



<p><strong>啟動服務</strong>：</p>



<p><code>docker-compose up frontend</code></p>



<p><strong>如果你的 React 是用 Vite 建立的（現在比較流行），<code>CMD</code> 可能需要改成 <code>npm run dev -- --host</code>，你需要我確認你的 <code>package.json</code> 腳本內容嗎？</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>既然你的資料夾目前是空白的，我們來快速建立一個**「全端連通」**的範例。這個範例會讓 React 前端呼叫 FastAPI 後端，而後端會從 MSSQL 資料庫讀取版本資訊。</p>



<p>請依照以下檔案內容填入你的資料夾：</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">1. 後端 (backend/main.py)</h3>



<p>這是一個簡單的 API，它會嘗試連線資料庫並回傳 SQL Server 的版本。</p>



<p>Python</p>



<pre class="wp-block-code"><code>from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import pyodbc
import os

app = FastAPI()

# 允許前端跨域存取 (CORS)
app.add_middleware(
    CORSMiddleware,
    allow_origins=&#91;"*"],
    allow_methods=&#91;"*"],
    allow_headers=&#91;"*"],
)

@app.get("/api/status")
def get_status():
    conn_str = os.getenv("DB_CONNECTION_STRING")
    try:
        # 建立連線
        conn = pyodbc.connect(conn_str, timeout=5)
        cursor = conn.cursor()
        cursor.execute("SELECT @@VERSION")
        db_version = cursor.fetchone()&#91;0]
        conn.close()
        return {
            "status": "Connected",
            "database": "Microsoft SQL Server",
            "version": db_version
        }
    except Exception as e:
        return {
            "status": "Error",
            "message": str(e)
        }

@app.get("/")
def read_root():
    return {"message": "FastAPI is running!"}
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">2. 前端 (frontend/src/App.js)</h3>



<p>我們簡單用 <code>fetch</code> 來抓取後端的 API 資料。</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>import React, { useEffect, useState } from 'react';

function App() {
  const &#91;data, setData] = useState({ status: "Loading...", version: "" });

  useEffect(() =&gt; {
    // 指向後端容器的對外 Port
    fetch('http://localhost:8000/api/status')
      .then(response =&gt; response.json())
      .then(json =&gt; setData(json))
      .catch(err =&gt; setData({ status: "Error", message: "Cannot connect to API" }));
  }, &#91;]);

  return (
    &lt;div style={{ textAlign: 'center', marginTop: '50px', fontFamily: 'sans-serif' }}&gt;
      &lt;h1&gt;React + FastAPI + MSSQL&lt;/h1&gt;
      &lt;div style={{ border: '1px solid #ccc', padding: '20px', display: 'inline-block' }}&gt;
        &lt;p&gt;&lt;strong&gt;DB Status:&lt;/strong&gt; {data.status}&lt;/p&gt;
        {data.version &amp;&amp; &lt;p&gt;&lt;strong&gt;DB Version:&lt;/strong&gt; {data.version}&lt;/p&gt;}
        {data.message &amp;&amp; &lt;p style={{ color: 'red' }}&gt;{data.message}&lt;/p&gt;}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">3. 前端必要設定 (frontend/package.json)</h3>



<p>如果你還沒建立 React 專案，最簡單的方式是在 <code>frontend/</code> 資料夾下建立這個檔案：</p>



<p>JSON</p>



<pre class="wp-block-code"><code>{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build"
  },
  "eslintConfig": {
    "extends": &#91;
      "react-app"
    ]
  },
  "browserslist": {
    "production": &#91;"&gt;0.2%", "not dead", "not op_mini all"],
    "development": &#91;"last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
  }
}
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">4. 啟動與驗證</h3>



<p>現在再次執行：</p>



<pre class="wp-block-code"><code>docker-compose up --build
</code></pre>



<p><strong>驗證方式：</strong></p>



<ol start="1" class="wp-block-list">
<li><strong>等待 MSSQL 啟動</strong>：SQL Server 啟動較慢（約 20-30 秒），剛開始 API 可能會報錯，請重新整理頁面。</li>



<li><strong>開啟瀏覽器</strong>：輸入 <code>http://localhost:3000</code>。</li>



<li><strong>預期結果</strong>：你應該會看到網頁顯示 <strong>&#8220;DB Status: Connected&#8221;</strong> 以及 <strong>&#8220;Microsoft SQL Azure&#8230;&#8221;</strong> 的版本文字。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>小提示：</strong></p>



<p>由於我們是在 Docker 外部的瀏覽器看網頁，前端代碼 <code>fetch('http://localhost:8000/api/status')</code> 使用 <code>localhost</code> 是正確的。</p>



<p>但如果是後端要找資料庫，<code>docker-compose.yml</code> 裡的連線字串必須寫 <code>SERVER=db</code>。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>這個錯誤 <code>"/requirements.txt": not found</code> 代表 Docker 在執行 <code>COPY requirements.txt .</code> 這行指令時，在你的 <strong><code>backend/</code> 資料夾</strong> 裡找不到這個檔案。</p>



<p>在 Docker 的世界裡，<code>COPY</code> 指令尋找檔案的起點是 <code>docker-compose.yml</code> 中定義的 <code>context</code> 路徑。</p>



<h3 class="wp-block-heading">1. 解決方法：建立必要的相依檔案</h3>



<p>請確保你的目錄結構與檔案名稱完全一致（注意大小寫）：</p>



<p>在 <code>\my-fast-app\backend\</code> 資料夾下，建立一個名為 <strong><code>requirements.txt</code></strong> 的檔案，內容如下：</p>



<pre class="wp-block-code"><code>fastapi
uvicorn
pyodbc
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">2. 完整檢查清單</h3>



<p>如果還是報錯，請檢查以下三點：</p>



<p><strong>檔案位置</strong>：<code>requirements.txt</code> 必須與 <code>backend/Dockerfile</code> 放在同一個資料夾內。</p>



<p><strong>副檔名陷阱</strong>：在 Windows 中，檔案可能會被命名為 <code>requirements.txt.txt</code>（如果你開啟了隱藏已知檔案類型的擴充名）。請確認檔名只有一個 <code>.txt</code>。</p>



<p><strong>Docker Compose 路徑</strong>：確認你的 <code>docker-compose.yml</code> 是這樣寫的：</p>



<pre class="wp-block-code"><code>backend:
 build: ./backend 
# 這代表 Docker 會去 backend 資料夾找 Dockerfile 和 requirements.txt</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">3. 下一步：再次執行</h3>



<p>修正檔案後，請執行以下指令（加上 <code>--no-cache</code> 確保它不會抓到舊的錯誤暫存）：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code>docker-compose build --no-cache
docker-compose up
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 給你的開發小秘訣</h3>



<p>由於你之後要推送到 <strong>Azure</strong>，現在建立這些檔案時，可以順便檢查你的 <strong><code>frontend/</code></strong> 資料夾是否也已經準備好了：</p>



<ol start="1" class="wp-block-list">
<li><code>frontend/package.json</code> (前一個回答提供的內容)</li>



<li><code>frontend/src/App.js</code> (前一個回答提供的內容)</li>



<li><code>frontend/Dockerfile</code></li>
</ol>



<p><strong>如果 <code>backend</code> 編譯成功了，但換成 <code>frontend</code> 報錯 <code>package.json not found</code>，處理方式是一樣的：確保檔案真的躺在 <code>frontend/</code> 資料夾裡。</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>這是一個完整的測試指南，幫助你驗證 <strong>React (前端)</strong>、<strong>FastAPI (後端)</strong> 與 <strong>SQL Server (資料庫)</strong> 這三個組件是否已經成功串聯。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">1. 檢查容器運行狀態</h2>



<p>首先，確保所有服務都已啟動。在 Windows PowerShell 執行：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code>docker ps
</code></pre>



<p><strong>檢查重點：</strong> 你應該會看到三個容器，且狀態（STATUS）顯示為 <code>Up</code>。如果看到 <code>Exited</code>，代表該服務啟動失敗，請執行 <code>docker-compose logs [服務名稱]</code> 查看報錯。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">2. 測試後端 API (FastAPI)</h2>



<p>FastAPI 自帶強大的 <strong>Swagger UI</strong>，這是測試資料庫連線最快的方法。</p>



<ul class="wp-block-list">
<li><strong>網址：</strong> 打開瀏覽器輸入 <code>http://localhost:8000/docs</code></li>



<li><strong>操作：</strong> 1. 找到我們寫的 <code>GET /api/status</code> 端點。2. 點擊 <strong>&#8220;Try it out&#8221;</strong>，然後點擊 <strong>&#8220;Execute&#8221;</strong>。</li>



<li><strong>預期結果：</strong>
<ul class="wp-block-list">
<li><strong>Code 200 (Success)</strong>：你會看到包含 SQL Server 版本資訊的 JSON 字串。這代表 <code>FastAPI -&gt; MSSQL</code> 通訊正常。</li>



<li><strong>Error 500</strong>：通常是資料庫密碼錯誤或 Driver 設定問題。</li>



<li><strong>連線逾時</strong>：SQL Server 啟動較慢（通常需要 30 秒），請稍等片刻再試。</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">3. 測試前端網頁 (React)</h2>



<p>現在測試前端是否能成功抓到後端的資料。</p>



<ul class="wp-block-list">
<li><strong>網址：</strong> 打開瀏覽器輸入 <code>http://localhost:3000</code></li>



<li><strong>操作：</strong> 觀察網頁顯示的內容。</li>



<li><strong>預期結果：</strong>
<ul class="wp-block-list">
<li>畫面顯示 <strong>&#8220;DB Status: Connected&#8221;</strong>：恭喜！整個全端流程 <code>瀏覽器 -&gt; React -&gt; FastAPI -&gt; MSSQL</code> 全部打通。</li>



<li>畫面卡在 <strong>&#8220;Loading&#8230;&#8221;</strong> 或顯示 <strong>&#8220;Error&#8221;</strong>：
<ol start="1" class="wp-block-list">
<li>按下 <code>F12</code> 打開開發者工具。</li>



<li>切換到 <strong>Console (主控台)</strong>。如果是 <strong>CORS</strong> 報錯，代表 FastAPI 的中間件設定有誤；如果是 <strong>404</strong>，請確認 API 路徑是否正確。</li>
</ol>
</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">4. 測試資料庫 (SQL Server)</h2>



<p>如果你懷疑資料庫根本沒動，可以獨立測試它。</p>



<p><strong>查看日誌：</strong></p>



<pre class="wp-block-code"><code>docker logs mssql_db </code></pre>



<p>尋找這一行：<code>SQL Server is now ready for client connections.</code>。</p>



<p><strong>外部連線：</strong>如果你電腦有安裝 <strong>SSMS (SQL Server Management Studio)</strong> 或 <strong>Azure Data Studio</strong>，可以直接連線：</p>



<p><strong>伺服器：</strong> <code>127.0.0.1,1433</code></p>



<p><strong>帳號：</strong> <code>sa</code></p>



<p><strong>密碼：</strong> <code>YourStrongPassword123!</code> (你在 compose 檔設定的那組)</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">5. 總結連線邏輯 (除錯關鍵)</h2>



<p>在 Docker 環境下，連線位址會因為「誰發起請求」而不同：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>連線路徑</strong></td><td><strong>使用地址</strong></td><td><strong>原因</strong></td></tr></thead><tbody><tr><td><strong>你 -&gt; 前端</strong></td><td><code>localhost:3000</code></td><td>你在 Docker 網路外部。</td></tr><tr><td><strong>你 -&gt; 後端</strong></td><td><code>localhost:8000</code></td><td>你在 Docker 網路外部。</td></tr><tr><td><strong>前端 (瀏覽器) -&gt; 後端</strong></td><td><code>localhost:8000</code></td><td><strong>重要：</strong> React 代碼是在「你的瀏覽器」執行，所以對它來說後端就在 <code>localhost</code>。</td></tr><tr><td><strong>後端 -&gt; 資料庫</strong></td><td><code>db:1433</code></td><td><strong>重要：</strong> Python 代碼是在「容器內部」執行，必須使用 Compose 服務名稱 <code>db</code>。</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">常見問題排除</h3>



<ul class="wp-block-list">
<li><strong>修改代碼後沒反應？</strong> 確保 <code>docker-compose.yml</code> 有設定 <code>volumes</code> 掛載，這樣你在 Windows 修改程式碼時，容器內才會同步更新。</li>



<li><strong>無法連線資料庫？</strong> 請檢查 <code>main.py</code> 裡的連線字串是否包含 <code>TrustServerCertificate=yes</code>，因為自架的 Docker SQL Server 通常沒有正式憑證。</li>
</ul>



<p><strong>如果你現在測試成功了，下一步我們是否要來練習如何將這兩個 Image (Frontend/Backend) 推送到 Azure ACR 並準備部署？</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>port 3000 改 8080</p>



<p>沒問題！在 Windows 上，<code>3000</code> 埠確實常被一些系統服務（如某些版本的 Windows 服務或開發工具）佔用。將前端改為 <code>8080</code> 是一個好選擇。</p>



<p>請依照以下三個步驟修改設定：</p>



<h3 class="wp-block-heading">1. 修改 <code>docker-compose.yml</code></h3>



<p>將 <code>frontend</code> 服務的 <code>ports</code> 對應改為 <code>8080:3000</code>。這代表：<strong>瀏覽器輸入 8080，會轉發到容器內的 3000</strong>。</p>



<p>YAML</p>



<pre class="wp-block-code"><code>  frontend:
    build: ./frontend
    volumes:
      - ./frontend:/app
      - /app/node_modules
    ports:
      - "8080:3000"  # 修改這裡：外部 8080 -&gt; 內部 3000
    stdin_open: true
    environment:
      - WDS_SOCKET_PORT=8080 # 確保 React 的熱更新 (Hot Reload) 也能走 8080
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">2. 修改前端程式碼 (<code>frontend/src/App.js</code>)</h3>



<p>如果你的 React 程式碼中原本有寫死後端網址，請確認它依然指向 <code>8000</code>（後端沒改）。</p>



<p>但為了確保瀏覽器不會因為埠號變更而產生安全性阻攔，請檢查 <code>App.js</code> 中的 <code>fetch</code>：</p>



<p>JavaScript</p>



<pre class="wp-block-code"><code>// 確保後端依然是 8000
fetch('http://localhost:8000/api/status')
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">3. 重新啟動服務</h3>



<p>由於你修改了 <code>docker-compose.yml</code> 的結構，建議先停止舊的容器再重新啟動：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 停止並移除舊的容器
docker-compose down

# 重新啟動
docker-compose up
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">如何驗證？</h3>



<ol start="1" class="wp-block-list">
<li><strong>前端網頁</strong>：打開瀏覽器訪問 <code>http://localhost:8080</code>。</li>



<li><strong>後端 API</strong>：訪問 <code>http://localhost:8000/docs</code>。</li>
</ol>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 專業小撇步：如果連 8080 也被佔用？</h3>



<p>如果在啟動時依然看到 <code>bind: An attempt was made...</code>，這代表 <code>8080</code> 也有人在用。你可以執行這個指令來徹底「殺掉」佔用者：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 尋找誰在用 8080 (在管理員模式的 PowerShell)
$proc = Get-NetTCPConnection -LocalPort 8080 -ErrorAction SilentlyContinue
if($proc) { Stop-Process -Id $proc.OwningProcess -Force }
</code></pre>



<p><strong>現在你可以試試看 <code>http://localhost:8080</code> 能不能看到 React 畫面了。如果畫面出來了但顯示 &#8220;Connection Error&#8221;，我們再來調整 FastAPI 的 CORS 設定！</strong></p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/windows-docker-compose-to-cloud/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Python MySQL to MSSQL 資料匯出</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/python-mysql-to-mssql-export/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/python-mysql-to-mssql-export/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 16 Mar 2026 01:58:19 +0000</pubDate>
				<category><![CDATA[Python筆記]]></category>
		<category><![CDATA[mssql]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7960</guid>

					<description><![CDATA[mysql 使用 mysqldump 產生的 ....]]></description>
										<content:encoded><![CDATA[
<p>mysql 使用 mysqldump 產生的 .sql 檔案, 要匯入到 mssql 之中, 由於匯出的資料量太大, 直接轉換 mysql sql command 為  mssql sql command, 如果使用 SSMS(SQL Server Management Studio) 會遇到記憶體不足的問題.</p>



<p>透過 sqlcmd 匯入資料庫 (不經過 SSMS):</p>



<pre class="wp-block-code"><code>sqlcmd -S localhost -d %db_name% -E -i %output_file%</code></pre>



<p>這個也會遇到很多奇奇怪怪的問題, 明明在 SSMS 執行正常的 table, 在 sqlcmd 會遇到重覆插入 PK 值.</p>



<p>最佳解法是透過下列的python script 進行 export, 這是整合了「逐行讀取」、「自動切分 1000 筆」、「停用約束」以及修正後語法的完整版本：</p>



<pre class="wp-block-code"><code>import re
import pyodbc
import argparse
import sys

def parse_value(val_str):
    """Parse a single SQL value string and return a Python value."""
    val_str = val_str.strip()
    if val_str.upper() == 'NULL':
        return None
    if (val_str.startswith("'") and val_str.endswith("'")):
        # Remove surrounding quotes
        inner = val_str&#91;1:-1]
        # Unescape MySQL-style escape sequences
        result = &#91;]
        i = 0
        while i &lt; len(inner):
            if inner&#91;i] == '\\' and i + 1 &lt; len(inner):
                next_char = inner&#91;i + 1]
                if next_char == "'":
                    result.append("'")
                elif next_char == "\\":
                    result.append("\\")
                elif next_char == "n":
                    result.append("\n")
                elif next_char == "r":
                    result.append("\r")
                elif next_char == "t":
                    result.append("\t")
                elif next_char == "0":
                    result.append("\0")
                elif next_char == "%":
                    result.append("%")
                elif next_char == "_":
                    result.append("_")
                else:
                    result.append(next_char)
                i += 2
            elif inner&#91;i] == "'" and i + 1 &lt; len(inner) and inner&#91;i + 1] == "'":
                # SQL-style escaped quote
                result.append("'")
                i += 2
            else:
                result.append(inner&#91;i])
                i += 1
        return "".join(result)
    # Try numeric
    try:
        if '.' in val_str:
            return float(val_str)
        return int(val_str)
    except ValueError:
        return val_str

def split_row_values(row_str):
    """Split a single row '(val1, val2, ...)' into individual values."""
    # Remove outer parentheses
    inner = row_str.strip()
    if inner.startswith('('):
        inner = inner&#91;1:]
    if inner.endswith(')'):
        inner = inner&#91;:-1]
    
    values = &#91;]
    current = &#91;]
    in_string = False
    i = 0
    
    while i &lt; len(inner):
        char = inner&#91;i]
        
        if in_string:
            if char == '\\' and i + 1 &lt; len(inner):
                current.append(char)
                current.append(inner&#91;i + 1])
                i += 2
                continue
            elif char == "'":
                # Check for escaped quote ''
                if i + 1 &lt; len(inner) and inner&#91;i + 1] == "'":
                    current.append(char)
                    current.append(inner&#91;i + 1])
                    i += 2
                    continue
                else:
                    in_string = False
                    current.append(char)
            else:
                current.append(char)
        else:
            if char == "'":
                in_string = True
                current.append(char)
            elif char == ',':
                values.append("".join(current).strip())
                current = &#91;]
            else:
                current.append(char)
        i += 1
    
    if current:
        values.append("".join(current).strip())
    
    return values

def split_values_robust(values_str):
    """Split VALUES (...),(...),... into individual row strings."""
    rows = &#91;]
    buffer = &#91;]
    paren_depth = 0
    in_string = False
    i = 0
    length = len(values_str)
    
    while i &lt; length:
        char = values_str&#91;i]
        
        if in_string:
            if char == "\\" and i + 1 &lt; length:
                buffer.append(char)
                buffer.append(values_str&#91;i + 1])
                i += 2
                continue
            elif char == "'":
                # Check for '' escape
                if i + 1 &lt; length and values_str&#91;i + 1] == "'":
                    buffer.append(char)
                    buffer.append(values_str&#91;i + 1])
                    i += 2
                    continue
                in_string = False
                buffer.append(char)
            else:
                buffer.append(char)
        else:
            if char == "'":
                in_string = True
                buffer.append(char)
            elif char == "(":
                paren_depth += 1
                buffer.append(char)
            elif char == ")":
                paren_depth -= 1
                buffer.append(char)
                if paren_depth == 0:
                    rows.append("".join(buffer).strip())
                    buffer = &#91;]
            elif char == "," and paren_depth == 0:
                pass
            else:
                if paren_depth > 0:
                    buffer.append(char)
        i += 1
    return rows

def run_import(input_file):
    # 資料庫連線配置
    conn_config = {
        "DRIVER": "{ODBC Driver 18 for SQL Server}",
        "SERVER": "127.0.0.1",
        "DATABASE": "你的資料庫名稱",
        "UID": "帳號",
        "PWD": "密碼",
        "Encrypt": "yes",
        "TrustServerCertificate": "yes"
    }

    conn_str = ";".join(&#91;f"{k}={v}" for k, v in conn_config.items()])

    try:
        conn = pyodbc.connect(conn_str, autocommit=False)
        cursor = conn.cursor()
        print(f"檔案讀取中: {input_file}")
    except Exception as e:
        print(f"連線失敗: {e}")
        return

    try:
        with open(input_file, 'r', encoding='utf-8') as f:
            sql_content = f.read()
    except Exception as e:
        print(f"讀檔失敗: {e}")
        return

    # Handle INSERT INTO with parameterized queries
    insert_pattern = re.compile(
        r"INSERT INTO\s+`?(&#91;\w_]+)`?\s*(?:\((.*?)\))?\s*VALUES\s*(.+?);",
        re.S | re.I
    )

    truncated_tables = set()  # Track tables already truncated
    table_stats = {}  # Track cumulative success/error counts per table

    for match in insert_pattern.finditer(sql_content):
        table_name = match.group(1)
        cols_raw = match.group(2) if match.group(2) else ""
        raw_values = match.group(3)

        # Build column clause
        if cols_raw:
            col_names = &#91;c.strip().replace('`', '') for c in cols_raw.split(',')]
            columns_clause = "(&#91;" + "],&#91;".join(col_names) + "])"
        else:
            # No column list in SQL dump — fetch from MSSQL metadata
            # This is required when IDENTITY_INSERT is ON
            try:
                cursor.execute("""
                    SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
                    WHERE TABLE_NAME = ?
                    ORDER BY ORDINAL_POSITION
                """, table_name)
                db_cols = &#91;row&#91;0] for row in cursor.fetchall()]
                if db_cols:
                    columns_clause = "(&#91;" + "],&#91;".join(db_cols) + "])"
                else:
                    columns_clause = ""
            except:
                columns_clause = ""

        print(f"正在處理: {table_name}")

        # Truncate table before inserting (only once per table)
        if table_name not in truncated_tables:
            try:
                cursor.execute(f"TRUNCATE TABLE &#91;{table_name}]")
                conn.commit()
                print(f"  已清空資料表: {table_name}")
            except Exception as e:
                conn.rollback()
                # TRUNCATE fails if there are foreign key constraints, fallback to DELETE
                try:
                    cursor.execute(f"DELETE FROM &#91;{table_name}]")
                    conn.commit()
                    print(f"  已清空資料表 (DELETE): {table_name}")
                except Exception as e2:
                    conn.rollback()
                    print(f"  清空資料表失敗: {table_name}: {e2}")
            truncated_tables.add(table_name)

        all_rows = split_values_robust(raw_values)
        print(f"  共 {len(all_rows)} 筆資料")

        success_count = 0
        error_count = 0

        # Check if table has IDENTITY column, if so enable IDENTITY_INSERT
        has_identity = False
        try:
            cursor.execute(f"""
                SELECT COUNT(*) FROM sys.identity_columns 
                WHERE OBJECT_NAME(object_id) = ?
            """, table_name)
            row = cursor.fetchone()
            if row and row&#91;0] > 0:
                has_identity = True
        except:
            pass

        if has_identity:
            try:
                cursor.execute(f"SET IDENTITY_INSERT &#91;{table_name}] ON")
            except Exception as e:
                print(f"  無法開啟 IDENTITY_INSERT: {e}")

        for idx, row_str in enumerate(all_rows):
            raw_vals = split_row_values(row_str)
            parsed_vals = &#91;parse_value(v) for v in raw_vals]
            
            placeholders = ",".join(&#91;"?" for _ in parsed_vals])
            sql = f"INSERT INTO &#91;{table_name}] {columns_clause} VALUES ({placeholders})"
            
            try:
                cursor.execute(sql, parsed_vals)
                conn.commit()
                success_count += 1
            except Exception as e:
                conn.rollback()
                error_count += 1
                if error_count &lt;= 3:
                    print(f"  跳過錯誤列 {idx}: {e}")
                    # Print the values for debugging
                    preview_vals = &#91;str(v)&#91;:50] if v is not None else 'NULL' for v in parsed_vals]
                    print(f"  值預覽: {preview_vals}")
                elif error_count == 4:
                    print(f"  (後續錯誤將不再顯示...)")

        if has_identity:
            try:
                cursor.execute(f"SET IDENTITY_INSERT &#91;{table_name}] OFF")
            except:
                pass

        # Accumulate stats per table
        if table_name not in table_stats:
            table_stats&#91;table_name] = {"success": 0, "error": 0}
        table_stats&#91;table_name]&#91;"success"] += success_count
        table_stats&#91;table_name]&#91;"error"] += error_count
        print(f"  本批: 成功 {success_count} 筆, 失敗 {error_count} 筆")

    # Print summary for tables with multiple INSERT batches
    print("\n=== 匯入摘要 ===")
    for tbl, stats in table_stats.items():
        print(f"  {tbl}: 成功 {stats&#91;'success']} 筆, 失敗 {stats&#91;'error']} 筆")

    cursor.close()
    conn.close()
    print("全部完成。")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="MariaDB To MSSQL Migration Tool")
    parser.add_argument("input", help="Path to the .sql dump file")
    args = parser.parse_args()
    
    run_import(args.input)</code></pre>



<p>如果執行過程中遇到記憶體不足的問題，通常是因為 <code>split_values</code> 處理了單一極其巨大的 <code>INSERT</code> 字串。若發生此情況，請告訴我，我們可以改用生成器（Generator）模式來進一步優化字串解析。</p>



<p>上面這個版本針對一般情況是可以使用，針對特定的資料庫欄位類型（例如 Blob 或特殊的日期格式）則需要再進一步的轉換處理。</p>



<p>執行結果:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="528" height="133" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/mstsc_2026-03-16-09-47-1e.jpg?v=1773626286" alt="" class="wp-image-7961"/></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/python-mysql-to-mssql-export/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Python MSSQL 資料插入範例</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/python-mssql-insert/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/python-mssql-insert/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 16 Mar 2026 01:43:06 +0000</pubDate>
				<category><![CDATA[Python筆記]]></category>
		<category><![CDATA[mssql]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7957</guid>

					<description><![CDATA[插入成功的執行結果: 要在 Python 中將資...]]></description>
										<content:encoded><![CDATA[
<p>插入成功的執行結果:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="518" height="130" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/mstsc_2026-03-16-09-14-1d.jpg?v=1773623811" alt="" class="wp-image-7958"/></figure>



<p>要在 Python 中將資料寫入 MSSQL，最常見且穩定的組合是使用 pyodbc 套件配合微軟官方提供的 ODBC Driver。安裝檔下載:</p>



<p>下載適用於 SQL Server 的 ODBC 驅動程式<br><a href="https://learn.microsoft.com/zh-tw/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver17">https://learn.microsoft.com/zh-tw/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver17</a></p>



<h3 class="wp-block-heading">需安裝的組件</h3>



<p>系統層級需要安裝 Microsoft ODBC Driver for SQL Server。這是微軟提供的驅動程式，讓作業系統能夠與 SQL Server 通訊。建議安裝最新的版本（如 Driver 18 或 17）。</p>



<p>Python 環境則需要安裝 pyodbc 函式庫。你可以透過 pip install pyodbc 指令完成安裝。如果你的專案較具規模，通常也會搭配 sqlalchemy 配合 pandas 來處理大數據量，安裝指令為 pip install sqlalchemy pandas。</p>



<h3 class="wp-block-heading">實作範例</h3>



<p>以下是使用 pyodbc 進行單筆資料插入的標準寫法：</p>



<p>Python</p>



<pre class="wp-block-code"><code>import pyodbc

# 設定連線資訊
conn_config = {
    "DRIVER": "{ODBC Driver 18 for SQL Server}",
    "SERVER": "127.0.0.1", # 如果是本機也可以用 localhost 或 (local)
    "DATABASE": "你的資料庫名稱",
    "UID": "帳號",
    "PWD": "密碼",
    "Encrypt": "yes",
    "TrustServerCertificate": "yes"
}

conn_str = ";".join(&#91;f"{k}={v}" for k, v in conn_config.items()])

def sync_database():
    conn = None
    try:
        # 建立連線
        conn = pyodbc.connect(conn_str)
        cursor = conn.cursor()

        table_name = "UserLogs"
        
        # 1. 檢查資料表是否存在
        check_sql = "SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(?) AND type = 'U'"
        cursor.execute(check_sql, table_name)
        
        if not cursor.fetchone():
            print(f"資料表 {table_name} 不存在，正在建立...")
            # 2. 建立資料表
            create_sql = f"""
            CREATE TABLE {table_name} (
                ID INT IDENTITY(1,1) PRIMARY KEY,
                UserName NVARCHAR(50),
                ActionTime DATETIME DEFAULT GETDATE(),
                Status NVARCHAR(20)
            )
            """
            cursor.execute(create_sql)
            conn.commit()
            print("資料表建立成功")

        # 3. 執行資料插入
        insert_sql = f"INSERT INTO {table_name} (UserName, Status) VALUES (?, ?)"
        cursor.execute(insert_sql, ("Max", "Active"))
        conn.commit()
        print("資料插入成功")

        # 4. 刪除資料表 (Drop Table)
        print(f"正在刪除資料表 {table_name}...")
        drop_sql = f"DROP TABLE {table_name}"
        cursor.execute(drop_sql)
        conn.commit()
        print("資料表已刪除")

    except pyodbc.Error as e:
        print(f"資料庫操作失敗: {e}")
        if conn:
            conn.rollback()
    finally:
        if conn:
            cursor.close()
            conn.close()

if __name__ == "__main__":
    sync_database()</code></pre>



<h3 class="wp-block-heading">注意事項</h3>



<p>在連線字串中，如果你是連線到本機測試環境且沒有設定 SSL 憑證，務必加上 TrustServerCertificate=yes 參數，否則常會因為加密安全性檢查而導致連線失敗。另外，對於大量資料的寫入，建議使用 cursor.fast_executemany = True 或者是 pandas 的 to_sql 方法，這能顯著提升寫入效能。</p>



<p>在 conn_config 中明確加上了 Encrypt: yes。這是因為 ODBC Driver 18 預設要求加密連線。配合 TrustServerCertificate: yes，可以跳過 SSL 憑證的有效性檢查，通常能解決無法開啟具名管道或連線逾時的問題。</p>



<p>如果你的 SQL Server 是 Express 版本，SERVER 欄位可能需要寫成 127.0.0.1\SQLEXPRESS 這種格式，請根據你的實例名稱進行調整。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/python-mssql-insert/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>sqlite 在 multi-threads 情況下同時寫入的問題</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/sqlite-multi-threads-wal/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/sqlite-multi-threads-wal/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 04 Mar 2026 12:42:31 +0000</pubDate>
				<category><![CDATA[Python筆記]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[sqlite]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7816</guid>

					<description><![CDATA[在多執行緒與多核心 CPU 的環境下，使用 Py...]]></description>
										<content:encoded><![CDATA[
<p>在多執行緒與多核心 CPU 的環境下，使用 Python FastAPI 搭配 SQLite 處理多人同時寫入，確實有可能遇到存取錯誤。這主要與 SQLite 的鎖定機制以及 Python 的資料庫驅動程式行為有關。</p>



<h3 class="wp-block-heading">SQLite 的鎖定機制</h3>



<p>SQLite 是一個基於檔案的資料庫，它的併發控制相對單純。當一個進程或執行緒想要寫入資料時，SQLite 會對整個資料庫檔案加上排他鎖（Exclusive Lock）。在預設的資料庫模式下，當某個連線正在寫入，其他連線的讀取或寫入請求都會被阻擋。如果寫入操作耗時過長，其他嘗試寫入的連線會因為等不到鎖而拋出 sqlite3.OperationalError: database is locked 的錯誤。</p>



<h3 class="wp-block-heading">Python 與 FastAPI 的影響</h3>



<p>FastAPI 通常運行在非同步環境下，但標準的 sqlite3 套件是同步的。如果你在 FastAPI 中使用多執行緒（例如透過 thread pool 執行同步函式），多個執行緒同時存取同一個資料庫連線會引發程式崩潰，因為 sqlite3 預設不允許跨執行緒共享連線。即使每個執行緒都有獨立連線，上述的檔案鎖問題依然存在。此外，Python 有全域解釋器鎖（GIL），雖然這在處理 I/O 密集任務時影響較小，但它確保了 Python bytecode 一次只會由一個核心執行，這並不能解決資料庫層級的競爭問題。</p>



<h3 class="wp-block-heading">如何減少存取錯誤</h3>



<p>要讓 SQLite 在多人同時寫入時更穩定，最有效的做法是開啟預寫式日誌（WAL）模式。在 WAL 模式下，讀取和寫入可以同時進行，不會互相阻塞。你可以透過執行 PRAGMA journal_mode=WAL; 來切換。另外，設定適當的 timeout 參數也很重要。在建立連線時設定 sqlite3.connect(db_file, timeout=20)，這會讓連線在遇到鎖定時先等待一段時間，而不是立即報錯。</p>



<h3 class="wp-block-heading">替代方案與考量</h3>



<p>如果你的應用場景真的有非常頻繁的併發寫入需求，SQLite 可能不是最佳選擇。雖然 WAL 模式能改善情況，但 SQLite 終究是為了單機或中輕量級應用設計的。當寫入衝突頻繁到 timeout 都無法解決時，改用像 PostgreSQL 這種客戶端伺服器架構的資料庫會是比較根本的解決辦法，因為它們能處理更細粒度的行級鎖定（Row-level locking），在多核心環境下發揮更好的效能。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p id="p-rc_fe2fd4797b23df9d-16">要在 Python 的 sqlite3 模組中啟用 WAL 模式，你需要在建立資料庫連線後執行一段 PRAGMA 指令。這個設定會儲存在資料庫檔案中，但通常建議在每次程式啟動連線時都確認一次。<sup></sup></p>



<h3 class="wp-block-heading">啟用 WAL 模式的步驟</h3>



<p id="p-rc_fe2fd4797b23df9d-17">在 Python 中，你可以透過執行 execute 方法來切換模式。這會將日誌模式從預設的 DELETE 改為 WAL。<sup></sup></p>



<pre class="wp-block-code"><code>import sqlite3

# 建立連線，建議加上 timeout 避免 busy 錯誤
conn = sqlite3.connect('example.db', timeout=10)

# 啟用 WAL 模式
conn.execute('PRAGMA journal_mode=WAL;')

# 驗證是否成功切換
cursor = conn.execute('PRAGMA journal_mode;')
print(f"目前模式為: {cursor.fetchone()&#91;0]}")

conn.close()
</code></pre>



<h3 class="wp-block-heading">為什麼要在連線時設定</h3>



<p>雖然 WAL 模式一旦設定後會持久化在資料庫標頭中，但在程式碼中明確執行這行指令有助於確保環境一致。當你啟用 WAL 後，你會發現資料庫目錄下多了兩個暫存檔（結尾為 -wal 與 -shm），這是正常現象。</p>



<h3 class="wp-block-heading">在 FastAPI 中的建議配置</h3>



<p>如果你在 FastAPI 中使用，建議在建立連線的工具函式或是 SQLAlchemy 的 engine 配置中加入這個設定。如果你使用 SQLAlchemy，可以使用 event listener 來自動執行。</p>



<pre class="wp-block-code"><code>from sqlalchemy import create_engine, event

engine = create_engine("sqlite:///./test.db")

@event.listens_for(engine, "connect")
def set_sqlite_pragma(dbapi_connection, connection_record):
    cursor = dbapi_connection.cursor()
    cursor.execute("PRAGMA journal_mode=WAL")
    cursor.execute("PRAGMA synchronous=NORMAL")
    cursor.close()
</code></pre>



<p id="p-rc_fe2fd4797b23df9d-18">這裡額外加入了 PRAGMA synchronous=NORMAL;，在 WAL 模式下配合這個設定可以大幅提升寫入速度，同時兼顧資料安全性。<sup></sup></p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/sqlite-multi-threads-wal/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ubuntu 24 無法直接安裝 python package</title>
		<link>https://stackoverflow.max-everyday.com/2026/02/ubuntu-24-python-package/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/02/ubuntu-24-python-package/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 02 Feb 2026 12:59:13 +0000</pubDate>
				<category><![CDATA[Python筆記]]></category>
		<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7706</guid>

					<description><![CDATA[在 Ubuntu 24.04 中，系統預設安裝 ...]]></description>
										<content:encoded><![CDATA[
<p>在 Ubuntu 24.04 中，系統預設安裝 Python 3.12。為了維護系統穩定，這類較新的 Linux 版本採用了 PEP 668 規範，限制直接使用 pip 將套件安裝到全域環境。如果你直接執行 pip install，通常會看到外部管理環境的錯誤提示。</p>



<h3 class="wp-block-heading">使用虛擬環境安裝</h3>



<p>這是目前最推薦且標準的作法，可以避免不同專案之間的套件衝突。你必須先安裝 venv 工具，然後建立一個獨立的資料夾來存放套件。</p>



<pre class="wp-block-code"><code>sudo apt update
sudo apt install python3-venv
python3 -m venv myenv
source myenv/bin/activate</code></pre>



<p>進入虛擬環境後，終端機提示字元前會出現環境名稱，這時你就可以放心使用 pip install 安裝任何套件，它們會被存放在 myenv 資料夾內。</p>



<h3 class="wp-block-heading">使用 pipx 安裝工具</h3>



<p>如果你想安裝的是像 black、ansible 或 flake8 這種可以在終端機直接執行的工具，而不是要在程式碼裡 import 的函式庫，pipx 是最好的選擇。它會自動幫每個工具建立隔離環境並設定好執行路徑。</p>



<pre class="wp-block-code"><code>sudo apt install pipx
pipx ensurepath
pipx install 套件名稱</code></pre>



<h3 class="wp-block-heading">使用 APT 系統套件管理</h3>



<p>如果你需要的套件非常常見，例如 NumPy 或 Requests，Ubuntu 的官方軟體倉庫通常會有維護好的版本。這些套件會安裝在全域，且由系統統一管理。</p>



<pre class="wp-block-code"><code>sudo apt install python3-numpy</code></pre>



<h3 class="wp-block-heading">強制全域安裝</h3>



<p>雖然非常不建議，但如果你非得在全域環境安裝套件且不在乎系統穩定性，可以使用 break-system-packages 參數來跳過保護機制。請注意這可能會導致 Ubuntu 系統內建的 Python 工具故障。</p>



<pre class="wp-block-code"><code>pip install 套件名稱 --break-system-packages</code></pre>



<p>要退出已經啟動的 Python 虛擬環境，方法非常簡單，只需要在終端機輸入一個指令即可。</p>



<h3 class="wp-block-heading">使用 deactivate 指令</h3>



<p>當你完成工作，想要回到 Ubuntu 系統預設的 Python 環境時，直接輸入：</p>



<pre class="wp-block-code"><code>deactivate</code></pre>



<p>執行後，你會發現終端機提示字元前方的括號環境名稱（例如 (myenv)）消失了，這表示你已經成功登出虛擬環境。這個指令不需要加上 sudo，也不需要路徑，因為在你啟動環境時，deactivate 函式就已經被載入到目前的 shell 階段中。</p>



<h3 class="wp-block-heading">刪除虛擬環境</h3>



<p>如果你不再需要這個虛擬環境，想要徹底移除它，做法並非透過 pip 卸載，而是直接刪除該環境所在的資料夾。</p>



<pre class="wp-block-code"><code>rm -rf myenv</code></pre>



<p>這會刪除所有在該環境中安裝的套件與相關設定，且不會影響到系統其他的 Python 設定。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>python3 -m pip install pr pip-req.txt<br>error: externally-managed-environment</p>



<p>× This environment is externally managed<br>╰─&gt; To install Python packages system-wide, try apt install<br>python3-xyz, where xyz is the package you are trying to<br>install.</p>



<pre class="wp-block-code"><code>If you wish to install a non-Debian-packaged Python package,
create a virtual environment using python3 -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
sure you have python3-full installed.

If you wish to install a non-Debian packaged Python application,
it may be easiest to use pipx install xyz, which will manage a
virtual environment for you. Make sure you have pipx installed.

See /usr/share/doc/python3.12/README.venv for more information.</code></pre>



<p>note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing &#8211;break-system-packages.<br>hint: See PEP 668 for the detailed specification.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>python3 -m venv ./venv<br>The virtual environment was not created successfully because ensurepip is not<br>available. On Debian/Ubuntu systems, you need to install the python3-venv<br>package using the following command.</p>



<pre class="wp-block-code"><code>apt install python3.12-venv</code></pre>



<p>You may need to use sudo with that command. After installing the python3-venv<br>package, recreate your virtual environment.</p>



<p>Failing command: /root/venv/bin/python3</p>



<p>➜ ~ apt install python3-venv</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>在 Ubuntu 24.04 上為 Python 3.12 安裝 MySQL 連接器，最穩定的做法是透過虛擬環境。目前主流有兩種套件選擇：官方的 mysql-connector-python 或社群常用的 mysqlclient。</p>



<h3 class="wp-block-heading">準備系統依賴項目</h3>



<p>在安裝 Python 套件之前，建議先安裝編譯所需的系統開發庫。如果是要安裝 mysqlclient（效能較好），則必須先執行以下指令：</p>



<p>sudo apt update</p>



<p>sudo apt install python3-dev default-libmysqlclient-dev build-essential</p>



<h3 class="wp-block-heading">在虛擬環境中安裝</h3>



<p>首先建立並進入虛擬環境，接著使用 pip 安裝。</p>



<p>python3 -m venv env</p>



<p>source env/bin/activate</p>



<p>如果你想安裝官方提供的連接器：</p>



<p>pip install mysql-connector-python</p>



<p>如果你想安裝效能較佳、符合 Python DB API 規範的套件：</p>



<p>pip install mysqlclient</p>



<h3 class="wp-block-heading">驗證安裝結果</h3>



<p>你可以進入 Python 互動環境測試是否安裝成功：</p>



<p>python</p>



<p>import mysql.connector</p>



<p>print(mysql.connector.<strong>version</strong>)</p>



<p>或是針對 mysqlclient：</p>



<p>python</p>



<p>import MySQLdb</p>



<p>print(MySQLdb.<strong>version</strong>)</p>



<h3 class="wp-block-heading">使用 APT 安裝（全域環境）</h3>



<p>如果你不想使用虛擬環境，而是希望整個系統都能使用，可以使用 Ubuntu 官方維護的套件。這會自動處理所有依賴關係，且不會觸發 PEP 668 的外部環境錯誤。</p>



<p>sudo apt install python3-mysql.connector</p>



<p>這種方式安裝的版本通常比較舊，但保證與系統相容。</p>



<p>需要我提供一段連接 MySQL 資料庫並執行查詢的範例程式碼嗎？</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/02/ubuntu-24-python-package/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>faster-whisper max_speech_duration_s=1 數值過小, 造成模型幻覺</title>
		<link>https://stackoverflow.max-everyday.com/2025/12/faster-whisper-max_speech_duration_s/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/12/faster-whisper-max_speech_duration_s/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 31 Dec 2025 07:01:36 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7593</guid>

					<description><![CDATA[參數調整建議 你的設定中 max_speech_...]]></description>
										<content:encoded><![CDATA[
<h3 class="wp-block-heading">參數調整建議</h3>



<p>你的設定中 <code>max_speech_duration_s=1</code> 數值過小。這會強制 VAD 每秒鐘就切斷一次。當語音被切得太碎，模型反而容易因為上下文不足而產生幻覺，或是把零碎的聲音補償成重複的文字。建議將這個值放寬到 15 到 20 左右。</p>



<p>目前的字幕出現長達 30 秒的區間，通常是因為 VAD 沒有成功切斷聲音。模型認為這段時間內一直有人在說話。這可能與 <code>min_silence_duration_ms</code> 太短有關。</p>



<p>您可以嘗試以下參數組合：</p>



<pre class="wp-block-code"><code>segments, info = model.transcribe(
    input_file, 
    language=language, 
    vad_filter=True,
    condition_on_previous_text=False,
    vad_parameters=dict(
        min_silence_duration_ms=500,
        max_speech_duration_s=20,
        speech_pad_ms=400
    ),
    beam_size=5,
    initial_prompt="繁體中文字幕。",
    no_speech_threshold=0.6,
    log_prob_threshold=-1.0,
    compression_ratio_threshold=2.4
)
</code></pre>



<h3 class="wp-block-heading">關鍵調整說明</h3>



<p><code>min_silence_duration_ms</code> 增加到 500 毫秒。這樣模型比較能識別出正常的說話停頓。太短的設定會讓環境噪音干擾判定。</p>



<p><code>speech_pad_ms</code> 增加到 400 毫秒。這會在切分片段的前後多留一點空間。這能幫助模型完整辨識字首與字尾，減少因為聲音被截斷導致的重複亂碼。</p>



<p>加入 <code>initial_prompt</code>。指定為繁體中文可以導正模型的語氣。這能有效減少出現奇怪贅字或簡繁混雜的機率。</p>



<p>調整 <code>compression_ratio_threshold</code>。如果模型輸出的內容重複率太高，這個參數會觸發重新辨識。這對解決你提到的重複文字問題很有幫助。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/12/faster-whisper-max_speech_duration_s/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
