Skip to content

HYChou0515/qqabc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

QQabc

1. 基本介紹

qqabc.rurl 提供高效的 URL 資源下載與解析工具,支援多工、快取、檔案自動判斷與自訂解析規則。核心類別為 Resolver,可透過 resolve() 工廠方法建立。

2. 快速開始

from qqabc.rurl import resolve

with resolve() as resolver:
    od = resolver.add_wait("https://picsum.photos/200")
    data = od.data.read()
    # data 為url的下載結果binary

安裝

pip install qqabc[httpx]

不想要安裝httpx可使用

pip install qqabc

3. 主要功能

3.1 任務管理

  • add(url): 加入下載任務,回傳 task_id。
  • add_wait(url): 加入下載任務並等待完成,回傳下載結果。
  • wait(task_id): 等待指定任務完成。
  • completed(timeout): 取得所有已完成任務(可設定超時)。
  • iter_and_close(): 迭代所有完成任務並關閉解析器。

3.2 檔案自動判斷與打開

open(filepath, mode) 可自動判斷檔案內容是否為 URL,若是則下載並回傳資料流,否則回傳原始檔案內容。

# url.txt內容為URL
# https://picsum.photos/200
with resolve() as resolver:
    with resolver.open("url.txt", "rb") as fp:
        data = fp.read()
        # data 為url的下載結果binary

3.3 快取與硬碟儲存

  • cache_size:設定記憶體快取大小,超過則自動存回硬碟。
  • 關閉解析器時,所有未存回硬碟的資料會自動儲存。

3.4 多工下載

  • num_workers:設定同時下載的 worker 數量,預設 4。

3.5 自訂 Worker

可自訂 Worker 類別以擴充下載邏輯,以下演示使用requests作為下載工具

from qqabc.rurl import DefaultWorker, resolve

class RequestWorker(DefaultWorker):
    @contextmanager
    def start(self, worker_id: int):
        self.worker_id = worker_id
        import requests  # noqa: PLC0415

        with requests.Session() as client:
            self.client = client
            yield self

with resolve(worker=RequestWorker) as resolver:
    ...

3.6 自訂 URL 語法解析

可自訂 IUrlGrammar 來解析特殊格式的 URL:

from qqabc.rurl import BasicUrlGrammar, resolve

class CustomGrammar(BasicUrlGrammar):
    def main_rule(self, content: str) -> str | None:
        if content.startswith("custom://"):
            return "https://picsum.photos/{content.replace('custom://', '')}"
        return None

with resolve(grammars=[CustomGrammar()]) as resolver:
    ...

4. 例外處理

  • WorkersDiedOutError:所有 worker 異常終止時拋出。
  • DataDeletedError:資料已被刪除時拋出。
  • InvalidTaskError:無效 task_id 時拋出。

5. Example Usages

1. 一次給齊所有工作, 依結束順序處理結果

應用場景

  • 批次下載大量資源,例如圖片、檔案、API資料等。
  • 任務清單已知且固定,適合一次性處理所有任務。
  • 需要依照任務完成順序即時處理結果(如即時儲存、分析、轉換等)。
  • 適合高併發、批次任務、資料蒐集等場景。

NOTE: 開始iter_and_close之後就無法再次添加新任務

from qqabc.rurl import resolve

with resolve() as resolver:
    for i in range(100, 200):
        resolver.add(f"https://picsum.photos/{i}")
    for task in resolver.iter_and_close():
        # 會依照task結束順序給出結果
        b = task.data
        # b 為下載的二進位內容可繼續下游任務
    # 所有任務皆已完成

2. 邊跑邊加任務

應用場景

  • 動態任務生成:根據前一批任務的結果,決定是否要再加入新任務。例如爬蟲、批次下載、API輪詢等。
  • 資源分批處理:有些任務需要分批執行,根據已完成任務的狀態,持續補充新任務,確保資源利用率最大化。
  • 即時任務調度:在任務執行過程中,根據外部事件或條件,隨時加入新下載或處理任務。
  • 長時間監控/輪詢:持續監控某些資源,根據回應結果決定是否要再發起新請求。

