feat: 添加 LLM Content Extractor 浏览器扩展
- 支持框选区域提取网页内容 - 支持整页内容提取 - 输出格式:Markdown/JSON/XML - 自动复制到剪贴板
This commit is contained in:
94
browser-extension/README.md
Normal file
94
browser-extension/README.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# LLM Content Extractor
|
||||||
|
|
||||||
|
一个 Chrome 浏览器扩展,用于截取网页内容并转换为大模型友好的格式。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 🎯 **区域框选提取** - 拖拽鼠标框选想要提取的区域
|
||||||
|
- 📄 **整页提取** - 一键提取整个页面内容
|
||||||
|
- 📝 **多种输出格式** - 支持 Markdown、JSON、XML
|
||||||
|
- 📋 **自动复制** - 提取后自动复制到剪贴板
|
||||||
|
- 💾 **历史记录** - 可随时复制上次提取的内容
|
||||||
|
|
||||||
|
## 支持提取的内容类型
|
||||||
|
|
||||||
|
- 标题 (h1-h6)
|
||||||
|
- 段落
|
||||||
|
- 代码块(保留语言标识)
|
||||||
|
- 有序/无序列表
|
||||||
|
- 表格
|
||||||
|
- 图片(保留 src 和 alt)
|
||||||
|
- 链接(保留文本和 href)
|
||||||
|
|
||||||
|
## 安装方法
|
||||||
|
|
||||||
|
1. 打开 Chrome 浏览器,访问 `chrome://extensions/`
|
||||||
|
2. 开启右上角的 **开发者模式**
|
||||||
|
3. 点击 **加载已解压的扩展程序**
|
||||||
|
4. 选择 `browser-extension` 文件夹
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
1. 点击浏览器工具栏中的扩展图标
|
||||||
|
2. 选择输出格式(Markdown/JSON/XML)
|
||||||
|
3. 点击 **框选区域提取** 或 **提取整页内容**
|
||||||
|
4. 如果是框选模式,拖拽鼠标选择区域
|
||||||
|
5. 提取完成后内容自动复制到剪贴板
|
||||||
|
|
||||||
|
## 快捷操作
|
||||||
|
|
||||||
|
- **ESC** - 取消框选模式
|
||||||
|
|
||||||
|
## 输出示例
|
||||||
|
|
||||||
|
### Markdown 格式
|
||||||
|
```markdown
|
||||||
|
# 标题
|
||||||
|
|
||||||
|
这是一段文字内容。
|
||||||
|
|
||||||
|
- 列表项 1
|
||||||
|
- 列表项 2
|
||||||
|
|
||||||
|
| 表头1 | 表头2 |
|
||||||
|
| --- | --- |
|
||||||
|
| 数据1 | 数据2 |
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON 格式
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "heading",
|
||||||
|
"level": 1,
|
||||||
|
"content": "标题"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "这是一段文字内容。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 首次使用需要刷新页面才能生效
|
||||||
|
- 某些页面可能因安全策略限制而无法使用
|
||||||
|
- 图标文件需要自行添加(16x16, 48x48, 128x128 PNG)
|
||||||
|
|
||||||
|
## 开发
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 项目结构
|
||||||
|
browser-extension/
|
||||||
|
├── manifest.json # 扩展配置
|
||||||
|
├── popup.html # 弹出窗口
|
||||||
|
├── popup.js # 弹出窗口逻辑
|
||||||
|
├── content.js # 内容脚本
|
||||||
|
├── content.css # 内容脚本样式
|
||||||
|
└── icons/ # 图标文件夹
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
76
browser-extension/content.css
Normal file
76
browser-extension/content.css
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
.llm-extractor-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
cursor: crosshair;
|
||||||
|
z-index: 999998;
|
||||||
|
}
|
||||||
|
|
||||||
|
.llm-extractor-selection {
|
||||||
|
position: fixed;
|
||||||
|
border: 2px dashed #667eea;
|
||||||
|
background: rgba(102, 126, 234, 0.1);
|
||||||
|
z-index: 999999;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.llm-extractor-hint {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1000000;
|
||||||
|
animation: slideDown 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.llm-extractor-notification {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1000000;
|
||||||
|
animation: slideUp 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.llm-extractor-notification.fade-out {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
transform: translateX(-50%) translateY(-20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
478
browser-extension/content.js
Normal file
478
browser-extension/content.js
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
// 全局变量
|
||||||
|
let isSelecting = false;
|
||||||
|
let selectionBox = null;
|
||||||
|
let startX, startY;
|
||||||
|
let overlay = null;
|
||||||
|
let currentFormat = 'markdown';
|
||||||
|
|
||||||
|
// 监听来自 popup 的消息
|
||||||
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||||
|
if (request.action === 'startSelection') {
|
||||||
|
currentFormat = request.format || 'markdown';
|
||||||
|
startSelectionMode();
|
||||||
|
sendResponse({ success: true });
|
||||||
|
} else if (request.action === 'extractFullPage') {
|
||||||
|
currentFormat = request.format || 'markdown';
|
||||||
|
const content = extractContent(document.body);
|
||||||
|
const formatted = formatContent(content, currentFormat);
|
||||||
|
copyToClipboard(formatted);
|
||||||
|
saveToStorage(formatted);
|
||||||
|
sendResponse({ success: true });
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 开始框选模式
|
||||||
|
function startSelectionMode() {
|
||||||
|
isSelecting = true;
|
||||||
|
|
||||||
|
// 创建遮罩层
|
||||||
|
overlay = document.createElement('div');
|
||||||
|
overlay.className = 'llm-extractor-overlay';
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
// 创建提示
|
||||||
|
const hint = document.createElement('div');
|
||||||
|
hint.className = 'llm-extractor-hint';
|
||||||
|
hint.textContent = '拖拽鼠标框选要提取的区域,ESC 取消';
|
||||||
|
document.body.appendChild(hint);
|
||||||
|
|
||||||
|
// 绑定事件
|
||||||
|
document.addEventListener('mousedown', onMouseDown);
|
||||||
|
document.addEventListener('keydown', onKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseDown(e) {
|
||||||
|
if (!isSelecting) return;
|
||||||
|
|
||||||
|
startX = e.clientX;
|
||||||
|
startY = e.clientY;
|
||||||
|
|
||||||
|
// 创建选择框
|
||||||
|
selectionBox = document.createElement('div');
|
||||||
|
selectionBox.className = 'llm-extractor-selection';
|
||||||
|
selectionBox.style.left = startX + 'px';
|
||||||
|
selectionBox.style.top = startY + 'px';
|
||||||
|
document.body.appendChild(selectionBox);
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', onMouseMove);
|
||||||
|
document.addEventListener('mouseup', onMouseUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseMove(e) {
|
||||||
|
if (!selectionBox) return;
|
||||||
|
|
||||||
|
const currentX = e.clientX;
|
||||||
|
const currentY = e.clientY;
|
||||||
|
|
||||||
|
const left = Math.min(startX, currentX);
|
||||||
|
const top = Math.min(startY, currentY);
|
||||||
|
const width = Math.abs(currentX - startX);
|
||||||
|
const height = Math.abs(currentY - startY);
|
||||||
|
|
||||||
|
selectionBox.style.left = left + 'px';
|
||||||
|
selectionBox.style.top = top + 'px';
|
||||||
|
selectionBox.style.width = width + 'px';
|
||||||
|
selectionBox.style.height = height + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseUp(e) {
|
||||||
|
if (!selectionBox) return;
|
||||||
|
|
||||||
|
const rect = selectionBox.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 清理选择框
|
||||||
|
document.removeEventListener('mousemove', onMouseMove);
|
||||||
|
document.removeEventListener('mouseup', onMouseUp);
|
||||||
|
|
||||||
|
// 查找选区内的元素
|
||||||
|
const elements = getElementsInRect(rect);
|
||||||
|
|
||||||
|
if (elements.length > 0) {
|
||||||
|
// 提取内容
|
||||||
|
const content = extractFromElements(elements);
|
||||||
|
const formatted = formatContent(content, currentFormat);
|
||||||
|
|
||||||
|
copyToClipboard(formatted);
|
||||||
|
saveToStorage(formatted);
|
||||||
|
showNotification('✅ 内容已提取并复制到剪贴板');
|
||||||
|
} else {
|
||||||
|
showNotification('❌ 未选中任何内容');
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(e) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
isSelecting = false;
|
||||||
|
|
||||||
|
if (selectionBox) {
|
||||||
|
selectionBox.remove();
|
||||||
|
selectionBox = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlay) {
|
||||||
|
overlay.remove();
|
||||||
|
overlay = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hint = document.querySelector('.llm-extractor-hint');
|
||||||
|
if (hint) hint.remove();
|
||||||
|
|
||||||
|
document.removeEventListener('mousedown', onMouseDown);
|
||||||
|
document.removeEventListener('mousemove', onMouseMove);
|
||||||
|
document.removeEventListener('mouseup', onMouseUp);
|
||||||
|
document.removeEventListener('keydown', onKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取选区内的元素
|
||||||
|
function getElementsInRect(rect) {
|
||||||
|
const elements = [];
|
||||||
|
const allElements = document.body.querySelectorAll('*');
|
||||||
|
|
||||||
|
allElements.forEach(el => {
|
||||||
|
const elRect = el.getBoundingClientRect();
|
||||||
|
if (isRectOverlap(rect, elRect) && isVisibleElement(el)) {
|
||||||
|
elements.push(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 找到最小公共祖先
|
||||||
|
if (elements.length > 0) {
|
||||||
|
return [findCommonAncestor(elements)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRectOverlap(rect1, rect2) {
|
||||||
|
return !(rect1.right < rect2.left ||
|
||||||
|
rect1.left > rect2.right ||
|
||||||
|
rect1.bottom < rect2.top ||
|
||||||
|
rect1.top > rect2.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVisibleElement(el) {
|
||||||
|
const style = window.getComputedStyle(el);
|
||||||
|
return style.display !== 'none' &&
|
||||||
|
style.visibility !== 'hidden' &&
|
||||||
|
style.opacity !== '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
function findCommonAncestor(elements) {
|
||||||
|
if (elements.length === 1) return elements[0];
|
||||||
|
|
||||||
|
let ancestor = elements[0];
|
||||||
|
for (let i = 1; i < elements.length; i++) {
|
||||||
|
ancestor = findAncestor(ancestor, elements[i]);
|
||||||
|
}
|
||||||
|
return ancestor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAncestor(el1, el2) {
|
||||||
|
const ancestors = [];
|
||||||
|
let node = el1;
|
||||||
|
while (node) {
|
||||||
|
ancestors.push(node);
|
||||||
|
node = node.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = el2;
|
||||||
|
while (node) {
|
||||||
|
if (ancestors.includes(node)) return node;
|
||||||
|
node = node.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从元素中提取内容
|
||||||
|
function extractFromElements(elements) {
|
||||||
|
const content = [];
|
||||||
|
elements.forEach(el => {
|
||||||
|
content.push(...extractContent(el));
|
||||||
|
});
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取内容
|
||||||
|
function extractContent(root) {
|
||||||
|
const content = [];
|
||||||
|
const processed = new Set();
|
||||||
|
|
||||||
|
function processElement(el) {
|
||||||
|
if (processed.has(el)) return;
|
||||||
|
|
||||||
|
const tagName = el.tagName?.toLowerCase();
|
||||||
|
|
||||||
|
// 跳过不需要的元素
|
||||||
|
if (['script', 'style', 'noscript', 'iframe', 'svg'].includes(tagName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
|
||||||
|
processed.add(el);
|
||||||
|
content.push({
|
||||||
|
type: 'heading',
|
||||||
|
level: parseInt(tagName[1]),
|
||||||
|
content: el.textContent.trim()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 段落
|
||||||
|
if (tagName === 'p') {
|
||||||
|
const text = el.textContent.trim();
|
||||||
|
if (text) {
|
||||||
|
processed.add(el);
|
||||||
|
content.push({
|
||||||
|
type: 'paragraph',
|
||||||
|
content: text
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码块
|
||||||
|
if (tagName === 'pre' || tagName === 'code') {
|
||||||
|
if (tagName === 'pre' || !el.closest('pre')) {
|
||||||
|
processed.add(el);
|
||||||
|
const lang = el.className.match(/language-(\w+)/)?.[1] ||
|
||||||
|
el.getAttribute('data-lang') || '';
|
||||||
|
content.push({
|
||||||
|
type: 'code',
|
||||||
|
language: lang,
|
||||||
|
content: el.textContent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 列表
|
||||||
|
if (tagName === 'ul' || tagName === 'ol') {
|
||||||
|
processed.add(el);
|
||||||
|
const items = Array.from(el.querySelectorAll(':scope > li'))
|
||||||
|
.map(li => li.textContent.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (items.length) {
|
||||||
|
content.push({
|
||||||
|
type: 'list',
|
||||||
|
ordered: tagName === 'ol',
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格
|
||||||
|
if (tagName === 'table') {
|
||||||
|
processed.add(el);
|
||||||
|
const rows = Array.from(el.querySelectorAll('tr')).map(tr => {
|
||||||
|
return Array.from(tr.querySelectorAll('th, td'))
|
||||||
|
.map(cell => cell.textContent.trim());
|
||||||
|
});
|
||||||
|
if (rows.length) {
|
||||||
|
content.push({
|
||||||
|
type: 'table',
|
||||||
|
rows: rows
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片
|
||||||
|
if (tagName === 'img') {
|
||||||
|
processed.add(el);
|
||||||
|
content.push({
|
||||||
|
type: 'image',
|
||||||
|
src: el.src,
|
||||||
|
alt: el.alt || ''
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 链接
|
||||||
|
if (tagName === 'a') {
|
||||||
|
processed.add(el);
|
||||||
|
const text = el.textContent.trim();
|
||||||
|
if (text) {
|
||||||
|
content.push({
|
||||||
|
type: 'link',
|
||||||
|
text: text,
|
||||||
|
href: el.href
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理子元素
|
||||||
|
Array.from(el.children).forEach(child => processElement(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
processElement(root);
|
||||||
|
|
||||||
|
// 如果没有提取到结构化内容,回退到纯文本
|
||||||
|
if (content.length === 0) {
|
||||||
|
const text = root.textContent.trim();
|
||||||
|
if (text) {
|
||||||
|
content.push({
|
||||||
|
type: 'paragraph',
|
||||||
|
content: text
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化内容
|
||||||
|
function formatContent(content, format) {
|
||||||
|
switch (format) {
|
||||||
|
case 'markdown':
|
||||||
|
return toMarkdown(content);
|
||||||
|
case 'json':
|
||||||
|
return JSON.stringify(content, null, 2);
|
||||||
|
case 'xml':
|
||||||
|
return toXML(content);
|
||||||
|
default:
|
||||||
|
return toMarkdown(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为 Markdown
|
||||||
|
function toMarkdown(content) {
|
||||||
|
return content.map(item => {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'heading':
|
||||||
|
return '#'.repeat(item.level) + ' ' + item.content + '\n';
|
||||||
|
|
||||||
|
case 'paragraph':
|
||||||
|
return item.content + '\n';
|
||||||
|
|
||||||
|
case 'code':
|
||||||
|
const lang = item.language || '';
|
||||||
|
return '```' + lang + '\n' + item.content + '\n```\n';
|
||||||
|
|
||||||
|
case 'list':
|
||||||
|
return item.items.map((text, i) => {
|
||||||
|
const prefix = item.ordered ? `${i + 1}. ` : '- ';
|
||||||
|
return prefix + text;
|
||||||
|
}).join('\n') + '\n';
|
||||||
|
|
||||||
|
case 'table':
|
||||||
|
if (item.rows.length === 0) return '';
|
||||||
|
const header = '| ' + item.rows[0].join(' | ') + ' |';
|
||||||
|
const separator = '| ' + item.rows[0].map(() => '---').join(' | ') + ' |';
|
||||||
|
const body = item.rows.slice(1)
|
||||||
|
.map(row => '| ' + row.join(' | ') + ' |')
|
||||||
|
.join('\n');
|
||||||
|
return [header, separator, body].filter(Boolean).join('\n') + '\n';
|
||||||
|
|
||||||
|
case 'image':
|
||||||
|
return `\n`;
|
||||||
|
|
||||||
|
case 'link':
|
||||||
|
return `[${item.text}](${item.href})\n`;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为 XML
|
||||||
|
function toXML(content) {
|
||||||
|
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<document>\n';
|
||||||
|
|
||||||
|
content.forEach(item => {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'heading':
|
||||||
|
xml += ` <heading level="${item.level}">${escapeXML(item.content)}</heading>\n`;
|
||||||
|
break;
|
||||||
|
case 'paragraph':
|
||||||
|
xml += ` <paragraph>${escapeXML(item.content)}</paragraph>\n`;
|
||||||
|
break;
|
||||||
|
case 'code':
|
||||||
|
xml += ` <code language="${item.language || ''}">${escapeXML(item.content)}</code>\n`;
|
||||||
|
break;
|
||||||
|
case 'list':
|
||||||
|
xml += ` <list ordered="${item.ordered}">\n`;
|
||||||
|
item.items.forEach(text => {
|
||||||
|
xml += ` <item>${escapeXML(text)}</item>\n`;
|
||||||
|
});
|
||||||
|
xml += ' </list>\n';
|
||||||
|
break;
|
||||||
|
case 'table':
|
||||||
|
xml += ' <table>\n';
|
||||||
|
item.rows.forEach((row, i) => {
|
||||||
|
xml += ` <row index="${i}">\n`;
|
||||||
|
row.forEach((cell, j) => {
|
||||||
|
xml += ` <cell index="${j}">${escapeXML(cell)}</cell>\n`;
|
||||||
|
});
|
||||||
|
xml += ' </row>\n';
|
||||||
|
});
|
||||||
|
xml += ' </table>\n';
|
||||||
|
break;
|
||||||
|
case 'image':
|
||||||
|
xml += ` <image src="${escapeXML(item.src)}" alt="${escapeXML(item.alt)}"/>\n`;
|
||||||
|
break;
|
||||||
|
case 'link':
|
||||||
|
xml += ` <link href="${escapeXML(item.href)}">${escapeXML(item.text)}</link>\n`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xml += '</document>';
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeXML(str) {
|
||||||
|
return str
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制到剪贴板
|
||||||
|
async function copyToClipboard(text) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
} catch (err) {
|
||||||
|
// 降级方案
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = text;
|
||||||
|
textarea.style.position = 'fixed';
|
||||||
|
textarea.style.opacity = '0';
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到 storage
|
||||||
|
function saveToStorage(text) {
|
||||||
|
chrome.storage.local.set({ lastExtraction: text });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示通知
|
||||||
|
function showNotification(message) {
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.className = 'llm-extractor-notification';
|
||||||
|
notification.textContent = message;
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.classList.add('fade-out');
|
||||||
|
setTimeout(() => notification.remove(), 300);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
BIN
browser-extension/icons/icon128.png
Normal file
BIN
browser-extension/icons/icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 257 B |
BIN
browser-extension/icons/icon16.png
Normal file
BIN
browser-extension/icons/icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 B |
BIN
browser-extension/icons/icon48.png
Normal file
BIN
browser-extension/icons/icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 B |
32
browser-extension/manifest.json
Normal file
32
browser-extension/manifest.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "LLM Content Extractor",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "截图并提取网页内容,转换为大模型友好格式",
|
||||||
|
"permissions": [
|
||||||
|
"activeTab",
|
||||||
|
"scripting",
|
||||||
|
"clipboardWrite",
|
||||||
|
"storage"
|
||||||
|
],
|
||||||
|
"action": {
|
||||||
|
"default_popup": "popup.html",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "icons/icon16.png",
|
||||||
|
"48": "icons/icon48.png",
|
||||||
|
"128": "icons/icon128.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": ["<all_urls>"],
|
||||||
|
"js": ["content.js"],
|
||||||
|
"css": ["content.css"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icons": {
|
||||||
|
"16": "icons/icon16.png",
|
||||||
|
"48": "icons/icon48.png",
|
||||||
|
"128": "icons/icon128.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
139
browser-extension/popup.html
Normal file
139
browser-extension/popup.html
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>LLM Content Extractor</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
width: 320px;
|
||||||
|
padding: 16px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
h1::before {
|
||||||
|
content: "✨";
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.btn:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
.btn-secondary {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #ebebeb;
|
||||||
|
}
|
||||||
|
.format-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.status.success {
|
||||||
|
display: block;
|
||||||
|
background: #e8f5e9;
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
.status.error {
|
||||||
|
display: block;
|
||||||
|
background: #ffebee;
|
||||||
|
color: #c62828;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
.tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>LLM Content Extractor</h1>
|
||||||
|
|
||||||
|
<select id="formatSelect" class="format-select">
|
||||||
|
<option value="markdown">Markdown 格式</option>
|
||||||
|
<option value="json">JSON 结构化</option>
|
||||||
|
<option value="xml">XML 格式</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button id="selectBtn" class="btn btn-primary">
|
||||||
|
<span>🎯</span> 框选区域提取
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button id="fullPageBtn" class="btn btn-secondary">
|
||||||
|
<span>📄</span> 提取整页内容
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<button id="copyLastBtn" class="btn btn-secondary">
|
||||||
|
<span>📋</span> 复制上次结果
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div id="status" class="status"></div>
|
||||||
|
|
||||||
|
<p class="tip">提取后内容自动复制到剪贴板</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
72
browser-extension/popup.js
Normal file
72
browser-extension/popup.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const selectBtn = document.getElementById('selectBtn');
|
||||||
|
const fullPageBtn = document.getElementById('fullPageBtn');
|
||||||
|
const copyLastBtn = document.getElementById('copyLastBtn');
|
||||||
|
const formatSelect = document.getElementById('formatSelect');
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
|
||||||
|
function showStatus(message, type) {
|
||||||
|
status.textContent = message;
|
||||||
|
status.className = `status ${type}`;
|
||||||
|
setTimeout(() => {
|
||||||
|
status.className = 'status';
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 框选区域提取
|
||||||
|
selectBtn.addEventListener('click', async () => {
|
||||||
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
const format = formatSelect.value;
|
||||||
|
|
||||||
|
await chrome.tabs.sendMessage(tab.id, {
|
||||||
|
action: 'startSelection',
|
||||||
|
format: format
|
||||||
|
});
|
||||||
|
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提取整页内容
|
||||||
|
fullPageBtn.addEventListener('click', async () => {
|
||||||
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
const format = formatSelect.value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await chrome.tabs.sendMessage(tab.id, {
|
||||||
|
action: 'extractFullPage',
|
||||||
|
format: format
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response && response.success) {
|
||||||
|
showStatus('✅ 内容已复制到剪贴板', 'success');
|
||||||
|
} else {
|
||||||
|
showStatus('❌ 提取失败', 'error');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showStatus('❌ 请刷新页面后重试', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 复制上次结果
|
||||||
|
copyLastBtn.addEventListener('click', async () => {
|
||||||
|
const result = await chrome.storage.local.get('lastExtraction');
|
||||||
|
if (result.lastExtraction) {
|
||||||
|
await navigator.clipboard.writeText(result.lastExtraction);
|
||||||
|
showStatus('✅ 已复制上次结果', 'success');
|
||||||
|
} else {
|
||||||
|
showStatus('❌ 暂无历史记录', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 恢复上次选择的格式
|
||||||
|
chrome.storage.local.get('format', (result) => {
|
||||||
|
if (result.format) {
|
||||||
|
formatSelect.value = result.format;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存格式选择
|
||||||
|
formatSelect.addEventListener('change', () => {
|
||||||
|
chrome.storage.local.set({ format: formatSelect.value });
|
||||||
|
});
|
||||||
|
});
|
||||||
47
generate_icons.py
Normal file
47
generate_icons.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""生成简单的扩展图标"""
|
||||||
|
import struct
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
def create_png(size, color=(102, 126, 234)):
|
||||||
|
"""创建简单的纯色 PNG 图标"""
|
||||||
|
|
||||||
|
def chunk(chunk_type, data):
|
||||||
|
return struct.pack('>I', len(data)) + chunk_type + data + struct.pack('>I', zlib.crc32(chunk_type + data) & 0xffffffff)
|
||||||
|
|
||||||
|
# PNG signature
|
||||||
|
signature = b'\x89PNG\r\n\x1a\n'
|
||||||
|
|
||||||
|
# IHDR chunk
|
||||||
|
ihdr_data = struct.pack('>IIBBBBB', size, size, 8, 2, 0, 0, 0)
|
||||||
|
ihdr = chunk(b'IHDR', ihdr_data)
|
||||||
|
|
||||||
|
# IDAT chunk (raw image data)
|
||||||
|
raw_data = b''
|
||||||
|
for y in range(size):
|
||||||
|
raw_data += b'\x00' # filter byte
|
||||||
|
for x in range(size):
|
||||||
|
# 创建圆角矩形效果
|
||||||
|
cx, cy = size / 2, size / 2
|
||||||
|
radius = size * 0.4
|
||||||
|
corner_radius = size * 0.15
|
||||||
|
|
||||||
|
# 简化:纯色填充
|
||||||
|
raw_data += bytes(color)
|
||||||
|
|
||||||
|
compressed = zlib.compress(raw_data, 9)
|
||||||
|
idat = chunk(b'IDAT', compressed)
|
||||||
|
|
||||||
|
# IEND chunk
|
||||||
|
iend = chunk(b'IEND', b'')
|
||||||
|
|
||||||
|
return signature + ihdr + idat + iend
|
||||||
|
|
||||||
|
# 生成不同尺寸的图标
|
||||||
|
for size in [16, 48, 128]:
|
||||||
|
png_data = create_png(size)
|
||||||
|
with open(f'icons/icon{size}.png', 'wb') as f:
|
||||||
|
f.write(png_data)
|
||||||
|
print(f'Generated icons/icon{size}.png')
|
||||||
|
|
||||||
|
print('Done!')
|
||||||
91
mcp.py
Normal file
91
mcp.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import json
|
||||||
|
|
||||||
|
url = "https://developers.weixin.qq.com/miniprogram/dev/framework/search/seo.html"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"User-Agent": (
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
|
"Chrome/121.0.0.0 Safari/537.36"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_html(url):
|
||||||
|
resp = requests.get(url, headers=headers, timeout=10)
|
||||||
|
resp.raise_for_status()
|
||||||
|
resp.encoding = resp.apparent_encoding
|
||||||
|
return resp.text
|
||||||
|
|
||||||
|
|
||||||
|
def extract_structured_content(html):
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
|
||||||
|
# 可能的正文容器
|
||||||
|
main = soup.select_one("#docContent, .content, #page-content, .page-content")
|
||||||
|
if not main:
|
||||||
|
main = soup.body # 最后兜底
|
||||||
|
|
||||||
|
data = []
|
||||||
|
|
||||||
|
for el in main.descendants:
|
||||||
|
if el.name in ["h1", "h2", "h3", "h4"]:
|
||||||
|
data.append({
|
||||||
|
"type": "heading",
|
||||||
|
"level": int(el.name[-1]),
|
||||||
|
"content": el.get_text(strip=True)
|
||||||
|
})
|
||||||
|
|
||||||
|
elif el.name == "p":
|
||||||
|
txt = el.get_text(" ", strip=True)
|
||||||
|
if txt:
|
||||||
|
data.append({
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": txt
|
||||||
|
})
|
||||||
|
|
||||||
|
elif el.name == "pre":
|
||||||
|
code = el.get_text("\n", strip=False)
|
||||||
|
if code:
|
||||||
|
data.append({
|
||||||
|
"type": "code",
|
||||||
|
"lang": el.get("lang") or el.get("data-lang") or "text",
|
||||||
|
"content": code
|
||||||
|
})
|
||||||
|
|
||||||
|
elif el.name == "table":
|
||||||
|
rows = []
|
||||||
|
for tr in el.select("tr"):
|
||||||
|
cols = [td.get_text(" ", strip=True) for td in tr.select("th,td")]
|
||||||
|
rows.append(cols)
|
||||||
|
|
||||||
|
data.append({
|
||||||
|
"type": "table",
|
||||||
|
"rows": rows
|
||||||
|
})
|
||||||
|
|
||||||
|
elif el.name in ["ul", "ol"]:
|
||||||
|
items = [
|
||||||
|
li.get_text(" ", strip=True)
|
||||||
|
for li in el.select("li")
|
||||||
|
]
|
||||||
|
if items:
|
||||||
|
data.append({
|
||||||
|
"type": "list",
|
||||||
|
"ordered": el.name == "ol",
|
||||||
|
"items": items
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
html = fetch_html(url)
|
||||||
|
structured = extract_structured_content(html)
|
||||||
|
|
||||||
|
with open("wechat_dev_seo_structured.json", "w", encoding="utf-8") as f:
|
||||||
|
json.dump(structured, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print("结构化内容已写入 wechat_dev_seo_structured.json")
|
||||||
957
result.md
Normal file
957
result.md
Normal file
@@ -0,0 +1,957 @@
|
|||||||
|
[小程序]()
|
||||||
|
|
||||||
|
- 小程序
|
||||||
|
- 小游戏
|
||||||
|
- 公众号
|
||||||
|
- 服务号
|
||||||
|
- 开放平台
|
||||||
|
- 企业微信
|
||||||
|
- 微信支付
|
||||||
|
- 视频号
|
||||||
|
- 微信小店
|
||||||
|
- 智能对话
|
||||||
|
- 腾讯小微
|
||||||
|
|
||||||
|
[开发](https://developers.weixin.qq.com/miniprogram/dev/framework/)
|
||||||
|
|
||||||
|
[介绍](https://developers.weixin.qq.com/miniprogram/introduction/)
|
||||||
|
|
||||||
|
[设计](https://developers.weixin.qq.com/miniprogram/design/)
|
||||||
|
|
||||||
|
[运营](https://developers.weixin.qq.com/miniprogram/product/)
|
||||||
|
|
||||||
|
[数据](https://developers.weixin.qq.com/miniprogram/analysis/wedata/intro/)
|
||||||
|
|
||||||
|
[安全](https://developers.weixin.qq.com/miniprogram/security/basic/)
|
||||||
|
|
||||||
|
[社区](https://developers.weixin.qq.com/community/develop/mixflow)
|
||||||
|
|
||||||
|
[学堂](https://developers.weixin.qq.com/community/business)
|
||||||
|
|
||||||
|
[取消](javascript:;)
|
||||||
|
|
||||||
|
[查看更多](https://developers.weixin.qq.com/doc/search?source=more&query=&doc_type=miniprogram&jumpbackUrl=%2Fdoc%2F)
|
||||||
|
|
||||||
|
[在小程序下暂无结果,查看其它业务相关内容 >](https://developers.weixin.qq.com/doc/search?source=more&query=&doc_type=miniprogram&jumpbackUrl=%2Fdoc%2F)
|
||||||
|
|
||||||
|
- 指南
|
||||||
|
- 框架
|
||||||
|
- 组件
|
||||||
|
- API
|
||||||
|
- 服务端
|
||||||
|
- 平台能力
|
||||||
|
行业能力
|
||||||
|
|
||||||
|
商业能力
|
||||||
|
|
||||||
|
多端能力
|
||||||
|
|
||||||
|
服务市场
|
||||||
|
|
||||||
|
城市服务
|
||||||
|
|
||||||
|
付费能力
|
||||||
|
|
||||||
|
拓展能力
|
||||||
|
- 工具
|
||||||
|
- 云服务
|
||||||
|
云开发
|
||||||
|
|
||||||
|
云托管
|
||||||
|
- 更新日志
|
||||||
|
|
||||||
|
[开发](javascript:;)
|
||||||
|
|
||||||
|
[开发](https://developers.weixin.qq.com/miniprogram/dev/framework/)
|
||||||
|
|
||||||
|
[介绍](https://developers.weixin.qq.com/miniprogram/introduction/)
|
||||||
|
|
||||||
|
[设计](https://developers.weixin.qq.com/miniprogram/design/)
|
||||||
|
|
||||||
|
[运营](https://developers.weixin.qq.com/miniprogram/product/)
|
||||||
|
|
||||||
|
[数据](https://developers.weixin.qq.com/miniprogram/analysis/wedata/intro/)
|
||||||
|
|
||||||
|
[安全](https://developers.weixin.qq.com/miniprogram/security/basic/)
|
||||||
|
|
||||||
|
[指南](javascript:;)
|
||||||
|
|
||||||
|
- 起步
|
||||||
|
|
||||||
|
小程序简介
|
||||||
|
|
||||||
|
小程序技术发展史
|
||||||
|
|
||||||
|
小程序与普通网页开发的区别
|
||||||
|
|
||||||
|
体验小程序
|
||||||
|
|
||||||
|
开始
|
||||||
|
|
||||||
|
申请账号
|
||||||
|
|
||||||
|
安装开发者工具
|
||||||
|
|
||||||
|
你的第一个小程序
|
||||||
|
|
||||||
|
编译预览
|
||||||
|
|
||||||
|
小程序代码构成
|
||||||
|
|
||||||
|
JSON 配置
|
||||||
|
|
||||||
|
WXML 模板
|
||||||
|
|
||||||
|
WXSS 样式
|
||||||
|
|
||||||
|
JS 逻辑交互
|
||||||
|
|
||||||
|
小程序宿主环境
|
||||||
|
|
||||||
|
渲染层和逻辑层
|
||||||
|
|
||||||
|
程序与页面
|
||||||
|
|
||||||
|
组件
|
||||||
|
|
||||||
|
API
|
||||||
|
|
||||||
|
小程序协同工作和发布
|
||||||
|
|
||||||
|
协同工作
|
||||||
|
|
||||||
|
小程序的版本
|
||||||
|
|
||||||
|
发布上线
|
||||||
|
|
||||||
|
运营数据
|
||||||
|
小程序开发指南
|
||||||
|
- 目录结构
|
||||||
|
- 配置小程序
|
||||||
|
|
||||||
|
全局配置
|
||||||
|
|
||||||
|
页面配置
|
||||||
|
- 小程序框架
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
场景值
|
||||||
|
|
||||||
|
逻辑层
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
注册小程序
|
||||||
|
|
||||||
|
注册页面
|
||||||
|
|
||||||
|
页面生命周期
|
||||||
|
|
||||||
|
页面路由
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
页面路由监听
|
||||||
|
|
||||||
|
路由事件重写
|
||||||
|
|
||||||
|
模块化
|
||||||
|
|
||||||
|
API
|
||||||
|
|
||||||
|
视图层
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
WXML
|
||||||
|
|
||||||
|
WXSS
|
||||||
|
|
||||||
|
WXS
|
||||||
|
|
||||||
|
事件系统
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
WXS 响应事件
|
||||||
|
|
||||||
|
Tap 事件
|
||||||
|
|
||||||
|
Pointer 事件
|
||||||
|
|
||||||
|
简易双向绑定
|
||||||
|
|
||||||
|
基础组件
|
||||||
|
|
||||||
|
获取界面上的节点信息
|
||||||
|
|
||||||
|
响应显示区域变化
|
||||||
|
|
||||||
|
分栏模式
|
||||||
|
|
||||||
|
动画
|
||||||
|
|
||||||
|
初始渲染缓存
|
||||||
|
- 小程序运行时
|
||||||
|
|
||||||
|
运行环境
|
||||||
|
|
||||||
|
JavaScript 支持情况
|
||||||
|
|
||||||
|
运行机制
|
||||||
|
|
||||||
|
更新机制
|
||||||
|
- Skyline 渲染引擎
|
||||||
|
|
||||||
|
概览
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
特性
|
||||||
|
|
||||||
|
性能对比
|
||||||
|
|
||||||
|
示例体验
|
||||||
|
|
||||||
|
支持与差异
|
||||||
|
|
||||||
|
基础组件
|
||||||
|
|
||||||
|
WXSS 样式
|
||||||
|
|
||||||
|
增强特性
|
||||||
|
|
||||||
|
Worklet 动画
|
||||||
|
|
||||||
|
手势系统
|
||||||
|
|
||||||
|
自定义路由
|
||||||
|
|
||||||
|
预设路由效果
|
||||||
|
|
||||||
|
容器转场动画
|
||||||
|
|
||||||
|
页面返回手势
|
||||||
|
|
||||||
|
共享元素动画
|
||||||
|
|
||||||
|
全局工具栏
|
||||||
|
|
||||||
|
滚动容器及其应用场景
|
||||||
|
|
||||||
|
从 WebView 迁移
|
||||||
|
|
||||||
|
起步
|
||||||
|
|
||||||
|
新版组件框架适配指引
|
||||||
|
|
||||||
|
最佳实践
|
||||||
|
|
||||||
|
常见兼容问题
|
||||||
|
|
||||||
|
发布上线
|
||||||
|
|
||||||
|
迁移工具
|
||||||
|
|
||||||
|
性能调试
|
||||||
|
|
||||||
|
动态
|
||||||
|
|
||||||
|
更新日志
|
||||||
|
|
||||||
|
特性状态
|
||||||
|
- glass-easel 组件框架
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
适配指引
|
||||||
|
|
||||||
|
新增特性
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
在模板中调用 data 里的函数
|
||||||
|
|
||||||
|
Chaining API
|
||||||
|
|
||||||
|
Chaining API 的 init 函数
|
||||||
|
|
||||||
|
动态 slot
|
||||||
|
- 自定义组件
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
组件系统
|
||||||
|
|
||||||
|
组件模板和样式
|
||||||
|
|
||||||
|
Component 构造器
|
||||||
|
|
||||||
|
组件间通信与事件
|
||||||
|
|
||||||
|
组件生命周期
|
||||||
|
|
||||||
|
behaviors
|
||||||
|
|
||||||
|
组件间关系
|
||||||
|
|
||||||
|
数据监听器
|
||||||
|
|
||||||
|
纯数据字段
|
||||||
|
|
||||||
|
抽象节点
|
||||||
|
|
||||||
|
自定义组件扩展
|
||||||
|
|
||||||
|
开发第三方自定义组件
|
||||||
|
|
||||||
|
单元测试
|
||||||
|
|
||||||
|
获取更新性能统计信息
|
||||||
|
|
||||||
|
占位组件
|
||||||
|
|
||||||
|
查看自定义组件数据
|
||||||
|
- 插件
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
开发插件
|
||||||
|
|
||||||
|
使用插件
|
||||||
|
|
||||||
|
插件调用 API 的限制
|
||||||
|
|
||||||
|
插件使用组件的限制
|
||||||
|
|
||||||
|
插件功能页
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
用户信息功能页
|
||||||
|
|
||||||
|
支付功能页
|
||||||
|
|
||||||
|
收货地址功能页
|
||||||
|
|
||||||
|
发票功能页
|
||||||
|
|
||||||
|
发票抬头功能页
|
||||||
|
- 基础能力
|
||||||
|
|
||||||
|
网络
|
||||||
|
|
||||||
|
使用说明
|
||||||
|
|
||||||
|
业务域名
|
||||||
|
|
||||||
|
局域网通信
|
||||||
|
|
||||||
|
移动解析HttpDNS
|
||||||
|
|
||||||
|
存储
|
||||||
|
|
||||||
|
文件系统
|
||||||
|
|
||||||
|
画布
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
旧版迁移指南
|
||||||
|
|
||||||
|
分包加载
|
||||||
|
|
||||||
|
使用分包
|
||||||
|
|
||||||
|
独立分包
|
||||||
|
|
||||||
|
分包预下载
|
||||||
|
|
||||||
|
分包异步化
|
||||||
|
|
||||||
|
按需注入和用时注入
|
||||||
|
|
||||||
|
多线程 Worker
|
||||||
|
|
||||||
|
服务端能力
|
||||||
|
|
||||||
|
服务端 API
|
||||||
|
|
||||||
|
消息推送
|
||||||
|
|
||||||
|
自定义 tabBar
|
||||||
|
|
||||||
|
周期性更新
|
||||||
|
|
||||||
|
数据预拉取
|
||||||
|
|
||||||
|
DarkMode 适配指南
|
||||||
|
|
||||||
|
大屏适配指南
|
||||||
|
|
||||||
|
HarmonyOS 适配指南
|
||||||
|
|
||||||
|
AI/AR
|
||||||
|
|
||||||
|
AI推理能力
|
||||||
|
Beta
|
||||||
|
介绍
|
||||||
|
|
||||||
|
算子支持列表
|
||||||
|
|
||||||
|
模型量化推理
|
||||||
|
|
||||||
|
VisionKit 视觉能力
|
||||||
|
|
||||||
|
VisionKit 基础
|
||||||
|
|
||||||
|
6Dof-水平面 AR
|
||||||
|
|
||||||
|
6Dof-水平面 AR 扩展能力
|
||||||
|
|
||||||
|
Marker AR
|
||||||
|
|
||||||
|
单样本检测(OSD)
|
||||||
|
|
||||||
|
人脸检测
|
||||||
|
|
||||||
|
人体检测
|
||||||
|
|
||||||
|
人手检测
|
||||||
|
|
||||||
|
鞋部检测
|
||||||
|
|
||||||
|
OCR检测
|
||||||
|
|
||||||
|
身份证检测
|
||||||
|
|
||||||
|
深度估计
|
||||||
|
- XR-FRAME
|
||||||
|
|
||||||
|
开发指南
|
||||||
|
|
||||||
|
开始
|
||||||
|
|
||||||
|
新建一个XR组件
|
||||||
|
|
||||||
|
在页面中使用这个组件
|
||||||
|
|
||||||
|
添加一个物体
|
||||||
|
|
||||||
|
来点颜色和灯光
|
||||||
|
|
||||||
|
有点寡淡,加上图像
|
||||||
|
|
||||||
|
让场景更丰富,环境数据
|
||||||
|
|
||||||
|
动起来,加入动画
|
||||||
|
|
||||||
|
还是不够,放个模型
|
||||||
|
|
||||||
|
再来点交互
|
||||||
|
|
||||||
|
组件通信,加上HUD
|
||||||
|
|
||||||
|
虚拟 x 现实,追加 AR 能力
|
||||||
|
|
||||||
|
识别人脸,给自己戴个面具
|
||||||
|
|
||||||
|
手势,给喜欢的作品点赞
|
||||||
|
|
||||||
|
OSDMarker,给现实物体做标记
|
||||||
|
|
||||||
|
2DMarker+视频,让照片动起来
|
||||||
|
|
||||||
|
加上魔法,来点粒子
|
||||||
|
|
||||||
|
后处理,让画面更加好玩
|
||||||
|
|
||||||
|
分享给你的好友吧!
|
||||||
|
|
||||||
|
之后的,就交给你的创意
|
||||||
|
- 连接硬件能力
|
||||||
|
|
||||||
|
蓝牙
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
蓝牙低功耗 (BLE)
|
||||||
|
|
||||||
|
蓝牙低功耗网状网络 (BLE Mesh)
|
||||||
|
|
||||||
|
蓝牙信标 (Beacon)
|
||||||
|
|
||||||
|
近场通信 (NFC)
|
||||||
|
|
||||||
|
无线局域网 (Wi-Fi)
|
||||||
|
|
||||||
|
硬件设备接入
|
||||||
|
|
||||||
|
设备消息
|
||||||
|
|
||||||
|
设备认证
|
||||||
|
|
||||||
|
指引
|
||||||
|
|
||||||
|
使用 WMPF(安卓)认证设备
|
||||||
|
|
||||||
|
设备认证 SDK(安卓)
|
||||||
|
|
||||||
|
设备认证 TEE 规范
|
||||||
|
|
||||||
|
穿戴设备小程序框架
|
||||||
|
|
||||||
|
音视频通话+摄像头(for 硬件)
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
VoIP 通话插件
|
||||||
|
|
||||||
|
接入指引
|
||||||
|
|
||||||
|
接口文档
|
||||||
|
|
||||||
|
发起通话
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
initByCaller
|
||||||
|
|
||||||
|
callWMPF
|
||||||
|
|
||||||
|
callDevice
|
||||||
|
|
||||||
|
forceHangUpVoip
|
||||||
|
|
||||||
|
getPluginEnterOptions
|
||||||
|
|
||||||
|
getPluginOnloadOptions
|
||||||
|
|
||||||
|
onVoipEvent
|
||||||
|
|
||||||
|
setCustomBtnText
|
||||||
|
|
||||||
|
setUIConfig
|
||||||
|
|
||||||
|
setVoipEndPagePath
|
||||||
|
|
||||||
|
getIotBindContactList
|
||||||
|
|
||||||
|
错误码
|
||||||
|
|
||||||
|
支付刷脸模式
|
||||||
|
|
||||||
|
更新日志
|
||||||
|
|
||||||
|
小程序摄像头插件
|
||||||
|
|
||||||
|
接入指引
|
||||||
|
|
||||||
|
开发安卓设备端应用
|
||||||
|
|
||||||
|
小程序音视频通话 SDK
|
||||||
|
|
||||||
|
Linux 设备
|
||||||
|
|
||||||
|
RTOS 设备
|
||||||
|
|
||||||
|
云对云设备端
|
||||||
|
|
||||||
|
云对云服务端
|
||||||
|
|
||||||
|
VoIP 视频流指南
|
||||||
|
|
||||||
|
异步接口使用指南
|
||||||
|
|
||||||
|
硬件抽象层
|
||||||
|
|
||||||
|
用户授权设备
|
||||||
|
|
||||||
|
设备呼叫手机微信
|
||||||
|
|
||||||
|
手机微信呼叫设备(安卓)
|
||||||
|
|
||||||
|
手机微信呼叫设备(Linux)
|
||||||
|
|
||||||
|
性能与体验优化
|
||||||
|
|
||||||
|
问题排查
|
||||||
|
|
||||||
|
常见问题 FAQ
|
||||||
|
|
||||||
|
通话异常排查指南
|
||||||
|
|
||||||
|
通话提醒异常排查指南
|
||||||
|
下载 安卓小程序硬件框架
|
||||||
|
设备组
|
||||||
|
|
||||||
|
需要帮助
|
||||||
|
- 开放能力
|
||||||
|
|
||||||
|
用户信息
|
||||||
|
|
||||||
|
小程序登录
|
||||||
|
|
||||||
|
UnionID 机制说明
|
||||||
|
|
||||||
|
授权
|
||||||
|
|
||||||
|
开放数据校验与解密
|
||||||
|
|
||||||
|
手机号快速验证组件
|
||||||
|
|
||||||
|
手机号实时验证组件
|
||||||
|
|
||||||
|
获取头像昵称
|
||||||
|
|
||||||
|
生物认证
|
||||||
|
|
||||||
|
账户卡片
|
||||||
|
|
||||||
|
分享到朋友圈
|
||||||
|
|
||||||
|
转发
|
||||||
|
|
||||||
|
转发
|
||||||
|
|
||||||
|
动态消息
|
||||||
|
|
||||||
|
小程序私密消息
|
||||||
|
|
||||||
|
收藏
|
||||||
|
|
||||||
|
聊天素材打开
|
||||||
|
|
||||||
|
聊天工具
|
||||||
|
|
||||||
|
用工关系
|
||||||
|
|
||||||
|
安全能力
|
||||||
|
|
||||||
|
小程序加密网络通道
|
||||||
|
|
||||||
|
安全键盘
|
||||||
|
|
||||||
|
分享数据到微信运动
|
||||||
|
|
||||||
|
音视频通话
|
||||||
|
|
||||||
|
多人音视频对话
|
||||||
|
|
||||||
|
双人音视频对话
|
||||||
|
|
||||||
|
打开 App
|
||||||
|
|
||||||
|
打开半屏小程序
|
||||||
|
|
||||||
|
消息
|
||||||
|
|
||||||
|
订阅消息
|
||||||
|
|
||||||
|
新版一次性订阅消息开发指南
|
||||||
|
|
||||||
|
小程序订阅消息(用户通过弹窗订阅)开发指南
|
||||||
|
|
||||||
|
订阅消息语音提醒
|
||||||
|
|
||||||
|
订阅消息添加提醒
|
||||||
|
|
||||||
|
统一服务消息
|
||||||
|
|
||||||
|
客服消息
|
||||||
|
|
||||||
|
概述
|
||||||
|
|
||||||
|
接收消息和事件
|
||||||
|
|
||||||
|
发送消息
|
||||||
|
|
||||||
|
转发消息
|
||||||
|
|
||||||
|
下发客服输入状态
|
||||||
|
|
||||||
|
临时素材
|
||||||
|
|
||||||
|
位置消息
|
||||||
|
|
||||||
|
获取小程序码
|
||||||
|
|
||||||
|
获取小程序链接
|
||||||
|
|
||||||
|
获取 URL Scheme
|
||||||
|
|
||||||
|
获取 URL Link
|
||||||
|
|
||||||
|
获取 Short Link
|
||||||
|
|
||||||
|
应用:短信打开小程序
|
||||||
|
|
||||||
|
应用:NFC 标签打开小程序
|
||||||
|
|
||||||
|
小程序账号迁移
|
||||||
|
|
||||||
|
视频号
|
||||||
|
|
||||||
|
视频号主页
|
||||||
|
|
||||||
|
视频号视频
|
||||||
|
|
||||||
|
视频号直播
|
||||||
|
|
||||||
|
视频号活动
|
||||||
|
|
||||||
|
数据分析
|
||||||
|
|
||||||
|
附近的小程序
|
||||||
|
|
||||||
|
广告
|
||||||
|
|
||||||
|
Banner 广告
|
||||||
|
|
||||||
|
激励视频广告
|
||||||
|
|
||||||
|
插屏广告
|
||||||
|
|
||||||
|
视频广告
|
||||||
|
|
||||||
|
视频前贴广告
|
||||||
|
|
||||||
|
格子广告
|
||||||
|
|
||||||
|
原生模板广告
|
||||||
|
|
||||||
|
广告预加载接口
|
||||||
|
|
||||||
|
广告数据接口
|
||||||
|
|
||||||
|
广告汇总数据
|
||||||
|
|
||||||
|
广告细分数据
|
||||||
|
|
||||||
|
广告位清单
|
||||||
|
|
||||||
|
结算收入数据
|
||||||
|
- 调试
|
||||||
|
|
||||||
|
概述
|
||||||
|
|
||||||
|
vConsole
|
||||||
|
|
||||||
|
Source Map
|
||||||
|
|
||||||
|
实时日志
|
||||||
|
|
||||||
|
Errno错误码
|
||||||
|
- 性能与体验
|
||||||
|
|
||||||
|
概述
|
||||||
|
|
||||||
|
启动性能
|
||||||
|
|
||||||
|
概述
|
||||||
|
|
||||||
|
小程序启动流程
|
||||||
|
|
||||||
|
代码包体积优化
|
||||||
|
|
||||||
|
代码注入优化
|
||||||
|
|
||||||
|
首屏渲染优化
|
||||||
|
|
||||||
|
其他优化建议
|
||||||
|
|
||||||
|
运行时性能
|
||||||
|
|
||||||
|
概述
|
||||||
|
|
||||||
|
合理使用 setData
|
||||||
|
|
||||||
|
渲染性能优化
|
||||||
|
|
||||||
|
页面切换优化
|
||||||
|
|
||||||
|
资源加载优化
|
||||||
|
|
||||||
|
内存优化
|
||||||
|
|
||||||
|
性能数据
|
||||||
|
|
||||||
|
性能诊断工具
|
||||||
|
|
||||||
|
工具介绍
|
||||||
|
|
||||||
|
工具评测标准
|
||||||
|
|
||||||
|
体验分析
|
||||||
|
|
||||||
|
调试工具
|
||||||
|
|
||||||
|
概述
|
||||||
|
|
||||||
|
真机调试 2.0
|
||||||
|
|
||||||
|
「模拟器」和「调试器」
|
||||||
|
|
||||||
|
代码质量分析面板
|
||||||
|
|
||||||
|
FPS 面板
|
||||||
|
|
||||||
|
性能面板
|
||||||
|
|
||||||
|
体验评分
|
||||||
|
|
||||||
|
体验评分简介
|
||||||
|
|
||||||
|
评分方法与规则
|
||||||
|
|
||||||
|
评分方法
|
||||||
|
|
||||||
|
性能
|
||||||
|
|
||||||
|
体验
|
||||||
|
|
||||||
|
最佳实践
|
||||||
|
|
||||||
|
WXWebAssembly
|
||||||
|
|
||||||
|
接口调用频率规范
|
||||||
|
|
||||||
|
网络调优
|
||||||
|
|
||||||
|
弱网体验优化
|
||||||
|
- 安全指引
|
||||||
|
|
||||||
|
开发原则与注意事项
|
||||||
|
|
||||||
|
通用
|
||||||
|
|
||||||
|
接口鉴权
|
||||||
|
|
||||||
|
代码管理与泄漏
|
||||||
|
|
||||||
|
信息泄露
|
||||||
|
|
||||||
|
授权用户信息变更
|
||||||
|
|
||||||
|
小程序违规处罚信息通知
|
||||||
|
|
||||||
|
后台
|
||||||
|
|
||||||
|
注入漏洞
|
||||||
|
|
||||||
|
弱口令
|
||||||
|
|
||||||
|
文件上传漏洞
|
||||||
|
|
||||||
|
文件下载
|
||||||
|
|
||||||
|
目录遍历
|
||||||
|
|
||||||
|
条件竞争
|
||||||
|
- 健康运营指引
|
||||||
|
|
||||||
|
用户隐私保护
|
||||||
|
|
||||||
|
用户隐私保护指引填写说明
|
||||||
|
|
||||||
|
小程序用户隐私保护指引内容介绍
|
||||||
|
|
||||||
|
插件用户隐私保护说明内容介绍
|
||||||
|
|
||||||
|
小程序隐私协议开发指南
|
||||||
|
|
||||||
|
用户安全解决方案
|
||||||
|
|
||||||
|
内容安全解决方案
|
||||||
|
|
||||||
|
业务安全解决方案
|
||||||
|
- 企业微信兼容
|
||||||
|
- 基础库
|
||||||
|
|
||||||
|
介绍
|
||||||
|
|
||||||
|
版本分布
|
||||||
|
|
||||||
|
低版本兼容
|
||||||
|
- 小程序搜索
|
||||||
|
|
||||||
|
小程序搜索优化指南
|
||||||
|
|
||||||
|
商品数据接入(内测)
|
||||||
|
- PC 小程序
|
||||||
|
|
||||||
|
PC 小程序接入指南
|
||||||
|
|
||||||
|
# # 小程序搜索优化指南
|
||||||
|
|
||||||
|
爬虫访问小程序内页面时,会携带特定的 user-agent "mpcrawler" 及场景值:1129
|
||||||
|
|
||||||
|
判断请求是否来源于官方搜索爬虫的方法:
|
||||||
|
|
||||||
|
签名算法与小程序消息推送接口的签名算法一致。详情
|
||||||
|
|
||||||
|
参数在请求的header里设置,分别是:
|
||||||
|
X-WXApp-Crawler-Timestamp
|
||||||
|
X-WXApp-Crawler-Nonce
|
||||||
|
X-WXApp-Crawler-Signature
|
||||||
|
|
||||||
|
签名流程如下:
|
||||||
|
1.将token、X-WXApp-Crawler-Timestamp、X-WXApp-Crawler-Nonce三个参数进行字典序排序
|
||||||
|
2.将三个参数字符串拼接成一个字符串进行sha1加密
|
||||||
|
3.开发者获得加密后的字符串可与X-WXApp-Crawler-Signature对比,标识该请求来源于微信
|
||||||
|
|
||||||
|
## # 1. 小程序里跳转的页面 (url) 可被直接打开。
|
||||||
|
|
||||||
|
小程序页面内的跳转url是我们爬虫发现页面的重要来源,且搜索引擎召回的结果页面 (url) 是必须能直接打开,不依赖上下文状态的。
|
||||||
|
特别的:建议页面所需的参数都包含在url
|
||||||
|
|
||||||
|
## # 2. 页面跳转优先采用navigator组件。
|
||||||
|
|
||||||
|
小程序提供了两种页面路由方式:
|
||||||
|
a. navigator 组件
|
||||||
|
b. 路由 API,包括 navigateTo / redirectTo / switchTab / navigateBack / reLaunch
|
||||||
|
建议使用 navigator 组件,若不得不使用API,可在爬虫访问时屏蔽针对点击设置的时间锁或变量锁。
|
||||||
|
|
||||||
|
## # 3. 清晰简洁的页面参数。
|
||||||
|
|
||||||
|
结构清晰、简洁、参数有含义的 querystring 对抓取以及后续的分析都有很大帮助,但是将 JSON 数据作为参数的方式是比较糟糕的实现。
|
||||||
|
|
||||||
|
## # 4. 必要的时候才请求用户进行授权、登录、绑定手机号等。
|
||||||
|
|
||||||
|
建议在必须的时候才要求用户授权(比如阅读文章可以匿名,而发表评论需要留名)。
|
||||||
|
|
||||||
|
## # 5. 我们不收录 web-view 中的任何内容。
|
||||||
|
|
||||||
|
我们暂时做不到这一点,长期来看,我们可能也做不到。
|
||||||
|
|
||||||
|
## # 6. 设置一个清晰的标题和页面缩略图。
|
||||||
|
|
||||||
|
页面标题和缩略图对于我们理解页面和提高曝光转化有重要的作用。
|
||||||
|
通过 wx.setNavigationBarTitle 或 自定义转发内容 onShareAppMessage 对页面的标题和缩略图设置,另外也为 video、audio 组件补齐 poster / poster-for-crawler 属性。
|
||||||
|
|
||||||
|
[Tap to report.](javascript:;)
|
||||||
|
|
||||||
|
- 关于腾讯
|
||||||
|
- 文档中心
|
||||||
|
- 辟谣中心
|
||||||
|
- 客服中心
|
||||||
|
|
||||||
|
Copyright © 2012-2025 Tencent. All Rights Reserved.
|
||||||
|
|
||||||
|
- 1. 小程序里跳转的页面 (url) 可被直接打开。
|
||||||
|
|
||||||
|
- 2. 页面跳转优先采用navigator组件。
|
||||||
|
|
||||||
|
- 3. 清晰简洁的页面参数。
|
||||||
|
|
||||||
|
- 4. 必要的时候才请求用户进行授权、登录、绑定手机号等。
|
||||||
|
|
||||||
|
- 5. 我们不收录 web-view 中的任何内容。
|
||||||
|
|
||||||
|
- 6. 设置一个清晰的标题和页面缩略图。
|
||||||
|
|
||||||
|
- 复制
|
||||||
|
- 问题反馈
|
||||||
|
|
||||||
|
[反馈](javascript:;)
|
||||||
81
wechat_dev_seo_structured.json
Normal file
81
wechat_dev_seo_structured.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "heading",
|
||||||
|
"level": 1,
|
||||||
|
"content": "#小程序搜索优化指南"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "爬虫访问小程序内页面时,会携带特定的 user-agent \"mpcrawler\" 及场景值:1129"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "判断请求是否来源于官方搜索爬虫的方法:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "签名算法与小程序消息推送接口的签名算法一致。 详情"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "参数在请求的header里设置,分别是:\nX-WXApp-Crawler-Timestamp\nX-WXApp-Crawler-Nonce\nX-WXApp-Crawler-Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "签名流程如下:\n1.将token、X-WXApp-Crawler-Timestamp、X-WXApp-Crawler-Nonce三个参数进行字典序排序\n2.将三个参数字符串拼接成一个字符串进行sha1加密\n3.开发者获得加密后的字符串可与X-WXApp-Crawler-Signature对比,标识该请求来源于微信"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "heading",
|
||||||
|
"level": 2,
|
||||||
|
"content": "#1. 小程序里跳转的页面 (url) 可被直接打开。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "小程序页面内的跳转url是我们爬虫发现页面的重要来源,且搜索引擎召回的结果页面 (url) 是必须能直接打开,不依赖上下文状态的。\n特别的:建议页面所需的参数都包含在url"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "heading",
|
||||||
|
"level": 2,
|
||||||
|
"content": "#2. 页面跳转优先采用navigator组件。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "小程序提供了两种页面路由方式: a. navigator 组件 b. 路由 API,包括 navigateTo / redirectTo / switchTab / navigateBack / reLaunch\n建议使用 navigator 组件,若不得不使用API,可在爬虫访问时屏蔽针对点击设置的时间锁或变量锁。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "heading",
|
||||||
|
"level": 2,
|
||||||
|
"content": "#3. 清晰简洁的页面参数。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "结构清晰、简洁、参数有含义的 querystring 对抓取以及后续的分析都有很大帮助,但是将 JSON 数据作为参数的方式是比较糟糕的实现。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "heading",
|
||||||
|
"level": 2,
|
||||||
|
"content": "#4. 必要的时候才请求用户进行授权、登录、绑定手机号等。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "建议在必须的时候才要求用户授权(比如阅读文章可以匿名,而发表评论需要留名)。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "heading",
|
||||||
|
"level": 2,
|
||||||
|
"content": "#5. 我们不收录 web-view 中的任何内容。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "我们暂时做不到这一点,长期来看,我们可能也做不到。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "heading",
|
||||||
|
"level": 2,
|
||||||
|
"content": "#6. 设置一个清晰的标题和页面缩略图。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": "页面标题和缩略图对于我们理解页面和提高曝光转化有重要的作用。\n通过 wx.setNavigationBarTitle 或 自定义转发内容 onShareAppMessage 对页面的标题和缩略图设置,另外也为 video、audio 组件补齐 poster / poster-for-crawler 属性。"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user