Python-only utilities for Feishu/Lark Bitable and Sheets automation. The project is designed for direct import in scripts, Cursor Skills, and RPA tools.
- Read Bitable records by
app_token, table name, and optional view. - Convert Feishu nested
fieldsintocolumns,rows, androws_by_record_id. - Preserve
record_idfor reliable readback and updates. - Expand linked-record fields to the linked table primary value, so fields like
所属活动can display日常销售instead ofrec.... - Read raw linked-record
record_idvalues from a linked-record column when automation needs IDs instead of display text. - Filter date columns with
Today,Yesterday,CurrentMonth,LastMonth, exact dates, or inclusive date ranges. - Update and create records by field names with schema validation and value normalization.
- Default to dry-run for writes; real writes require
confirm_write=True. - Cache tenant tokens, table/view metadata, and schemas in the current Python process.
- Include a no-type-annotation RPA version for automation platforms that struggle with typed Python.
| Path | Purpose |
|---|---|
feishu_bitable_utils.py |
Main Bitable utility with type hints. Best for Python projects and Cursor Skill use. |
feishu_bitable_utils_rpa.py |
RPA-friendly Bitable utility without function type annotations. |
feishu_sheets_utils_rpa.py |
Feishu Sheets helper for row lookup and update by sheet name and column name. |
SKILL.md |
Cursor Skill instructions and implementation constraints. |
.env.example |
Local credential template. |
debug/ |
Small local demos and sample API payloads. |
- Python 3.7+
requests
Install the only runtime dependency if your environment does not already provide it:
pip install requestsCreate a local .env file next to the Python files:
FEISHU_APP_ID=cli_xxx
FEISHU_APP_SECRET=xxx.env is ignored by Git. Do not commit app secrets, tenant tokens, authorization headers, cookies, or exported browser state.
Bitable targets are business parameters, not global configuration. Pass them explicitly when calling functions:
app_token = "base_xxx"
table_name = "数据表"
view_name = "默认视图"from feishu_bitable_utils import query_records_by_time, query_records_by_ids
result = query_records_by_time(
app_token=app_token,
table_name=table_name,
time_column="申请时间",
condition="Today",
query_columns=["状态", "名称", "数量"],
)
print(result.columns)
print(result.rows)
print(result.rows_by_record_id)
selected = query_records_by_ids(
app_token=app_token,
table_name=table_name,
record_ids=["recxxxx"],
query_columns=["状态", "名称"],
)When query_records_by_ids cannot read one of the requested record IDs, it skips that ID and appends {"record_id": "...", "error": "..."} to result.errors.
Linked-record fields are expanded automatically when records are read through query_records_by_time or query_records_by_ids.
For example, if 赠品配置表.所属活动 links to 活动周期表.活动名称, the returned row displays:
["recvm8boBWJE1l", "日常销售"]If a cell links to multiple records, the value is returned as a list of linked primary-field values.
If you need the raw linked record IDs instead of display values, query the linked column directly:
from feishu_bitable_utils import query_linked_record_ids_by_records
linked = query_linked_record_ids_by_records(
app_token=app_token,
table_name=table_name,
record_ids=["recxxxx"],
column_name="关联活动机制",
)
# {"recxxxx": ["recyyyy"]}Update helpers are dry-run by default. This returns a preview and does not write to Feishu:
from feishu_bitable_utils import update_record_by_names
preview = update_record_by_names(
app_token=app_token,
table_name=table_name,
record_id="recxxxx",
columns=["状态"],
values=["已完成"],
)Set confirm_write=True to perform the write. For high-frequency RPA jobs, set readback=False to skip the extra post-update read:
result = update_record_by_names(
app_token=app_token,
table_name=table_name,
record_id="recxxxx",
columns=["状态"],
values=["已完成"],
confirm_write=True,
readback=False,
)from feishu_bitable_utils import create_records_by_names
result = create_records_by_names(
app_token=app_token,
table_name=table_name,
columns=["名称", "数量", "状态"],
rows=[
["示例名称", 1, "待处理"],
["示例名称 2", 2, "待处理"],
],
confirm_write=True,
)Multiple rows use Feishu batch_create automatically, with a default batch size of 500. Pass batch_size=1 to create records one by one.
feishu_sheets_utils_rpa.py is for regular Feishu Sheets, not Bitable. It uses the first row as headers and locates columns by name.
from feishu_sheets_utils_rpa import (
clear_sheet_columns_by_headers,
clear_sheet_columns_by_letters,
query_sheet_all_rows,
query_sheet_row_by_column,
query_sheet_rows_by_column,
update_sheet_row_by_column,
write_sheet_rows_by_headers,
write_sheet_rows_by_letters,
)
all_rows = query_sheet_all_rows(
spreadsheet_token=spreadsheet_token,
sheet_name="示例Sheet",
)
clear_preview = clear_sheet_columns_by_headers(
spreadsheet_token=spreadsheet_token,
sheet_name="示例Sheet",
headers=["主播名", "对账日期"],
)
write_preview = write_sheet_rows_by_headers(
spreadsheet_token=spreadsheet_token,
sheet_name="示例Sheet",
headers=["主播名", "对账日期"],
rows=[
["示例主播", "2026-05-12"],
["示例主播 2", "2026-05-13"],
],
)
clear_letters_preview = clear_sheet_columns_by_letters(
spreadsheet_token=spreadsheet_token,
sheet_name="示例Sheet",
columns=["A", "B", "AB"],
)
write_letters_preview = write_sheet_rows_by_letters(
spreadsheet_token=spreadsheet_token,
sheet_name="示例Sheet",
columns=["A", "B"],
rows=[
["示例主播", "2026-05-12"],
["示例主播 2", "2026-05-13"],
],
)
matched = query_sheet_row_by_column(
spreadsheet_token=spreadsheet_token,
sheet_name="示例Sheet",
match_column="主播名",
match_value="示例主播",
)
matched_rows = query_sheet_rows_by_column(
spreadsheet_token=spreadsheet_token,
sheet_name="示例Sheet",
match_column="主播名",
match_value="示例主播",
)
updated = update_sheet_row_by_column(
spreadsheet_token=spreadsheet_token,
sheet_name="示例Sheet",
match_column="主播名",
match_value="示例主播",
update_columns=["对账日期"],
update_values=["2026-05-12"],
confirm_write=True,
)Sheets rich-text cells returned as objects, such as {"type": "url", "text": "...", "link": "..."}, are normalized to readable values. Duplicate headers are preserved in row_dicts with suffixes such as 签约人_2.
| Function | Description |
|---|---|
list_bitable_tables(app_token) |
List Bitable tables and views. |
query_records_by_time(...) |
Read records, optionally filter by date, and return a ListReadResult. |
query_records_by_ids(...) |
Read records by Feishu record_id list, skip missing IDs, and return a ListReadResult. |
dry_run_update_by_names(...) |
Preview a field-name-based update. |
update_record_by_names(...) |
Update one record after validation. Defaults to dry-run. |
create_records_by_names(...) |
Create one or more records after validation. Defaults to dry-run. |
query_linked_record_ids_by_records(...) |
Read a linked-record column and return linked record_id values by source record. |
list_result_to_csv_string(result) |
Export a ListReadResult to CSV text. |
clear_feishu_cache() |
Clear in-process token/table/view/schema caches. |
| Function | Description |
|---|---|
list_sheets(spreadsheet_token) |
List sheet names and IDs. |
query_sheet_all_rows(...) |
Return all rows from a sheet by sheet name. |
clear_sheet_columns_by_headers(...) |
Clear data below selected headers. Defaults to dry-run. |
write_sheet_rows_by_headers(...) |
Write rows below selected headers from row 2. Defaults to dry-run. |
clear_sheet_columns_by_letters(...) |
Clear data below selected column letters. Defaults to dry-run. |
write_sheet_rows_by_letters(...) |
Write rows below selected column letters in the given order. Defaults to dry-run. |
query_sheet_row_by_column(...) |
Return the first row where a column exactly matches a value. |
query_sheet_rows_by_column(...) |
Return all rows where a column exactly matches a value. |
update_sheet_row_by_column(...) |
Update columns in the first matched row. Defaults to dry-run. |
clear_feishu_sheets_cache() |
Clear in-process Sheets caches. |
- Write helpers validate schema, field writability, and value types before calling write APIs.
record_idis treated as Feishu's technical primary key and is never written back as a normal field.- Read and write helpers resolve table/view names at runtime; pass
table_idorview_idwhen names are duplicated. - If a long-running process sees stale table/view/schema data after a Feishu-side change, call
clear_feishu_cache(). - Use
feishu_bitable_utils_rpa.pywhen an RPA platform cannot parse Python type annotations.
Run a syntax check before publishing changes:
python3 -m py_compile feishu_bitable_utils.py feishu_bitable_utils_rpa.py feishu_sheets_utils_rpa.pyThe debug/ folder contains small examples for flattening records and dry-run update reports.