ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

使用 dlt 实现 Chess.com 棋局数据按月回填与 Filesystem 部分替换(Backfill with Partial Replace)

使用 dlt 实现 Chess.com 棋局数据按月回填与 Filesystem 部分替换(Backfill with Partial Replace) 使用 dlt 实现 Chess.com 棋局数据按月回填与 Filesystem 部分替换Backfill with Partial Replace【免费下载链接】dltdata load tool (dlt) is an open source Python library that makes data loading easy ️项目地址: https://gitcode.com/GitHub_Trending/dl/dlt本文以dlt官方示例 partial_loading.md 为主线讲解如何通过 REST API source 声明式抽取 Chess.com 用户棋局数据并利用文件系统目标Filesystem destination的目录布局与load_id机制在按月追加新数据的同时删除旧回填文件实现“部分替换式”回填去重。读完本文你将掌握rest_api_resources的端点配置、response_actions容错、FilesystemClient的文件遍历与删除以及自定义回填清理函数的完整实战方案。示例背景为什么需要“部分替换”式回填在数据管道中“回填backfill”通常指补录历史时间段的数据。Chess.com 公开 REST API 按“年/月”提供用户棋局数据例如player/{username}/games/{year}/{month}。当我们按月分批把 2023 年全年的对局加载到本地文件系统时会自然产生两个问题数据去重同一个月的文件可能因重复运行而重复写入需要借助主键或文件替换机制避免重复。旧文件清理当某一时间段的数据被重新加载后之前生成的文件对应旧的 load package仍然残留在表目录里必须显式删除否则读取该表时会看到“新老文件叠加”的脏数据。dlt内建的replace写策略是针对整个表的原子替换而本示例展示的是一种更精细的“部分替换”每个资源对应每个月份各自独立加载append加载完成后由自定义函数删除同表目录下不属于当前load_id的旧文件。这样既可以保留其它月份的数据又能保证同一月份的文件始终来自最新一次加载。源码总览示例完整源码位于 docs/website/docs/examples/partial_loading.md整体流程如下chess_com_sourcedlt.source装饰的 source遍历月份列表为每个月通过rest_api_resources生成独立资源generate_months生成[start_year, start_month]到[end_year, end_month]的连续月份delete_old_backfills加载完成后删除目标表目录中不匹配当前load_id的旧文件load_chess_data装配 pipeline 并执行。声明式配置 Chess.com REST API 源示例使用dlt内置的 REST API source 实现声明式抽取核心入口为rest_api_resources定义于 dlt/sources/rest_api/init.py。它接收一个RESTAPIConfig字典返回资源列表可被dlt.source直接 yield。dlt.source def chess_com_source( username: str, months: list[dict[str, str]] ) - Iterator[DltResource]: for month in months: year month[year] month_str month[month] # Configure REST API endpoint for the specific month config: RESTAPIConfig { client: { base_url: https://api.chess.com/pub/, # Base URL for Chess.com API }, resources: [ { name: fchess_com_games_{year}_{month_str}, # Unique resource name write_disposition: append, endpoint: { path: fplayer/{username}/games/{year}/{month_str}, # API endpoint path response_actions: [ {status_code: 404, action: ignore}, ], }, primary_key: [url], # Primary key to prevent duplicates } ], } yield from rest_api_resources(config)配置要点拆解配置项值作用client.base_urlhttps://api.chess.com/pub/REST API 公共端点前缀无需鉴权resources[].namechess_com_games_{year}_{month}资源名即目标表名按月唯一resources[].write_dispositionappend每次加载只追加不触碰其它月份数据resources[].endpoint.pathplayer/{username}/games/{year}/{month}按月拉取对局的路径模板resources[].endpoint.response_actions[{status_code: 404, action: ignore}]无对局的月份404静默跳过不中断管道resources[].primary_key[url]以棋局 URL 为主键在加载阶段做去重值得展开说明的是response_actions的容错语义。在 dlt/sources/rest_api/config_setup.py 的create_response_hooks实现中所有配置的 response action 会被转换为 HTTP 响应钩子并且默认追加一个raise_for_status钩子凡是未被response_actions显式处理的非 2xx 状态码都会抛出 HTTP 错误终止该资源。示例中对 404 配置action: ignore对应的内部行为是抛出IgnoreResponseException从而将该月无数据视为正常情况——这对“用户某月没下棋”的场景至关重要。primary_key: [url]则让 Chess.com 返回的每局棋的url字段成为记录指纹。dlt在 normalize 阶段会依据主键对同批次数据做合并去重避免同一局棋因 API 返回重复或重跑而重复入库。与rest_api_source的区别REST API source 还有另一个入口rest_api_source它返回的是完整的DltSource对象而本示例使用rest_api_resources返回资源列表再在自定义dlt.source中逐月 yield本质上是把“REST 配置驱动”与“自定义循环生成”两种方式组合起来实现按月动态扩展资源集合。从源码结构看dlt/sources/rest_api/init.pyrest_api_resources与rest_api_source都经由rest_api克隆的工厂逻辑完成配置校验与资源创建二者配置格式完全一致可按需选用。生成连续月份列表generate_months是一个纯粹的时间迭代辅助函数返回{year: ..., month: ...}字典迭代器月份统一格式化为两位字符串如01与 API 路径模板及表名拼接保持一致def generate_months( start_year: int, start_month: int, end_year: int, end_month: int ) - Iterator[dict[str, str]]: start_date p.datetime(start_year, start_month, 1) end_date p.datetime(end_year, end_month, 1) current_date start_date while current_date end_date: yield {year: str(current_date.year), month: f{current_date.month:02d}} # Move to the next month if current_date.month 12: current_date current_date.replace(yearcurrent_date.year 1, month1) else: current_date current_date.replace(monthcurrent_date.month 1)其中p是from dlt.common import pendulum as p引入的 pendulum 时间库别名dlt 对 pendulum 的封装见 dlt/common/pendulum.py示例调用list(generate_months(2023, 1, 2023, 12))即可生成 2023 全年 12 个月的字典列表。该函数同样适用于跨年回填如(2022, 11, 2023, 2)会正确依次产出 11、12、1、2 月。配置 Filesystem 目标与管道装配示例在代码中直接使用本地文件系统作为目标_storage目录dest_ dlt.destinations.filesystem(_storage) pipeline dlt.pipeline( pipeline_namechess_com_data, destinationdest_, dataset_namechess_games )dlt.destinations.filesystem(_storage)创建一个本地目录_storage作为数据落地位置pipeline_namechess_com_data管道名用于隔离状态与配置dataset_namechess_games数据集名文件将写入_storage/chess_games/下。Filesystem destination 官方文档docs/website/docs/dlt-ecosystem/destinations/filesystem.md指出其底层基于 fsspec 抽象文件操作因此同一套代码可平滑切换到 AWS S3、GCS、Azure Blob 等对象存储只需把bucket_url改为对应协议如s3://your-bucket并在.dlt/secrets.toml中配置凭证。对象存储的文件布局由 key 模拟语义上与本地目录一致因此下文的自定义清理逻辑在云存储上同样适用。理解文件布局与 load_idFilesystem 目标默认布局为{table_name}/{load_id}.{file_id}.{ext}详见 filesystem.md其中table_name表名本示例中即chess_com_games_2023_01这类按月生成的资源名load_id本次 load package 的唯一 ID同一批管道运行产生的所有文件共享同一个load_idfile_id同一表同批次的文件序号ext文件格式jsonl/parquet等。正是“表目录 load_id命名的文件”这一布局让示例的自定义删除逻辑有了可依赖的规律凡是路径中不含当前load_id的文件都属于更早的回填批次应当清理。你也可以在config.toml或代码中通过layout参数定制布局例如加入{YYYY}/{MM}时间分区但若后续使用replace写策略布局中必须包含{table_name}占位符且保持前缀规则否则dlt无法正确推算需删除的文件集。核心delete_old_backfills 实现部分替换清理示例的精华在于delete_old_backfills函数——它在每次pipeline.run之后按表执行“保留本批、删除旧批”def delete_old_backfills(load_info: LoadInfo, p: dlt.Pipeline, table_name: str) - None: # Fetch current load id load_id load_info.loads_ids[0] pattern re.compile(rf{load_id}) # Compile regex pattern for the current load ID # Initialize the filesystem client fs_client: FilesystemClient p._get_destination_clients()[0] # type: ignore # Construct the table directory path table_dir os.path.join(fs_client.dataset_path, table_name) # Check if the table directory exists if fs_client.fs_client.exists(table_dir): # Traverse the table directory for root, _dirs, files in fs_client.fs_client.walk(table_dir, maxdepthNone): for file in files: # Construct the full file path file_path os.path.join(root, file) # If the file does not match the current load ID, delete it if not pattern.search(file_path): try: fs_client.fs_client.rm( file_path ) # Remove the old backfill file except Exception as e: print(fError deleting file {file_path}: {e})逐段解读取当前 load_idload_info.loads_ids[0]取自pipeline.run返回的LoadInfo类型定义见 dlt/common/pipeline.py。示例末尾assert len(info.loads_ids) 1也验证了整次运行只有一个 load package。获取目标客户端p._get_destination_clients()实现于 dlt/pipeline/pipeline.py返回当前管道的 destination client 元组取第一个即FilesystemClient。该客户端在 dlt/destinations/impl/filesystem/filesystem.py 中定义封装了fs_clientfsspec 文件系统句柄与dataset_path数据集在存储中的绝对路径等属性。构造表目录os.path.join(fs_client.dataset_path, table_name)定位到chess_games数据集下某个月份的表目录如_storage/chess_games/chess_com_games_2023_01/。遍历并删除fs_client.fs_client.walk(table_dir, maxdepthNone)递归列出目录下所有文件对每个文件若完整路径中不包含当前load_id的匹配则调用fs_client.fs_client.rm(file_path)删除。删除异常被捕获并打印保证单个文件失败不会中断整个清理流程。边界条件与注意事项无目录即跳过exists检查保证首次运行或尚未生成该表时不会报错。加载失败的文件dlt的文件布局把load_id放进文件名因此同一load_id下的失败/重试文件也会被一并保留或覆盖示例按“整个 load package 为准”的粒度清理逻辑自洽。空目录残留删除文件后目录本身未清理对象存储下空目录无成本本地文件系统下会留下空文件夹通常可接受。依赖内部 API_get_destination_clients以下划线开头属于 dlt 的内部接口升级 dlt 大版本时需关注签名变化示例代码中用# type: ignore弱化了类型检查。主流程按月加载并逐表清理def load_chess_data(): # Initialize the dlt pipeline with filesystem destination, here we use local storage dest_ dlt.destinations.filesystem(_storage) pipeline dlt.pipeline( pipeline_namechess_com_data, destinationdest_, dataset_namechess_games ) # Generate the list of months for the desired date range months list(generate_months(2023, 1, 2023, 12)) # Create the source with all specified months source chess_com_source(MagnusCarlsen, months) # Run the pipeline to fetch and load data info pipeline.run(source) # print(info) # After the run, delete old backfills for each table to maintain data consistency for month in months: table_name fchess_com_games_{month[year]}_{month[month]} delete_old_backfills(info, pipeline, table_name) return info info load_chess_data() assert len(info.loads_ids) 1执行过程pipeline.run(source)一次性抽取 12 个月的棋局并按各自表名落地到_storage/chess_games/循环 12 个月逐表调用delete_old_backfills删除这些表目录中不属于本次load_id的历史文件断言确认本次运行仅产生一个 load package随后info可被上层使用如打印加载统计。由此第二次重跑脚本时会呈现出“部分替换”效果12 个表目录内的文件全部替换为本轮新加载的文件而数据集中其它任何未参与本次回填的表例如后续追加的 2024 年表完全不受影响。验证与运行前提运行本示例需要已安装dlt建议同时安装 filesystem 依赖pip install dlt[filesystem]可访问外网以请求https://api.chess.com/pub/该公共端点无需认证示例硬编码用户名MagnusCarlsen可替换为任意存在对局记录的 Chess.com 用户名。验证方式运行脚本后检查_storage/chess_games/下各月份表目录确认每个目录中仅剩包含当前load_id的文件重复运行一次观察旧load_id文件被删除、新load_id文件保留。相关 REST API source 的完整配置手册见 REST API source 基础文档Filesystem 目标的凭证与布局细节见 Object store filesystem 文档。小结本示例展示了 dlt 生态中一个可复用的“部分替换回填”模式声明式抽取借助rest_api_resources与RESTAPIConfig把按月拉取棋局表达为配置而非手写 HTTP 客户端按资源独立控制写策略每个月份一个资源、统一append避免replace误伤其它表基于文件布局的精细清理利用{table_name}/{load_id}...默认布局与FilesystemClient的 walk/rm 能力实现“保留本批、删除旧批”的幂等回填。这一模式同样适用于任何“按时间分片、需分批重灌”的文件型数据管道日志回灌、指标历史补数等只需把“月份”换成你的分片维度即可在文件系统或云对象存储上实现低成本、可控的部分替换加载。【免费下载链接】dltdata load tool (dlt) is an open source Python library that makes data loading easy ️项目地址: https://gitcode.com/GitHub_Trending/dl/dlt创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表