【已解决】利用douban插件,将图书信息下载后再按需要的格式填写

给孩子做了一个图书馆库,专门管理各年级图书,本来都是很顺利很方便的,如图

需要添加新书A的时候通过快捷按钮:从豆瓣添加书籍(实际是调用了bookfromdouban.js)

2024年12月,忽然这个按钮就失效了,我查了下,应该是douban那边调整了,所以无法爬去数据了,OB恰好出了一个豆瓣的插件,我就装上了。
可是,通过douban插件下载到的图书信息和我之前下载的完全不一样,

由于存在偏差,我所有的统计工作就都无法实现,所以跪求大佬,这种情况应该如何处理?

最希望的方式是通过修改js片段实现数据调用,因此附上book from douban.js的代码:

const notice = (msg) => new Notice(msg, 5000);
const log = (msg) => console.log(msg);

module.exports = bookfromdouban;

let QuickAdd;

async function bookfromdouban(params) {
    QuickAdd = params;
    const isbn_reg = /^(?=(?:\D*\d){10}(?:(?:\D*\d){3})?$)[\d-]+$/g;
    const http_reg = /(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|&|-)+)/g;
    const http_reg_book = /(http:\/\/book|https:\/\/book|m)((\w|=|\?|\.|\/|&|-)+)/g;
    let detailurl;
    const query = await QuickAdd.quickAddApi.inputPrompt("请输入豆瓣图书网址或者ISBN:");

    if (!query) {
        notice("No url entered.");
        throw new Error("No url entered.");
    }
    if (isbn_reg.exec(query)) {
        isbn = query.replace(/-/g, "");
        detailurl = await getBookByisbn(isbn);
    } else {
        if (!http_reg.exec(query)) {
            new Notice('复制的内容需要包含网址或者ISBN码', 3000);
            throw new Error("No results found.");
        } else {
            detailurl = query.match(http_reg)[0];
        }
    }
    console.log('detailUrl:' + detailurl);
    if (http_reg_book.exec(detailurl)) {
        let bookdata = await getbookByurl(detailurl);
        if (bookdata) {
            new Notice('图书数据获取成功!', 3000);
            QuickAdd.variables = {
              ...bookdata
            };
        }
    } else {
        new Notice('只能解析book.douban.com相关网址', 3000);
        throw new Error("No results found.");
    }

}

function isNotEmptyStr(s) {
    s = s.trim();
    if (typeof s === 'string' && s.length > 0) {
        return true;
    }
    return false;
}

async function getbookByurl(url) {
    let page = await urlGet(url);

    if (!page) {
        notice("No results found.");
        throw new Error("No results found.");
    }
    let p = new DOMParser();
    let doc = p.parseFromString(page, "text/html");
    let $ = s => doc.querySelector(s);
    let $2 = z => doc.querySelectorAll(z);

    // 获取书名,尝试新的可能属性和结构来定位元素
    let bookname = '';
    const titleElement = doc.querySelector('h1[data-doubanid="book-title"]');
    if (titleElement) {
        bookname = titleElement.textContent.trim();
    }

    // 获取作者信息,考虑新的作者展示结构(假设可能变为列表形式等情况)
    let author = '';
    const authorElements = doc.querySelectorAll('.author > a');
    if (authorElements.length > 0) {
        author = Array.from(authorElements).map(el => el.textContent).join(', ');
    }

    // 获取内容简介,根据可能变化的包裹元素和类名等调整选择器
    let intro = '';
    const introElements = doc.querySelectorAll('.book-intro > p');
    if (introElements.length > 0) {
        introElements.forEach(element => {
            intro += element.textContent + '\n';
        });
    }
    intro = isNotEmptyStr(intro)? intro.trim() : ' ';

    // 获取作者简介,同样按可能的新结构查找元素
    let authorintro = '';
    const authorIntroElements = doc.querySelectorAll('.author-intro > p');
    if (authorIntroElements.length > 0) {
        authorIntroElements.forEach(element => {
            authorintro += element.textContent + '\n';
        });
    }
    authorintro = isNotEmptyStr(authorintro)? '> [!tip]- **作者简介**\n>\n' + authorintro.trim() : ' ';

    // 原文摘录部分,假设结构可能变化,重新适配选择器
    let quote1 = '';
    let quote2 = '';
    const quoteFigureElements = doc.querySelectorAll('.quote-content');
    const quoteSourceElements = doc.querySelectorAll('.quote-source');
    if (quoteFigureElements.length > 0 && quoteSourceElements.length > 0) {
        quote1 = `${quoteFigureElements[0].textContent.trim()}\n${quoteSourceElements[0].textContent.trim()}`;
        if (quoteFigureElements.length > 1 && quoteSourceElements.length > 1) {
            quote2 = `${quoteFigureElements[1].textContent.trim()}\n${quoteSourceElements[1].textContent.trim()}`;
        }
    }
    quote1 = isNotEmptyStr(quote1)? '> [!quote]- **原文摘录**\n>\n' + '>>' + quote1 : ' ';
    quote2 = isNotEmptyStr(quote2)? '>\n>> ' + quote2 : ' ';

    let bookinfo = {};
    // 提取页数,根据新的信息展示位置和格式调整正则(假设)
    const pagecountMatch = doc.textContent.match(/页数:\s*(\d+)/);
    bookinfo.pagecount = pagecountMatch? pagecountMatch[1] : '未知';

    // 提取出版社,按可能变化的结构和文本格式调整查找方式(假设)
    const publishMatch = doc.textContent.match(/出版社:\s*(.*?)\s*(出版年|ISBN)/);
    bookinfo.publish = publishMatch? publishMatch[1].trim() : '未知';

    // 提取出版年,同理调整提取逻辑(假设)
    const publishyearMatch = doc.textContent.match(/出版年:\s*(.*)/);
    bookinfo.publishyear = publishyearMatch? publishyearMatch[1].trim() : '未知';

    bookinfo.bookname = bookname.replace(/(^\s*)|\^|\.|\*|\?|\!|\/|\\|\$|\#|\&|\||,|\[|\]|\{|\}|\(|\)|\-|\+|\=|(\s*$)/g, "");
    bookinfo.cover = $("meta[property='og:image']")?.content;
    bookinfo.type = 'book';
    bookinfo.description = $("meta[property='og:description']")?.content;
    bookinfo.douban_url = $("meta[property='og:url']")?.content;
    bookinfo.author = "'" + author + "'";
    bookinfo.isbn = $("meta[property='book:isbn']")?.content;
    bookinfo.rating = $("#interest_sectl > div > div.rating_self > strong")?.textContent?? '-';
    bookinfo.intro = intro;
    bookinfo.authorintro = authorintro;
    bookinfo.quote1 = quote1;
    bookinfo.quote2 = quote2;
    for (var i in bookinfo) {
        if (bookinfo[i] === "" || bookinfo[i] === null) {
            bookinfo[i] = "未知";
        }
    }
    return bookinfo;
}


async function getBookByisbn(isbn) {
    let isbnurl = "https://m.douban.com/search/?query=" + isbn;
    let page = await urlGet(isbnurl);

    if (!page) {
        notice("No results found.");
        throw new Error("No results found.");
    }
    let p = new DOMParser();
    let doc = p.parseFromString(page, "text/html");
    let $ = s => doc.querySelector(s);
    let $2 = z => doc.querySelectorAll(z);

    // 获取图书标题,按新的可能结构查找元素(假设)
    let title = '';
    const titleElements = doc.querySelectorAll('.search-result-item h3');
    if (titleElements.length > 0) {
        title = titleElements[0].textContent;
    }

    // 获取图书详情页网址,按新的结构定位链接元素(假设)
    let detailUrl = '';
    const linkElements = doc.querySelectorAll('.search-result-item a');
    if (linkElements.length > 0) {
        detailUrl = linkElements[0].href;
        if (detailUrl.startsWith('app://')) {
            detailUrl = detailUrl.replace('app://', 'https://');
        }
    }
    if (!detailUrl) {
        return null;
    }
    return detailUrl;
}

async function urlGet(url) {
    console.log(url);
    let finalURL = new URL(url);
    const res = await request({
        url: finalURL.href,
        method: "GET",
        cache: "no-cache",
        headers: {
            "Content-Type": "text/html; charset=utf-8",
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
        },
    });

    return res;
}

唉, 当家长辛苦了

根据楼主的描述, 我调查了半天, 才大致看明白是怎么回事, 个人理解现状是这意思:

有个结合 QuickAdd 使用的 bookfromdouban.js, 作用是每本书抓取成一个笔记, 然后造个 dv 表格去管理
bookfromdouban.js 曾经是好用的, 但在豆瓣改版后无法抓取书籍内容

其中:

这具体怎么使用的, 还有一堆细节, 我以上猜的不一定对

但我确实看到了 bookfromdouban.js 输入 url 后无法拿到书籍信息, 具体是 const titleElement = doc.querySelector('h1[data-doubanid="book-title"]'); 这里拿不到书名


如果就是这件事, 那我赞同楼主的 “最希望的方式是通过修改js片段实现数据调用”

所以可能的办法

方案1 还用 bookfromdouban.js 的流程, 给 bookfromdouban.js 打补丁

方案2 改用 obsidian-douban, 研究 obsidian-douban 与现有数据的兼容转换

  • 楼主说 “通过douban插件下载到的图书信息和我之前下载的完全不一样” 但我看 obsidian-douban 也可以配模板啊

在具体信息不明朗的情况下, 我真的不能说更多了
如果楼主能补充点详情, 我感觉这问题还是有希望解决的

太感谢了,竟然如此认真的回复了。
作为一个纯外行,我现在的解决方案是:用豆瓣插件套用我的模板,生成第一个文件,然后再用手动方式按原来直接抓取文件用的那个模板生成第二个文件(模版里有好多设置成选择项,不用自己填写),再将第一个文件里从豆瓣上抓下来的信息复制替换到第二个文件中,重命名,就可以使用了。

总结起来大概就是:
原来:从豆瓣抓过来的同时可以按固定模板直接录入一些信息,然后放在固定文件夹就可以了。
现在:用插件按模板抓过来一部分信息,再填写一部分信息,把两次的结果合并替换,然后放在固定文件夹。

总之真的好麻烦,可JS片段我实在是不会改,完全是外行,当初做这个库,也是在大佬模板基础上搞得,内部的原理其实一知半解。所以可能就得等和我有一样困惑的大佬帮忙改改代码了,其实就差抓取信息那一步,但是实在不会啊


这是原来我通过js片段使用的模板,一般运行之后上面的内容会自动填写,然后需要我选择我同时选择就好了。

现在我用手动方式调用它,然后把介质、阅读状态、购买渠道、购买时间…这些可以选择内容填好。

image
这是现在豆瓣插件配套的模板,和我之前的体系保持一致,用插件搜出某本书,自动把这些信息填好,再和上一截图的内容合并。

如果必须用douban插件解决现有的问题,我希望能有一个Js代码或者css片段之类的,帮我实现这两步的联动,但是目前我自己肯定是无法完成。

我大概看明白了

原先的使用方式: bookfromdouban.js 用于抓取豆瓣数据, 自己定制了模板用于辅助填几个固定选项 (介质 阅读状态 …), 其中定制模板中后加的部分是拿 Templater 做的, 但整个操作是在 QuickAdd 里完成的

现在被迫的使用方式: 拿 obsidian-douban 抓豆瓣的公开基础元数据, 拿自己的模板辅助录入个人数据, 再把两笔记融在一起


根据现状, 我觉得最简洁的改法是借助 QuickAdd Script Yet another douban book script - 经验分享 - Obsidian 中文论坛 实测这个可以抓到大部分豆瓣基本数据

如图, 左侧作者提供的默认模板, 右侧抓取结果, 可以看到除了命名跟 bookfromdouban.js 不同 (cover → coverUrl, pagecount → pageCount 等等), 其实也没差别


具体是:

  1. 首先备份原先数据 (至少整个 Ob 仓库打个压缩包就行)
  2. 整个流程还是用 QuickAdd + 自己本地脚本, 不用豆瓣插件
  3. Obsidian-scripts/scripts/fetchDoubanBook.js · Pray3r/Obsidian-scripts 代替 bookfromdouban.js
    • 正常的替代方式, 应该是造个新 QuickAdd macro, 里面两行: 第一行调用户脚本 fetchDoubanBook.js ; 第二行还填自己的用户模板, 这样子比较清楚, 未来不会混
    • 如果实在不想弄 QuickAdd 的一大堆选项, 把代码抄到 bookfromdouban.js 应该也行…
  4. 设法抹平 “抓取的数据 vs 本地模板期待的数据” 之间的命名差异, 需要让 “自己的模板笔记” 和 fetchDoubanBook.js 的命名协调
    • 本地模板里, 所有的私人属性例如 介质: <% tp.system.suggester(["纸书", "电子书", "视频"], ["纸书", "电子书", "视频"]) %> 全都不变, 还能用
    • 但是来自豆瓣的那几个属性, 要么改掉 “本地模板的写法”, 要么改掉 fetchDoubanBook.js 的写法

总之目前我觉得就是这么一回事
如果我正确理解了楼主这个问题, 那我们可以试试这些方案

我可能会首选: 保持 fetchDoubanBook.js 不动 + 造个新 QuickAdd macro + 改掉 “本地模板的写法” (嗯, 可能稍微有点折腾)

要是楼主同意的话, 我们可以试试上面的办法

不行的话就再琢磨更简单的方案

我觉得理解上是没有问题了
但是您提到的这部分内容:
根据现状, 我觉得最简洁的改法是借助 QuickAdd Script Yet another douban book script - 经验分享 - Obsidian 中文论坛 实测这个可以抓到大部分豆瓣基本数据

如图, 左侧作者提供的默认模板, 右侧抓取结果, 可以看到除了命名跟 bookfromdouban.js 不同 (cover → coverUrl, pagecount → pageCount 等等), 其实也没差别

我想补充一下,即便我用这个抓下来数据了,在yaml区显示的字段名称会和我想要的名称不一样也不行,因为我后面是用dataview调用这些字段名称实现的各种分类啊什么的,所以必须要和我本地模板的字段名称保持一致才行。

另外我上次说的流程可能有点误差,我更正一下,实际操作的时候我是先用本地模板创建一个文件,再用douban插件抓取数据填入这个文件(等于是一种合并),最后重命名。

另外,能不能留一个邮箱,如果您方便的话,我打包数据库给您,帮我具体看一看,我的邮箱是[email protected],期待您的回信

即便我用这个抓下来数据了,在yaml区显示的字段名称会和我想要的名称不一样也不行

明白的, 但其实本地模板里 title: {{VALUE:bookTitle}} 把这个 title: 改掉就可以了, 按照您的习惯, 这里应该用 书名:


要不我们先不讨论细节了,

可以试试拿以下脚本完全替换掉 bookfromdouban.js 中的内容

– edit at 2024-12-24 返回数据里添加 key bookname intro authorintro 以适配模板所需字段

/**
 *  
 *  Yet another douban book script by Pray3r(z) 
 *  https://kernfunny.org
 *  来自 https://github.com/Pray3r/Obsidian-scripts/blob/master/scripts/fetchDoubanBook.js
 *
 */
const notice = msg => new Notice(msg, 5000);

const headers = {
    "Content-Type": "text/html; charset=utf-8",
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.100.4758.11 Safari/537.36'
};

module.exports = fetchDoubanBook;

async function fetchDoubanBook(params) {
    QuickAdd = params;

    try {
        const query = await QuickAdd.quickAddApi.inputPrompt("🔍请输入要搜索的图书名称或ISBN:");
        if (!query) return notice("没有输入任何内容");

        const searchResult = await searchBooksByTitle(query);
        if (!searchResult?.length) return notice("找不到你搜索的内容");

        const selectedBook = await QuickAdd.quickAddApi.suggester(
            obj => obj.text,
            searchResult
        );
        if (!selectedBook) return notice("没有选择任何内容");

        const bookInfo = await getBookInfo(selectedBook.link);

        QuickAdd.variables = { 
            ...bookInfo,
            fileName: formatFileName(bookInfo.bookTitle) ?? " ",
            
            // 2024-12-22 补充, 也按照 bookfromdouban.js 的对象 key value 结构, 抄一份数据
            author: bookInfo['authors'],
            publish: bookInfo['publisher'],
            publishyear: bookInfo['publicationYear'],
            pagecount: bookInfo['pageCount'],
            cover: bookInfo['coverUrl'],
            douban_url: bookInfo['bookUrl'],
            // 2024-12-22 QuickAdd 的 macro 设置里可能是小写 filename
            filename: formatFileName(bookInfo.bookTitle) ?? " ",
            // 2024-12-24 添加 key bookname 以适配模板里的写法
            bookname: bookInfo['bookTitle'],
            intro: bookInfo['summary'],
            authorintro: bookInfo['authorIntro'],
        };
        
    } catch (error) {
        console.error("Error in fetchDoubanBook:", error);
        notice("处理请求时发生错误");
    }
}

async function searchBooksByTitle(title) {
    const searchURL = `https://www.douban.com/search?cat=1001&q=${encodeURIComponent(title)}`;

    try {
        const response = await fetchContent(searchURL);
        return parseSearchResults(response);
    } catch (error) {
        console.error("Failed to fetch book data:", error);
        return null;
    }
}

async function getBookInfo(url) {
    try {
        const response = await fetchContent(url);
        return parseBookInfo(response, url);
    } catch (error) {
        console.error(`Failed to fetch or parse the URL: ${url}`, error);
        return { message: 'Failed to parse the content due to an error.' };
    }
}

async function fetchContent(url) {
    return await request({
        url: url,
        method: "GET",
        cache: "no-cache",
        headers: headers,
        timeout: 5000
    });
}

function parseSearchResults(html) {
    const document = new DOMParser().parseFromString(html, "text/html");
    const doubanBaseURL = "https://book.douban.com/subject/";

    return Array.from(document.querySelectorAll(".result-list .result")).map(result => {
        const bookLinkElement = result.querySelector("h3 a");
        const bookID = bookLinkElement?.getAttribute('onclick')?.match(/\d+(?=,)/)?.[0];
        if (!bookID) return null;

        const bookTitle = bookLinkElement.textContent.trim();
        const bookInfo = result.querySelector(".subject-cast")?.textContent.trim() ?? "信息不详";
        const bookRating = result.querySelector(".rating_nums")?.textContent || "暂无评分";
        const ratingNum = bookRating === "暂无评分" 
            ? '' : result.querySelector('.rating-info span:nth-of-type(3)')?.textContent || '评价人数不足';

        return {
            text: `📚 《${bookTitle}》 ${bookInfo} / ${bookRating} ${ratingNum}`,
            link: `${doubanBaseURL}${bookID}`
        };
    }).filter(Boolean);
}

function parseBookInfo(html, url) {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    const $ = selector => doc.querySelector(selector);
    const $$ = selector => doc.querySelectorAll(selector);

    const infoSection = $('#info');
    const metaSection = $$('meta');
    const bookId = url.match(/\d+/)?.[0] ?? '';

    return cleanFields({
        bookTitle: extractContent(metaSection, 'og:title', 'content', 'Unknown Title'),
        subtitle: extractText(infoSection, /副标题:\s*([\S ]+)/),
        authors: formatAuthors(extractMultipleContent(metaSection, 'book:author')),
        isbn: extractContent(metaSection, 'book:isbn', 'content', ' '),
        coverUrl: extractContent(metaSection, 'og:image', 'content', ' '),
        publisher: extractText(infoSection, /出版社:\s*([^\n\r]+)/),
        originalTitle: extractText(infoSection, /原作名:\s*([^\n\r]+)/),
        translators: extractText(infoSection, /译者:\s*([^\n\r]+)/, '/'),
        publicationYear: extractText(infoSection, /出版年:\s*([^\n\r]+)/),
        pageCount: extractText(infoSection, /页数:\s*([^\n\r]+)/),
        rating: extractText($('#interest_sectl strong.rating_num'), /([\d.]+)/) ?? ' ',
        summary: extractIntroText($$, "内容简介"),
        authorIntro: extractIntroText($$, "作者简介"),
        quotes: extractQuotes($$),
        contents: extractText($(`#dir_${bookId}_full`), '<br>'),
        tags: extractTags(doc, $$),
        bookUrl: url
    });
}

function extractContent(elements, property, attr = 'textContent', defaultValue = ' ') {
    return Array.from(elements).find(el => el.getAttribute('property') === property)?.[attr]?.trim() ?? defaultValue;
}

function extractMultipleContent(elements, property) {
    return Array.from(elements)
        .filter(el => el.getAttribute('property') === property)
        .map(el => el.content.trim());
}

function extractText(element, regex = null, split = '') {
    if (!element) return ' ';
    const value = regex ? element.textContent.match(regex)?.[1]?.trim() : element.textContent.trim();
    return split && value ? value.split(split).map(item => item.trim()) : value;
}

function formatFileName(fileName) {
    return fileName.replace(/[\/\\:]/g, ', ').replace(/["]/g, ' ');
}

function formatAuthors(authors) {
    return authors.length ? `"${authors.join(', ')}"` : ' ';
}

function extractIntroText($$, introType) {
    const introHeader = Array.from($$("h2")).find(h2 => h2.textContent.includes(introType));
    if (!introHeader) return ' ';

    return Array.from(introHeader.nextElementSibling?.querySelectorAll("div.intro") || [])
        .filter(intro => !intro.textContent.includes("(展开全部)"))
        .map(intro => Array.from(intro.querySelectorAll("p"))
            .map(p => p?.textContent?.trim() || '')
            .filter(text => text)
            .map(text => text.replace(/([*_`~-])/g, '\\$1'))
            .join("\n")
        )
        .join("\n")
        .trim() || ' ';
}

function extractQuotes($$) {
    return Array.from($$("figure")).map(figure => {
        const quote = figure.childNodes[0]?.textContent.replace(/\(/g, "").trim() || ' ';
        const source = figure.querySelector("figcaption")?.textContent.replace(/\s/g, "") || ' ';
        return quote || source ? `${quote}\n${source}`.trim() : null;
    }).filter(Boolean);
}

function extractTags(doc, $$) {
    const scriptContent = $$("script")[doc.scripts.length - 3]?.textContent;
    return scriptContent?.match(/(?<=:)[\u4e00-\u9fa5·]+/g)?.join(', ') || ' ';
}

function cleanFields(fields) {
    return Object.fromEntries(
        Object.entries(fields).map(([key, value]) => [key, value ?? ' '])
    );
}

然后, 不动任何 QuickAdd 插件的设置, 就用以前配合 bookfromdouban.js 的那套操作流程, 那个模板 (模板指 这个图里的 别用配豆瓣的那个)

在确认备份过仓库后, 试试这个更新后的 bookfromdouban.js , 能不能管用


注:

交互细节可能会有点差异, 比如以前可能是输 豆瓣网址, 现在是输 ISBN

image

最后达到的效果应该是差不多

能不能留一个邮箱,如果您方便的话,我打包数据库给您

感谢您的信任, 说实话, 我对这脚本也一知半解
细节搞不定很有可能, 确实不适合在这里刷屏讨论一堆小问题
… 但我们俩最后得有一个人把成果总结出来记录到这里, 这事先交给我吧

成功啦!!!
我现在把运行细节截图发上来,我觉得还可以优化一些地方。输入ISBN以后要首先输入书名


接下来是这个

还有这个


上面这五项内容,除了书名,其他的是不是都可以不要了。

接下来就是熟悉的选择环节,都没有问题


填入价格也没问题

最后


这本书也在我的统计里成功出现了。

1 个赞

从抓取到的信息来看,除了书名是我输入的,其他内容应该是根据ISBN自动获取的,所以是不是填入intro\authorintro\quote1\quote2这几项内容是多余的,可以不要了。

刚刚又测试了一下,在bookname弹出的时候,我什么都不填写,直接按回车跳过,后面四项(intro\authorintro\quote1\quote2)也都跳过的情况,最后的结果是包括书名在内的所有内容都自动填写成功了,不需要我调整任何。填写这五项看来整体都是多余的,直接用自动抓取的数据就好。可能还得对应修改一下JS代码,这个可能就得有劳大神了

是不是填入intro\authorintro\quote1\quote2这几项内容是多余的,可以不要了

这几个项目, 不是来自 更新前 / 更新后的 bookfromdouban.js (哦, 这些就是来自原先 bookfromdouban.js)

大概率来自您的本地模板, 或来自 QuickAdd 插件界面里的某些设置

在当前使用的本地模板里, 直接找找 "bookname", "quote1", ... 这类关键词就行, 实在不行就把模板完整发出来我看看

---
书名: <% tp.file.title %>
作者: {{VALUE:author}}
出版社: {{VALUE:publish}}
出版时间: {{VALUE:publishyear}}
总页数: {{VALUE:pagecount}}
封面: {{VALUE:cover}}
豆瓣页面: {{VALUE:douban_url}}
豆瓣评分: {{VALUE:rating}}
ISBN: {{VALUE:isbn}}
录入时间: <% tp.date.now() %>

介质: <% tp.system.suggester(["纸书","电子书","视频"],["纸书","电子书","视频"]) %>
阅读状态: <% tp.system.suggester(["在读","已读","想读","未读"],["在读","已读","想读","未读"]) %>
已读页数: 0
重读: 0
开始时间: 
读完时间: 
字数: 
我的评级: 
购买渠道: <% tp.system.suggester(["拼多多","京东","淘宝", "天猫", "抖音商城", "当当", "百度网盘", "闲鱼","其他"],["拼多多","京东","淘宝", "天猫", "抖音商城", "当当", "百度网盘", "闲鱼", "其他"]) %>
购买时间: <% tp.system.prompt("请输入购买时间:", {defaultValue: "2019-01-01"}) %>
定价: <% tp.system.prompt("请输入定价:", {defaultValue: "0"}) %>
买入价: <% tp.system.prompt("请输入买入价格:", {defaultValue: "0"}) %>
备注: <% tp.system.suggester(["教育部中小学生阅读指导目录(2020年版小学段)110种","教育部中小学生阅读指导目录(2020年版初中段)110种","教育部中小学生阅读指导目录(2020年版高中段)90种","学校书单","1-6年级快乐读书吧","无","美国2020-2021年阅读书单"],["教育部中小学生阅读指导目录(2020年版小学段)110种","教育部中小学生阅读指导目录(2020年版初中段)110种","教育部中小学生阅读指导目录(2020年版高中段)90种","学校书单","1-6年级快乐读书吧","无","美国2020-2021年阅读书单"]) %>
年龄: <% tp.system.suggester(["0-1","1-2","3-4","5-6","初中","高中"],["0-1","1-2","3-4","5-6","初中","高中"]) %>
书籍状态: <% tp.system.suggester(["收藏","借阅"],["收藏","借阅"]) %>
tags: <% tp.system.suggester(["绘本故事","漫画","连环画","语文","文学","艺术","哲学","历史","地理","数学","物理","化学","生物","天文","自然科学","人文社科","英文绘本","英文书籍"],["绘本故事","漫画","连环画","语文","文学","艺术","哲学","历史","地理","数学","物理","化学","生物","天文","自然科学","人文社科","英文绘本","英文书籍"]) %>

---
> [!bookinfo]+ **《{{VALUE:bookname}}》** 
> ![bookcover|200]({{VALUE:cover}})
>
| 作者   | {{VALUE:author}}                           |
|:------: |:------------------------------------------: |
| ISBN   | {{VALUE:isbn}}                             |
| 出版年 | {{VALUE:publishyear}}                      | 
| 出版社 | {{VALUE:publish}}                          |
| 来源   | [{{VALUE:bookname}}]({{VALUE:douban_url}}) |
| 豆瓣评分   | {{VALUE:rating}}                           |
| 页码   | {{VALUE:pagecount}}                        |
| 标签分类   | `=this.file.etags`                       |
| 我的评级  | `=this.file.link.我的评级`                     |




> [!abstract]- **内容简介**
> 

---
 ## 📓**我的笔记**

我修改了一下模板,这四个内容都已经找到并修改完成,intro\authorintro\quote1\quote2,但是bookname这个部分,还是不行,需要填写,具体的模版内容如上图,麻烦帮我看看是哪里出了问题。

我看错了, 实际上最早的 bookfromdouban.js 里确实有这个 bookname

我在 #10 的新 bookfromdouban.js 脚本里, 也补充了这个 bookname, 楼主可以替换掉 bookfromdouban.js, 试试


为了防止这问题变得更混乱, 我再说明一下, 我这边目前修改只限于改掉 bookfromdouban.js 除此之外的任何东西 (本地笔记模板文件, QuickAdd 设置) 我目前都没碰

明白,最后的结果是我用了我新改的模板,替换了您提供的这个JS片段,完美了!!!!! :100: :100: :100:

接下来是优化的问题,还有dataview的一些具体使用问题,不然新开一个讨论的窗口吧,毕竟这个问题已经完美解决了,感谢感谢