篩選:從抓取到郵箱推送的完整實(shí)踐)
每天早上一睜眼arXiv 上都會(huì)多出兩三百篇新論文。如果你只盯著某個(gè)細(xì)分方向可能只有兩三篇值得讀但為了這兩三篇你得翻幾十個(gè)搜索頁(yè)、點(diǎn)開(kāi)一堆標(biāo)題最后大概率還是漏掉了更重要的。我花了幾個(gè)晚上搭了一個(gè)基于 DeepSeek 的自動(dòng)篩選系統(tǒng)把“人肉刷 arXiv”變成“讓模型幫我刷”順手還推送到郵箱。今天把這套方案從思路到代碼完整拆一遍給同樣被論文淹沒(méi)的科研黨一個(gè)可以照抄的作業(yè)。這個(gè)系統(tǒng)不挑方向只要你在 prompt 里寫(xiě)清楚自己的研究興趣它每天早上都會(huì)自動(dòng)把當(dāng)天 arXiv 上相關(guān)度高的論文挑出來(lái)附上評(píng)分和一句推薦理由發(fā)到你的郵箱。如果你剛接觸編程照著代碼改一改也能跑起來(lái)如果你已經(jīng)寫(xiě)過(guò)不少腳本后面關(guān)于提示詞工程和閾值調(diào)優(yōu)的經(jīng)驗(yàn)應(yīng)該能幫你把篩選質(zhì)量再拉高一個(gè)檔次。1. 整體設(shè)計(jì)與思路拆解1.1 為什么選擇DeepSeek做論文篩選傳統(tǒng)關(guān)鍵字篩選用正則表達(dá)式匹配標(biāo)題和摘要遇到同義詞基本就廢。比如你研究“多模態(tài)推理”論文標(biāo)題可能寫(xiě)“vision-language co-reasoning”純字符匹配根本抓不到。用 DeepSeek 這類(lèi)大語(yǔ)言模型來(lái)做本質(zhì)上是把它當(dāng)成一個(gè)“讀過(guò)大量論文的助教”給它看標(biāo)題和摘要讓它判斷這篇和你研究方向有沒(méi)有關(guān)系還順帶給出理由和標(biāo)簽。這種語(yǔ)義層面的篩選比關(guān)鍵詞規(guī)則靈活得多。我把關(guān)鍵詞過(guò)濾和 DeepSeek 篩選做過(guò)一次對(duì)比結(jié)果很直觀維度關(guān)鍵詞過(guò)濾DeepSeek 篩選同義詞識(shí)別差只能字面匹配好能理解同義表述跨領(lǐng)域關(guān)聯(lián)無(wú)法識(shí)別可以發(fā)現(xiàn)間接相關(guān)的論文結(jié)果可解釋性只能說(shuō)明命中了哪個(gè)詞能輸出一句自然語(yǔ)言理由成本免費(fèi)、實(shí)時(shí)有 API 費(fèi)用需要耗時(shí)穩(wěn)定性穩(wěn)定但漏報(bào)率高有隨機(jī)性需要加規(guī)則兜底所以最后我采取的策略是兩者結(jié)合先用關(guān)鍵詞把候選集縮小再用 DeepSeek 做精排。這樣省掉大量無(wú)效的模型調(diào)用同時(shí)保留語(yǔ)義理解的優(yōu)勢(shì)。DeepSeek 的 API 對(duì)中文支持很好。我的研究方向描述是用中文寫(xiě)的模型也能準(zhǔn)確理解返回的推薦理由可以用中文解釋讀起來(lái)非常順。同樣一篇英文摘要它能用“這篇提出了一個(gè)跨模態(tài)的對(duì)比學(xué)習(xí)框架和你的多模態(tài)推理方向高度相關(guān)”來(lái)總結(jié)這不就是私人助教么。1.2 系統(tǒng)架構(gòu)模塊劃分我把它分成四個(gè)模塊抓取、預(yù)篩、評(píng)分、通知。每個(gè)模塊盡量獨(dú)立方便單獨(dú)調(diào)。抓取模塊通過(guò) arXiv 的 API 拉取當(dāng)天更新的論文拿到標(biāo)題、摘要、作者、鏈接、分類(lèi)號(hào)。這里用到的是官方提供的數(shù)據(jù)不需要爬網(wǎng)頁(yè)穩(wěn)定很多。拉下來(lái)的論文先存在內(nèi)存里不落庫(kù)也行因?yàn)槊刻焯幚硗昃屯晔铝巳绻L(zhǎng)期積累可以接一個(gè) SQLite。預(yù)篩模塊做兩件事一是排除明顯不相關(guān)的分類(lèi)比如你只做 NLP那定量生物學(xué)方向的論文就不看了二是做關(guān)鍵詞粗篩把包含指定詞組的論文撈出來(lái)。如果粗篩后數(shù)量還是太多比如遇到某個(gè)方向當(dāng)天井噴可以把關(guān)鍵詞匹配做成“必須命中標(biāo)題”或“摘要中至少出現(xiàn)兩次”。評(píng)分模塊把預(yù)篩后的論文逐條發(fā)給 DeepSeek讓它輸出一個(gè) 0 到 100 的相關(guān)度評(píng)分結(jié)構(gòu)化成 JSON。這個(gè)模塊是核心后面我會(huì)專(zhuān)門(mén)講提示詞怎么寫(xiě)。通知模塊把得分超過(guò)閾值的論文整理成 Markdown 格式的郵件正文發(fā)送到你自己的郵箱。不想用郵件的話(huà)也可以改成調(diào)用即時(shí)通訊工具的 webhook。1.3 技術(shù)選型與關(guān)鍵取舍整體實(shí)現(xiàn)我用 Python因?yàn)榈谌綆?kù)最全。arXiv 的 API 用 urllib 就能調(diào)沒(méi)必要上 requests模型調(diào)用用 OpenAI 兼容的 SDKDeepSeek 提供同樣的接口格式設(shè)置 base_url 和 api_key 就行。定時(shí)任務(wù)直接寫(xiě)進(jìn)腳本里用系統(tǒng)的 cron 或計(jì)劃任務(wù)觸發(fā)不依賴(lài)后臺(tái)框架。有一個(gè)讓我糾結(jié)很久的點(diǎn)要不要把所有論文都過(guò)一遍模型從效果看全量模型處理肯定召回最高但成本也高。如果當(dāng)天論文有 500 篇每篇摘要平均 1500 token一天就要處理 75 萬(wàn) token長(zhǎng)期下來(lái)不是小數(shù)目。所以最終選了“規(guī)則預(yù)篩 模型精排”的漏斗結(jié)構(gòu)。實(shí)測(cè)下來(lái)預(yù)篩能過(guò)濾掉 70% 的不相關(guān)論文剩下 30% 再讓模型打分成本和效果取得了不錯(cuò)的平衡。這里還有一個(gè)容易被忽略的細(xì)節(jié)模型評(píng)分有隨機(jī)性。同一篇論文跑兩次分?jǐn)?shù)可能會(huì)波動(dòng) 10 分左右。所以在設(shè)計(jì)時(shí)不能讓模型直接決定“要不要”而是讓它輸出連續(xù)分?jǐn)?shù)再配合下面的規(guī)則閾值來(lái)過(guò)濾。這樣即使模型偶爾抽風(fēng)只要不是極端情況結(jié)果基本穩(wěn)定。2. 核心細(xì)節(jié)解析與實(shí)操要點(diǎn)2.1 從arXiv抓取論文元數(shù)據(jù)arXiv 官方 API 的入口是export.arxiv.org/api/query。它支持按時(shí)間、分類(lèi)、關(guān)鍵詞查詢(xún)返回 Atom XML 格式。最常用的參數(shù)是search_query比如cat:cs.CL篩選自然語(yǔ)言處理all:transformer搜索標(biāo)題和摘要。還有一個(gè)關(guān)鍵參數(shù)sortBysubmittedDate能保證拿到的是當(dāng)天新增論文。實(shí)際請(qǐng)求時(shí)要注意官方 API 有頻率限制如果抓太頻繁會(huì)被拒絕訪問(wèn)。我的經(jīng)驗(yàn)是每次請(qǐng)求之間至少間隔 3 秒并且設(shè)置一個(gè)明確的 User-Agent 標(biāo)明用途。另外arXiv 的摘要里可能包含換行和特殊字符解析后要清洗一下否則喂給模型時(shí)容易讓 prompt 結(jié)構(gòu)錯(cuò)亂。用 XML 解析時(shí)要先處理命名空間。下面這段代碼能提取出最基本的信息import urllib.request import xml.etree.ElementTree as ET ARXIV_API http://export.arxiv.org/api/query NS {atom: http://www.w3.org/2005/Atom, arxiv: http://arxiv.org/schemas/atom} def fetch_papers(query: str, max_results: int 50): url f{ARXIV_API}?search_query{query}sortBysubmittedDatesortOrderdescendingmax_results{max_results} req urllib.request.Request(url, headers{User-Agent: PaperFilter/0.1}) with urllib.request.urlopen(req) as resp: xml_text resp.read().decode(utf-8) root ET.fromstring(xml_text) papers [] for entry in root.findall(atom:entry, NS): papers.append({ title: entry.find(atom:title, NS).text.strip().replace(\n, ), abstract: entry.find(atom:summary, NS).text.strip(), link: entry.find(atom:id, NS).text.strip(), category: [c.attrib[term] for c in entry.findall(atom:category, NS)], }) return papers注意sortBysubmittedDate不一定百分之百只返回當(dāng)天的可能混著前一天沒(méi)入庫(kù)的所以在后續(xù)腳本里要用解析出的發(fā)布日期再過(guò)濾一次。日期可以從arxiv:published標(biāo)簽里取解析成 UTC 再比較from datetime import datetime, timezone published entry.find(arxiv:published, NS).text.strip() pub_date datetime.fromisoformat(published.replace(Z, 00:00)) if pub_date.date() datetime.now(timezone.utc).date(): # 只保留今天的論文 pass另外arXiv 的 API 接口是http如果服務(wù)器環(huán)境要求更嚴(yán)格可以走h(yuǎn)ttps不過(guò)在大多數(shù)場(chǎng)景下都能直接訪問(wèn)。拉取結(jié)果后建議把標(biāo)題和摘要里的多余空白壓縮掉正則替換連續(xù)空格為單個(gè)空格這樣模型請(qǐng)求時(shí)能省一點(diǎn) token。2.2 提示詞工程讓DeepSeek明白“相關(guān)性”論文篩選的核心不是代碼而是 prompt。我調(diào)試了很多版本最后穩(wěn)定下來(lái)的是一個(gè)帶角色設(shè)定、評(píng)分標(biāo)準(zhǔn)和 few-shot 示例的模板。先說(shuō)角色設(shè)定告訴模型“你是某課題組的研究助手”比直接說(shuō)“請(qǐng)你判斷是否相關(guān)”效果更穩(wěn)定。因?yàn)榻巧珪?huì)給模型一個(gè)行為框架。然后是研究方向描述這個(gè)描述不能太短比如“自然語(yǔ)言處理”這種太籠統(tǒng)模型會(huì)給你很多泛泛的分?jǐn)?shù)。要把關(guān)鍵詞、方法、目標(biāo)都寫(xiě)進(jìn)去比如“多模態(tài)大模型中的視覺(jué)-語(yǔ)言對(duì)齊機(jī)制尤其關(guān)注跨模態(tài)注意力”。評(píng)分標(biāo)準(zhǔn)要明確如果論文提出新方法或新數(shù)據(jù)集且方法可復(fù)現(xiàn)給 80 分以上如果只是應(yīng)用既有方法到新場(chǎng)景給 50 到 70 分如果完全不沾邊給 20 分以下。把評(píng)分維度和具體尺度寫(xiě)在 prompt 里能顯著降低模型亂打分的概率。few-shot 示例特別重要。我會(huì)在 prompt 里給兩個(gè)示例一個(gè)高度相關(guān)但標(biāo)題里沒(méi)有關(guān)鍵詞的論文模型應(yīng)該給高分另一個(gè)看起來(lái)沾邊但實(shí)際方向偏移的論文模型應(yīng)該給低分。這樣模型就理解了“相關(guān)性不是單純看字面詞”。最后要求模型返回嚴(yán)格 JSON字段包括 score、reason、tags。下面是一個(gè)我實(shí)際在用的完整 prompt 模板def build_prompt(paper: dict) - str: research_field 多模態(tài)大語(yǔ)言模型中的視覺(jué)-語(yǔ)言對(duì)齊機(jī)制尤其關(guān)注跨模態(tài)注意力、指令微調(diào)中的對(duì)齊策略、以及圖文推理鏈 return f 你是某課題組的研究助手。你要根據(jù)下面的研究方向判斷一篇論文是否值得課題組成員精讀。 研究方向{research_field} 評(píng)分標(biāo)準(zhǔn) - 90-100提出了新方法、新模型或新數(shù)據(jù)集且直接解決該方向的關(guān)鍵問(wèn)題 - 70-89將現(xiàn)有方法深度應(yīng)用到該方向有明確的參考價(jià)值 - 40-69僅僅部分相關(guān)例如使用了某個(gè)相似名詞但沒(méi)有實(shí)質(zhì)關(guān)聯(lián) - 0-39基本不相關(guān)只是 keywords 有巧合 輸出要求 只允許輸出一個(gè) JSON 對(duì)象不要輸出其他任何文字。 格式如下 {{score: 0, reason: 不超過(guò)30字的中文理由, tags: [標(biāo)簽]}} tags 只能從 [new_method, new_dataset, survey, application] 中選擇沒(méi)有匹配的就給空列表。 示例1 論文標(biāo)題Aligning Vision-Language Representations with Learnable Cross-Modal Tokens 論文摘要本文提出一種可學(xué)習(xí)的跨模態(tài) token 對(duì)齊方法增強(qiáng)了視覺(jué)編碼器與語(yǔ)言模型的交互。 正確輸出{{score: 95, reason: 直接提出新的對(duì)齊方法針對(duì)性極強(qiáng), tags: [new_method]}} 示例2 論文標(biāo)題ImageNet Classification with Visual Features 論文摘要僅使用視覺(jué)特征進(jìn)行圖像分類(lèi)沒(méi)有涉及任何語(yǔ)言模型或跨模態(tài)交互。 正確輸出{{score: 12, reason: 純圖像分類(lèi)方向完全不符, tags: []}} 現(xiàn)在開(kāi)始 論文標(biāo)題{paper[title]} 論文摘要{paper[abstract][:2000]} 這個(gè)模板有幾個(gè)細(xì)節(jié)是刻意設(shè)計(jì)的。首先輸出要求里明確“只允許輸出一個(gè) JSON 對(duì)象”并且給了示例格式模型基本不會(huì)跑偏。其次標(biāo)簽候選只有 4 個(gè)限制范圍比放開(kāi)讓模型自由發(fā)揮要穩(wěn)定得多。最后摘要限制 2000 字符因?yàn)檎撐恼暮蟀攵瓮菍?shí)驗(yàn)設(shè)置和結(jié)論對(duì)“相關(guān)性”判斷的邊際貢獻(xiàn)不大反而會(huì)拖慢響應(yīng)速度。這里有個(gè)坑要求返回 “strict JSON” 還不夠模型偶爾會(huì)在前面加解釋文字或者用中文標(biāo)點(diǎn)。所以在解析時(shí)不能只json.loads要先做一層正則清理提取{...}部分。后面常見(jiàn)問(wèn)題里會(huì)細(xì)說(shuō)。2.3 評(píng)分規(guī)則與閾值設(shè)定DeepSeek 輸出的 0-100 分?jǐn)?shù)是“軟性”的直接拿它和閾值比較之前我建議先做一個(gè)“關(guān)鍵詞加權(quán)”的二次校正。具體做法是在 prompt 外自己再算一個(gè)關(guān)鍵詞命中分標(biāo)題命中一個(gè)關(guān)鍵詞 10摘要命中 5命中多個(gè)關(guān)鍵詞累加。然后最終分?jǐn)?shù) 0.8 * 模型分 0.2 * 關(guān)鍵詞加權(quán)分。之所以保留關(guān)鍵詞加權(quán)是為了防止模型對(duì)某些過(guò)長(zhǎng)摘要產(chǎn)生“注意力漂移”把核心點(diǎn)漏掉。閾值設(shè)定可以先無(wú)腦取 60 分然后跑一周記錄每天命中數(shù)量。如果每天都很多說(shuō)明閾值低了如果三天打魚(yú)兩天曬網(wǎng)說(shuō)明閾值高了。我的經(jīng)驗(yàn)是寧可每周多篩出兩篇讀也別漏掉真正重要的那篇。所以閾值不要定太高65 分是個(gè)不錯(cuò)的起點(diǎn)。另外可以給論文打標(biāo)簽比如“survey”“new_dataset”“SOTA”。這個(gè)標(biāo)簽不是篩選必需但如果推送郵件里帶了標(biāo)簽?zāi)銙咭谎劬湍軟Q定要不要點(diǎn)開(kāi)。DeepSeek 在這方面表現(xiàn)不錯(cuò)給它列幾個(gè)候選標(biāo)簽它會(huì)照做。不過(guò)模型對(duì) “SOTA” 這類(lèi)需要嚴(yán)謹(jǐn)驗(yàn)證的標(biāo)簽有時(shí)會(huì)誤判所以最后推送前我自己加了一道小檢查如果摘要里出現(xiàn) “state-of-the-art”“ outperforms ” 這些字樣才允許打上 “SOTA” 標(biāo)簽。3. 實(shí)操過(guò)程與核心環(huán)節(jié)實(shí)現(xiàn)3.1 環(huán)境準(zhǔn)備與依賴(lài)安裝需要 Python 3.9 以上裝兩個(gè)庫(kù)就夠requests用于調(diào)用模型接口標(biāo)準(zhǔn)庫(kù)里的urllib、xml、smtplib就能解決其他問(wèn)題。我在虛擬環(huán)境里安裝避免污染全局python -m venv venv source venv/bin/activate pip install requestsDeepSeek 的 API 是 OpenAI 兼容的所以也可以用openai這個(gè)庫(kù)把base_url指向 DeepSeek 的接口。但我試下來(lái)直接用requests反而少一層依賴(lài)出錯(cuò)更可控。下面是調(diào)用函數(shù)import requests import json API_KEY your_deepseek_api_key BASE_URL https://api.deepseek.com/v1/chat/completions MODEL_NAME deepseek-chat def chat_json(prompt: str, temperature: float 0.3): headers { Authorization: fBearer {API_KEY}, Content-Type: application/json, } payload { model: MODEL_NAME, messages: [{role: user, content: prompt}], temperature: temperature, } resp requests.post(BASE_URL, headersheaders, jsonpayload) data resp.json() return data[choices][0][message][content]注意這里我故意把temperature設(shè)成 0.3。論文篩選的任務(wù)需要一定的一致性溫度太高會(huì)隨機(jī)打亂評(píng)分設(shè)得太低又可能讓模型拒絕輸出。0.3 到 0.4 是性?xún)r(jià)比比較高的區(qū)間。API Key 一定要通過(guò)環(huán)境變量或者配置文件讀取不要寫(xiě)死在代碼里。我見(jiàn)過(guò)太多人把 key 發(fā)到公開(kāi)博客然后被拿去刷爆。用os.getenv(DEEPSEEK_API_KEY)取既安全又方便換。3.2 核心代碼采集、評(píng)分、推送整體流程函數(shù)寫(xiě)成一個(gè)pipeline()每天跑一次。采集部分用前面的fetch_papers預(yù)篩部分簡(jiǎn)單判斷目標(biāo)關(guān)鍵詞評(píng)分部分組裝 prompt 后調(diào)用chat_json推送部分用smtplib發(fā)送郵件。下面是一個(gè)可運(yùn)行的骨架我刪掉了日志和異常處理的細(xì)節(jié)只保留主線(xiàn)import os import re import json import smtplib from datetime import datetime, timezone from email.mime.text import MIMEText RESEARCH_FIELD 多模態(tài)大語(yǔ)言模型中的視覺(jué)-語(yǔ)言對(duì)齊機(jī)制 KEYWORDS [vision-language, multimodal, cross-modal, alignment] THRESHOLD 65 def pre_filter(papers): result [] for p in papers: text (p[title] p[abstract]).lower() if any(k.lower() in text for k in KEYWORDS): result.append(p) return result def parse_model_output(content: str) - dict: # 提取第一個(gè) { 到最后一個(gè) } 之間的內(nèi)容 start content.find({) end content.rfind(}) if start -1 or end -1: raise ValueError(no json found) data json.loads(content[start:end1]) data[score] int(data[score]) return data def score_paper(paper: dict) - dict: prompt build_prompt(paper) # 復(fù)用 2.2 節(jié)的模板 content chat_json(prompt) parsed parse_model_output(content) # 關(guān)鍵詞加權(quán) text (paper[title] paper[abstract]).lower() kw_score 0 for k in KEYWORDS: if k.lower() in paper[title].lower(): kw_score 10 elif k.lower() in text: kw_score 5 final_score int(0.8 * parsed[score] 0.2 * kw_score) parsed[final_score] final_score return parsed def send_mail(papers_with_meta: list): lines [# 今日論文推薦\n] for item in papers_with_meta: lines.append(f## {item[title]}) lines.append(f相關(guān)度{item[final_score]} 分) lines.append(f理由{item[reason]}) lines.append(f鏈接{item[link]}) if item.get(tags): lines.append(f標(biāo)簽{, .join(item[tags])}) lines.append() body \n.join(lines) msg MIMEText(body, markdown, utf-8) msg[Subject] f每日論文篩選 {datetime.now().strftime(%Y-%m-%d)} msg[From] os.getenv(MAIL_FROM) msg[To] os.getenv(MAIL_TO) with smtplib.SMTP_SSL(os.getenv(SMTP_HOST), int(os.getenv(SMTP_PORT))) as server: server.login(os.getenv(SMTP_USER), os.getenv(SMTP_PASS)) server.send_message(msg)運(yùn)行主流程時(shí)我通常會(huì)把不合格的論文也存到一個(gè) debug 文件里方便回頭檢查是不是漏掉了好論文。這里有個(gè)細(xì)節(jié)parse_model_output里的int(data[score])必須加因?yàn)槟P团紶柗祷刈址?lèi)型你不強(qiáng)轉(zhuǎn)后續(xù) THRESHOLD時(shí)就會(huì) TypeError。3.3 定時(shí)運(yùn)行與結(jié)果分析腳本寫(xiě)好后最省事的方式是配置 cron。比如每天早上八點(diǎn)跑一次0 8 * * * cd /path/to/project /path/to/venv/bin/python pipeline.py run.log 21記住要用虛擬環(huán)境的解釋器不能用直接python否則可能用到錯(cuò)誤版本。跑一周之后我建議做一個(gè)簡(jiǎn)單的結(jié)果分析把每天推送論文的標(biāo)題存下來(lái)手動(dòng)抽查 20 篇看哪些被漏掉、哪些是誤報(bào)。漏掉多的就降低閾值或增加關(guān)鍵詞誤報(bào)多就反過(guò)來(lái)調(diào)整 prompt 或提高閾值。這一步才是整個(gè)系統(tǒng)最有價(jià)值的部分。因?yàn)槟P团袛嘤肋h(yuǎn)有瑕疵人工閉環(huán)調(diào)整才是提升精度的關(guān)鍵。我自己的系統(tǒng)跑了兩周之后誤報(bào)率從剛開(kāi)始的 40% 降到了 15% 左右。我每周會(huì)做一個(gè)表格記錄效果日期預(yù)篩論文數(shù)模型調(diào)用數(shù)最終推送數(shù)手動(dòng)抽查判斷周一868698 篇值得讀周二102102129 篇值得讀周三737376 篇值得讀當(dāng)你連續(xù)記錄半個(gè)月就能看出閾值和關(guān)鍵詞設(shè)置是否合理了。如果某天預(yù)篩論文數(shù)異常高很可能是某篇熱門(mén)論文霸占了多個(gè)分類(lèi)去重邏輯沒(méi)生效。如果連續(xù)兩天推送數(shù)為 0先別急著降閾值大概率是 arXiv API 沒(méi)抓到新數(shù)據(jù)而不是真的沒(méi)有好論文。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 請(qǐng)求被限流或超時(shí)arXiv 的 API 不會(huì)給明確的 429但會(huì)出現(xiàn)連接超時(shí)或者返回一段比較少見(jiàn)的 XML。解決辦法是添加重試和退避邏輯。我寫(xiě)了一個(gè)簡(jiǎn)單的retry裝飾器三次重試每次等待時(shí)間遞增。另外把max_results拆小比如一次拉 20 篇分批次拉避免一次拉幾百篇被服務(wù)器拒絕。還有一次我遇到urlopen error [Errno 110] Connection timed out排查后發(fā)現(xiàn)是網(wǎng)絡(luò)代理環(huán)境太復(fù)雜。后來(lái)在服務(wù)器上直接清空代理變量才恢復(fù)正常。如果你也用了代理記得在腳本里顯式清除HTTP_PROXY和HTTPS_PROXY不然很隱蔽。DeepSeek API 偶爾也會(huì)返回 503 或者 429這類(lèi)狀態(tài)碼意味著服務(wù)端過(guò)載。我的處理邏輯是遇到 5xx 就退避重試遇到 429 就等待更長(zhǎng)的時(shí)間再試一次。不要在重試邏輯里直接 sleep 60那樣會(huì)把整個(gè) pipeline 卡住最好給每次重試拋出一個(gè)異常讓主流程跳過(guò)這篇論文最后把失敗列表打印出來(lái)。4.2 模型輸出不穩(wěn)定與JSON解析失敗這是最常遇到的問(wèn)題。DeepSeek 返回的內(nèi)容偶爾會(huì)在 JSON 前后加一句廢話(huà)比如“好的為你生成結(jié)果”導(dǎo)致json.loads直接拋異常。我的處理方法是先找第一個(gè){和最后一個(gè)}截取中間部分再解析如果還是失敗就重新調(diào)用一次。因?yàn)闇囟炔桓咧卦嚭筝敵龌緯?huì)正常。還有一個(gè)很隱蔽的問(wèn)題模型給出的 score 可能是字符串75而不是數(shù)字 75。所以解析后要用int()強(qiáng)制轉(zhuǎn)換否則后面比較時(shí)會(huì)報(bào)錯(cuò)。解析代碼可以封裝成一個(gè)小函數(shù)并且支持容錯(cuò)def safe_parse_json(content: str): try: return json.loads(content) except json.JSONDecodeError: start content.find({) end content.rfind(}) if start -1 or end -1: return None try: return json.loads(content[start:end1]) except json.JSONDecodeError: return None如果safe_parse_json返回 None我會(huì)把這篇文章標(biāo)記為“待處理”不去重試也是可以的。因?yàn)樘崾驹~里的示例已經(jīng)把輸出格式規(guī)定得很?chē)?yán)偶爾一次失敗不影響整體效果。但你一定要在日志里保留原文方便事后分析是不是某些特殊論文觸發(fā)了模型異常。4.3 篩選結(jié)果漂移與閾值調(diào)優(yōu)有時(shí)候同一個(gè)研究方向上周跑得很好這周突然推了一堆不太相關(guān)的論文。原因可能是 arXiv 這周的新論文里恰好有大批擦邊球或者模型在長(zhǎng)上下文中的注意力被稀釋。我的經(jīng)驗(yàn)是把研究方向描述拆分得更細(xì)加上“不需要泛泛的綜述性工作”這樣的負(fù)面約束。同時(shí)閾值不要固定死可以根據(jù)近三天推送數(shù)量做動(dòng)態(tài)調(diào)整比如平均超過(guò) 10 篇就上調(diào) 5 分少于 3 篇就下調(diào) 5 分。還有一種情況研究方向本身在演進(jìn)。比如你從“多模態(tài)對(duì)齊”細(xì)化到“細(xì)粒度視覺(jué)定位”那么方向描述里要同步加一句“當(dāng)前最后關(guān)注細(xì)粒度區(qū)域定位與語(yǔ)言引導(dǎo)的交互”。我經(jīng)常忘了更新 prompt導(dǎo)致系統(tǒng)還在推舊方向的內(nèi)容那時(shí)候就會(huì)覺(jué)得“這系統(tǒng)怎么越跑越偏”。其實(shí)不是系統(tǒng)變笨了而是你的研究方向變了。4.4 成本控制與性能優(yōu)化如果預(yù)篩后每天還有 200 篇論文意味著要調(diào)用 200 次模型 API每次等待 1-3 秒整體跑下來(lái)要 10 分鐘以上。性能瓶頸主要在串行等待。解決辦法是用線(xiàn)程池并發(fā)調(diào)用把并發(fā)數(shù)控制在 5 左右能提速 3-4 倍且不容易觸發(fā)限流。另外可以把論文抽象出前 2000 字符因?yàn)楹竺婊径际?related work對(duì)判斷相關(guān)性幫助不大。使用 ThreadPoolExecutor 的時(shí)候要保證每個(gè)線(xiàn)程都有獨(dú)立的 prompt沒(méi)有共享狀態(tài)from concurrent.futures import ThreadPoolExecutor, as_completed def batch_score(papers: list, max_workers: int 5): results [] with ThreadPoolExecutor(max_workersmax_workers) as executor: future_map {executor.submit(score_paper, p): p for p in papers} for future in as_completed(future_map): paper future_map[future] try: parsed future.result() parsed[link] paper[link] parsed[title] paper[title] results.append(parsed) except Exception as e: print(ffailed: {paper[title]} - {e}) return results成本方面可以用一個(gè)簡(jiǎn)單的統(tǒng)計(jì)函數(shù)定期記錄每天調(diào)用 token 數(shù)。一個(gè)月下來(lái)你心里就有底了。我的實(shí)際體驗(yàn)是預(yù)篩加并發(fā)之后每天大概消耗 30 萬(wàn) token不是一個(gè)大數(shù)字但如果不做預(yù)篩會(huì)多出三四倍。4.5 安全與隱私提醒這個(gè)系統(tǒng)本身不涉及敏感數(shù)據(jù)但有幾點(diǎn)容易忽視。第一API Key 和郵箱密碼必須用環(huán)境變量管理不要把配置文件丟到公開(kāi)倉(cāng)庫(kù)。第二論文摘要屬于公開(kāi)數(shù)據(jù)但如果你后續(xù)要對(duì)推送結(jié)果做二次處理比如訓(xùn)練自己的模型要留意 arXiv 的使用條款。第三定時(shí)任務(wù)跑出來(lái)的日志可能包含論文標(biāo)題和個(gè)人郵箱不要在公開(kāi)空間隨意粘貼完整日志。還有一點(diǎn)如果你把系統(tǒng)交給別人維護(hù)一定不要把研究方向描述和個(gè)人偏好寫(xiě)得過(guò)于私人化。這個(gè)文件可能會(huì)流傳出去盡量保持學(xué)術(shù)化的表達(dá)。5. 擴(kuò)展玩法讓系統(tǒng)更像你的私人學(xué)術(shù)助理5.1 接入即時(shí)通知郵件雖然穩(wěn)定但不是所有人都習(xí)慣每天看郵箱??梢园淹扑徒Y(jié)果轉(zhuǎn)成 markdown 發(fā)到即時(shí)通訊工具或者生成一個(gè)簡(jiǎn)單的 HTML 頁(yè)面放到本地。我目前用郵件因?yàn)樵O(shè)置簡(jiǎn)單、不受設(shè)備限制。如果你想要更及時(shí)的通知可以用即時(shí)通訊工具的 webhook調(diào)用方式也就是一次 POST 請(qǐng)求比郵件還容易。下面是一個(gè)極其簡(jiǎn)單的 webhook 發(fā)送函數(shù)import requests def send_webhook(webhook_url: str, text: str): resp requests.post(webhook_url, json{text: text}, timeout10) resp.raise_for_status()這個(gè)方法唯一的隱患是 webhook 地址泄露后容易被別人盜刷所以不要把 webhook 地址放在公開(kāi)倉(cāng)庫(kù)里。如果平臺(tái)支持關(guān)鍵字過(guò)濾也可以先做一層脫敏再發(fā)送。5.2 增量更新與去重arXiv 上同一篇論文可能在多個(gè)分類(lèi)里出現(xiàn)抓取時(shí)會(huì)造成重復(fù)。我的做法是用論文 ID 的前幾位去掉版本號(hào)作為去重鍵在內(nèi)存里維護(hù)一個(gè)集合。這樣既不會(huì)重復(fù)推送也能追蹤論文是否有更新版本。seen set() def is_duplicate(paper: dict) - bool: # 例如 arxiv.org/abs/2401.00123v2 - 2401.00123 paper_id paper[link].split(/)[-1].split(v)[0] if paper_id in seen: return True seen.add(paper_id) return False這個(gè)去重邏輯對(duì)“同 ID 不同版本”也有效。我之前沒(méi)做去重時(shí)一天收到過(guò)兩封完全重復(fù)的郵件排查了半天才發(fā)現(xiàn)是不同分類(lèi)入口抓到了同一篇論文。5.3 多主題并行篩選如果你的研究方向有兩個(gè)不想每天看兩份郵件可以讓一個(gè)腳本同時(shí)跑多個(gè) prompt把不同主題的結(jié)果合并到同一封郵件里。這里要注意各主題的分?jǐn)?shù)不能直接比較因?yàn)?prompt 的評(píng)分標(biāo)準(zhǔn)可能不一樣所以郵件里要明確標(biāo)注主題名。實(shí)現(xiàn)上不用把文件拆成多份只要把RESEARCH_FIELD、KEYWORDS和THRESHOLD做成參數(shù)循環(huán)調(diào)用即可。我遇到過(guò)一個(gè)小問(wèn)題兩個(gè)主題的關(guān)鍵詞有交叉導(dǎo)致同一條論文被兩個(gè)主題都推了一次。解決辦法是在同一個(gè)去重集合里增加“主題名 論文ID”的復(fù)合鍵或者按主題的優(yōu)先級(jí)去重。這些擴(kuò)展都不復(fù)雜核心還是抓取、評(píng)分、通知這個(gè)鏈路。系統(tǒng)真正跑起來(lái)以后你就會(huì)發(fā)現(xiàn)它省下來(lái)的不只是刷論文的時(shí)間還有你注意力切換的隱性成本。每天花 3 分鐘掃一眼郵件比在 arXiv 上漫無(wú)目的地刷一小時(shí)要高效得多。我個(gè)人在實(shí)際操作中最深的一點(diǎn)體會(huì)是這類(lèi)自動(dòng)篩選系統(tǒng)效果好壞 80% 取決于那一段研究方向描述怎么寫(xiě)代碼反而是最不費(fèi)腦的地方。你不需要把方向?qū)懙枚鄬W(xué)術(shù)要把你自己看論文時(shí)的直覺(jué)描述出來(lái)比如“我關(guān)心方法不是任務(wù)”“我更在意可復(fù)現(xiàn)性”模型才能真正理解你的偏好。另外不要追求一個(gè)永遠(yuǎn)不變的參數(shù)論文領(lǐng)域的熱點(diǎn)在變你的研究興趣也會(huì)微調(diào)每?jī)扇齻€(gè)星期回頭看看篩選結(jié)果順手改幾句 prompt、動(dòng)一下閾值系統(tǒng)才能一直好用。最后再分享一個(gè)小技巧如果你打算長(zhǎng)期用這套系統(tǒng)記得把每天推送的論文鏈接和評(píng)分存成一個(gè)歷史文件。哪天想找一篇之前讀過(guò)但忘了收藏的論文一份包含摘要和評(píng)分的本地索引比 arXiv 站內(nèi)搜索好用太多。這就是“論文篩選”之外意外收獲的額外價(jià)值。