Skip to main content
Glama
h-lu
by h-lu
03_arxiv_searcher.md11.3 kB
# ArxivSearcher 详解 > **文件位置**: `paper_search_mcp/academic_platforms/arxiv.py` > **难度**: ⭐⭐ (适合入门) > **更新**: 2025年12月 - 使用 PyMuPDF4LLM 替代 PyPDF2 --- ## 概述 `ArxivSearcher` 是一个完整的学术平台搜索器实现,展示了: - 如何调用 arXiv API - 如何解析 Atom/RSS 格式的响应 - **如何使用 PyMuPDF4LLM 进行高质量 PDF 文本提取**(2025 最佳实践) - 如何生成 LLM 友好的 Markdown 输出 --- ## 2025 最佳实践更新 ### 为什么从 PyPDF2 迁移到 PyMuPDF4LLM? | 特性 | PyPDF2 (旧) | PyMuPDF4LLM (新) | |------|:-----------:|:----------------:| | 表格提取 | ⭐ 差 | ⭐⭐⭐⭐⭐ 优秀 | | 数学公式 | ⭐ 乱码 | ⭐⭐⭐ 可读 | | Markdown 输出 | ❌ 不支持 | ✅ 原生支持 | | LLM 友好度 | ⭐ 低 | ⭐⭐⭐⭐⭐ 高 | | 速度 | ⭐⭐⭐ 中等 | ⭐⭐⭐⭐ 快 | ### PyMuPDF4LLM 特点 ```python import pymupdf4llm # 一行代码提取 Markdown 格式内容 md_text = pymupdf4llm.to_markdown("paper.pdf") # 支持表格检测策略 md_text = pymupdf4llm.to_markdown( "paper.pdf", table_strategy="lines_strict" # 严格表格检测 ) ``` --- ## 完整代码分析 ### 1. 导入和配置 ```python # paper_search_mcp/academic_platforms/arxiv.py """ ArxivSearcher - arXiv 论文搜索、下载与阅读 2025 年最佳实践版本: - 使用 PyMuPDF4LLM 替代 PyPDF2,提供更好的表格和公式提取 - 输出 Markdown 格式,对 LLM 更友好 - 支持多种表格检测策略 """ from typing import List, Literal, Optional from datetime import datetime import requests import feedparser import pymupdf4llm # 2025 最佳实践:专为 LLM 优化的 PDF 提取 import pymupdf # 底层 PDF 库,用于备用文本提取 import os import logging from ..paper import Paper logger = logging.getLogger(__name__) ``` **💡 学习要点**: 1. **模块文档字符串**: 简要说明模块功能和版本特点 2. **类型提示**: 使用 `Literal` 限定参数取值范围 3. **日志**: 使用 `logging` 而不是 `print` --- ### 2. 表格检测策略 ```python class ArxivSearcher(PaperSource): """arXiv 论文搜索器""" BASE_URL = "http://export.arxiv.org/api/query" # 表格检测策略说明 TABLE_STRATEGIES = { "lines_strict": "严格模式:只检测有完整边框线的表格", "lines": "线条模式:检测有部分边框线的表格", "text": "文本模式:基于文本对齐检测表格(适合无边框表格)", "explicit": "显式模式:只检测 PDF 中明确标记的表格", } ``` **💡 如何选择表格策略**: | 策略 | 适用场景 | 准确度 | 速度 | |------|---------|:------:|:----:| | `lines_strict` | 有完整边框的表格 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | | `lines` | 有部分边框的表格 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | | `text` | 无边框的对齐文本 | ⭐⭐⭐ | ⭐⭐⭐ | | `explicit` | PDF 明确标记的表格 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | --- ### 3. 搜索功能 ```python def search(self, query: str, max_results: int = 10) -> List[Paper]: """搜索 arXiv 论文 Args: query: 搜索关键词,支持 arXiv 查询语法 例如: "ti:attention" (标题), "au:hinton" (作者) max_results: 最大返回数量 Returns: List[Paper]: 论文列表 """ params = { 'search_query': query, 'max_results': max_results, 'sortBy': 'submittedDate', 'sortOrder': 'descending' } try: # 添加超时防止阻塞 response = requests.get(self.BASE_URL, params=params, timeout=30) response.raise_for_status() # 检查 HTTP 状态码 except requests.RequestException as e: logger.error(f"arXiv API request failed: {e}") return [] # 返回空列表而不是抛出异常 feed = feedparser.parse(response.content) papers = [] for entry in feed.entries: try: # 清理标题和摘要中的换行符 papers.append(Paper( paper_id=entry.id.split('/')[-1], title=entry.title.replace('\n', ' ').strip(), abstract=entry.summary.replace('\n', ' ').strip(), # ... 其他字段 )) except Exception as e: logger.warning(f"Error parsing arXiv entry: {e}") return papers ``` **💡 改进点**: - 添加了 `timeout=30` 防止网络阻塞 - 使用 `raise_for_status()` 检查 HTTP 状态码 - 清理文本中的换行符 --- ### 4. 下载功能 ```python def download_pdf(self, paper_id: str, save_path: str = "./downloads") -> str: """下载 arXiv 论文 PDF 改进: - 自动创建目录 - 检查文件是否已存在(避免重复下载) - 处理特殊文件名(斜杠、冒号等) """ # 确保目录存在 os.makedirs(save_path, exist_ok=True) # 处理带版本号的 ID (例如 2106.12345v2) safe_id = paper_id.replace('/', '_').replace(':', '_') output_file = os.path.join(save_path, f"{safe_id}.pdf") # 检查文件是否已存在 if os.path.exists(output_file): logger.info(f"PDF already exists: {output_file}") return output_file # 下载 PDF pdf_url = f"https://arxiv.org/pdf/{paper_id}.pdf" try: response = requests.get(pdf_url, timeout=60) response.raise_for_status() with open(output_file, 'wb') as f: f.write(response.content) logger.info(f"PDF downloaded: {output_file}") return output_file except requests.RequestException as e: raise RuntimeError(f"Failed to download PDF: {e}") ``` --- ### 5. 核心:read_paper() 方法 ```python def read_paper( self, paper_id: str, save_path: str = "./downloads", output_format: Literal["markdown", "text"] = "markdown", table_strategy: Literal["lines_strict", "lines", "text", "explicit"] = "lines_strict", pages: Optional[List[int]] = None ) -> str: """读取论文并提取内容 使用 PyMuPDF4LLM 进行高质量文本提取,支持: - Markdown 格式输出(推荐,对 LLM 友好) - 表格自动转换为 Markdown 表格 - 多种表格检测策略 Args: paper_id: arXiv 论文 ID save_path: PDF 存储目录 output_format: 输出格式 - "markdown": Markdown 格式(推荐,包含表格) - "text": 纯文本格式 table_strategy: 表格检测策略 pages: 要提取的页面列表(0-indexed),None 表示全部页面 Returns: str: 提取的论文内容 """ # 确保 PDF 已下载 pdf_path = self._ensure_pdf_downloaded(paper_id, save_path) if output_format == "markdown": return self._extract_markdown(pdf_path, table_strategy, pages) else: return self._extract_text(pdf_path, pages) ``` **💡 设计模式**: - 使用 `Literal` 类型提示限定参数取值 - 分离下载和提取逻辑 - 支持两种输出格式 --- ### 6. Markdown 提取实现 ```python def _extract_markdown( self, pdf_path: str, table_strategy: str, pages: Optional[List[int]] = None ) -> str: """使用 PyMuPDF4LLM 提取 Markdown 格式内容 PyMuPDF4LLM 特点: - 专为 LLM 优化的输出格式 - 自动检测并格式化表格 - 保留文档结构(标题、列表等) """ try: md_text = pymupdf4llm.to_markdown( pdf_path, pages=pages, table_strategy=table_strategy, show_progress=False # 静默模式 ) return md_text except Exception as e: logger.error(f"Markdown extraction failed: {e}") # 回退到纯文本提取 logger.info("Falling back to plain text extraction") return self._extract_text(pdf_path, pages) ``` **💡 学习要点**: - 优雅降级:Markdown 提取失败时回退到纯文本 - 静默模式:`show_progress=False` 避免输出进度条 --- ### 7. 纯文本提取(备用) ```python def _extract_text( self, pdf_path: str, pages: Optional[List[int]] = None ) -> str: """使用 PyMuPDF 提取纯文本内容""" try: doc = pymupdf.open(pdf_path) text_parts = [] page_range = pages if pages else range(len(doc)) for page_num in page_range: if 0 <= page_num < len(doc): page = doc[page_num] text_parts.append(page.get_text()) doc.close() return "\n".join(text_parts) except Exception as e: logger.error(f"Text extraction failed: {e}") return f"Error extracting text: {e}" ``` --- ## 输出示例对比 ### PyPDF2 (旧) 输出 ``` Table 1: Results Model Accuracy F1 BERT 0.89 0.87 GPT 0.92 0.90 ``` 表格结构完全丢失! ### PyMuPDF4LLM (新) 输出 ```markdown **Table 1: Results** | Model | Accuracy | F1 | |-------|----------|-----| | BERT | 0.89 | 0.87 | | GPT | 0.92 | 0.90 | ``` 表格结构完美保留! --- ## 使用示例 ```python from paper_search_mcp.academic_platforms.arxiv import ArxivSearcher searcher = ArxivSearcher() # 搜索论文 papers = searcher.search("attention mechanism", max_results=5) # 下载并读取(Markdown 格式) content = searcher.read_paper( papers[0].paper_id, output_format="markdown", table_strategy="lines_strict" ) # 只提取前两页 content = searcher.read_paper( papers[0].paper_id, pages=[0, 1] ) ``` --- ## 可以继续改进的地方 ### 1. 添加数学公式转 LaTeX 如果需要完美的数学公式转换,可以考虑添加 `marker-pdf`: ```python # 安装: pip install marker-pdf # 使用 marker 转换(速度较慢但公式更准确) from marker.converters.pdf import PdfConverter from marker.models import create_model_dict converter = PdfConverter(artifact_dict=create_model_dict()) result = converter("paper.pdf") ``` ### 2. 添加缓存 ```python from functools import lru_cache @lru_cache(maxsize=100) def read_paper_cached(self, paper_id: str, output_format: str) -> str: return self.read_paper(paper_id, output_format=output_format) ``` ### 3. 异步下载 ```python import aiohttp import asyncio async def download_pdf_async(self, paper_id: str, save_path: str) -> str: async with aiohttp.ClientSession() as session: async with session.get(f"https://arxiv.org/pdf/{paper_id}.pdf") as response: content = await response.read() # ... ``` --- ## 依赖要求 ```toml # pyproject.toml dependencies = [ "pymupdf4llm>=0.2.0", # 替代 PyPDF2 "requests", "feedparser", # ... ] ``` --- ## 总结 更新后的 `ArxivSearcher` 体现了 2025 年 PDF 提取最佳实践: | 改进 | 效果 | |------|------| | PyMuPDF4LLM | 高质量 Markdown 输出 | | 表格策略 | 灵活的表格检测 | | 优雅降级 | 失败时自动回退 | | 类型提示 | 更好的代码提示 | | 日志记录 | 便于调试 | 这是一个生产级别的 PDF 提取实现!

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/h-lu/paper-search-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server