LlamaIndex节点创建:生成Chunk时如何跟踪PDF页码?
实现跨页Chunk并跟踪来源页码的方案
你可以通过两种核心方式实现需求——既保留跨段落/跨页的Chunk完整性,又能准确跟踪来源页码:
方法一:先合并连续页的不完整段落,再拆分并标记页码范围
这种方式先处理PDF加载逻辑,判断连续页的文本是否属于同一未完成段落,合并后再生成带页码元数据的Document,最后用你当前的TokenTextSplitter和SimpleNodeParser拆分。
代码示例
import pdfplumber from llama_index import Document, SimpleNodeParser, TokenTextSplitter def load_pdf_with_merged_blocks(pdf_path): # 提取每页文本及页码 page_items = [] with pdfplumber.open(pdf_path) as pdf: for page_num, page in enumerate(pdf.pages, start=1): page_items.append({"page": page_num, "text": page.extract_text().strip()}) # 合并连续页的不完整段落 merged_blocks = [] if not page_items: return [] current_block = {"pages": [page_items[0]["page"]], "text": page_items[0]["text"]} for item in page_items[1:]: prev_text_end = current_block["text"][-1] if current_block["text"] else "" # 判断前一页末尾是否为不完整段落(可根据实际标点调整规则) if prev_text_end not in ('.', '!', '?', '”', '’'): current_block["pages"].append(item["page"]) current_block["text"] += " " + item["text"] else: merged_blocks.append(current_block) current_block = {"pages": [item["page"]], "text": item["text"]} merged_blocks.append(current_block) # 生成带页码元数据的Document列表 docs = [] file_name = pdf_path.split("/")[-1] for block in merged_blocks: page_range = f"{min(block['pages'])}-{max(block['pages'])}" if len(block['pages'])>1 else f"{block['pages'][0]}" docs.append(Document( text=block["text"], metadata={"file_name": file_name, "pages": page_range} )) return docs # 初始化拆分器与解析器 text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=64) parser = SimpleNodeParser(text_splitter=text_splitter) # 加载PDF并生成Nodes pdf_docs = load_pdf_with_merged_blocks("target_document.pdf") nodes = parser.get_nodes_from_documents(pdf_docs) # 查询时获取页码信息 # (假设已构建index并生成query_engine) response = query_engine.query("你的查询问题") for node in response.source_nodes: print(f"来源页码:{node.metadata['pages']} | 文件:{node.metadata['file_name']}") print(f"片段内容:{node.text[:200]}...\n")
方法二:插入页码标记后拆分,再解析标记获取页码范围
这种方式先将整个PDF的文本拼接,插入页码标记,再拆分Chunk,最后通过解析Chunk中的标记确定覆盖的页码范围,无需提前判断段落完整性。
代码示例
import pdfplumber from llama_index import Document, SimpleNodeParser, TokenTextSplitter def load_pdf_with_page_markers(pdf_path): full_text = "" file_name = pdf_path.split("/")[-1] with pdfplumber.open(pdf_path) as pdf: for page_num, page in enumerate(pdf.pages, start=1): # 在每页文本前插入页码标记 full_text += f"[PAGE:{page_num}]\n{page.extract_text().strip()}\n" return Document(text=full_text, metadata={"file_name": file_name}) def attach_page_metadata(nodes): for node in nodes: # 从Chunk文本中提取所有页码标记 page_numbers = [] for token in node.text.split(): if token.startswith("[PAGE:") and token.endswith("]"): try: page_num = int(token[6:-1]) page_numbers.append(page_num) except ValueError: continue if page_numbers: min_page = min(page_numbers) max_page = max(page_numbers) node.metadata["pages"] = f"{min_page}-{max_page}" if min_page != max_page else f"{min_page}" else: node.metadata["pages"] = "未知" return nodes # 加载PDF并拆分 doc = load_pdf_with_page_markers("target_document.pdf") text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=64) parser = SimpleNodeParser(text_splitter=text_splitter) nodes = parser.get_nodes_from_documents([doc]) nodes = attach_page_metadata(nodes) # 查询时获取页码信息 response = query_engine.query("你的查询问题") for node in response.source_nodes: print(f"来源页码:{node.metadata['pages']} | 文件:{node.metadata['file_name']}")
方案对比
- 方法一更适合优先保证段落完整性的场景,元数据直接标记Chunk对应的原始页码范围,无需后续解析。
- 方法二更灵活,不需要提前定义段落结束规则,适合文本格式复杂的PDF。
内容的提问来源于stack exchange,提问作者Joanne B
相关产品推荐
相关产品推荐

