一、环境要求电脑、Edge浏览器二、安装浏览器插件安装“篡改猴（原tampermonkey）”，安装链接（从Edge插件商店下载）：直达链接点击获取，再点击添加扩展三、设置浏览器在搜索框输入edge://extensions/并回车打开扩展设置页面打开浏览器开发者模式，点击插件详细信息勾选允许用户脚本四、安装AI解题脚本点击右上角拼图状“扩展”图形点击“篡改猴”插件点击添加新脚本将默认的代码全部删除，并且粘贴我写的代码，最后按ctrl+S保存就大功告成了五、学习通网页版地址点击链接直达：链接六、其他我的代码（点击右上角copy按钮可直接复制）：// ==UserScript== // @name 学习通考试 · DeepSeek AI 解题助手 // @namespace cx-deepseek-helper // @version 1.6.0 // @description 提取学习通考试题目，调用 DeepSeek API 进行 AI 解析，结果浮窗展示 // @match *://*.chaoxing.com/* // @match *://*.xuexitong.com/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @connect api.deepseek.com // @run-at document-end // ==/UserScript== (function () { 'use strict'; const CONFIG = { apiKey: GM_getValue('ds_api_key', ''), model: 'deepseek-v4-pro', maxTokens: 4096, systemPrompt: `你是一个专业的考试解题助手。 用户会给你提供题目类型、题干和选项（如有）。 请按以下格式回答： 【答案】直接给出答案（单选给选项字母，多选给多个字母，判断题给"正确"或"错误"，填空/简答直接给答案文字） 【解析】用2-4句话解释理由，要简洁准确。`, }; GM_addStyle(` #cx-ds-panel { position: fixed; top: 60px; right: 20px; z-index: 2147483647; width: 460px; max-height: 88vh; background: #fff; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,.18); font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif; font-size: 13px; overflow: hidden; display: flex; flex-direction: column; pointer-events: auto !important; } .ds-header { background: linear-gradient(135deg,#1a73e8,#0d47a1); color:#fff; padding:10px 14px; display:flex; align-items:center; justify-content:space-between; cursor:move; user-select:none; flex-shrink:0; } .ds-header .title { font-weight:600; font-size:14px; } .ds-header .ds-btns { display:flex; gap:5px; flex-wrap:wrap; } .ds-header button { background:rgba(255,255,255,.2); border:none; color:#fff; border-radius:6px; padding:4px 10px; cursor:pointer; font-size:11px; pointer-events: auto !important; } .ds-header button:hover { background:rgba(255,255,255,.38); } .ds-settings { padding:8px 12px; background:#f5f7fa; border-bottom:1px solid #e8eaed; flex-shrink:0; } .ds-settings input { width:100%; box-sizing:border-box; padding:6px 8px; border:1px solid #ccc; border-radius:6px; font-size:12px; margin-top:3px; } .ds-settings label { font-size:12px; color:#555; } #cx-ds-progress-wrap { height:3px; background:#e8eaed; flex-shrink:0; } #cx-ds-progress-bar { height:100%; width:0%; background:linear-gradient(90deg,#1a73e8,#34a853); transition:width .4s ease; } #cx-ds-body { overflow-y:auto; padding:12px; flex:1; } .ds-q-card { border:1px solid #dce3f0; border-radius:10px; margin-bottom:16px; overflow:hidden; box-shadow:0 2px 8px rgba(26,115,232,.06); } .ds-q-head { background:linear-gradient(90deg,#e8f0fe,#f0f4ff); padding:8px 12px; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid #dce3f0; } .ds-q-num { font-weight:700; color:#1a73e8; font-size:13px; } .ds-q-type { font-size:11px; font-weight:700; background:#1a73e8; color:#fff; border-radius:4px; padding:2px 8px; } .ds-q-title-block { padding:10px 14px; border-bottom:1px dashed #e0e7f5; background:#fff; } .ds-block-label { font-size:10px; font-weight:700; color:#888; letter-spacing:.5px; margin-bottom:5px; display:flex; align-items:center; gap:4px; } .ds-q-title-text { color:#111; font-size:13px; line-height:1.8; white-space:pre-wrap; word-break:break-word; background:#f7f9ff; border:1px solid #dce3f0; border-radius:6px; padding:8px 10px; max-height:200px; overflow-y:auto; } .ds-q-options-block { padding:8px 14px 10px; background:#f8faff; border-bottom:1px dashed #e0e7f5; } .ds-q-option-row { display:flex; gap:6px; align-items:baseline; padding:3px 6px; border-radius:5px; font-size:12px; line-height:1.7; color:#333; margin-bottom:1px; } .ds-q-option-row:hover { background:#e8f0fe; } .ds-q-option-letter { font-weight:700; color:#1a73e8; min-width:18px; flex-shrink:0; } .ds-q-option-text { word-break:break-word; white-space:pre-wrap; } .ds-no-option { font-size:11px; color:#aaa; padding:4px 14px 8px; font-style:italic; } .ds-status-bar { display:flex; align-items:center; gap:8px; padding:8px 12px; font-size:12px; background:#fafafa; border-top:1px solid #eee; } .ds-status-bar.waiting { color:#999; } .ds-status-bar.loading { color:#1a73e8; background:rgba(232,240,254,.4); } .ds-status-bar.done { color:#00897b; background:rgba(246,255,248,.6); } .ds-status-bar.error { color:#d32f2f; background:rgba(255,240,240,.6); } .ds-q-answer-wrap { padding:10px 14px; background:#fff; } .ds-answer-box { background:#f6fff8; border:1px solid #b2dfdb; border-radius:7px; padding:8px 10px; margin-bottom:8px; } .ds-analysis-box { background:#f0f4ff; border:1px solid #c5cae9; border-radius:7px; padding:8px 10px; } .ds-answer-label-tag { display:inline-block; font-size:11px; font-weight:700; border-radius:4px; padding:1px 7px; margin-bottom:5px; } .ds-answer-label-tag.answer { background:#00897b; color:#fff; } .ds-answer-label-tag.analysis { background:#3949ab; color:#fff; } .ds-answer-content { color:#1a1a1a; line-height:1.7; font-size:13px; } .ds-spinner { width:14px; height:14px; border:2px solid #ccc; border-top-color:#1a73e8; border-radius:50%; animation:ds-spin .7s linear infinite; flex-shrink:0; } @keyframes ds-spin { to{transform:rotate(360deg);} } #cx-ds-panel.minimized .ds-settings, #cx-ds-panel.minimized #cx-ds-body, #cx-ds-panel.minimized #cx-ds-progress-wrap { display:none; } .ds-count-badge { background:#ff5722; color:#fff; border-radius:10px; padding:1px 7px; font-size:11px; margin-left:6px; } /* 复制成功提示 toast */ #cx-ds-toast { position:fixed; bottom:32px; left:50%; transform:translateX(-50%) translateY(20px); background:#323232; color:#fff; border-radius:8px; padding:8px 20px; font-size:13px; z-index:2147483648; opacity:0; transition:opacity .3s, transform .3s; pointer-events:none; } #cx-ds-toast.show { opacity:1; transform:translateX(-50%) translateY(0); } `); // ── 工具函数 ────────────────────────────────────── function escHtml(s) { return String(s) .replace(/&/g,'&').replace(/</g,'<') .replace(/>/g,'>').replace(/"/g,'"'); } function showToast(msg) { let t = document.getElementById('cx-ds-toast'); if (!t) { t = document.createElement('div'); t.id = 'cx-ds-toast'; document.documentElement.appendChild(t); } t.textContent = msg; t.classList.add('show'); clearTimeout(t._timer); t._timer = setTimeout(() => t.classList.remove('show'), 2000); } function cleanText(node) { if (!node) return ''; const clone = node.cloneNode(true); clone.querySelectorAll('.colorShallow, input, script, style').forEach(el => el.remove()); clone.querySelectorAll('br').forEach(el => el.replaceWith('\n')); return (clone.textContent || '').replace(/[ \t]{2,}/g,' ').replace(/\n{3,}/g,'\n\n').trim(); } const TYPE_NAME = { '0':'单选题','1':'多选题','2':'填空题','3':'判断题', '4':'简答题','5':'名词解释','6':'论述题','7':'计算题', '13':'排序题','15':'阅读理解','20':'共用选项题','999':'未知类型' }; const TYPE_MAP = new Map([ ['单选题','0'],['A1型题','0'],['A1A2型题','0'],['A2','0'],['A1','0'], ['多选题','1'],['X型题','1'], ['填空题','2'],['判断题','3'],['简答题','4'], ['名词解释','5'],['论述题','6'],['计算题','7'],['排序题','13'], ['阅读理解','15'], ['B型题','20'],['B1型题','20'],['B1','20'],['共用选项题','20'], ]); function resolveTypeCode(raw) { if (!raw) return '999'; const s = raw.replace(/[\[\]【】()（）\s]/g,'').trim(); if (TYPE_MAP.has(s)) return TYPE_MAP.get(s); for (const [k,v] of TYPE_MAP) { if (s.includes(k)||k.includes(s)) return v; } if (/^\d+$/.test(s)) return s; return '999'; } // ── 题目提取 ────────────────────────────────────── function extractQuestions(doc) { if (!doc) return []; const questions = []; let containers = [...doc.querySelectorAll('.questionLi')]; let mode = 'ks'; if (!containers.length) { containers = [...doc.querySelectorAll('.noSplitBx')]; mode = 'ks'; } if (!containers.length) { containers = [...doc.querySelectorAll('.TiMu')]; mode = 'zj'; } containers.forEach((el, idx) => { let title = '', rawType = '', options = []; try { if (mode === 'zj') { title = cleanText(el.querySelector('.fontLabel')); rawType = cleanText(el.querySelector('.newZy_TItle')); el.querySelectorAll('[class*="before-after"]').forEach(o => { const t = cleanText(o.querySelector('.fl.after') || o); if (t) options.push(t); }); } else { const shallow = el.querySelector('.colorShallow'); if (shallow) rawType = shallow.textContent.replace(/[()（）\[\]【】]/g,'').trim(); if (!rawType) rawType = el.getAttribute('typename') || ''; if (!rawType) { const ti = el.querySelector('input[id^="typeName"]'); if (ti) rawType = ti.value; } const typeValInput = el.querySelector('input[name^="type"]'); const numericType = typeValInput ? typeValInput.value : ''; const typeCode = resolveTypeCode(rawType) !== '999' ? resolveTypeCode(rawType) : (numericType || '999'); const h3 = el.querySelector('h3.mark_name') || el.querySelector('h3'); if (h3) title = cleanText(h3); title = title.replace(/^\d+\.\s*/,'').replace(/^【.*?】\s*/,'').replace(/\s*（\d+\.\d+分）$/,'').trim(); const numCode = parseInt(typeCode); if (numCode === 20) { const sharedOpts = []; el.querySelectorAll('.stem_answer .clearfix').forEach(row => { const text = row.querySelector('.p_wid805, .fr') ? (row.querySelector('.p_wid805, .fr').textContent||'').trim() : ''; if (text) sharedOpts.push(text); }); el.querySelectorAll('.B-answer-ct').forEach((sub, si) => { const subTitle = sub.querySelector('.B-tit') ? sub.querySelector('.B-tit').textContent.trim() : ('子题'+(si+1)); questions.push({ index: questions.length + 1, type: '20', typeName: '共用选项题', title: title ? (title + '\n' + subTitle) : subTitle, options: [...sharedOpts], rawType, }); }); return; } else if (numCode === 15) { el.querySelectorAll('.reading_answer').forEach((sub, si) => { const titEl = sub.querySelector('.reader_answer_tit'); const subTit = titEl ? titEl.textContent.replace(/\(.*?\)/g,'').trim() : ('子题'+(si+1)); const typeEl = sub.querySelector('.read_type'); const subType = typeEl ? typeEl.textContent.replace(/[()（）]/g,'').trim() : '简答题'; const subCode = resolveTypeCode(subType); questions.push({ index: questions.length + 1, type: subCode, typeName: TYPE_NAME[subCode] || subType, title: subTit, options: [], rawType: subType, }); }); return; } else { el.querySelectorAll('.answerBg').forEach(opt => { const ap = opt.querySelector('.answer_p'); const t = (ap ? ap.textContent : opt.textContent).trim(); if (t) options.push(t); }); } if (!title) return; questions.push({ index: questions.length + 1, type: typeCode, typeName: TYPE_NAME[typeCode] || '未知类型', title, options, rawType, }); return; } // 章节测试通用 push if (!title) return; const typeCode = resolveTypeCode(rawType); questions.push({ index: questions.length + 1, type: typeCode, typeName: TYPE_NAME[typeCode] || '未知类型', title, options, rawType, }); } catch(e) { /* 静默忽略单题错误 */ } }); questions.forEach((q,i) => q.index = i + 1); return questions; } function getAllQuestions() { let qs = extractQuestions(document); if (qs.length) return qs; for (const f of document.querySelectorAll('iframe')) { try { const fd = f.contentDocument || f.contentWindow?.document; if (!fd) continue; const r = extractQuestions(fd); if (r.length) return r; } catch(e) {} } return []; } // ── 题目卡片渲染 ─────────────────────────────────── function renderCards(panel, questions, mode) { const body = panel.querySelector('#cx-ds-body'); body.innerHTML = ''; questions.forEach(q => { const card = document.createElement('div'); card.className = 'ds-q-card'; card.id = 'ds-card-' + q.index; let optBlock = ''; if (q.options.length) { const rows = q.options.map((o,i) => ' ' + '' + String.fromCharCode(65+i) + '.' + '' + escHtml(o) + '' + ' ' ).join(''); optBlock = ' 📋 选项（共'+q.options.length+'项） '+rows+' '; } else { optBlock = ' （无选项 — ' + q.typeName + '） '; } const statusHtml = mode === 'solving' ? ' ⏳ 等待 AI 解析… ' : ' ✅ 已提取 '; const answerBlock = mode === 'solving' ? ' ' : ''; card.innerHTML = ' ' + '第 ' + q.index + ' 题' + '' + q.typeName + '' + ' ' + ' ' + ' 📌 题干 ' + ' ' + escHtml(q.title) + ' ' + ' ' + optBlock + statusHtml + answerBlock; body.appendChild(card); }); } // ── 复制题目为纯文本 ────────────────────────────── function copyQuestionsText(questions) { const lines = questions.map(q => { let s = '第' + q.index + '题【' + q.typeName + '】\n' + q.title; if (q.options.length) { s += '\n' + q.options.map((o,i) => String.fromCharCode(65+i) + '. ' + o).join('\n'); } return s; }); const text = lines.join('\n\n'); try { navigator.clipboard.writeText(text).then( () => showToast('✅ 已复制 ' + questions.length + ' 道题目到剪贴板'), () => fallbackCopy(text) ); } catch(e) { fallbackCopy(text); } } function fallbackCopy(text) { const ta = document.createElement('textarea'); ta.value = text; ta.style.cssText = 'position:fixed;opacity:0;top:0;left:0;'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); showToast('✅ 已复制题目到剪贴板'); } catch(e) { showToast('❌ 复制失败，请手动复制'); } document.body.removeChild(ta); } // ── DeepSeek API（非流式）──────────────────────── function buildPrompt(q) { let t = '题目类型：' + q.typeName + '\n题干：' + q.title; if (q.options.length) t += '\n选项：\n' + q.options.map((o,i) => ' ' + String.fromCharCode(65+i) + '. ' + o).join('\n'); return t; } function callDeepSeek(prompt, onDone, onError) { if (!CONFIG.apiKey) { onError('未配置 API Key，请在面板顶部填写'); return; } GM_xmlhttpRequest({ method: 'POST', url: 'https://api.deepseek.com/chat/completions', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + CONFIG.apiKey, 'Accept': 'application/json', }, data: JSON.stringify({ model: CONFIG.model, max_tokens: CONFIG.maxTokens, stream: false, messages: [ { role: 'system', content: CONFIG.systemPrompt }, { role: 'user', content: prompt }, ], }), timeout: 60000, onload(res) { try { if (res.status !== 200) { onError('HTTP ' + res.status + '：' + res.responseText.slice(0,200)); return; } const json = JSON.parse(res.responseText); const text = json.choices?.[0]?.message?.content || ''; if (!text) { onError('API 返回内容为空'); return; } onDone(text); } catch(e) { onError('解析响应失败：' + e.message); } }, onerror(e) { onError('网络错误：' + (e.statusText || JSON.stringify(e))); }, ontimeout() { onError('请求超时（60s），请检查网络或 API Key 余额'); }, }); } function formatBoxes(text) { if (!text) return ' （无内容） '; const am = text.match(/【答案】([\s\S]*?)(?=【解析】|$)/); const sm = text.match(/【解析】([\s\S]*?)$/); if (!am && !sm) { return ' ' + escHtml(text) + ' '; } let h = ''; if (am) h += ' 📌 答案' + ' ' + escHtml(am[1].trim()).replace(/\n/g,' ') + ' '; if (sm) h += ' 💡 解析' + ' ' + escHtml(sm[1].trim()).replace(/\n/g,' ') + ' '; return h; } // ── 解题流程 ────────────────────────────────────── function startSolving(panel, questions) { const bar = panel.querySelector('#cx-ds-progress-bar'); const count = panel.querySelector('#cx-ds-count'); count.textContent = questions.length; count.style.display = 'inline-block'; renderCards(panel, questions, 'solving'); (async () => { for (let i = 0; i < questions.length; i++) { const q = questions[i]; const status = document.getElementById('ds-status-' + q.index); const wrap = document.getElementById('ds-answer-' + q.index); if (!wrap) continue; if (status) { status.className = 'ds-status-bar loading'; status.innerHTML = ' AI 正在思考第 ' + q.index + ' 题…'; } document.getElementById('ds-card-' + q.index) ?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); wrap.innerHTML = ' ' + ' 正在请求 DeepSeek API… '; await new Promise(resolve => { callDeepSeek( buildPrompt(q), (final) => { wrap.innerHTML = ' 🤖 AI 解答 ' + formatBoxes(final); if (status) { status.className = 'ds-status-bar done'; status.textContent = '✅ 解析完成'; } bar.style.width = ((i + 1) / questions.length * 100) + '%'; resolve(); }, (err) => { wrap.innerHTML = ' ❌ ' + escHtml(err) + ' '; if (status) { status.className = 'ds-status-bar error'; status.textContent = '❌ 失败'; } bar.style.width = ((i + 1) / questions.length * 100) + '%'; resolve(); } ); }); await new Promise(r => setTimeout(r, 500)); } bar.style.width = '100%'; showToast('✅ 全部 ' + questions.length + ' 题解析完成'); })(); } // ── 面板创建 ────────────────────────────────────── function createPanel() { if (document.getElementById('cx-ds-panel')) return; const panel = document.createElement('div'); panel.id = 'cx-ds-panel'; panel.innerHTML = ' ' + '🤖解析(QingYi♥)' + '0' + '' + ' ' + ' 📄 复制题目 ' + ' 🚀 开始解析 ' + ' 🗑 清空 ' + ' — ' + ' ' + ' ' + ' DeepSeek API Key ' + ' ' + ' ' + ' ' + ' ' + ' ' + '点「📄 复制题目」可将所有题目复制到剪贴板 点「🚀 开始解析」自动提取题目并调用 AI 解析' + ' '; document.documentElement.appendChild(panel); panel.querySelector('#cx-ds-apikey').addEventListener('input', e => { CONFIG.apiKey = e.target.value.trim(); GM_setValue('ds_api_key', CONFIG.apiKey); }); let minimized = false; panel.querySelector('#cx-ds-min').addEventListener('click', () => { minimized = !minimized; panel.classList.toggle('minimized', minimized); panel.querySelector('#cx-ds-min').textContent = minimized ? '□' : '—'; }); panel.querySelector('#cx-ds-clear').addEventListener('click', () => { panel._qs = null; panel.querySelector('#cx-ds-body').innerHTML = ' 已清空 '; panel.querySelector('#cx-ds-count').style.display = 'none'; panel.querySelector('#cx-ds-progress-bar').style.width = '0%'; }); // ★ 复制题目 panel.querySelector('#cx-ds-copy').addEventListener('click', () => { const qs = panel._qs && panel._qs.length ? panel._qs : getAllQuestions(); if (!qs.length) { showToast('❌ 未检测到题目'); return; } panel._qs = qs; copyQuestionsText(qs); }); // ★ 开始解析（自动提取 + 解析） panel.querySelector('#cx-ds-start').addEventListener('click', () => { if (!CONFIG.apiKey) { alert('请先填写 DeepSeek API Key！'); return; } const qs = getAllQuestions(); if (!qs.length) { alert('未检测到题目，请确认已进入考试/作业/章节测试页面。'); return; } panel._qs = qs; startSolving(panel, qs); }); makeDraggable(panel, panel.querySelector('#cx-ds-drag')); } function makeDraggable(el, handle) { let sx, sy, il, it; handle.addEventListener('mousedown', e => { if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return; sx = e.clientX; sy = e.clientY; const r = el.getBoundingClientRect(); il = r.left; it = r.top; el.style.right = 'auto'; el.style.left = il + 'px'; el.style.top = it + 'px'; const mv = ev => { el.style.left=(il+ev.clientX-sx)+'px'; el.style.top=(it+ev.clientY-sy)+'px'; }; const up = () => { document.removeEventListener('mousemove',mv); document.removeEventListener('mouseup',up); }; document.addEventListener('mousemove', mv); document.addEventListener('mouseup', up); e.preventDefault(); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createPanel); } else { createPanel(); } })();
我做了一个药物 lead 优化 Agent：从一条 SMILES 到多轮候选筛选
最近做完了一个项目——Pharma Target Agent，一个面向药物化学早期筛选场景的 AI Agent，核心功能是给定靶点名称和一条起始 SMILES，自动生成候选分子并按多目标约束排序，支持持续迭代。这篇文章记录一下它解决了什么问题、架构怎么设计的。背景：lead optimization 为什么难用 AI 做？药物发现里有一个阶段叫 lead optimization（先导化合物优化）：你已经找到了一个对靶点有初步活性的分子，现在要在保持活性的前提下，把它的 ADME 性质、毒性、口服开发性都调到一个可接受的范围。这件事难在哪里？第一，它不是单目标优化。你不能只追 QED 高或者只追 LogP 合理，真实的药化优化需要同时盯着十几个指标，而且这些指标经常互相矛盾——把溶解性调好了，渗透性可能变差；降低毒性风险，活性可能跟着下去。第二，它是迭代的过程，不是一次性筛选。药化学家不会看一轮候选就下结论，他们会根据这一轮的结果调整方向，选一个最有希望的分子作为下一轮的起点，一轮一轮往前推。现有的 AI 工具大多是单次打分：扔进去一批分子，输出一个综合分，排个序。这对于"我想沿着某条 SAR 方向持续优化"的需求来说帮助有限。Pharma Target Agent 想解决的就是这个问题。架构设计整体技术栈：LangChain ReAct + FAISS RAG + RDKit + SwissADME + ProTox3 + Streamlit。Agent 层用 LangChain 的 ReAct 框架搭 Agent 核心。每一轮优化大致是这个流程：从 PubMed 检索靶点相关文献摘要从本地 FAISS 索引检索药化规则（Lipinski、PAINS、Brenk 等）把当前最优分子的 SMILES、文献摘要、规则约束一起喂给大模型大模型在保留核心 scaffold 的前提下生成若干候选 SMILES对候选做多参数评估按多目标约束排序，选出下一轮种子分子Agent 有两种运行模式。自动模式：系统自己选下一轮种子，满足目标或达到最大轮数后停止，适合快速跑通一个靶点。手动模式：每轮输出候选池，由用户指定下一轮种子，适合需要人工判断 SAR 方向的场景。RAG 层本地 FAISS 索引存的是药化规则库（drug_rules.txt），按块嵌入后检索。嵌入目前走的是本地 hash 模式（LOCAL_EMBEDDING_MODE=hash），不依赖外部 embedding 服务，离线可用。如果有更好的嵌入模型，换一下配置就行。评分层评分是这个项目里最花时间的部分，涉及三个数据源：SwissADME 负责 ADME 性质：GI absorption、Bioavailability score、Lipinski / Veber / Ghose / Egan / Muegge 规则检查、PAINS / Brenk alerts、合成可及性、CYP 抑制、P-gp 情况。RDKit 负责结构层面：合法性检查、理化性质补充、Bemis-Murcko scaffold 提取、指纹相似性、QED 计算和结构图渲染。ProTox3 负责毒性档案：Toxicity Class（1–6）、LD50、active toxicity model 数量、organ toxicity active 数量、toxicity target binding。这三个数据源的输出最终整合成四个内部分数：基础综合分、风险分、ProTox3 毒性分、最终综合分。多目标约束排序系统不是简单地按综合分排序，而是用五个目标参数做多目标约束：参数方向目标综合分越高越好目标风险分上限越低越好目标 ProTox3 分越高越好目标 Bioavailability越高越好目标毒性等级下限越高越好（对应 ProTox3 的 Class 1–6）排序逻辑是：先看满足了多少个目标，再看综合分，再看风险分和毒性分。一个综合分略低但更安全、更满足约束的分子，会排在前面。这比"只追高分"更接近药化真实优化逻辑。当前限制说清楚这几点比较重要：这套评分是早期药化筛选的经验型指标，不是实验结论。SwissADME 的 Bioavailability Score 不等于真实体内暴露，ProTox3 的 Toxicity Class 不等于真实毒性实验结果。高综合分不代表真实活性强，也不代表体内 PK 理想。大模型生成的候选 SMILES 在结构合法性上做了检查，但化学合理性仍需人工核查。代码和使用项目开源在 GitHub：github.com/MaLanhui/pharma_agent依赖 Python 3.10 / 3.11，候选生成默认走 DeepSeek API，.env.example 里有完整配置说明。py -3.11 -m venv .venv .venv\Scripts\activate pip install -r requirements.txt copy .env.example .env python -m pharma_agent.rag.build_index .\start_app.ps1结果展示致谢评分层依赖了两个非常好用的免费工具：SwissADME（Daina et al., Scientific Reports, 2017）— ADME 和药化规则预测ProTox 3.0（Banerjee et al., Nucleic Acids Research, 2024）— 毒性档案预测没有这两个工具，这个项目的评分层基本上要重写一半。
用 OCRmyPDF 让扫描文件符合 eCTD OCR 要求（Win11 系统）
前言在准备 eCTD 申报资料时，经常会遇到扫描版 PDF 文件，这类文件本质上只是图片，无法复制文本，也无法进行关键词搜索。但根据规范要求，PDF 内容必须具备“可复制、可搜索”的能力，因此对于扫描文件必须进行 OCR（光学字符识别）处理，否则会存在合规风险，严重时甚至可能被退回补正。在实际工具选择上，OCRmyPDF 是一个非常适合该场景的开源工具。它可以在不改变原始版式的情况下，为 PDF 自动添加文本层，并支持输出为 PDF/A 格式，这一点非常符合 eCTD 的技术要求。同时它支持批量处理、自动跳过已有文本页面、压缩优化等功能，基本可以覆盖申报资料处理中的大部分需求。在 Windows 11 上使用之前，需要先完成基础环境部署。首先需要安装 Python，然后再安装 OCRmyPDF，以及它依赖的 OCR 引擎 Tesseract OCR。这里需要特别说明的是：OCRmyPDF 本身并不负责“识别文字”，真正的识别是由 Tesseract 完成的，如果这个环境没有配置好，后续命令会直接报错。环境部署（Windows 11）安装 Python（建议 3.9+ 版本，安装时一定勾选 Add Python to PATH，否则后面命令会报错找不到 python；如果你无法访问Python官网可以使用华为镜像）：python --version如果能正常输出版本号，说明安装成功。安装 OCRmyPDF：pip install ocrmypdf如果出现权限问题，可以使用：pip install --user ocrmypdf安装 Tesseract OCR（建议使用 UB Mannheim 提供的 Windows 版本，兼容性更好;下载链接，详细安装步骤可看这个教程）。安装完成后，需要把路径加入环境变量，例如：C:\Program Files\Tesseract-OCR这一步可以在“系统环境变量 → Path”中手动添加。验证是否安装成功：tesseract --version如果能输出版本信息，说明 OCR 引擎已经可以正常调用。另外，如果你需要识别中文，还需要确认语言包是否存在：tesseract --list-langs如果没有 chi_sim，需要额外下载中文语言包（否则中文会识别失败或乱码）。基本使用方法ocrmypdf input.pdf output.pdf执行后会发生三件事：程序会逐页分析 PDF、调用 Tesseract 识别文字、然后把识别结果嵌入到原 PDF 中，生成一个新的 output.pdf 文件。原始版式不会改变，但已经可以复制和搜索。推荐（eCTD 用）eCTD 申报建议使用更规范的参数组合，以确保文件格式、识别效果以及体积都符合要求。ocrmypdf -l chi_sim --optimize 3 --output-type pdfa input.pdf output.pdf这条命令里几个关键参数需要理解：-l chi_sim：指定中文简体识别（如果是英文可以用 eng，多语言可以写 chi_sim+eng）--optimize 3：最高级别压缩优化，适合申报资料控制体积--output-type pdfa：输出为 PDF/A 格式，符合长期归档要求（eCTD 推荐）如果你的文件页数很多，可以增加多线程加速：ocrmypdf --jobs 4 input.pdf output.pdf（数字根据 CPU 核心数调整）在实际工作中，经常会遇到一些特殊情况，例如 PDF 已经包含部分文本层。OCRmyPDF 默认会自动检测，如果页面已经有文本，它不会重复识别，这可以避免文字重叠问题。如果你希望强制重新识别，可以加参数：ocrmypdf --force-ocr input.pdf output.pdf结尾处理完成后，一定要对文件进行验证，这是 eCTD 要求中非常关键的一步。可以直接在 PDF 中拖动鼠标尝试选中文字，如果可以高亮说明 OCR 成功；同时使用 Ctrl+F 搜索任意关键词，如果能够命中结果，则说明文件已经具备可搜索能力。如果这两项都无法实现，就说明 OCR 没有正确执行，需要重新处理。需要特别注意的是，如果原始文件是由 Word 等源文件直接导出的 PDF，一般已经具备文本层，不需要重复 OCR，否则反而可能引入错误。而对于带有手写签名、盖章或历史扫描档案的文件，则必须通过 OCR 补充文本层，这也是 eCTD 明确允许的例外场景。此外，扫描质量会直接影响识别效果。如果扫描分辨率过低（例如 150 DPI 以下），或者存在倾斜、模糊、阴影等问题，OCR 准确率会明显下降。建议使用不低于 300 DPI 的扫描文件，并尽量保证页面干净、对比度清晰。如果原始文件质量较差，可以考虑先进行图像预处理（如去噪、二值化）再进行 OCR。总体来说，OCRmyPDF 可以非常高效地帮助你把扫描 PDF 转换为符合 eCTD 要求的标准文件。对于从事药品注册或申报工作的人来说，这不仅是一个工具问题，更是一个合规问题。掌握这一流程，不仅可以提升效率，还能避免因为格式问题导致的反复修改甚至退回风险。如果进一步结合批量脚本或自动化流程，还可以大幅提升整套申报资料的处理效率。
河北省公务员招录趋势分析：扩招结束了吗？结构正在发生什么变化？
近四年，河北省公务员招录规模经历了明显起伏。从 2023 年开始连续扩招，到 2026 年出现回落。如果只看“人数增减”，容易得出“开始缩编”的结论，但把数据拆开看，会发现变化更多来自财政环境、治理结构和用人逻辑的调整，而不是简单的收紧。本文基于 2023—2026 年河北省公务员招录数据，从规模、区域、学历、专业与条件设置几个维度，尝试梳理真实的趋势。一、整体招录规模：先扩后收，但并未逆转长期稳定态势招录规模年度对比年份职位数招录人数平均每职位招录同比增长率20235,6717,9221.40—20246,3729,0071.41+13.70%20257,14310,1041.41+12.18%20266,4878,6501.33-14.39%几个关键观察：2023—2025 连续两年扩招，属于明显的上行阶段；2026 年招录人数回落，但职位数下降幅度相对有限；“平均每职位招录人数”下降，说明更多岗位是单人补缺，而非一个岗位招多人。这更像是从“稳就业导向下的扩招”回到“财政约束下的精准补充”，而不是长期趋势反转。二、区域分布：省会与战略新区更稳定，传统地市波动明显各地区招录人数对比地区2023202420252026石家庄6937381,1271,156保定1,2791,1361,2891,075唐山9031,009982865邯郸5451,1891,353994邢台725943947812承德484674741547沧州744637773696雄安新区00179201趋势解读：石家庄招录规模持续上行，是少数在 2026 年仍保持增长的地区；多数传统地级市在 2026 年出现不同程度回落；雄安新区从 2025 年开始集中释放岗位，且仍在增长。这说明招录资源正在向省会城市与战略功能区集中，而一般地市更容易受到财政和编制约束影响。三、学历与学位要求：本科仍是主力，门槛并未显著抬高学历低限分布（职位数）学历2023202420252026本科4,6035,5615,9505,435专科748545913792研究生320266280260学历要求占比（%）学历2023202420252026本科81.1787.2783.3083.78专科13.198.5512.7812.21研究生5.644.173.924.01可以得出的结论很清晰：本科长期占据 80% 以上；研究生岗位占比没有上升，甚至略有下降；2026 年出现博士低限岗位，但数量极少，不具代表性。整体来看，河北省考仍是以本科为主体的广覆盖招录体系，并未走向学历内卷化。四、专业结构：三条主线越来越清晰专业大类需求趋势专业大类2023202420252026法学类1,6102,0242,1262,209经济类1,2991,7331,9571,906管理类1,1911,5171,6861,738计算机类7929871,1771,271工程类8051,0701,3431,482中文类9641,2101,2281,0811. 法学长期第一：治理方式法治化法学类连续四年居首，且稳步上行。对应的是行政执法规范化、行政复议、司法协同、营商环境建设等现实需求。2. 财会审计持续走强：财政压力下的必然选择会计、财务管理、审计等专业在 Top 榜单中整体上行，反映出地方治理正在向精细化、合规化、可审计方向转变。3. 计算机与工程类整体增长：数字政府的长期需求虽然单一“计算机类”词条在 2026 年略有回落，但数据科学、信息安全、网络工程等细分专业全面铺开，说明需求并未减少，而是被拆分得更细。五、条件设置变化：入口更开放，匹配更精准各类条件占比变化条件2023202420252026要求党员13.51%11.88%8.82%8.80%要求基层经历1.06%0.99%0.50%0.54%应届生可报51.01%47.96%45.93%45.07%限定性别66.78%65.98%66.05%69.28%核心变化：党员要求明显下降，拓宽选人来源；基层经历要求始终很低，河北并未大规模推行“先基层再上岸”；应届生占比缓慢下降，对社会考生相对友好；性别限制依然偏高，主要由执法与一线岗位属性决定。六、放到当下大环境中怎么看？综合四年数据，可以看到几个明确方向：招录规模进入“稳态波动期”不再连续扩张，但也未进入系统性收缩。治理需求在变化，而不是简单“要人更多或更少”法治、财政、数据、工程类岗位持续增长，体现的是治理能力升级。选人逻辑从“身份筛选”转向“岗位适配”党员、基层经历不再是主要门槛，但专业、资格、岗位属性更重要。结语河北省公务员招录并没有走向“全面收紧”，而是在财政约束和治理升级的背景下，逐步转向结构更清晰、专业更明确、补人更精准的阶段。对考生而言，与其纠结“明年会不会再缩招”，不如更早看清哪些专业在长期增长、哪些岗位在结构性集中。这，才是真正决定上岸概率的变量。
旧花落尽处，新花正当时。一枯一荣，是为新年。
舞台从未如此拥挤——人人争演主角，却拿着三千的片酬，在像素搭建的剧场里，拙劣模仿自己从未真正拥有的生活。
山东继续教育学习辅助脚本：技术实现与功能解析
山东继续教育学习辅助脚本：技术实现与功能解析前言在线学习平台已经成为继续教育的重要方式，但重复的操作流程往往会影响学习效率。本文将介绍一个基于 Tampermonkey 的浏览器脚本，它能够自动处理学习过程中的重复操作，实现视频连续播放和学习节点自动切换。脚本概述这是一个针对山东继续教育平台（yxlearning.com）开发的用户脚本，主要功能包括：自动播放视频内容智能跳过提示和弹窗自动切换未完成章节可视化控制面板快捷键操作支持技术架构1. 样式注入脚本使用 GM_addStyle 注入自定义样式，创建了一个简洁的控制按钮：.shi_study_btn { position: fixed; top: 0; left: 0; z-index: 999999; /* 其他样式... */ }控制按钮采用固定定位，确保在页面滚动时始终可见。通过不同的类名（running/paused）实现运行状态的视觉反馈。2. 核心学习逻辑learn() 函数是整个脚本的核心，采用了多层兜底策略：第一层：处理平台特定元素自动点击"跳过"按钮移除答题容器和弹窗触发播放器的播放按钮第二层：通用视频处理遍历页面中的所有 标签静音并自动播放处于暂停状态的视频支持多种播放器实现方式第三层：学习进度管理智能识别学习进度（通过 .badge 元素）自动跳转到未完成章节完成后自动进入下一节点3. 状态管理脚本实现了简单而有效的状态管理机制：let isRunning = true; let timer = null;isRunning：控制脚本运行状态timer：定时器引用，用于周期性执行学习逻辑每 3 秒执行一次检查，确保及时响应页面变化。4. 用户交互可视化控制面板创建了一个可点击的控制按钮，实时显示运行状态：运行中：绿色文字已暂停：红色文字快捷键支持实现了 Ctrl + Shift + S 快捷键，方便快速切换运行状态。5. SPA 路由监听针对单页应用（SPA）的特点，使用 MutationObserver 监听 DOM 变化：new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(learn, 500); } }).observe(document, { childList: true, subtree: true });当检测到 URL 变化时，自动执行学习逻辑，确保页面跳转后脚本继续工作。功能特点1. 智能跳过机制脚本能够识别并处理多种类型的干扰元素：课程中的"跳过"按钮过程性答题窗口各种弹窗提示2. 多重播放保障采用了多种方式确保视频正常播放：平台特定的播放按钮标准 HTML5 video 元素通用播放按钮选择器视频重播处理3. 进度自动推进通过分析页面中的进度标识（百分比），自动定位并切换到未完成的学习内容。4. 容错处理在视频播放时使用了 .catch() 捕获异常，避免因个别视频加载失败导致整个脚本停止运行。使用说明安装准备安装 Tampermonkey 浏览器扩展访问山东继续教育平台安装脚本创建新脚本复制粘贴下面代码// ==UserScript== // @name 山东继续教育学习辅助脚本 // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description 自动处理学习过程中的重复操作，支持视频连续播放与学习节点自动切换 // @author Modified // @match *://*.yxlearning.com/* // @icon [binary] @grant GM_addStyle // @license MIT // ==/UserScript== (function () { 'use strict'; /* ========================= * 样式注入：控制面板按钮 * ========================= */ GM_addStyle(` .shi_study_btn { outline: 0; border: 0; position: fixed; top: 0; left: 0; border-radius: 5px; cursor: pointer; color: #d90609; font-weight: bold; text-align: center; box-shadow: 0 0 5px #666777; z-index: 999999; background: #fff; padding: 5px 10px; font-size: 12px; } .shi_study_btn.running { color: #4CAF50; } .shi_study_btn.paused { color: #d90609; } `); /* ========================= * 核心逻辑：学习流程自动处理 * ========================= */ function learn() { console.log('[StudyHelper] 执行学习检查'); // 1. 处理播放器内的“跳过”操作 const skipBtn = document.querySelector('.skip.bplayer-btn'); if (skipBtn) { skipBtn.click(); console.log('[StudyHelper] 已触发跳过'); } // 2. 移除过程性答题容器 const questionWrap = document.querySelector('.bplayer-question-wrap'); if (questionWrap) { questionWrap.remove(); console.log('[StudyHelper] 已移除答题区域'); } // 3. 移除弹出式提问窗口 const askModal = document.querySelector('.pv-ask-modal'); if (askModal) { askModal.remove(); console.log('[StudyHelper] 已关闭弹窗'); } // 4. 确保播放器处于播放状态 const playSvg = document.querySelector('.icon.bplayer-play-btn svg#play'); if (playSvg) { playSvg.parentElement.click(); console.log('[StudyHelper] 播放器已启动'); } // 5. 备用跳过入口 const ccJumpOver = document.querySelector('#ccJumpOver'); if (ccJumpOver) { ccJumpOver.click(); } // 6. 处理常见 video 播放节点 const pvVideo = document.querySelector('.pv-video'); if (pvVideo && pvVideo.paused) { pvVideo.volume = 0; pvVideo.play(); } // 7. 处理内联播放视频 const inlineVideo = document.querySelector('video[webkit-playsinline]'); if (inlineVideo && inlineVideo.paused) { inlineVideo.volume = 0; inlineVideo.play(); } // 8. 自动定位未完成的学习章节 const badges = document.querySelectorAll('.badge'); for (const badge of badges) { if (badge.innerHTML !== '100%') { badge.click(); console.log('[StudyHelper] 已切换至未完成章节'); break; } } // 9. 检查是否需要重新播放当前视频 const replayBtn = document.querySelector('#replaybtn'); if (replayBtn && replayBtn.style.display === 'block') { replayBtn.click(); } // 10. 通用播放按钮兜底处理 const anyPlayBtn = document.querySelector( "button[class*='play'], .play-btn, [onclick*='play']" ); if (anyPlayBtn) { anyPlayBtn.click(); } // 11. 最终兜底：遍历页面中所有 video document.querySelectorAll('video').forEach(video => { if (video.paused) { video.volume = 0; video.play().catch(() => {}); } }); // 12. 尝试进入下一个学习节点 const nextBtn = document.querySelector('.next, .next-btn, [class*="next"]'); if (nextBtn) { nextBtn.click(); } } /* ========================= * 运行状态管理 * ========================= */ let isRunning = true; let timer = null; function createControlButton() { const btn = document.createElement('div'); btn.className = 'shi_study_btn running'; btn.innerText = '学习辅助：运行中'; btn.onclick = () => { isRunning = !isRunning; btn.className = `shi_study_btn ${isRunning ? 'running' : 'paused'}`; btn.innerText = `学习辅助：${isRunning ? '运行中' : '已暂停'}`; if (isRunning) { learn(); timer = setInterval(learn, 3000); } else { clearInterval(timer); timer = null; } }; document.body.appendChild(btn); return btn; } /* ========================= * 初始化入口 * ========================= */ function init() { console.log('[StudyHelper] 脚本初始化完成'); const controlBtn = createControlButton(); timer = setInterval(learn, 3000); setTimeout(learn, 1000); // 监听页面 URL 变化（SPA 场景） let lastUrl = location.href; new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(learn, 500); } }).observe(document, { childList: true, subtree: true }); // 快捷键支持 document.addEventListener('keydown', e => { if (e.ctrlKey && e.shiftKey && e.key === 'S') { e.preventDefault(); controlBtn.click(); } }); } document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init(); })();保存并启用操作方式脚本会自动运行点击左上角控制按钮可暂停/恢复使用 Ctrl + Shift + S 快捷键切换状态技术亮点优雅的兜底策略：通过多层选择器和逻辑判断，确保在不同页面状态下都能正常工作低侵入性：仅在必要时操作 DOM，不影响页面正常功能良好的用户体验：提供可视化反馈和便捷的控制方式高可维护性：代码结构清晰，注释详细，易于理解和修改注意事项本脚本仅用于辅助学习，提高操作效率建议在使用时关注学习内容，确保学习质量不同平台版本可能需要适当调整选择器总结这个学习辅助脚本展示了如何使用浏览器用户脚本技术提升在线学习体验。通过自动化处理重复操作，让学习者可以更专注于内容本身。代码采用了多重保障机制，确保在各种情况下都能稳定运行。对于开发者来说，这个项目也提供了一个很好的示例，展示了如何构建一个实用的浏览器脚本，包括 DOM 操作、事件监听、状态管理和用户交互等多个方面的技术实践。
基本概念锂-卤交换反应受动力学控制。平衡位置取决于所涉及的碳负离子中间体的稳定性（sp杂化 >> sp2杂化 >> sp3杂化）。在上述示例中，新生成的烷基锂试剂通过烷基化实现分子内捕获，从而驱动了原本不利的交换反应。碘代烷的反应活性高于相应的溴代烷，而氯代烷基本呈惰性。使用叔丁基锂进行锂-卤素交换反应时，通常需要加入两当量或更多的叔丁基锂。第一当量用于交换反应，第二当量则会与反应生成的叔丁基碘进一步反应，最终生成异丁烯、异丁烷和碘化锂。烯基卤化物的锂-卤素交换反应具有立体专一性，该反应以构型保持的方式进行。脂肪族烷基锂试剂通常由相应的伯碘代物在低温下的戊烷-醚混合溶剂体系中制备锂-卤交换机理添加苯基碘会减缓碘丁烷与苯基锂的反应，这一现象为反应过程中存在低反应活性“ate-复合物”中间体提供了证据。已获得与TMEDA配位的碘代双(五氟苯基)锂酸盐的X射线晶体结构，为锂-卤素交换过程中存在"ate-复合物"中间体提供了直接证据。反应速率特征锂-卤素交换反应速率极快。在某些情况下，锂-卤素交换的速率甚至可能超过质子转移的速率。锂-卤素交换反应通常远快于可能与之竞争的加成反应。9-苯基芴基保护基在最大程度减缓相邻易外消旋中心（如上文所述的α-氨基酮）的差向异构化速率方面具有特殊优势。合成中的锂-卤素交换反应实例与普通脂肪族溴化物不同，环丙基溴化物能够稳定转化为相应的有机锂试剂。在上例中，通过用溴化镁乙醚复合物对环丙基阴离子进行预处理，有效阻止了环丙基锂试剂与芳基碘之间发生第二次不必要的锂-卤素交换反应。考虑上述转化过程中必须发生的各个过程的相对速率。
1. 创建归档页面模板首先，在你的 Typecho 主题目录下创建一个名为 links.php 的文件。sudo nano links.php打开文件后，将以下代码复制粘贴进去（在终端中使用 Ctrl+Shift+V 粘贴即可）：<?php /** * 友情链接页面模板 * * @package custom */ if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?> <?php $this->need('header.php'); ?> <a itemtype="url" href="<?php $this->permalink() ?>"><?php $this->title() ?> <time datetime="<?php $this->date('c'); ?>" itemprop="datePublished"><?php $this->date(); ?> <?php $content = $this->content; // 提取友链列表 if (preg_match('/ (.*?)<\/ul>/is', $content, $matches)) { $linksHtml = $matches[1]; preg_match_all('/ (.*?)<\/li>/is', $linksHtml, $items); if (!empty($items[1])) { $links = $items[1]; $linkCount = count($links); echo ' '; for ($i = 0; $i <= $linkCount - 4; $i += 4) { $name = trim(strip_tags($links[$i])); $url = trim(strip_tags($links[$i + 1])); $avatar = trim(strip_tags($links[$i + 2])); $desc = trim(strip_tags($links[$i + 3])); $colors = ['#42b983', '#4299e1', '#9f7aea', '#ed8936', '#f56565', '#38b2ac']; $color = $colors[$i / 4 % count($colors)]; echo ' '; echo ' <img src="' . $avatar . '" alt="' . $name . '" onerror="this.src=\'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22%3E%3Crect fill=%22%23ddd%22 width=%22100%22 height=%22100%22/%3E%3Ctext x=%2250%25%22 y=%2250%25%22 dominant-baseline=%22middle%22 text-anchor=%22middle%22 font-size=%2240%22 fill=%22%23999%22%3E' . mb_substr($name, 0, 1) . '%3C/text%3E%3C/svg%3E\'"> '; echo ' '; echo '' . $name . ''; echo ' ' . $desc . ' '; echo ' '; echo ' '; } echo ' '; // 页面样式 echo ' '; } // 删除原有 ul 标签 $content = preg_replace('/ .*?<\/ul>/is', '', $content); } echo $content; ?> <?php $this->need('comments.php'); ?> <?php $this->need('sidebar.php'); ?> <?php $this->need('footer.php'); ?>保存文件：按 Ctrl+X，然后输入 y 并回车即可。2. 在 Typecho 后台启用模板登录 Typecho 后台，进入 管理 → 页面 → 新建页面。设置页面标题，例如“友链”，并在 自定义模板 中选择 友情链接，并按照下面示例填写友链信息。 YXVM https://yxvm.com/ https://mlho.net/usr/uploads/2025/11/749855776.png 本博客服务器提供商 NodeSeek https://www.nodeseek.com/ https://www.nodeseek.com/static/image/favicon/android-chrome-512x512.png Nodeseek is a place for people who love web development, hosting, vps / server and other geek things. LinuxDo https://linux.do/ https://mlho.net/usr/uploads/2025/11/3061448033.png 中国AI需要一个良好的用户生态供其发展，Linux.do正在扛起大旗。 ## 友链申请 如需添加友链，请在评论区留言： - 网站名称 - 网站地址 - 网站图标 - 网站简介点击保存并发布。访问该页面，即可看到美观的友链列表效果。效果演示
邻位锂化反应芳香化合物的邻位锂化反应，指有机锂试剂拔除芳环上邻位定位基团的α位质子，形成一个新的有机锂试剂，再与各种亲电试剂反应。邻位定位基团通常包含一个路易斯碱杂原子，能够增强芳环α位质子的酸性；定位效果最好的基团是酰胺，而氨基甲酸酯、砜、磺酰胺、卤素属于较弱的邻位定位基。直接金属化基团（The direct metalation group，DMG）通常是一个路易斯碱性基团，它能够与路易斯酸性的锂阳离子发生相互作用，从而使烷基锂试剂能够从芳环上最近的邻位进行去质子化。只要去质子化过程不是由强碱性的芳基锂自身引发，生成的芳基锂中间体就可在锂化位点与各类亲电试剂发生原位取代反应。相较于传统的亲电取代反应（通常会生成邻位和对位取代的混合物），邻位金属化反应随后淬灭亲电试剂的路径因其区域选择性优势而更受青睐——该反应专一性地生成邻位取代产物。DMG基团的性质DMG基团应能够有效配位烷基锂化合物（这意味着结构中必须包含杂原子），以形成由配位复合物诱导近邻效应（Complex-induced proximity effect，CIPE）。同时，这些基团又需具备较弱的电亲性，以避免被烷基锂通过亲核进攻而发生反应。在某些情形下，诱导效应也可能发挥作用，例如增强芳环α位质子的酸性。值得注意的是，定向金属化基团（DMGs）并非单独决定金属化位置；芳环上的空间位阻以及其他官能团同样具有显著影响。强DMG基团： 中DMG基团： 弱DMG基团： 影响锂取代位置的因素当两个DMG基团在芳环上呈1,3-取代模式时，它们会通过协同配位作用引导烷基锂试剂对二者之间的2-位进行锂化反应：当两个DMG基团在芳环上呈1,4-取代模式时，金属化反应将优先发生在较强DMG的邻位；若二者强度相近，则可能生成混合产物：即使在DMG基团存在的情况下，苄位仍可能更易发生金属化反应：碱与溶剂的性质通常这类金属化反应需要使用强烷基锂试剂，其中甲基锂、正丁基锂、仲丁基锂和叔丁基锂最为常见。这些烷基锂在溶液中会以不同聚集态存在，而胺类添加剂的加入能够解离这些聚集体，由于碱性增强从而加速反应进程。四甲基乙二胺（TMEDA）作为一种双齿胺配体效果显著，因此应用更为广泛。金属化芳烃的反应性金属化芳烃可与多种亲电试剂发生反应，常见示例如下：ArCHO、RCHO、D₂O、RX、CO₂、(RS)₂、TMSCl、B(OR)₃、DMF、Bu₃SnCl、TsN₃、BnX、HCO₂Et、RCOCl、Ac₂O、RNCO、RCN、X₂、末端环氧化物、S₈、Se、ZnCl₂通过金属化芳烃的转金属化反应，可实现过渡金属催化的转化过程。若亲电试剂本身含有酸性质子，其亲核进攻速率应快于去质子化反应。实例:杂环化合物的金属化理化位置这些杂环许多可以二次理化吡啶的直接锂化方法非常实用，因为烷基锂可对吡啶进行加成反应；另外，通过锂-卤交换反应能够专一性地获得2-锂代吡啶。注意事项除了杂原子之外，DMG基团也会影响金属化反应位点。对于杂环化合物的金属化反应，通常使用烷基锂以及锂酰胺类试剂。这类锂化反应一般选择醚类或四氢呋喃作为溶剂，并常添加TMEDA或HMPA作为配体来络合金属阳离子。另外，需注意杂环化合物有时可能发生分解：侧链金属化实例:非芳香化合物金属化一些常见的例子：这些金属化反应之所以能够实现，得益于底物中存在的定向基团，并且其反应进程受到多种因素的综合调控，例如：共振效应、立体电子效应、诱导效应、位阻效应以及复合诱导邻近效应（CIPE）。CIPE是一种用于描述定向基团上路易斯碱性杂原子与烷基锂之间形成的预锂化复合物的非经典理论模型。通过该复合物的形成，锂化试剂被引导至底物中酸性相对较高的质子附近，从而解释了所观察到的区域选择性。实例