NOTE: 開始completed之前需要至少有一個任務

from qqabc.rurl import resolve

url = "https://picsum.photos/200"
with resolve() as resolver:
    i = 100
    resolver.add(f"https://picsum.photos/{i}")
    for task in resolver.completed(timeout=5): # 超過timeout無新任務將認為全部做完並跳出迴圈
        b = task.data
        # b 為下載的二進位內容
        # 動態添加新任務
        if i < 1000 and b.seek(0, 2) > 100: # 如果size > 100
            resolver.add(f"https://picsum.photos/{i}")
            i += 2

3. 多任務 + wait

應用場景

  • 需要精確控制每個任務的完成時機:例如每個下載任務完成後要立即做後續處理(如解析、轉存、通知等)。
  • 任務之間有依賴或順序要求:例如先下載 A,再下載 B,或每個任務完成後要根據結果決定下一步。
  • 小量任務、同步流程:適合任務數量不多,或希望逐一確認每個任務結果的情境。
  • 簡單批次處理:例如批次下載幾個檔案,並逐一取得結果
from qqabc.rurl import resolve

tasks = set()
with resolve() as resolver:
    for i in range(100, 200):
        tasks.add(resolver.add(f"https://picsum.photos/{i}"))
    for task_id in tasks:
        od = resolver.wait(task_id)
        b = od.data
        # b 為下載的二進位內容

4. open 方法自動判斷 URL

應用場景

  • 自動判斷檔案內容是否為 URL:讓你用同一個 API 開啟本地檔案或遠端資源,無需手動判斷。
  • 資料前處理/轉存:可直接取得下載內容進行分析、轉存或後續處理。
  • 快取測試與效能優化:適合需要在記憶體中快取下載結果、避免重複下載的場景。
  • 混合型檔案處理:同時處理本地檔案與 URL 清單,程式碼更簡潔一致。
from qqabc.rurl import resolve

# url.txt內容為URL
# https://picsum.photos/200
with resolve() as resolver:
    with resolver.open("url.txt", "rb") as fp:
        data = fp.read()
        # data 為下載的二進位內容
    # 不會馬上存進disk (看cache_size決定)
    with open("url.txt") as fp:
        text = fp.read() # text 為原始 URL 字串

# 出去之後就會寫入原檔案
with open("url.txt", "rb") as fp:
    data = fp.read() # data 為下載的二進位內容
Cache size用法

設定cache_size=0, 則所有東西都會馬上寫回硬碟

from qqabc.rurl import resolve

# url.txt內容為URL
# https://picsum.photos/200
with resolve(cache_size=0) as resolver:
    with resolver.open("url.txt", "rb") as fp:
        data = fp.read() # data 為下載的二進位內容

以下演示cache size以及寫入硬碟的時機

from qqabc.rurl import DefaultWorker, InData, OutData, resolve

class Worker(DefaultWorker):
    def resolve(self, indata: InData) -> OutData:
        resp = self.client.get(indata.url)
        resp.raise_for_status()
        content = resp.content[:4500]
        b = BytesIO(content)
        return OutData(task_id=indata.task_id, data=b)

url = "https://picsum.photos/200"
with open("urls1.txt", "w") as f:
    f.write(url)
with open("urls2.txt", "w") as f:
    f.write(url)

with resolve(cache_size=5000, worker=Worker) as resolver:
    with resolver.open("urls1.txt", "rb") as fp:
        data1 = fp.read()
        # data1 為 4500 bytes 的下載內容
    with open("urls1.txt") as fp:
        text1 = fp.read()
        # text1 為原始 URL 字串
    with resolver.open("urls2.txt", "rb") as fp:
        data2 = fp.read()
        # data2 為 4500 bytes 的下載內容
    with open("urls1.txt", "rb") as fp:
        data1_disk = fp.read()
        # data1_disk 為 4500 bytes 的下載內容
    with open("urls2.txt") as fp:
        text2 = fp.read()
        # text2 為原始 URL 字串
with open("urls2.txt", "rb") as fp:
    data2_disk = fp.read()
    # data2_disk 為 4500 bytes 的下載內容

About

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors